tiny.reticulum.node.transport.retransmit
Defined in node.transport.
API (7)
Actions
Public operations.
canSchedule: Answers whether a slot stands free for the timer that drives the rebroadcast queue, so a caller checks before changing any table because a sweep with nowhere to put its timer has to fail the whole step.detect: Completes entries that neighbors rebroadcast, following Reticulum@1.5.0 RNS/Transport.py:2093-2116, so the node learns its own copy is no longer needed when a neighbor carries the same announce.enter: Queues the announce with its turn set one second ahead, as Reticulum@1.5.0 RNS/Transport.py:2248-2289 does, so the step turns the decision into a queued rebroadcast and arms the sweep that will send it.plan: Decides whether a transport node rebroadcasts, following Reticulum@1.5.0 RNS/Transport.py:2267-2289, so a caller can reserve effects for the rebroadcast in the same step before it accepts an announce.schedule: Moves the timer only when the second given falls sooner than the one already set, because entries share one timer.sweep: Sends due rebroadcasts by ascending hops when the timer fires, following Reticulum@1.5.0 RNS/Transport.py:737-799,1239-1240.
Types and contracts
Public types and contracts.
Plan: Describes what becomes of an accepted announce at the rebroadcast queue, so the receive path knows whether accepting an announce also queues one to send.
Source
Source: lib/reticulum/src/node/transport/retransmit.zig
zig
const std = @import("std");const reticulum = @import("../../root.zig");const announces = @import("announces.zig");const destination = reticulum.destination;const node = reticulum.node;const packet = reticulum.packet;const wire = reticulum.wire;/// Describes what becomes of an accepted announce at the rebroadcast queue, so/// the receive path knows whether accepting an announce also queues one to/// send.pub const Plan = enum { none, write, too_large,};const Visit = enum { kept, completed, stopped,};/// Decides whether a transport node rebroadcasts, following Reticulum@1.5.0/// RNS/Transport.py:2267-2289, so a caller can reserve effects for the/// rebroadcast in the same step before it accepts an announce. An announce the/// path table refused leaves the queue untouched, as does one that reached a/// node carrying no traffic for others. An announce marked a path response/// leaves the queue untouched, because it answers the one carrier that asked./// An announce at or past the pathfinder maximum leaves the queue untouched. A/// HEADER_1 payload above 465 bytes has no room left under a HEADER_2/// rebroadcast, so the step reports it and queues nothing.pub fn plan(node_owner: *const node.Node, value: wire.Packet, admitted: bool) Plan { std.debug.assert(value.packet_type == .announce); if (!admitted) return .none; if (!node_owner.transport.enabled) return .none; if (value.context == .path_response) return .none; if (value.hops >= wire.pathfinder_hops) return .none; if (value.payload.len > destination.announce.payload_bytes_max) return .too_large; return .write;}/// Answers whether a slot stands free for the timer that drives the rebroadcast/// queue, so a caller checks before changing any table because a sweep with/// nowhere to put its timer has to fail the whole step.pub fn canSchedule(node_owner: *const node.Node) bool { return node_owner.timers.canScheduleAfterCancel(.announces, null);}/// Completes entries that neighbors rebroadcast, following Reticulum@1.5.0/// RNS/Transport.py:2093-2116, so the node learns its own copy is no longer/// needed when a neighbor carries the same announce. An announce heard at this/// entry's own hop count counts as a neighbor rebroadcast, and two of them/// drop the entry once the node has sent it at least once. An announce heard/// one hop further on means a neighbor already passed this entry along, so the/// step drops the entry when the node has sent it and its next send is still/// ahead.pub fn detect(node_owner: *node.Node, value: wire.Packet, now: node.Seconds) void { if (!node_owner.transport.enabled) return; if (value.transport_id == null) return; const entry = node_owner.transport.announces.find(value.destination) orelse return; std.debug.assert(value.hops >= 1); std.debug.assert(entry.record.hops <= wire.pathfinder_hops); const heard: u16 = value.hops - 1; const record = &entry.record; if (heard == record.hops) { record.local_rebroadcasts +|= 1; if (record.retries > 0 and record.local_rebroadcasts >= announces.local_rebroadcasts_max) { _ = node_owner.transport.announces.remove(value.destination); return; } } const passed_on = heard == @as(u16, record.hops) + 1; if (passed_on and record.retries > 0 and now < record.due) { _ = node_owner.transport.announces.remove(value.destination); }}/// Queues the announce with its turn set one second ahead, as Reticulum@1.5.0/// RNS/Transport.py:2248-2289 does, so the step turns the decision into a/// queued rebroadcast and arms the sweep that will send it. A payload too large/// to relay reports `announce_relay_too_large`. A full announce table reports/// `announce_table_full`.pub fn enter( node_owner: *node.Node, value: wire.Packet, decision: Plan, hash: packet.Hash, now: node.Seconds,) void { switch (decision) { .none => {}, .too_large => node.inbound.diagnostic(node_owner, .announce_relay_too_large, hash), .write => { const due = now +| announces.first_delay; _ = node_owner.transport.announces.insert(announces.Record.init(.{ .destination = value.destination, .due = due, .timestamp = now, .retries = 0, .hops = value.hops, .block_rebroadcasts = false, .attached = null, .context_flag = value.context_flag, .payload = value.payload, })) catch { node.inbound.diagnostic(node_owner, .announce_table_full, hash); return; }; schedule(node_owner, due); }, }}/// Moves the timer only when the second given falls sooner than the one already/// set, because entries share one timer.pub fn schedule(node_owner: *node.Node, at: node.Seconds) void { if (node_owner.timers.scheduledAt(.announces)) |scheduled| { if (scheduled <= at) return; } node_owner.timers.schedule(.announces, at) catch unreachable; node_owner.effects.push(.{ .schedule_timer = .{ .id = .announces, .at = at, } }) catch unreachable;}/// Sends due rebroadcasts by ascending hops when the timer fires, following/// Reticulum@1.5.0 RNS/Transport.py:737-799,1239-1240. A pass that would/// overrun the room for effects breaks off and sets the timer at the second it/// broke off on. An empty table cancels the timer and returns. A destination/// whose identity the node no longer recalls is dropped from the queue.pub fn sweep(node_owner: *node.Node, now: node.Seconds) node.StepError!void { if (node_owner.transport.announces.count() == 0) { _ = node_owner.timers.cancel(.announces); return; } const widest = node.outbound.frameCount(node_owner, .all); try node.outbound.reserve(node_owner, widest + 1, widest); _ = node_owner.timers.cancel(.announces); var hops: u16 = 0; while (hops <= wire.pathfinder_hops) : (hops += 1) { var index: usize = 0; while (index < node_owner.transport.announces.len) { const entry = &node_owner.transport.announces.entries[index]; if (entry.record.hops != hops or entry.record.due > now) { index += 1; continue; } switch (try visit(node_owner, entry, now)) { .kept => index += 1, .completed => node_owner.transport.announces.removeAt(index), .stopped => { schedule(node_owner, now); return; }, } } } if (node_owner.transport.announces.earliestDue()) |due| schedule(node_owner, due);}/// Answers whether an entry has reached the rebroadcast or retry limit,/// following Reticulum@1.5.0 RNS/Transport.py:743-748, so an announce stops/// circulating.fn completed(record: *const announces.Record) bool { if (record.retries > 0 and record.retries >= announces.local_rebroadcasts_max) return true; return record.retries > announces.retries_max;}fn visit(node_owner: *node.Node, entry: *announces.Entry, now: node.Seconds) node.StepError!Visit { const record = &entry.record; std.debug.assert(record.retries <= announces.local_rebroadcasts_max); std.debug.assert(record.due <= now); if (completed(record)) return .completed; if (node_owner.known_identities.recall(record.destination) == null) return .completed; const fanout: node.outbound.Fanout = if (record.attached) |index| .{ .one = index } else .all; const routed = record.attached != null or node_owner.transport.paths.find(record.destination, now) != null; const carriers = if (routed) node.outbound.frameCount(node_owner, fanout) else 0; node.outbound.reserve(node_owner, carriers + 1, carriers) catch return .stopped; if (carriers > 0) { const frame = encode(node_owner, record); _ = node_owner.duplicate_hashes.insert(wire.hash.full(frame) catch unreachable); try node.outbound.transmit(node_owner, fanout, frame); } record.due = now +| announces.retry_delay; record.retries += 1; _ = entry.release(now); return .kept;}/// Assembles the outgoing packet under a HEADER_2 header from the payload the/// queue kept, as Reticulum@1.5.0 RNS/Transport.py:756-779 does, carrying this/// node's own transport identity so the next node learns who passed it on. A/// record that blocks further rebroadcasts is marked a path response.fn encode(node_owner: *node.Node, record: *const announces.Record) []const u8 { const transport_hash = node_owner.transport.identity_hash orelse unreachable; std.debug.assert(record.hops < wire.pathfinder_hops); return wire.encode(.{ .ifac = 0, .header = .two, .context_flag = record.context_flag, .transport = .transport, .destination_type = .single, .packet_type = .announce, .hops = record.hops, .transport_id = transport_hash, .destination = record.destination, .context = if (record.block_rebroadcasts) .path_response else .none, .payload = record.payload(), }, node_owner.scratch) catch unreachable;}Source: lib/reticulum/src/node/transport/root.zig:110
zig
pub const retransmit = @import("retransmit.zig");Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 3 |
| Version | 26.7.0 |
| Revision | daab053ee433 |