Skip to documentation
SLOP

tiny.reticulum.node.inbound

Reference tiny.reticulum node inbound

Defined in node.

API (5)

Actions

Public operations.

No direct callersNo direct callsnodeinbound
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callsprivate sourcelib.reticulum.src.node.inboundacceptProofprivate sourcelib.reticulum.src.node.linkacceptLinkProofnode.inboundconcludeReceipt
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate sourcelib.reticulum.src.node.inboundacceptAnnounceprivate sourcelib.reticulum.src.node.inboundacceptDataprivate sourcelib.reticulum.src.node.inboundacceptProofprivate sourcelib.reticulum.src.node.inboundauthenticatedFramenode.inboundrun+21 morenode.inbounddiagnostic
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsnode.transitionrunprivate sourcelib.reticulum.src.node.inboundsendProofnode.outboundframeCountnode.outboundreservenode.inboundprove
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsnode.transitionrunprivate sourcelib.reticulum.src.node.inboundacceptAnnounceprivate sourcelib.reticulum.src.node.inboundacceptDataprivate sourcelib.reticulum.src.node.inboundacceptProofprivate sourcelib.reticulum.src.node.inboundauthenticatedFramenode.inbounddiagnostic+9 morenode.inboundrun
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsnode.transitionrunnode.linktimerExpirednode.outboundreservenode.transport.link.relaytimerExpirednode.transport.retransmitsweepnode.inboundtimerExpired
Static calls · unresolved targets: 0 · external targets: 7.

Source: lib/reticulum/src/node/inbound.zig

zig
const std = @import("std");const reticulum = @import("../root.zig");const destination = reticulum.destination;const carrier = reticulum.carrier;const identity = reticulum.identity;const ifac = reticulum.interface.ifac;const node = reticulum.node;const packet = reticulum.packet;const wire = reticulum.wire;pub fn diagnostic(node_owner: *node.Node, code: node.Code, hash: ?packet.Hash) void {    node_owner.effects.push(.{ .diagnostic = .{        .code = code,        .packet_hash = hash,    } }) catch unreachable;}fn authenticatedFrame(    node_owner: *node.Node,    registration: *const node.Interface,    frame: []const u8,    scratch: *[carrier.frame_bytes_max]u8,) ?[]const u8 {    const access_config = if (registration.access) |*value| value else {        if (frame.len > wire.mtu) {            diagnostic(node_owner, .frame_too_large, null);            return null;        }        if (frame.len > 0 and ifac.hasFlag(frame[0])) {            diagnostic(node_owner, .ifac_unexpected_flag, null);            return null;        }        return frame;    };    const frame_max = @as(usize, wire.mtu) + access_config.size.byte();    std.debug.assert(frame_max <= carrier.frame_bytes_max);    if (frame.len > frame_max) {        diagnostic(node_owner, .frame_too_large, null);        return null;    }    if (frame.len == 0 or !ifac.hasFlag(frame[0])) {        diagnostic(node_owner, .ifac_missing_flag, null);        return null;    }    const raw = ifac.strip(&access_config.key, access_config.size, frame, scratch) catch |err| {        switch (err) {            error.InvalidCode => diagnostic(node_owner, .ifac_invalid_code, null),            error.Truncated => diagnostic(node_owner, .ifac_truncated, null),            error.MissingFlag,            error.OutputTooSmall,            error.OverlappingBuffers,            error.PacketTooLarge,            => unreachable,        }        return null;    };    std.debug.assert(raw.len + access_config.size.byte() == frame.len);    return raw;}fn localKind(kind: wire.DestinationType) destination.Type {    return @fromBackingInt(@intCast(@backingInt(kind)));}fn unsupportedContext(context: wire.Context) bool {    const raw = context.encode();    return raw >= wire.Context.resource.encode() and        raw <= wire.Context.resource_rcl.encode();}fn candidateFor(    value: wire.Packet,    interface: carrier.Index,    hash: packet.Hash,    blob: node.transport.path.Blob,) node.transport.path.Candidate {    std.debug.assert(value.packet_type == .announce);    return .{        .destination = value.destination,        .next_hop = value.transport_id orelse value.destination,        .announce_hash = hash,        .hops = value.hops,        .carrier = interface,        .context_flag = value.context_flag,        .blob = blob,        .payload = value.payload,    };}fn acceptAnnounce(    node_owner: *node.Node,    value: wire.Packet,    interface: carrier.Index,    hash: packet.Hash,    now: node.Seconds,) node.StepError!void {    const validated = destination.announce.validate(        value.destination,        value.header,        value.context_flag,        value.payload,    ) catch {        diagnostic(node_owner, .invalid_announce, hash);        return;    };    if (node_owner.destinations.find(value.destination) != null) {        diagnostic(node_owner, .own_announce, hash);        return;    }    std.debug.assert(value.hops <= wire.pathfinder_hops);    const public_key = validated.public_key.toBytes();    if (node_owner.known_identities.recall(value.destination)) |known| {        if (!std.mem.eql(u8, &known.public_key, &public_key)) {            diagnostic(node_owner, .announce_key_changed, hash);            return;        }    }    const candidate = candidateFor(value, interface, hash, validated.random_hash[0..10].*);    const known = node_owner.transport.paths.find(value.destination, now);    const admitted = node.transport.path.admits(known, candidate, now);    const decision = node.transport.retransmit.plan(node_owner, value, admitted);    var waiting: usize = 0;    if (admitted) waiting = node.transport.requests.waitingFrames(node_owner, value, now);    const accepted: usize = @intFromBool(admitted);    const rebroadcast: usize = @intFromBool(decision != .none);    try node.outbound.reserve(node_owner, accepted + rebroadcast + waiting, accepted + waiting);    if (decision == .write and !node.transport.retransmit.canSchedule(node_owner)) {        return error.TimerFull;    }    node_owner.known_identities.remember(.{        .destination_hash = value.destination,        .public_key = public_key,        .announce_packet_hash = hash,        .received = now,        .app_data = validated.app_data,    }) catch unreachable;    if (validated.rotating_public_key) |rotating| node_owner.known_ratchets.remember(.{        .destination_hash = value.destination,        .public_key = rotating[0..32].*,        .received = now,    });    node.transport.retransmit.detect(node_owner, value, now);    if (!admitted) return;    _ = node_owner.transport.paths.learn(candidate, now);    node.transport.retransmit.enter(node_owner, value, decision, hash, now);    try node.transport.requests.answerWaiting(node_owner, value, now);    _ = node_owner.transport.inflight_requests.remove(value.destination);    node_owner.effects.push(.{ .announce_received = .{        .destination_hash = value.destination,        .identity_hash = reticulum.hash.truncated(&public_key),        .public_key = public_key,        .app_data = validated.app_data,        .hops = value.hops,        .rotating_key_present = validated.rotating_public_key != null,        .path_response = value.context == .path_response,    } }) catch unreachable;}fn decryptData(    node_owner: *node.Node,    entry: *const destination.registry.Entry,    ciphertext: []const u8,) ?destination.cipher.Decrypted {    return switch (entry.kind) {        .single => blk: {            const index = entry.identity_index orelse break :blk null;            const private = node_owner.identityAt(index) orelse break :blk null;            const binding = node_owner.ratchet_bindings[index];            const empty: [0]identity.Ratchet = .{};            const rotating = if (binding.ring) |ring| ring.all() else empty[0..];            break :blk destination.cipher.decrypt(.{ .single = .{                .private = private,                .ratchets = rotating,                .enforce_ratchets = binding.enforce,            } }, ciphertext, node_owner.scratch) catch null;        },        .group => destination.cipher.decrypt(.{            .group = .{ .key = &entry.group_key },        }, ciphertext, node_owner.scratch) catch null,        .plain => destination.cipher.decrypt(.{            .plain = {},        }, ciphertext, node_owner.scratch) catch null,        .link => null,    };}fn proofFrame(    node_owner: *node.Node,    hash: packet.Hash,    private: *const identity.Private,) node.StepError![]const u8 {    const payload_start: usize = wire.header_one_bytes;    const payload = packet.proof.build(        hash,        private,        node_owner.proof_mode == .implicit,        node_owner.scratch[payload_start..],    ) catch return error.PacketTooLarge;    return wire.encode(        packet.proof.makePacket(hash, payload),        node_owner.scratch,    ) catch return error.PacketTooLarge;}fn sendProof(    node_owner: *node.Node,    destination_hash: [16]u8,    hash: packet.Hash,    interface: carrier.Index,) node.StepError!void {    const entry = node_owner.destinations.find(destination_hash) orelse        return error.UnknownDestination;    if (entry.kind == .plain or entry.kind == .link) return error.InvalidDestination;    const index = entry.identity_index orelse return error.InvalidDestination;    const private = node_owner.identityAt(index) orelse return error.InvalidDestination;    const frame = try proofFrame(node_owner, hash, private);    try node.outbound.transmit(node_owner, .{ .one = interface }, frame);}fn acceptData(    node_owner: *node.Node,    value: wire.Packet,    interface: carrier.Index,    hash: packet.Hash,) node.StepError!void {    const entry = node_owner.destinations.find(value.destination) orelse {        diagnostic(node_owner, .unknown_destination, hash);        return;    };    if (entry.kind != localKind(value.destination_type)) {        diagnostic(node_owner, .destination_type_mismatch, hash);        return;    }    const decrypted = decryptData(node_owner, entry, value.payload) orelse {        diagnostic(node_owner, .decryption_failed, hash);        return;    };    const carriers = node.outbound.frameCount(node_owner, .{ .one = interface });    std.debug.assert(carriers <= 1);    const proves_now = entry.proof_strategy == .all and        entry.identity_index != null and carriers > 0;    const effect_count = 1 + if (proves_now) carriers else 0;    try node.outbound.reserve(node_owner, effect_count, effect_count);    node_owner.effects.push(.{ .application_delivery = .{        .destination = value.destination,        .packet_hash = hash,        .plaintext = decrypted.plaintext,        .ratchet_id = decrypted.ratchet_id,        .proof_requested = entry.proof_strategy == .app,        .interface = interface,    } }) catch unreachable;    if (proves_now) try sendProof(node_owner, value.destination, hash, interface);}fn receiptPublic(node_owner: *node.Node, hash: [16]u8) ?identity.Public {    if (node_owner.known_identities.recall(hash)) |known| return known.public();    const entry = node_owner.destinations.find(hash) orelse return null;    if (entry.kind == .plain or entry.kind == .link) return null;    const index = entry.identity_index orelse return null;    const private = node_owner.identityAt(index) orelse return null;    return private.public();}fn validateReceipt(    node_owner: *node.Node,    receipt: *packet.receipt.Receipt,    proof: wire.proof.Proof,    now: node.Seconds,) bool {    if (receipt.status != .sent) return false;    var public = receiptPublic(node_owner, receipt.destination) orelse return false;    defer public.zero();    return receipt.validateProof(proof, &public, now);}/// Marks one receipt delivered and gives its round trip time, counted in seconds from the moment/// the packet left to the current instant, as Reticulum@1.5.0 RNS/Packet.py:439-461,530-536/// concludes it, so a caller learns that the bytes it sent arrived and how long the round trip/// took. The call then releases the receipt together with its timer. The caller has already found/// the receipt, and a hash with no receipt behind it is unreachable here.pub fn concludeReceipt(node_owner: *node.Node, hash: packet.Hash, now: node.Seconds) void {    const receipt = node_owner.receipts.find(hash) orelse unreachable;    const rtt = now -| receipt.sent_at;    _ = node_owner.timers.cancel(.{ .receipt = hash });    _ = node_owner.receipts.remove(hash);    node_owner.effects.push(.{ .receipt_update = .{        .packet_hash = hash,        .status = .delivered,        .rtt = rtt,    } }) catch unreachable;}fn acceptProof(    node_owner: *node.Node,    value: wire.Packet,    raw: []const u8,    interface: carrier.Index,    frame_hash: packet.Hash,    now: node.Seconds,) node.StepError!void {    const relayed = try node.transport.relay.proof(        node_owner,        value,        raw,        interface,        frame_hash,        now,    );    try node.outbound.reserve(node_owner, 1, 0);    const decoded = wire.proof.decode(value.payload) catch {        if (!relayed) diagnostic(node_owner, .proof_rejected, frame_hash);        return;    };    switch (decoded) {        .explicit => |explicit| {            const receipt = node_owner.receipts.find(explicit.packet_hash) orelse {                if (!relayed) diagnostic(node_owner, .proof_rejected, frame_hash);                return;            };            if (!validateReceipt(node_owner, receipt, decoded, now)) {                if (!relayed) diagnostic(node_owner, .proof_rejected, frame_hash);                return;            }            concludeReceipt(node_owner, explicit.packet_hash, now);        },        .implicit => {            for (0..node_owner.receipts.capacity.receipts_max) |index| {                if (index == node_owner.receipts.len) break;                const receipt = &node_owner.receipts.entries[index];                const hash = receipt.hash;                if (!validateReceipt(node_owner, receipt, decoded, now)) continue;                concludeReceipt(node_owner, hash, now);                return;            }            if (!relayed) diagnostic(node_owner, .proof_rejected, frame_hash);        },    }}/// Handles one frame that arrived on a carrier, from authenticating it through to delivering,/// relaying, or refusing the packet inside, so every byte the node hears from the network passes/// through this call. A carrier index the caller has yet to register reports/// `unregistered_interface`. The carrier's access code is checked and stripped, as Reticulum@1.5.0/// RNS/Transport.py:1636-1701 authenticates carrier frames, and a frame that is too large, that/// carries the wrong flag, or whose code fails reports which of those happened. Bytes that do not/// decode as a packet report `malformed_packet`. The packet then passes the admission rules of/// Reticulum@1.5.0 RNS/Transport.py:1700-1716,1868-1872: one the node has already handled reports/// `duplicate_packet`, and any other refusal reports `packet_filtered`. The hop count rises by one/// before anything else reads it. A path request goes to the path request code. Link traffic goes/// to the link code, which says whether it handled the packet. A context this node carries no/// support for reports `unsupported_context`. A transport node relays a packet that names it as the/// next hop, as Reticulum@1.5.0 RNS/Transport.py:1907-2028 relays HEADER_2 packets, and one with no/// path reports `no_path`. An announce is validated and its path learned, as Reticulum@1.5.0/// RNS/Transport.py:2083-2135,2137-2213,2374-2375 does, and a transport node rebroadcasts it as/// Reticulum@1.5.0 RNS/Transport.py:2401-2455 publishes it. Data addressed to one of the node's own/// destinations is decrypted and delivered, and proved at once under the `.all` strategy, as/// Reticulum@1.5.0 RNS/Transport.py:2490-2533 delivers and proves it. A proof is matched against an/// outstanding receipt, as Reticulum@1.5.0 RNS/Transport.py:2658-2697 validates delivery proofs,/// and one that matches none reports `proof_rejected`. Packets bound for endpoint links reach them/// as Reticulum@1.5.0 RNS/Transport.py:2456-2487,2490-2516,2600-2650 hands them over.pub fn run(node_owner: *node.Node, frame: node.CarrierFrame) node.StepError!void {    const interface_index: usize = frame.interface;    if (interface_index >= node_owner.interfaces.len or        node_owner.interfaces[interface_index].registered == 0)    {        diagnostic(node_owner, .unregistered_interface, null);        return;    }    var access_scratch: [carrier.frame_bytes_max]u8 = undefined;    const raw = authenticatedFrame(        node_owner,        &node_owner.interfaces[interface_index],        frame.bytes,        &access_scratch,    ) orelse return;    var value = wire.decode(raw) catch {        diagnostic(node_owner, .malformed_packet, null);        return;    };    const hash = wire.hash.full(raw) catch unreachable;    const seen = node_owner.duplicate_hashes.contains(hash);    if (packet.filter.admit(&value, seen, node_owner.transport.identity_hash) == .reject) {        diagnostic(node_owner, if (seen) .duplicate_packet else .packet_filtered, hash);        return;    }    value.hops += 1;    if (node.transport.requests.addressed(value)) {        return node.transport.requests.receive(node_owner, value, frame.interface, hash, frame.now);    }    const relayed_link = node.transport.link.relay.find(node_owner, value.destination) != null;    const lrproof = value.packet_type == .proof and value.context == .lrproof;    if (!relayed_link and !lrproof) _ = node_owner.duplicate_hashes.insert(hash);    if (try node.link.receive(node_owner, value, raw, frame, hash)) return;    if (unsupportedContext(value.context)) {        diagnostic(node_owner, .unsupported_context, hash);        return;    }    if (node.transport.relay.applies(node_owner, value)) {        const outcome = try node.transport.relay.run(            node_owner,            value,            raw,            frame.interface,            hash,            frame.now,        );        if (outcome == .relayed) return;        diagnostic(node_owner, .no_path, hash);        if (value.packet_type == .data) return;    }    switch (value.packet_type) {        .announce => try acceptAnnounce(node_owner, value, frame.interface, hash, frame.now),        .data => try acceptData(node_owner, value, frame.interface, hash),        .proof => try acceptProof(node_owner, value, raw, frame.interface, hash, frame.now),        .link_request => unreachable,    }}/// Sends a proof of one packet the node delivered earlier, which Reticulum@1.5.0/// RNS/Transport.py:2527-2530 leaves to the application, so an application under the `.app`/// strategy answers a delivery and the sender learns its packet arrived. The proof goes out on the/// carrier the packet arrived on and on no other, as Reticulum@1.5.0 RNS/Identity.py:943-954/// proves. A packet hash the node has already forgotten returns `error.ProofUnavailable`. A carrier/// that carries no outgoing traffic returns `error.NoOutgoingCarrier`.pub fn prove(node_owner: *node.Node, value: node.ApplicationProve) node.StepError!void {    if (!node_owner.duplicate_hashes.contains(value.packet_hash)) return error.ProofUnavailable;    const carriers = node.outbound.frameCount(node_owner, .{ .one = value.interface });    if (carriers == 0) return error.NoOutgoingCarrier;    try node.outbound.reserve(node_owner, carriers, carriers);    try sendProof(node_owner, value.destination, value.packet_hash, value.interface);}/// Handles one timer that came due, by what the timer names, so every deadline the node armed comes/// back through here because the caller owns the clock. The duplicate-hash timer rotates that table/// to its next generation. The announce timer sweeps the rebroadcast queue, a link timer runs that/// link's watchdog, and the relayed-link timer runs the one a transport node keeps. A receipt timer/// checks its deadline, as Reticulum@1.5.0 RNS/Packet.py:540-548 concludes receipt deadlines: a/// receipt past its timeout is reported failed and dropped, and one still waiting is armed again/// one second after its deadline. Arming it again with no free timer returns `error.TimerFull`, and/// with no free effect slot returns `error.EffectsFull`.pub fn timerExpired(node_owner: *node.Node, value: node.TimerExpired) node.StepError!void {    switch (value.id) {        .hashlist => {            _ = node_owner.timers.cancel(.hashlist);            node_owner.duplicate_hashes.rotate();        },        .announces => try node.transport.retransmit.sweep(node_owner, value.now),        .link => |link_id| try node.link.timerExpired(node_owner, link_id, value),        .link_entries => try node.transport.link.relay.timerExpired(node_owner, value),        .receipt => |hash| {            _ = node_owner.timers.cancel(value.id);            const receipt = node_owner.receipts.find(hash) orelse return;            receipt.checkTimeout(value.now);            if (receipt.status == .failed) {                try node.outbound.reserve(node_owner, 1, 0);                _ = node_owner.receipts.remove(hash);                node_owner.effects.push(.{ .receipt_update = .{                    .packet_hash = hash,                    .status = .failed,                    .rtt = null,                } }) catch unreachable;                return;            }            const deadline = receipt.sent_at +| receipt.timeout +| 1;            node_owner.timers.schedule(value.id, deadline) catch return error.TimerFull;            node_owner.effects.push(.{ .schedule_timer = .{                .id = value.id,                .at = deadline,            } }) catch return error.EffectsFull;        },    }}

Source: lib/reticulum/src/node/root.zig:75

zig
pub const inbound = @import("inbound.zig");

Complete caller list for node.inbound.diagnostic

26 direct callers.

Complete call list for node.inbound.run

14 direct calls.

Audit

Definitions6
Public names6
Members0
Version26.7.0
Revisiondaab053ee433