tiny.reticulum.node.outbound
Defined in node.
API (7)
Actions
Public operations.
frameCount: Counts the carriers one fanout reaches, so a step can reserve room before it appends frames.insertReceipt: Keeps one packet the node has sent, so the node can conclude its delivery later, and arms a timer one second past the timeout the caller gave, as Reticulum@1.5.0 RNS/Transport.py:1307-1318 and Reticulum@1.5.0 RNS/Packet.py:540-548 time it.reserverun: Sends one application packet to a destination, path or no path, choosing its carriers from what the node knows about reaching it, as Reticulum@1.5.0 RNS/Transport.py:1302-1402,1536-1552 routes and sends a local packet.transmit: Turns a built frame into work for the caller to do by appending one carrier send for each carrier the fanout reaches.
Types and contracts
Public types and contracts.
Fanout: Names the reach of one transmission for every send, so the node counts carriers from it: a single carrier given by name, the whole set of outgoing carriers, or that set less one member.
Values and defaults
Public values and defaults.
first_hop_timeout: Floor for every receipt deadline the node sets: six seconds, the wait the node allows for the first hop of a packet it sent, which Reticulum@1.5.0 RNS/Reticulum.py:142 fixes.
Source
Source: lib/reticulum/src/node/outbound.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;/// Floor for every receipt deadline the node sets: six seconds, the wait the node allows for the/// first hop of a packet it sent, which Reticulum@1.5.0 RNS/Reticulum.py:142 fixes. A receipt's/// deadline adds six more seconds for each hop past the first.pub const first_hop_timeout: node.Seconds = 6;const Target = union(destination.Type) { single: identity.KeyBytes, group: *const [64]u8, plain: void, link: void,};fn target(node_owner: *node.Node, hash: [16]u8) ?Target { if (node_owner.destinations.find(hash)) |entry| { return switch (entry.kind) { .single => if (entry.identity_index) |index| if (node_owner.identityAt(index)) |private| .{ .single = private.publicBytes() } else null else null, .group => .{ .group = &entry.group_key }, .plain => .{ .plain = {} }, .link => .{ .link = {} }, }; } const known = node_owner.known_identities.recall(hash) orelse return null; return .{ .single = known.public_key };}fn packetDestinationType(value: Target) wire.DestinationType { return switch (value) { .single => .single, .group => .group, .plain => .plain, .link => .link, };}fn plaintextContext(context: wire.Context) bool { const raw = context.encode(); if (raw >= wire.Context.resource.encode() and raw <= wire.Context.resource_rcl.encode()) { return true; } return context == .keepalive or context == .cache_request;}fn encryptSingle( node_owner: *node.Node, send: node.ApplicationSend, public_bytes: identity.KeyBytes, out: []u8,) node.StepError![]u8 { var public = identity.Public.fromBytes(public_bytes); defer public.zero(); var rotating = node_owner.known_ratchets.get(send.destination, send.now); const rotating_pointer = if (rotating) |*value| value else null; const encrypted = destination.cipher.encrypt(.{ .single = .{ .public = &public, .ratchet_public = rotating_pointer, .ephemeral_private = &send.ephemeral_private, .iv = send.iv, } }, send.plaintext, out) catch return error.EncryptionFailed; return encrypted.ciphertext;}fn encryptPayload( node_owner: *node.Node, send: node.ApplicationSend, resolved: Target, out: []u8,) node.StepError![]u8 { if (plaintextContext(send.context)) { const plain = destination.cipher.encrypt(.{ .plain = {} }, send.plaintext, out) catch return error.PacketTooLarge; return plain.ciphertext; } return switch (resolved) { .single => |public| encryptSingle(node_owner, send, public, out), .group => |key| blk: { const encrypted = destination.cipher.encrypt(.{ .group = .{ .key = key, .iv = send.iv, } }, send.plaintext, out) catch return error.EncryptionFailed; break :blk encrypted.ciphertext; }, .plain => blk: { const plain = destination.cipher.encrypt(.{ .plain = {} }, send.plaintext, out) catch return error.PacketTooLarge; break :blk plain.ciphertext; }, .link => error.InvalidDestination, };}fn encodeFrame( node_owner: *node.Node, send: node.ApplicationSend, resolved: Target,) node.StepError![]const u8 { const payload_start: usize = wire.header_one_bytes; const payload = try encryptPayload( node_owner, send, resolved, node_owner.scratch[payload_start..], ); const value = wire.Packet{ .ifac = 0, .header = .one, .context_flag = 0, .transport = .broadcast, .destination_type = packetDestinationType(resolved), .packet_type = .data, .hops = send.hops, .transport_id = null, .destination = send.destination, .context = send.context, .payload = payload, }; return wire.encode(value, node_owner.scratch) catch |err| switch (err) { error.InvalidHops => error.InvalidHops, else => error.PacketTooLarge, };}fn createsReceipt(send: node.ApplicationSend, resolved: Target) bool { if (!send.create_receipt) return false; switch (resolved) { .plain, .link => return false, .single, .group => {}, } const context = send.context.encode(); if (context >= wire.Context.resource.encode() and context <= wire.Context.resource_rcl.encode()) return false; if (context >= wire.Context.keepalive.encode() and context <= wire.Context.lrproof.encode()) return false; return true;}/// Names the reach of one transmission for every send, so the node counts carriers from it: a/// single carrier given by name, the whole set of outgoing carriers, or that set less one member.pub const Fanout = union(enum) { one: carrier.Index, all: void, all_except: carrier.Index,};fn selects(node_owner: *const node.Node, fanout: Fanout, index: usize) bool { std.debug.assert(index < node_owner.interfaces.len); const registration = node_owner.interfaces[index]; if (registration.registered == 0) return false; if (registration.outgoing == 0) return false; return switch (fanout) { .one => |selected| index == selected, .all => true, .all_except => |excluded| index != excluded, };}/// Counts the carriers one fanout reaches, so a step can reserve room before it appends frames. A/// carrier counts only when the caller registered it and allowed sending on it.pub fn frameCount(node_owner: *const node.Node, fanout: Fanout) usize { var count: usize = 0; for (0..node_owner.interfaces.len) |index| { if (selects(node_owner, fanout, index)) count += 1; } std.debug.assert(count <= node_owner.interfaces.len); return count;}pub fn reserve(node_owner: *node.Node, effects: usize, frames: usize) node.StepError!void { std.debug.assert(effects <= node.effects_per_event_max); std.debug.assert(frames <= node.effect_frames_per_event_max); if (effects > node_owner.effects.capacity.effects_max - node_owner.effects.len) { return error.EffectsFull; } if (frames > node_owner.effects.capacity.frames_max - node_owner.effects.frames_used) { return error.EffectsFull; }}fn protect( registration: *const node.Interface, frame: []const u8, protected: *[carrier.frame_bytes_max]u8,) []const u8 { std.debug.assert(frame.len >= 2); std.debug.assert(frame.len <= wire.mtu); const access_config = if (registration.access) |*value| value else return frame; const outgoing = ifac.apply(&access_config.key, access_config.size, frame, protected) catch unreachable; std.debug.assert(outgoing.len == frame.len + access_config.size.byte()); return outgoing;}/// Turns a built frame into work for the caller to do by appending one carrier send for each/// carrier the fanout reaches. Each frame leaves carrying that carrier's access code, which/// Reticulum@1.5.0 RNS/Transport.py:1244-1278 applies to every transmitted frame. No room left in/// the effect list returns `error.EffectsFull`.pub fn transmit(node_owner: *node.Node, fanout: Fanout, frame: []const u8) node.StepError!void { var protected: [carrier.frame_bytes_max]u8 = undefined; for (node_owner.interfaces, 0..) |*registration, index| { if (!selects(node_owner, fanout, index)) continue; node_owner.effects.push(.{ .carrier_send = .{ .interface = @intCast(index), .frame = protect(registration, frame, &protected), } }) catch return error.EffectsFull; }}/// Keeps one packet the node has sent, so the node can conclude its delivery later, and arms a/// timer one second past the timeout the caller gave, as Reticulum@1.5.0 RNS/Transport.py:1307-1318/// and Reticulum@1.5.0 RNS/Packet.py:540-548 time it. A full receipt table drops its oldest entry/// and cancels that entry's timer. The node reports that dropped receipt to the application as a/// `receipt_update` effect with status `culled` and no round trip time. No free timer returns/// `error.TimerFull`, and no free effect slot returns `error.EffectsFull`.pub fn insertReceipt( node_owner: *node.Node, destination_hash: [16]u8, hash: packet.Hash, now: node.Seconds, timeout: node.Seconds,) node.StepError!void { const timer_id = node.TimerId{ .receipt = hash }; const candidate = node_owner.receipts.cullCandidate(hash); const cancel_id: ?node.TimerId = if (candidate) |value| .{ .receipt = value.hash } else null; if (!node_owner.timers.canScheduleAfterCancel(timer_id, cancel_id)) return error.TimerFull; const value = packet.receipt.Receipt{ .hash = hash, .truncated = hash[0..16].*, .destination = destination_hash, .sent_at = now, .timeout = timeout, }; if (node_owner.receipts.insert(value)) |culled| { std.debug.assert(culled.status == .culled); std.debug.assert(std.mem.eql(u8, &culled.hash, &candidate.?.hash)); _ = node_owner.timers.cancel(.{ .receipt = culled.hash }); node_owner.effects.push(.{ .receipt_update = .{ .packet_hash = culled.hash, .status = .culled, .rtt = null, } }) catch return error.EffectsFull; } const deadline = now +| timeout +| 1; node_owner.timers.schedule(timer_id, deadline) catch return error.TimerFull; node_owner.effects.push(.{ .schedule_timer = .{ .id = timer_id, .at = deadline, } }) catch return error.EffectsFull;}fn route( node_owner: *node.Node, send: node.ApplicationSend, resolved: Target,) ?*node.transport.path.Entry { if (resolved != .single) return null; return node_owner.transport.paths.find(send.destination, send.now);}/// Sends one application packet to a destination, path or no path, choosing its carriers from what/// the node knows about reaching it, as Reticulum@1.5.0 RNS/Transport.py:1302-1402,1536-1552 routes/// and sends a local packet. A hop count at or past the pathfinder hop maximum returns/// `error.InvalidHops`. A destination the node has neither registered nor learned from an announce/// returns `error.UnknownDestination`, and a link destination returns `error.InvalidDestination`./// With a known path the packet goes out on that path's carrier, and with none it goes out on every/// outgoing carrier. A path longer than one hop gains a transport header naming the next hop. The/// payload is encrypted to the destination's public key, to a group key, or left plain, by the kind/// of destination and the packet's context. A receipt is created for single and group destinations/// outside the resource and link contexts, with a deadline of six seconds plus six for each hop./// Without a path the node stores the packet's hash, so the copy that comes back is a duplicate. A/// fanout that reaches no carrier returns `error.NoOutgoingCarrier`, and a packet that will not fit/// returns `error.PacketTooLarge`.pub fn run(node_owner: *node.Node, send: node.ApplicationSend) node.StepError!void { if (send.hops >= wire.pathfinder_hops) return error.InvalidHops; const resolved = target(node_owner, send.destination) orelse return error.UnknownDestination; switch (resolved) { .link => return error.InvalidDestination, .single, .group, .plain => {}, } const path = route(node_owner, send, resolved); const fanout: Fanout = if (path) |entry| .{ .one = entry.carrier } else .all; const carriers = frameCount(node_owner, fanout); if (carriers == 0) return error.NoOutgoingCarrier; const raw = try encodeFrame(node_owner, send, resolved); const hash = wire.hash.full(raw) catch unreachable; const inserts = if (path) |entry| entry.hops > 1 else false; const frame = if (inserts) try node.transport.rewrite.insert(raw, path.?.next_hop, node_owner.scratch) else raw; const with_receipt = createsReceipt(send, resolved); const culls = with_receipt and node_owner.receipts.cullCandidate(hash) != null; try reserve(node_owner, carriers + @intFromBool(with_receipt) + @intFromBool(culls), carriers); const hops = if (path) |entry| entry.hops else wire.pathfinder_hops; if (with_receipt) { const timeout = packet.receipt.timeoutFor(first_hop_timeout, hops); try insertReceipt(node_owner, send.destination, hash, send.now, timeout); } if (path == null) _ = node_owner.duplicate_hashes.insert(hash); if (inserts) path.?.timestamp = send.now; try transmit(node_owner, fanout, frame);}Source: lib/reticulum/src/node/root.zig:73
zig
pub const outbound = @import("outbound.zig");Complete caller list for node.outbound.frameCount
29 direct callers.
tiny.reticulum.node.announce.run[function] atlib/reticulum/src/node/announce.zig:96lib.reticulum.src.node.inbound.acceptData[function] — private source atlib/reticulum/src/node/inbound.zig:221in nearest public ownertiny.reticulum.node.inboundtiny.reticulum.node.inbound.prove[function] atlib/reticulum/src/node/inbound.zig:426lib.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.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.linktiny.reticulum.node.link.close[function] atlib/reticulum/src/node/link.zig:549lib.reticulum.src.node.link.deliver[function] — private source atlib/reticulum/src/node/link.zig:711in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.link.open[function] atlib/reticulum/src/node/link.zig:470tiny.reticulum.node.link.prove[function] atlib/reticulum/src/node/link.zig:569tiny.reticulum.node.link.send[function] atlib/reticulum/src/node/link.zig:522lib.reticulum.src.node.link.sendOnLink[function] — private source atlib/reticulum/src/node/link.zig:294in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.watchActive[function] — private source atlib/reticulum/src/node/link.zig:1163in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.watchStale[function] — private source atlib/reticulum/src/node/link.zig:1185in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.outbound.run[function] atlib/reticulum/src/node/outbound.zig:291tiny.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:74tiny.reticulum.node.transport.relay.run[function] atlib/reticulum/src/node/transport/relay.zig:37lib.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:117tiny.reticulum.node.transport.requests.rediscover[function] atlib/reticulum/src/node/transport/requests.zig:321tiny.reticulum.node.transport.requests.rediscoveryFrames[function] atlib/reticulum/src/node/transport/requests.zig:306tiny.reticulum.node.transport.requests.send[function] atlib/reticulum/src/node/transport/requests.zig:281tiny.reticulum.node.transport.requests.waitingFrames[function] atlib/reticulum/src/node/transport/requests.zig:377tiny.reticulum.node.transport.retransmit.sweep[function] atlib/reticulum/src/node/transport/retransmit.zig:134lib.reticulum.src.node.transport.retransmit.visit[function] — private source atlib/reticulum/src/node/transport/retransmit.zig:172in nearest public ownertiny.reticulum.node.transport.retransmit
Complete caller list for node.outbound.reserve
32 direct callers.
tiny.reticulum.node.announce.run[function] atlib/reticulum/src/node/announce.zig:96lib.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.inboundtiny.reticulum.node.inbound.prove[function] atlib/reticulum/src/node/inbound.zig:426tiny.reticulum.node.inbound.timerExpired[function] atlib/reticulum/src/node/inbound.zig:442lib.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.linktiny.reticulum.node.link.close[function] atlib/reticulum/src/node/link.zig:549lib.reticulum.src.node.link.deliver[function] — private source atlib/reticulum/src/node/link.zig:711in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.expireUnactivated[function] — private source atlib/reticulum/src/node/link.zig:1141in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.link.open[function] atlib/reticulum/src/node/link.zig:470tiny.reticulum.node.link.prove[function] atlib/reticulum/src/node/link.zig:569tiny.reticulum.node.link.send[function] atlib/reticulum/src/node/link.zig:522tiny.reticulum.node.link.timerExpired[function] atlib/reticulum/src/node/link.zig:1111lib.reticulum.src.node.link.watchActive[function] — private source atlib/reticulum/src/node/link.zig:1163in nearest public ownertiny.reticulum.node.linklib.reticulum.src.node.link.watchStale[function] — private source atlib/reticulum/src/node/link.zig:1185in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.outbound.run[function] atlib/reticulum/src/node/outbound.zig:291tiny.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.timerExpired[function] atlib/reticulum/src/node/transport/link/relay.zig:354tiny.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:74tiny.reticulum.node.transport.relay.run[function] atlib/reticulum/src/node/transport/relay.zig:37tiny.reticulum.node.transport.requests.receive[function] atlib/reticulum/src/node/transport/requests.zig:117tiny.reticulum.node.transport.requests.send[function] atlib/reticulum/src/node/transport/requests.zig:281tiny.reticulum.node.transport.retransmit.sweep[function] atlib/reticulum/src/node/transport/retransmit.zig:134lib.reticulum.src.node.transport.retransmit.visit[function] — private source atlib/reticulum/src/node/transport/retransmit.zig:172in nearest public ownertiny.reticulum.node.transport.retransmit
Complete call list for node.outbound.run
11 direct calls.
lib.reticulum.src.node.outbound.createsReceipt[function] — private source atlib/reticulum/src/node/outbound.zig:139in nearest public ownertiny.reticulum.node.outboundlib.reticulum.src.node.outbound.encodeFrame[function] — private source atlib/reticulum/src/node/outbound.zig:108in nearest public ownertiny.reticulum.node.outboundtiny.reticulum.node.outbound.frameCount[function] atlib/reticulum/src/node/outbound.zig:175tiny.reticulum.node.outbound.insertReceipt[function] atlib/reticulum/src/node/outbound.zig:230tiny.reticulum.node.outbound.reserve[function] atlib/reticulum/src/node/outbound.zig:184lib.reticulum.src.node.outbound.route[function] — private source atlib/reticulum/src/node/outbound.zig:269in nearest public ownertiny.reticulum.node.outboundlib.reticulum.src.node.outbound.target[function] — private source atlib/reticulum/src/node/outbound.zig:24in nearest public ownertiny.reticulum.node.outboundtiny.reticulum.node.outbound.transmit[function] atlib/reticulum/src/node/outbound.zig:213tiny.reticulum.node.transport.rewrite.insert[function] atlib/reticulum/src/node/transport/rewrite.zig:16tiny.reticulum.packet.receipt.timeoutFor[function] atlib/reticulum/src/packet/receipt.zig:86tiny.reticulum.wire.hash.full[function] atlib/reticulum/src/wire/hash.zig:40
Complete caller list for node.outbound.transmit
15 direct callers.
tiny.reticulum.node.announce.run[function] atlib/reticulum/src/node/announce.zig:96lib.reticulum.src.node.inbound.sendProof[function] — private source atlib/reticulum/src/node/inbound.zig:206in nearest public ownertiny.reticulum.node.inboundtiny.reticulum.node.link.open[function] atlib/reticulum/src/node/link.zig:470lib.reticulum.src.node.link.sendOnLink[function] — private source atlib/reticulum/src/node/link.zig:294in nearest public ownertiny.reticulum.node.linktiny.reticulum.node.outbound.run[function] atlib/reticulum/src/node/outbound.zig:291tiny.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:74tiny.reticulum.node.transport.relay.run[function] atlib/reticulum/src/node/transport/relay.zig:37tiny.reticulum.node.transport.requests.answerWaiting[function] atlib/reticulum/src/node/transport/requests.zig:395lib.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.rediscover[function] atlib/reticulum/src/node/transport/requests.zig:321tiny.reticulum.node.transport.requests.send[function] atlib/reticulum/src/node/transport/requests.zig:281lib.reticulum.src.node.transport.retransmit.visit[function] — private source atlib/reticulum/src/node/transport/retransmit.zig:172in nearest public ownertiny.reticulum.node.transport.retransmit
Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 3 |
| Version | 26.7.0 |
| Revision | daab053ee433 |