Skip to documentation
SLOP

tiny.reticulum.node.transport.links

Reference tiny.reticulum node transport links

Defined in node.transport.

API (22)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...test sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.Stateactivatenode.transport.links.Tableactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...test sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.links.Tablecount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...test sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.Statedeinitnode.transport.links.Tabledeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsnode.transport.links.Tableinsertnode.transport.links.Tableremovetest sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.links.Tablefind
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsnode.transport.links.Tableinserttest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...test sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.links.Tablefull
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...test sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.Stateinitnode.transport.links.Tableinit
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallstest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...test sourcelib.reticulum.src.node.transport.linkstest: links admit maximum and reject ...node.transport.links.Tablefindnode.transport.links.Tablefullnode.transport.links.Tableinsert
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.node.transport.linkstest: link removal keeps order and ze...node.transport.links.Tablefindnode.transport.links.Tableremove
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/reticulum/src/node/transport/links.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const carrier = @import("../../carrier/root.zig");const ProofStrategy = @import("../../destination/root.zig").registry.ProofStrategy;pub const Seconds = u64;/// Distinguishes which of a link's two ends this node is, so a caller tells/// which end of a session this node holds, because the two ends answer for/// different things. Reticulum@1.5.0 RNS/Link.py:274-280 carries the same/// distinction on its own object.pub const Role = enum(u1) {    initiator,    responder,};/// Tracks how far along a link in the pool is, so a caller knows whether a/// session may carry traffic yet and how close it is to closing. The end that/// opened a link waits at `pending` until a proof checks out. Silence lasting/// twice the keepalive period moves a link to `stale`, and five seconds more of/// it closes the link. One packet arriving over the link's own carrier brings/// the link back to `active`, as Reticulum@1.5.0/// RNS/Link.py:110-113,753-766,938-946 arranges.pub const Status = enum(u2) {    pending,    handshake,    active,    stale,};/// Records one link this node holds an end of, so a caller sees the keys and/// the instants that decide a session's deadlines. On an initiator, the/// ephemeral Ed25519 private key stays and the X25519 one is wiped at the/// moment the link activates. On a responder, both private key fields read as/// zero throughout. The instants of the last inbound packet, the last outbound/// packet, the last keepalive sent, and the last validated data proof only move/// forward, and they fix the keepalive and stale deadlines. The closing second/// carries a value only while a link sits at `stale`, and reads zero at every/// other moment, as Reticulum@1.5.0 RNS/Link.py:245-249,744-766 arranges.pub const Entry = struct {    id: [16]u8,    destination: [16]u8,    encryption_private: [32]u8,    signing_private: [32]u8,    peer_signing_public: [32]u8,    derived_key: [64]u8,    request_time: Seconds,    establishment_timeout: Seconds,    activated_at: Seconds,    last_inbound: Seconds,    last_outbound: Seconds,    last_keepalive: Seconds,    last_proof: Seconds,    close_at: Seconds,    rtt: f64,    expected_hops: u8,    attached: carrier.Index,    role: Role,    status: Status,    rebalanced: bool,    proof_strategy: ProofStrategy,    /// Writes zero over every byte of the record, key material included, so a    /// closed session leaves no key material behind in the pool.    pub fn zero(self: *Entry) void {        std.crypto.secureZero(u8, std.mem.asBytes(self));    }};const TableLimits = struct {    links_max: usize,};const TableCapacity = struct {    links_max: usize,    storage_bytes: usize,    pub const DeriveError = error{ InvalidLimit, CapacityOverflow };    pub fn derive(limits: TableLimits) DeriveError!TableCapacity {        if (limits.links_max == 0) return error.InvalidLimit;        const storage_bytes = alloc_phase.capacity.mul(            usize,            limits.links_max,            @sizeOf(Entry),        ) catch return error.CapacityOverflow;        return .{ .links_max = limits.links_max, .storage_bytes = storage_bytes };    }};/// Provides room for `links_max` links this node holds an end of, carved from/// bytes the caller supplies. Links sit in the order they were written, and one/// link id appears at most once.pub const Table = struct {    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    storage: Storage,    entries: []Entry,    len: usize = 0,    pub const storage_alignment: usize = 8;    pub const Storage = []align(storage_alignment) u8;    pub const Limits: type = TableLimits;    pub const Capacity: type = TableCapacity;    pub const Exhaustion = error{Full};    pub const InitError = Capacity.DeriveError || error{StorageLengthMismatch};    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = 65_536,        .cleanup_steps_per_call_max = 0,        .cleanup_calls_at_capacity_max = 0,    };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "reticulum.links",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{.{                    .id = "caller_link_table",                    .lifetime = .transferred,                    .detail = "caller storage for open link endpoints and their keys",                }},                .excluded = &.{                    "link frames in effect storage",                    "local destination identities",                },            },            .capacity = .{                .inputs = &.{alloc_phase.capacity.bindInput(                    Limits,                    "links_max",                    "links_max",                )},                .type_selectors = &.{alloc_phase.capacity.bindType(Entry, "link")},                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{                        .node = 0,                        .coefficient = .{ .size_of_concrete_type = 0 },                    } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 1,                }},            },            .overload = .{                .kind = .reject_before_mutation,                .detail = "a full link table rejects a new link and keeps every open link",            },            .risks = .{                .transitive = .{                    .status = .excluded,                    .detail = "link operations call no allocating owner",                },                .foreign = .{                    .status = .excluded,                    .detail = "link storage crosses no foreign boundary",                },            },            .work = .{ .equation = "link operations scan at most links_max entries" },            .obligations = &.{                .{ .key = "reticulum_links_capacity", .role = .capacity_model },                .{ .key = "reticulum_links_overload", .role = .overload },                .{ .key = "reticulum_links_work", .role = .work_bound },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{ .class = .checked_semantic_fact, .authority = .checker },            },        },    };    pub fn init(storage: Storage, limits: Limits) InitError!Table {        const capacity = try Capacity.derive(limits);        if (storage.len != capacity.storage_bytes) return error.StorageLengthMismatch;        const entries = std.mem.bytesAsSlice(Entry, storage);        std.debug.assert(entries.len == capacity.links_max);        for (entries) |*entry| entry.zero();        return .{            .phase = .initialization,            .capacity = capacity,            .storage = storage,            .entries = entries,        };    }    pub fn activate(self: *Table) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.len == 0);        self.phase = .steady;    }    /// Looks one link id up in the pool, answering null when the id is absent,    /// so a caller finds the session an arriving packet belongs to. The lookup    /// asserts along the way that the id matched at most one link.    pub fn find(self: *Table, id: [16]u8) ?*Entry {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.links_max);        var found: ?*Entry = null;        for (self.entries[0..self.len]) |*entry| {            if (!std.mem.eql(u8, &entry.id, &id)) continue;            std.debug.assert(found == null);            found = entry;        }        return found;    }    /// Returns whether the pool holds `links_max` links, so a caller asks    /// before it opens or accepts a session, because a full pool refuses one.    pub fn full(self: *const Table) bool {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.links_max);        return self.len == self.capacity.links_max;    }    /// Writes a link at the end of the pool and hands back a pointer to it, so    /// a caller records a session it has opened or accepted. The write asserts    /// that the link id is new to the pool. A pool already holding `links_max`    /// links returns `error.Full` and leaves every link as it was.    pub fn insert(self: *Table, entry: Entry) Exhaustion!*Entry {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.find(entry.id) == null);        if (self.full()) return error.Full;        self.entries[self.len] = entry;        self.len += 1;        std.debug.assert(self.len <= self.capacity.links_max);        return &self.entries[self.len - 1];    }    /// Takes one link id out of the pool and answers whether the id was    /// present, so a caller drops a session that closed. Links written after it    /// move down one place, and the freed place is written over with zeros.    pub fn remove(self: *Table, id: [16]u8) bool {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len], 0..) |entry, index| {            if (!std.mem.eql(u8, &entry.id, &id)) continue;            std.mem.copyForwards(                Entry,                self.entries[index .. self.len - 1],                self.entries[index + 1 .. self.len],            );            self.len -= 1;            self.entries[self.len].zero();            std.debug.assert(self.len < self.capacity.links_max);            std.debug.assert(self.find(id) == null);            return true;        }        return false;    }    pub fn count(self: *const Table) usize {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.links_max);        return self.len;    }    pub fn deinit(self: *Table) Storage {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.links_max);        for (self.entries[0..self.len]) |*entry| entry.zero();        self.phase = .teardown;        const storage = self.storage;        self.* = undefined;        return storage;    }};comptime {    alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Table);}fn entryFor(byte: u8) Entry {    return .{        .id = @splat(byte),        .destination = @splat(0xd0),        .encryption_private = @splat(0xa1),        .signing_private = @splat(0xa2),        .peer_signing_public = @splat(0xa4),        .derived_key = @splat(0xa5),        .request_time = 100,        .establishment_timeout = 366,        .activated_at = 0,        .last_inbound = 100,        .last_outbound = 100,        .last_keepalive = 0,        .last_proof = 0,        .close_at = 0,        .rtt = 0,        .expected_hops = 1,        .attached = 0,        .role = .responder,        .status = .handshake,        .rebalanced = false,        .proof_strategy = .none,    };}test "links admit maximum and reject maximum plus one" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_links_capacity",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_links_overload",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_links_work",        ), null, null, null, null, null, null);    }    const capacity = comptime TableCapacity.derive(.{ .links_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, .{ .links_max = 3 });    table.activate();    defer _ = table.deinit();    for (1..4) |value| _ = try table.insert(entryFor(@intCast(value)));    try std.testing.expect(table.full());    try std.testing.expectError(error.Full, table.insert(entryFor(4)));    try std.testing.expectEqual(@as(usize, 3), table.count());    for (1..4) |value| try std.testing.expect(table.find(@splat(@intCast(value))) != null);    try std.testing.expect(table.find(@splat(4)) == null);}test "link removal keeps order and zeroes the vacated slot" {    const capacity = comptime TableCapacity.derive(.{ .links_max = 3 }) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, .{ .links_max = 3 });    table.activate();    defer _ = table.deinit();    for (1..4) |value| _ = try table.insert(entryFor(@intCast(value)));    try std.testing.expect(table.remove(@splat(1)));    try std.testing.expect(!table.remove(@splat(1)));    try std.testing.expectEqual(@as(usize, 2), table.count());    try std.testing.expectEqual(@as(u8, 2), table.entries[0].id[0]);    try std.testing.expectEqual(@as(u8, 3), table.entries[1].id[0]);    try std.testing.expect(std.mem.allEqual(u8, std.mem.asBytes(&table.entries[2]), 0));    _ = try table.insert(entryFor(4));    try std.testing.expect(table.full());}test "link capacity rejects zero and overflowing limits" {    try std.testing.expectError(error.InvalidLimit, TableCapacity.derive(.{ .links_max = 0 }));    const overflowing = std.math.maxInt(usize) / @sizeOf(Entry) + 1;    try std.testing.expectError(        error.CapacityOverflow,        TableCapacity.derive(.{ .links_max = overflowing }),    );}

Source: lib/reticulum/src/node/transport/root.zig:106

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

Audit

Definitions23
Public names23
Members33
Version26.7.0
Revisiondaab053ee433