tiny.reticulum.node.transport.link.entries
Defined in node.transport.link.
API (23)
Actions
Public operations.
Entry.dropsAt: The earliest second at which this record leaves the store.Table.activateTable.countTable.deinitTable.earliest: Answers with the soonest second any record in the store leaves it.Table.expired: Hands back the first record whose second has arrived bynow, and null once none has.Table.find: Looks one link id up among the records, answering null when the id is absent.Table.full: Returns whether the table holdslink_entries_maxrelays.Table.initTable.insert: Writes a record at the end of the store and hands back a pointer to it.Table.remove: Takes the record for one link id out and answers whether the id was present.
Types and contracts
Public types and contracts.
Entry: One link between two other nodes whose packets this node moves.SecondsTable: The store of links this node moves packets for, holding what Reticulum@1.5.0 RNS/Transport.py:1998-2009 keeps.Table.CapacityTable.ExhaustionTable.InitErrorTable.LimitsTable.Storage
Values and defaults
Public values and defaults.
Table.claimTable.storage_alignmentTable.work_limitsvalidated_timeout: Nine hundred seconds, the lifetime Reticulum@1.5.0 RNS/Transport.py:149 gives a validated relay.
Source
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
| Definitions | 24 |
|---|---|
| Public names | 24 |
| Members | 16 |
| Version | 26.7.0 |
| Revision | daab053ee433 |