tiny.reticulum.node.transport.announces
Defined in node.transport.
API (31)
Actions
Public operations.
Entry.hold: Puts the entry's current record behind a new one, following Reticulum@1.5.0 RNS/Transport.py:3438-3444.Entry.release: Returns a held record to the front and gives it a due second later thannow, following Reticulum@1.5.0 RNS/Transport.py:791-794.Record.init: Builds a record carrying its own copy of the announce payload, which runs to 465 bytes at most.Record.payloadTable.activateTable.admits: Answers whetherinsertwould take a record for this destination.Table.countTable.deinitTable.earliestDue: Returns the earliest due second among the pending records.Table.findTable.initTable.insert: Writes the entry for one destination, following Reticulum@1.5.0 RNS/Transport.py:2279-2289.Table.removeTable.removeAt: Takes the entry at one position out, leaving the rest in the order they were written.
Types and contracts
Public types and contracts.
Entry: One destination with the rebroadcast queued for it and any second record waiting its turn.Fields: The values that describe one new pending rebroadcast.Record: One pending rebroadcast, matching the record at Reticulum@1.5.0 RNS/Transport.py:4061-4069.SecondsTable: Pending announce rebroadcasts, matching Reticulum@1.5.0 RNS/Transport.py:2279-2289.Table.CapacityTable.ExhaustionTable.InitErrorTable.LimitsTable.Storage
Values and defaults
Public values and defaults.
Table.claimTable.storage_alignmentTable.work_limitsfirst_delay: The whole second this port waits before a first rebroadcast, standing in for the wait of up to half a second at Reticulum@1.5.0 RNS/Transport.py:2254.local_rebroadcasts_max: Two rebroadcasts heard from neighbors finish a pending entry, the count Reticulum@1.5.0 RNS/Transport.py:129 fixes.retries_max: One retry follows the first rebroadcast, the allowance Reticulum@1.5.0 RNS/Transport.py:120 sets.retry_delay: The whole second this port waits before a retry, standing in for the 5.5 second wait at Reticulum@1.5.0 RNS/Transport.py:751.
Source
Source: lib/reticulum/src/node/transport/announces.zig
zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const carrier = @import("../../carrier/root.zig");const destination = @import("../../destination/root.zig");pub const Seconds = u64;/// Two rebroadcasts heard from neighbors finish a pending entry, the count/// Reticulum@1.5.0 RNS/Transport.py:129 fixes.pub const local_rebroadcasts_max: u8 = 2;/// One retry follows the first rebroadcast, the allowance Reticulum@1.5.0/// RNS/Transport.py:120 sets.pub const retries_max: u8 = 1;/// The whole second this port waits before a first rebroadcast, standing in for/// the wait of up to half a second at Reticulum@1.5.0 RNS/Transport.py:2254.pub const first_delay: Seconds = 1;/// The whole second this port waits before a retry, standing in for the 5.5/// second wait at Reticulum@1.5.0 RNS/Transport.py:751.pub const retry_delay: Seconds = 6;/// The values that describe one new pending rebroadcast.pub const Fields = struct { destination: [16]u8, due: Seconds, timestamp: Seconds, retries: u8, hops: u8, block_rebroadcasts: bool, attached: ?carrier.Index, context_flag: u1, payload: []const u8,};/// One pending rebroadcast, matching the record at Reticulum@1.5.0/// RNS/Transport.py:4061-4069.pub const Record = struct { destination: [16]u8, due: Seconds, timestamp: Seconds, retries: u8, hops: u8, local_rebroadcasts: u8, block_rebroadcasts: bool, attached: ?carrier.Index, context_flag: u1, payload_len: u16, payload_bytes: [destination.announce.payload_bytes_max]u8, /// Builds a record carrying its own copy of the announce payload, which /// runs to 465 bytes at most. pub fn init(fields: Fields) Record { std.debug.assert(fields.payload.len <= destination.announce.payload_bytes_max); std.debug.assert(fields.retries <= retries_max); var record = Record{ .destination = fields.destination, .due = fields.due, .timestamp = fields.timestamp, .retries = fields.retries, .hops = fields.hops, .local_rebroadcasts = 0, .block_rebroadcasts = fields.block_rebroadcasts, .attached = fields.attached, .context_flag = fields.context_flag, .payload_len = @intCast(fields.payload.len), .payload_bytes = @splat(0), }; @memcpy(record.payload_bytes[0..fields.payload.len], fields.payload); return record; } pub fn payload(self: *const Record) []const u8 { std.debug.assert(self.payload_len <= self.payload_bytes.len); return self.payload_bytes[0..self.payload_len]; }};/// One destination with the rebroadcast queued for it and any second record/// waiting its turn.pub const Entry = struct { record: Record, held: ?Record, /// Puts the entry's current record behind a new one, following /// Reticulum@1.5.0 RNS/Transport.py:3438-3444. pub fn hold(self: *Entry, record: Record) void { std.debug.assert(std.mem.eql(u8, &self.record.destination, &record.destination)); self.held = self.record; self.record = record; } /// Returns a held record to the front and gives it a due second later than /// `now`, following Reticulum@1.5.0 RNS/Transport.py:791-794. The call /// reports whether a held record was there. pub fn release(self: *Entry, now: Seconds) bool { const held = self.held orelse return false; std.debug.assert(std.mem.eql(u8, &self.record.destination, &held.destination)); self.record = held; self.record.due = @max(held.due, now +| 1); self.held = null; return true; }};const TableLimits = struct { announces_max: usize,};const TableCapacity = struct { announces_max: usize, storage_bytes: usize, pub const DeriveError = error{ InvalidLimit, CapacityOverflow }; pub fn derive(limits: TableLimits) DeriveError!TableCapacity { if (limits.announces_max == 0) return error.InvalidLimit; const storage_bytes = alloc_phase.capacity.mul( usize, limits.announces_max, @sizeOf(Entry), ) catch return error.CapacityOverflow; return .{ .announces_max = limits.announces_max, .storage_bytes = storage_bytes }; }};/// Pending announce rebroadcasts, matching Reticulum@1.5.0/// RNS/Transport.py:2279-2289.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.announces", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{.{ .id = "caller_announce_table", .lifetime = .transferred, .detail = "caller storage for pending and held announce rebroadcasts", }}, .excluded = &.{ "borrowed announce payload inputs", "rebroadcast frames in effect storage", }, }, .capacity = .{ .inputs = &.{alloc_phase.capacity.bindInput( Limits, "announces_max", "announces_max", )}, .type_selectors = &.{alloc_phase.capacity.bindType(Entry, "announce")}, .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 the oldest rebroadcast entry, else rejects", }, .risks = .{ .transitive = .{ .status = .excluded, .detail = "announce operations call no allocating owner", }, .foreign = .{ .status = .excluded, .detail = "announce storage crosses no foreign boundary", }, }, .work = .{ .equation = "operations scan at most announces_max entries" }, .obligations = &.{ .{ .key = "reticulum_announces_capacity", .role = .capacity_model }, .{ .key = "reticulum_announces_overload", .role = .overload }, .{ .key = "reticulum_announces_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; } pub fn find(self: *Table, hash: [16]u8) ?*Entry { std.debug.assert(self.phase == .steady); const index = self.indexOf(hash) orelse return null; return &self.entries[index]; } /// Answers whether `insert` would take a record for this destination. pub fn admits(self: *const Table, hash: [16]u8) bool { std.debug.assert(self.phase == .steady); if (self.indexOf(hash) != null) return true; if (self.len < self.capacity.announces_max) return true; return self.oldestRebroadcast() != null; } /// Writes the entry for one destination, following Reticulum@1.5.0 /// RNS/Transport.py:2279-2289. A destination already in the table keeps its /// slot and the record held behind it. With every place taken, the one /// given up is the longest-standing entry the node has already sent at /// least once. A full table with no such entry returns `error.Full` and /// leaves every entry as it was. pub fn insert(self: *Table, record: Record) Exhaustion!*Entry { std.debug.assert(self.phase == .steady); if (self.indexOf(record.destination)) |index| { self.entries[index].record = record; return &self.entries[index]; } if (self.len < self.capacity.announces_max) { self.entries[self.len] = .{ .record = record, .held = null }; self.len += 1; return &self.entries[self.len - 1]; } const index = self.oldestRebroadcast() orelse return error.Full; self.entries[index] = .{ .record = record, .held = null }; return &self.entries[index]; } /// Takes the entry at one position out, leaving the rest in the order they /// were written. pub fn removeAt(self: *Table, index: usize) void { std.debug.assert(self.phase == .steady); std.debug.assert(index < self.len); std.mem.copyForwards( Entry, self.entries[index .. self.len - 1], self.entries[index + 1 .. self.len], ); self.len -= 1; } pub fn remove(self: *Table, hash: [16]u8) bool { const index = self.indexOf(hash) orelse return false; self.removeAt(index); return true; } /// Returns the earliest due second among the pending records. An empty /// table gives null. pub fn earliestDue(self: *const Table) ?Seconds { std.debug.assert(self.phase == .steady); var earliest: ?Seconds = null; for (self.entries[0..self.len]) |*entry| { const due = entry.record.due; if (earliest) |selected| { if (due >= selected) continue; } earliest = due; } return earliest; } 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, hash: [16]u8) ?usize { std.debug.assert(self.len <= self.capacity.announces_max); for (self.entries[0..self.len], 0..) |*entry, index| { if (std.mem.eql(u8, &entry.record.destination, &hash)) return index; } return null; } fn oldestRebroadcast(self: *const Table) ?usize { var oldest: ?usize = null; for (self.entries[0..self.len], 0..) |*entry, index| { if (entry.record.retries == 0) continue; if (oldest) |selected| { if (entry.record.timestamp >= self.entries[selected].record.timestamp) continue; } oldest = index; } return oldest; }};comptime { alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Table);}fn pending(byte: u8, timestamp: Seconds, retries: u8) Record { return Record.init(.{ .destination = @splat(byte), .due = timestamp + first_delay, .timestamp = timestamp, .retries = retries, .hops = 1, .block_rebroadcasts = false, .attached = null, .context_flag = 0, .payload = "announce payload", });}test "announces admit maximum and reject a new destination before any rebroadcast" { comptime { @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_announces_capacity", ), null, null, null, null, null, null); @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_announces_overload", ), null, null, null, null, null, null); @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_announces_work", ), null, null, null, null, null, null); } const capacity = comptime TableCapacity.derive(.{ .announces_max = 3 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .announces_max = 3 }); table.activate(); defer _ = table.deinit(); for (1..4) |value| _ = try table.insert(pending(@intCast(value), 100, 0)); try std.testing.expectEqual(@as(usize, 3), table.count()); try std.testing.expect(!table.admits(@splat(4))); try std.testing.expectError(error.Full, table.insert(pending(4, 101, 0))); try std.testing.expectEqual(@as(usize, 3), table.count()); try std.testing.expect(table.admits(@splat(2))); for (1..4) |value| try std.testing.expect(table.find(@splat(@intCast(value))) != null);}test "announces replace the oldest rebroadcast entry at maximum plus one" { const capacity = comptime TableCapacity.derive(.{ .announces_max = 3 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .announces_max = 3 }); table.activate(); defer _ = table.deinit(); _ = try table.insert(pending(1, 10, 1)); _ = try table.insert(pending(2, 5, 0)); _ = try table.insert(pending(3, 7, 1)); try std.testing.expect(table.admits(@splat(4))); _ = try table.insert(pending(4, 20, 0)); try std.testing.expectEqual(@as(usize, 3), table.count()); try std.testing.expect(table.find(@splat(3)) == null); for ([_]u8{ 1, 2, 4 }) |byte| try std.testing.expect(table.find(@splat(byte)) != null); try std.testing.expectEqual(@as(Seconds, 6), table.earliestDue().?);}test "Reticulum@1.5.0 RNS/Transport.py:791-794 releases a held record for the next sweep" { const capacity = comptime TableCapacity.derive(.{ .announces_max = 1 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .announces_max = 1 }); table.activate(); defer _ = table.deinit(); const entry = try table.insert(pending(9, 10, 0)); var response = pending(9, 12, 1); response.block_rebroadcasts = true; entry.hold(response); try std.testing.expect(entry.record.block_rebroadcasts); _ = try table.insert(response); try std.testing.expect(table.find(@splat(9)).?.held != null); try std.testing.expect(entry.release(40)); try std.testing.expect(!entry.record.block_rebroadcasts); try std.testing.expectEqual(@as(Seconds, 41), entry.record.due); try std.testing.expect(entry.held == null); try std.testing.expect(!entry.release(41)); try std.testing.expect(table.remove(@splat(9))); try std.testing.expectEqual(@as(usize, 0), table.count());}Source: lib/reticulum/src/node/transport/root.zig:102
zig
pub const announces = @import("announces.zig");Audit
| Definitions | 32 |
|---|---|
| Public names | 32 |
| Members | 28 |
| Version | 26.7.0 |
| Revision | daab053ee433 |