Skip to documentation
SLOP

tiny.reticulum.node.transport.link.entries

Reference tiny.reticulum node transport link entries

Defined in node.transport.link.

API (23)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.link.entries...py:849-856 drops relays one second af...test sourcelib.reticulum.src.node.transport.link.entriestest: a relay deadline at the largest...test sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...test sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tableactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...test sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tablecount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.link.entries...py:849-856 drops relays one second af...test sourcelib.reticulum.src.node.transport.link.entriestest: a relay deadline at the largest...test sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...test sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tabledeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.link.entries...py:849-856 drops relays one second af...test sourcelib.reticulum.src.node.transport.link.entriestest: a relay deadline at the largest...node.transport.link.entries.Tableearliest
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.link.entries...py:849-856 drops relays one second af...test sourcelib.reticulum.src.node.transport.link.entriestest: a relay deadline at the largest...node.transport.link.entries.Tableexpired
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsnode.transport.link.entries.Tableinsertnode.transport.link.entries.Tableremovetest sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...node.transport.link.entries.Tablefind
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsnode.transport.link.entries.Tableinserttest sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...test sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tablefull
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.reticulum.src.node.transport.link.entries...py:849-856 drops relays one second af...test sourcelib.reticulum.src.node.transport.link.entriestest: a relay deadline at the largest...test sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...test sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tableinit
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.node.transport.link.entries...py:849-856 drops relays one second af...test sourcelib.reticulum.src.node.transport.link.entriestest: a relay deadline at the largest...test sourcelib.reticulum.src.node.transport.link.entriestest: link entries admit maximum and ...test sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tablefindnode.transport.link.entries.Tablefullnode.transport.link.entries.Tableinsert
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.reticulum.src.node.transport.link.entriestest: link entry removal keeps order ...node.transport.link.entries.Tablefindnode.transport.link.entries.Tableremove
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/reticulum/src/node/transport/link/entries.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const carrier = @import("../../../carrier/root.zig");pub const Seconds = u64;/// Nine hundred seconds, the lifetime Reticulum@1.5.0 RNS/Transport.py:149/// gives a validated relay. The figure is the stale time of Reticulum@1.5.0/// RNS/Link.py:97-99 multiplied by 1.25, and this package computes the same/// stale time as twice a keepalive period of at most 360 seconds.pub const validated_timeout: Seconds = 900;/// One link between two other nodes whose packets this node moves. A record/// appears at the moment the node sends an arriving link request onward. The/// record turns validated once the node has sent the answering proof back. The/// record leaves the store at whichever second falls first: the one its proof/// was due by, or the one 900 seconds past its last traffic.pub const Entry = struct {    link_id: [16]u8,    destination: [16]u8,    next_hop: [16]u8,    timestamp: Seconds,    proof_deadline: Seconds,    next_hop_carrier: carrier.Index,    receiving_carrier: carrier.Index,    remaining_hops: u8,    taken_hops: u8,    validated: bool,    /// The earliest second at which this record leaves the store. A record    /// lives out the second its deadline names under Reticulum@1.5.0    /// RNS/Transport.py:850,855, so the answer here is the second after that    /// one.    pub fn dropsAt(self: *const Entry) Seconds {        if (!self.validated) return self.proof_deadline +| 1;        return self.timestamp +| validated_timeout +| 1;    }};const TableLimits = struct {    link_entries_max: usize,};const TableCapacity = struct {    link_entries_max: usize,    storage_bytes: usize,    pub const DeriveError = error{ InvalidLimit, CapacityOverflow };    pub fn derive(limits: TableLimits) DeriveError!TableCapacity {        if (limits.link_entries_max == 0) return error.InvalidLimit;        const storage_bytes = alloc_phase.capacity.mul(            usize,            limits.link_entries_max,            @sizeOf(Entry),        ) catch return error.CapacityOverflow;        return .{            .link_entries_max = limits.link_entries_max,            .storage_bytes = storage_bytes,        };    }};/// The store of links this node moves packets for, holding what Reticulum@1.5.0/// RNS/Transport.py:1998-2009 keeps. Records 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.link_entries",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{.{                    .id = "caller_link_entry_table",                    .lifetime = .transferred,                    .detail = "caller storage for relayed links and their carriers",                }},                .excluded = &.{"relayed link frames in effect storage"},            },            .capacity = .{                .inputs = &.{alloc_phase.capacity.bindInput(                    Limits,                    "link_entries_max",                    "link_entries_max",                )},                .type_selectors = &.{alloc_phase.capacity.bindType(Entry, "link_entry")},                .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 table drops the forwarded request and keeps every relay",            },            .risks = .{                .transitive = .{                    .status = .excluded,                    .detail = "link entry operations call no allocating owner",                },                .foreign = .{                    .status = .excluded,                    .detail = "link entry storage crosses no foreign boundary",                },            },            .work = .{ .equation = "operations scan at most link_entries_max entries" },            .obligations = &.{                .{ .key = "reticulum_link_entries_capacity", .role = .capacity_model },                .{ .key = "reticulum_link_entries_overload", .role = .overload },                .{ .key = "reticulum_link_entries_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.link_entries_max);        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 among the records, answering null when the id is    /// absent. The lookup asserts along the way that the id matched at most one    /// record.    pub fn find(self: *Table, link_id: [16]u8) ?*Entry {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.link_entries_max);        var found: ?*Entry = null;        for (self.entries[0..self.len]) |*entry| {            if (!std.mem.eql(u8, &entry.link_id, &link_id)) continue;            std.debug.assert(found == null);            found = entry;        }        return found;    }    /// Returns whether the table holds `link_entries_max` relays.    pub fn full(self: *const Table) bool {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.link_entries_max);        return self.len == self.capacity.link_entries_max;    }    /// Writes a record at the end of the store and hands back a pointer to it.    /// The write asserts that the link id is new to the store. A table already    /// holding `link_entries_max` relays returns `error.Full` and leaves every    /// relay as it was.    pub fn insert(self: *Table, entry: Entry) Exhaustion!*Entry {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.find(entry.link_id) == null);        if (self.full()) return error.Full;        self.entries[self.len] = entry;        self.len += 1;        return &self.entries[self.len - 1];    }    /// Takes the record for one link id out and answers whether the id was    /// present. Records written after it move down one place, so the rest stay    /// in the order they were written. A pointer handed out earlier for that    /// place or a later one reaches a different record afterwards, so a caller    /// reads what it needs out of a record before it takes one out.    pub fn remove(self: *Table, link_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.link_id, &link_id)) continue;            std.mem.copyForwards(                Entry,                self.entries[index .. self.len - 1],                self.entries[index + 1 .. self.len],            );            self.len -= 1;            std.debug.assert(self.find(link_id) == null);            return true;        }        return false;    }    /// Answers with the soonest second any record in the store leaves it. An    /// empty table gives null.    pub fn earliest(self: *const Table) ?Seconds {        std.debug.assert(self.phase == .steady);        var soonest: ?Seconds = null;        for (self.entries[0..self.len]) |*entry| {            const at = entry.dropsAt();            if (soonest == null or at < soonest.?) soonest = at;        }        return soonest;    }    /// Hands back the first record whose second has arrived by `now`, and null    /// once none has.    pub fn expired(self: *Table, now: Seconds) ?*Entry {        std.debug.assert(self.phase == .steady);        for (self.entries[0..self.len]) |*entry| {            if (now >= entry.dropsAt()) return entry;        }        return null;    }    pub fn count(self: *const Table) usize {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.link_entries_max);        return self.len;    }    pub fn deinit(self: *Table) Storage {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.len <= self.capacity.link_entries_max);        self.phase = .teardown;        const storage = self.storage;        self.* = undefined;        return storage;    }};comptime {    alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Table);}fn entryFor(byte: u8) Entry {    return .{        .link_id = @splat(byte),        .destination = @splat(0xd0),        .next_hop = @splat(0xb0),        .timestamp = 100,        .proof_deadline = 112,        .next_hop_carrier = 1,        .receiving_carrier = 0,        .remaining_hops = 1,        .taken_hops = 1,        .validated = false,    };}test "link entries admit maximum and reject maximum plus one" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_link_entries_capacity",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_link_entries_overload",        ), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(            Table,            "reticulum_link_entries_work",        ), null, null, null, null, null, null);    }    const limits = TableLimits{ .link_entries_max = 3 };    const capacity = comptime TableCapacity.derive(limits) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, limits);    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 entry removal keeps order and frees one slot" {    const limits = TableLimits{ .link_entries_max = 3 };    const capacity = comptime TableCapacity.derive(limits) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, limits);    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].link_id[0]);    try std.testing.expectEqual(@as(u8, 3), table.entries[1].link_id[0]);    _ = try table.insert(entryFor(4));    try std.testing.expect(table.full());}test "Reticulum@1.5.0 RNS/Transport.py:849-856 drops relays one second after each deadline" {    const limits = TableLimits{ .link_entries_max = 2 };    const capacity = comptime TableCapacity.derive(limits) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, limits);    table.activate();    defer _ = table.deinit();    const unvalidated = try table.insert(entryFor(1));    try std.testing.expectEqual(@as(Seconds, 113), unvalidated.dropsAt());    try std.testing.expect(table.expired(112) == null);    try std.testing.expect(table.expired(113) != null);    unvalidated.validated = true;    unvalidated.timestamp = 200;    try std.testing.expectEqual(@as(Seconds, 201 + validated_timeout), unvalidated.dropsAt());    var later = entryFor(2);    later.proof_deadline = 300;    _ = try table.insert(later);    try std.testing.expectEqual(@as(Seconds, 301), table.earliest().?);    try std.testing.expect(table.expired(300) == null);    try std.testing.expect(table.expired(301) != null);}test "a relay deadline at the largest instant saturates and still sweeps" {    const limits = TableLimits{ .link_entries_max = 2 };    const capacity = comptime TableCapacity.derive(limits) catch unreachable;    var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined;    var table = try Table.init(&bytes, limits);    table.activate();    defer _ = table.deinit();    const last: Seconds = std.math.maxInt(Seconds);    var unproved = entryFor(1);    unproved.proof_deadline = last;    const held = try table.insert(unproved);    try std.testing.expectEqual(last, held.dropsAt());    var proved = entryFor(2);    proved.validated = true;    proved.timestamp = last;    const kept = try table.insert(proved);    try std.testing.expectEqual(last, kept.dropsAt());    try std.testing.expectEqual(last, table.earliest().?);    try std.testing.expect(table.expired(last - 1) == null);    try std.testing.expect(table.expired(last) != null);}test "link entry capacity rejects zero and overflowing limits" {    try std.testing.expectError(        error.InvalidLimit,        TableCapacity.derive(.{ .link_entries_max = 0 }),    );    const overflowing = std.math.maxInt(usize) / @sizeOf(Entry) + 1;    try std.testing.expectError(        error.CapacityOverflow,        TableCapacity.derive(.{ .link_entries_max = overflowing }),    );}

Source: lib/reticulum/src/node/transport/link/root.zig:47

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

Audit

Definitions24
Public names24
Members16
Version26.7.0
Revisiondaab053ee433