tiny.reticulum.node.inbound
Defined in node.
API (5)
Actions
Public operations.
concludeReceipt: 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.diagnosticprove: 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.appstrategy answers a delivery and the sender learns its packet arrived.run: 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.timerExpired: 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.
Source
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.
lib.reticulum.src.node.inbound.acceptAnnounce[function] — private source atlib/reticulum/src/node/inbound.zig:91in nearest public ownertiny.reticulum.node.inboundlib.reticulum.src.node.inbound.acceptData[function] — private source atlib/reticulum/src/node/inbound.zig:221in nearest public ownertiny.reticulum.node.inboundlib.reticulum.src.node.inbound.acceptProof[function] — private source atlib/reticulum/src/node/inbound.zig:294in nearest public ownertiny.reticulum.node.inboundlib.reticulum.src.node.inbound.authenticatedFrame[function] — private source atlib/reticulum/src/node/inbound.zig:19in nearest public ownertiny.reticulum.node.inboundtiny.reticulum.node.inbound.run[function] atlib/reticulum/src/node/inbound.zig:362lib.reticulum.src.node.link.acceptClose[function] — private source atlib/reticulum/src/node/link.zig:759in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.acceptKeepalive[function] — private source atlib/reticulum/src/node/link.zig:736in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.acceptLinkProof[function] — private source atlib/reticulum/src/node/link.zig:833in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.acceptProof[function] — private source atlib/reticulum/src/node/link.zig:981in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.acceptRtt[function] — private source atlib/reticulum/src/node/link.zig:1048in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.answerRequest[function] — private source atlib/reticulum/src/node/link.zig:902in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.deliver[function] — private source atlib/reticulum/src/node/link.zig:711in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.link.receive[function] atlib/reticulum/src/node/link.zig:602lib.reticulum.src.node.link.receiveTraffic[function] — private source atlib/reticulum/src/node/link.zig:690in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.routeLinkPacket[function] — private source atlib/reticulum/src/node/link.zig:661in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.routeProof[function] — private source atlib/reticulum/src/node/link.zig:644in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.routeRequest[function] — private source atlib/reticulum/src/node/link.zig:627in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.transport.link.relay.proof[function] atlib/reticulum/src/node/transport/link/relay.zig:296tiny.reticulum.node.transport.link.relay.request[function] atlib/reticulum/src/node/transport/link/relay.zig:169tiny.reticulum.node.transport.link.relay.traffic[function] atlib/reticulum/src/node/transport/link/relay.zig:239tiny.reticulum.node.transport.relay.proof[function] atlib/reticulum/src/node/transport/relay.zig:74lib.reticulum.src.node.transport.requests.answerKnown[function] — private source atlib/reticulum/src/node/transport/requests.zig:223in nearest public ownertiny.reticulum.node.transport.requestslib.reticulum.src.node.transport.requests.discover[function] — private source atlib/reticulum/src/node/transport/requests.zig:259in nearest public ownertiny.reticulum.node.transport.requeststiny.reticulum.node.transport.requests.receive[function] atlib/reticulum/src/node/transport/requests.zig:117lib.reticulum.src.node.transport.requests.respond[function] — private source atlib/reticulum/src/node/transport/requests.zig:197in nearest public ownertiny.reticulum.node.transport.requeststiny.reticulum.node.transport.retransmit.enter[function] atlib/reticulum/src/node/transport/retransmit.zig:85
Complete call list for node.inbound.run
14 direct calls.
lib.reticulum.src.node.inbound.acceptAnnounce[function] — private source atlib/reticulum/src/node/inbound.zig:91in nearest public ownertiny.reticulum.node.inboundlib.reticulum.src.node.inbound.acceptData[function] — private source atlib/reticulum/src/node/inbound.zig:221in nearest public ownertiny.reticulum.node.inboundlib.reticulum.src.node.inbound.acceptProof[function] — private source atlib/reticulum/src/node/inbound.zig:294in nearest public ownertiny.reticulum.node.inboundlib.reticulum.src.node.inbound.authenticatedFrame[function] — private source atlib/reticulum/src/node/inbound.zig:19in nearest public ownertiny.reticulum.node.inboundtiny.reticulum.node.inbound.diagnostic[function] atlib/reticulum/src/node/inbound.zig:12lib.reticulum.src.node.inbound.unsupportedContext[function] — private source atlib/reticulum/src/node/inbound.zig:66in nearest public ownertiny.reticulum.node.inboundtiny.reticulum.node.link.receive[function] atlib/reticulum/src/node/link.zig:602tiny.reticulum.node.transport.link.relay.find[function] atlib/reticulum/src/node/transport/link/relay.zig:134tiny.reticulum.node.transport.relay.applies[function] atlib/reticulum/src/node/transport/relay.zig:22tiny.reticulum.node.transport.relay.run[function] atlib/reticulum/src/node/transport/relay.zig:37tiny.reticulum.node.transport.requests.addressed[function] atlib/reticulum/src/node/transport/requests.zig:102tiny.reticulum.node.transport.requests.receive[function] atlib/reticulum/src/node/transport/requests.zig:117tiny.reticulum.packet.filter.admit[function] atlib/reticulum/src/packet/filter.zig:35tiny.reticulum.wire.hash.full[function] atlib/reticulum/src/wire/hash.zig:40
Audit
| Definitions | 6 |
|---|---|
| Public names | 6 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |