tiny.reticulum.node.transport.discoveries
Defined in node.transport.
API (22)
Actions
Public operations.
Table.activateTable.admits: Answers whether a request about this destination would find room, so a caller asks before it reserves effects because the insert that follows can refuse.Table.batch: Adds a requesting carrier to the entry for one destination, following Reticulum@1.5.0 RNS/Transport.py:1779-1792, so the answer reaches all of them together.Table.countTable.deinitTable.engage: Marks the entry for one destination engaged and keeps the carriers already waiting, following Reticulum@1.5.0 RNS/Transport.py:3478-3497.Table.find: Returns the live entry for one destination, so a caller checks whether a question for this destination is already outstanding before it sends another.Table.initTable.take: Removes the entry that an announce answers and returns it, following Reticulum@1.5.0 RNS/Transport.py:2350-2352, so a caller sends the answer to each carrier the entry holds.
Types and contracts
Public types and contracts.
Entry: One destination that requesting carriers wait on.Requesters: The set of carriers that wait for one discovered path.SecondsTable: Waiting discovery requests, matching Reticulum@1.5.0 RNS/Transport.py:1777-1792,3478-3497.Table.CapacityTable.ExhaustionTable.InitErrorTable.LimitsTable.Storage
Values and defaults
Public values and defaults.
Table.claimTable.storage_alignmentTable.work_limitstimeout: Fifteen seconds, the wait Reticulum@1.5.0 RNS/Transport.py:131,1786,3482 gives a discovered path, so a caller learns how long a waiting question stays worth answering.
Source
Source: lib/reticulum/src/node/transport/discoveries.zig
zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const carrier = @import("../../carrier/root.zig");pub const Seconds = u64;/// Fifteen seconds, the wait Reticulum@1.5.0 RNS/Transport.py:131,1786,3482/// gives a discovered path, so a caller learns how long a waiting question/// stays worth answering.pub const timeout: Seconds = 15;/// The set of carriers that wait for one discovered path. The set covers every/// carrier index the node can name.pub const Requesters = std.bit_set.ArrayBitSet(u64, 256);/// One destination that requesting carriers wait on.pub const Entry = struct { destination: [16]u8, expires: Seconds, requesters: Requesters, engaged: bool,};/// Reports whether an entry still counts. This port keeps an entry through the/// second it was created plus fifteen and expires it at plus sixteen.fn live(entry: *const Entry, now: Seconds) bool { return now <= entry.expires;}comptime { std.debug.assert(Requesters.bit_length == @as(usize, std.math.maxInt(carrier.Index)) + 1);}const TableLimits = struct { discoveries_max: usize,};const TableCapacity = struct { discoveries_max: usize, storage_bytes: usize, pub const DeriveError = error{ InvalidLimit, CapacityOverflow }; pub fn derive(limits: TableLimits) DeriveError!TableCapacity { if (limits.discoveries_max == 0) return error.InvalidLimit; const storage_bytes = alloc_phase.capacity.mul( usize, limits.discoveries_max, @sizeOf(Entry), ) catch return error.CapacityOverflow; return .{ .discoveries_max = limits.discoveries_max, .storage_bytes = storage_bytes }; }};/// Waiting discovery requests, matching Reticulum@1.5.0/// RNS/Transport.py:1777-1792,3478-3497.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 InitError = Capacity.DeriveError || error{StorageLengthMismatch}; pub const Exhaustion = error{Full}; 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.discoveries", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{.{ .id = "caller_discovery_table", .lifetime = .transferred, .detail = "caller storage for waiting discovery requests and requesters", }}, .excluded = &.{"forwarded request and answer frames in effect storage"}, }, .capacity = .{ .inputs = &.{alloc_phase.capacity.bindInput( Limits, "discoveries_max", "discoveries_max", )}, .type_selectors = &.{alloc_phase.capacity.bindType(Entry, "discovery")}, .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 new destination replaces an expired entry, else rejects", }, .risks = .{ .transitive = .{ .status = .excluded, .detail = "discovery operations call no allocating owner", }, .foreign = .{ .status = .excluded, .detail = "discovery storage crosses no foreign boundary", }, }, .work = .{ .equation = "operations scan at most discoveries_max entries" }, .obligations = &.{ .{ .key = "reticulum_discoveries_capacity", .role = .capacity_model }, .{ .key = "reticulum_discoveries_overload", .role = .overload }, .{ .key = "reticulum_discoveries_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; return .{ .phase = .initialization, .capacity = capacity, .storage = storage, .entries = std.mem.bytesAsSlice(Entry, storage), }; } pub fn activate(self: *Table) void { std.debug.assert(self.phase == .initialization); std.debug.assert(self.len == 0); self.phase = .steady; } /// Returns the live entry for one destination, so a caller checks whether a /// question for this destination is already outstanding before it sends /// another. pub fn find(self: *Table, destination: [16]u8, now: Seconds) ?*Entry { std.debug.assert(self.phase == .steady); const index = self.indexOf(destination) orelse return null; const entry = &self.entries[index]; if (!live(entry, now)) return null; return entry; } /// Answers whether a request about this destination would find room, so a /// caller asks before it reserves effects because the insert that follows /// can refuse. pub fn admits(self: *const Table, destination: [16]u8, now: Seconds) bool { std.debug.assert(self.phase == .steady); if (self.indexOf(destination) != null) return true; if (self.len < self.capacity.discoveries_max) return true; return self.expiredIndex(now) != null; } /// Adds a requesting carrier to the entry for one destination, following /// Reticulum@1.5.0 RNS/Transport.py:1779-1792, so the answer reaches all of /// them together. A new entry starts unengaged, which marks a question this /// node has yet to pass on. A full table with nothing expired to replace /// returns `error.Full`. pub fn batch( self: *Table, destination: [16]u8, requester: carrier.Index, now: Seconds, ) Exhaustion!void { std.debug.assert(self.phase == .steady); if (self.find(destination, now)) |entry| { entry.requesters.set(requester); return; } const target = try self.slot(destination, now); target.* = .{ .destination = destination, .expires = now +| timeout, .requesters = Requesters.empty, .engaged = false, }; target.requesters.set(requester); } /// Marks the entry for one destination engaged and keeps the carriers /// already waiting, following Reticulum@1.5.0 RNS/Transport.py:3478-3497. /// The entry's wait restarts at the second of the call. pub fn engage( self: *Table, destination: [16]u8, requester: carrier.Index, now: Seconds, ) Exhaustion!void { std.debug.assert(self.phase == .steady); var requesters = Requesters.empty; if (self.find(destination, now)) |entry| requesters = entry.requesters; requesters.set(requester); const target = try self.slot(destination, now); target.* = .{ .destination = destination, .expires = now +| timeout, .requesters = requesters, .engaged = true, }; } /// Removes the entry that an announce answers and returns it, following /// Reticulum@1.5.0 RNS/Transport.py:2350-2352, so a caller sends the answer /// to each carrier the entry holds. An entry past its wait is removed and /// reported as null. pub fn take(self: *Table, destination: [16]u8, now: Seconds) ?Entry { std.debug.assert(self.phase == .steady); const index = self.indexOf(destination) orelse return null; const entry = self.entries[index]; self.len -= 1; self.entries[index] = self.entries[self.len]; if (!live(&entry, now)) return null; return entry; } pub fn count(self: *const Table) usize { std.debug.assert(self.phase == .steady); return self.len; } pub fn deinit(self: *Table) Storage { std.debug.assert(self.phase == .steady); self.phase = .teardown; const storage = self.storage; self.* = undefined; return storage; } fn indexOf(self: *const Table, destination: [16]u8) ?usize { std.debug.assert(self.len <= self.capacity.discoveries_max); for (self.entries[0..self.len], 0..) |*entry, index| { if (std.mem.eql(u8, &entry.destination, &destination)) return index; } return null; } fn expiredIndex(self: *const Table, now: Seconds) ?usize { for (self.entries[0..self.len], 0..) |*entry, index| { if (!live(entry, now)) return index; } return null; } fn slot(self: *Table, destination: [16]u8, now: Seconds) Exhaustion!*Entry { if (self.indexOf(destination)) |index| return &self.entries[index]; if (self.len < self.capacity.discoveries_max) { self.len += 1; return &self.entries[self.len - 1]; } const index = self.expiredIndex(now) orelse return error.Full; return &self.entries[index]; }};comptime { alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Table);}test "discoveries admit maximum and reject a new destination at maximum plus one" { comptime { @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_discoveries_capacity", ), null, null, null, null, null, null); @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_discoveries_overload", ), null, null, null, null, null, null); @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_discoveries_work", ), null, null, null, null, null, null); } const limits = TableLimits{ .discoveries_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(); try table.batch(@splat(1), 0, 100); try table.batch(@splat(2), 1, 100); try std.testing.expect(!table.admits(@splat(3), 100)); try std.testing.expectError(error.Full, table.batch(@splat(3), 0, 100)); try std.testing.expectEqual(@as(usize, 2), table.count()); try table.batch(@splat(1), 2, 101); const entry = table.find(@splat(1), 101) orelse return error.TestUnexpectedResult; try std.testing.expect(entry.requesters.isSet(0) and entry.requesters.isSet(2)); try std.testing.expect(!entry.engaged);}test "Reticulum@1.5.0 RNS/Transport.py:975-984 keeps a discovery through plus 15, not plus 16" { const limits = TableLimits{ .discoveries_max = 1 }; 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(); try table.batch(@splat(7), 0, 1_000); try std.testing.expect(table.find(@splat(7), 1_000 + timeout) != null); try std.testing.expect(table.find(@splat(7), 1_000 + timeout + 1) == null); try std.testing.expect(table.admits(@splat(8), 1_000 + timeout + 1)); try table.batch(@splat(8), 1, 1_000 + timeout + 1); try std.testing.expectEqual(@as(usize, 1), table.count()); try std.testing.expect(table.take(@splat(7), 1_000 + timeout + 1) == null);}test "discoveries engage over batched requesters and cover every carrier" { const limits = TableLimits{ .discoveries_max = 1 }; 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(); try table.batch(@splat(5), 0, 10); try table.engage(@splat(5), 255, 12); const engaged = table.find(@splat(5), 12) orelse return error.TestUnexpectedResult; try std.testing.expect(engaged.engaged); try std.testing.expectEqual(@as(Seconds, 12 + timeout), engaged.expires); try std.testing.expect(engaged.requesters.isSet(0) and engaged.requesters.isSet(255)); for (0..256) |index| try table.batch(@splat(5), @intCast(index), 20); const waiting = table.take(@splat(5), 20) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 256), waiting.requesters.count()); try std.testing.expectEqual(@as(usize, 0), table.count());}Source: lib/reticulum/src/node/transport/root.zig:103
zig
pub const discoveries = @import("discoveries.zig");Audit
| Definitions | 23 |
|---|---|
| Public names | 23 |
| Members | 10 |
| Version | 26.7.0 |
| Revision | daab053ee433 |