Skip to documentation
SLOP

tiny.reticulum.node.outbound

Reference tiny.reticulum node outbound

Defined in node.

API (7)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsnode.announcerunprivate sourcelib.reticulum.src.node.inboundacceptDatanode.inboundproveprivate sourcelib.reticulum.src.node.linkacceptKeepaliveprivate sourcelib.reticulum.src.node.linkacceptProof+24 moreprivate sourcelib.reticulum.src.node.outboundselectsnode.outboundframeCount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsnode.linksendnode.outboundrunnode.outboundinsertReceipt
Static calls · unresolved targets: 0 · external targets: 6.
Called byCallsNo direct callsnode.announcerunprivate sourcelib.reticulum.src.node.inboundacceptAnnounceprivate sourcelib.reticulum.src.node.inboundacceptDataprivate sourcelib.reticulum.src.node.inboundacceptProofnode.inboundprove+27 morenode.outboundreserve
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsnode.transitionrunprivate sourcelib.reticulum.src.node.outboundcreatesReceiptprivate sourcelib.reticulum.src.node.outboundencodeFramenode.outboundframeCountnode.outboundinsertReceiptnode.outboundreserve+6 morenode.outboundrun
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsnode.announcerunprivate sourcelib.reticulum.src.node.inboundsendProofnode.linkopenprivate sourcelib.reticulum.src.node.linksendOnLinknode.outboundrun+10 moreprivate sourcelib.reticulum.src.node.outboundprotectprivate sourcelib.reticulum.src.node.outboundselectsnode.outboundtransmit
Static calls · unresolved targets: 0 · external targets: 1.

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.

Complete caller list for node.outbound.reserve

32 direct callers.

Complete call list for node.outbound.run

11 direct calls.

Complete caller list for node.outbound.transmit

15 direct callers.

Audit

Definitions8
Public names8
Members3
Version26.7.0
Revisiondaab053ee433