lib/reticulum/src/node/transport/relay.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const reticulum = @import("../../root.zig");
3
4 const carrier = reticulum.carrier;
5 const node = reticulum.node;
6 const packet = reticulum.packet;
7 const wire = reticulum.wire;
8
9 /// Whether a packet under a HEADER_2 header went out toward the next hop its
10 /// path names, so the receive path tells a packet that went onward from one it
11 /// has to handle itself.
12 pub const Outcome = enum {
13 relayed,
14 unrouted,
15 };
16
17 /// Answers whether an arriving packet is one for this node to move onward, so
18 /// the receive path tells a packet this node carries for others from one
19 /// addressed to the node itself. The condition holds when the node carries
20 /// traffic for others, the packet names a node it is crossing, the packet is no
21 /// announce, and its destination belongs to no address of this node.
22 pub fn applies(node_owner: *node.Node, value: wire.Packet) bool {
23 if (!node_owner.transport.enabled) return false;
24 if (value.transport_id == null) return false;
25 if (value.packet_type == .announce) return false;
26 return node_owner.destinations.find(value.destination) == null;
27 }
28
29 /// Forwards or strips one packet, following Reticulum@1.5.0
30 /// RNS/Transport.py:1929-1946,2012-2028, so the packet moves a hop closer to
31 /// its destination and the step records the way back for the proof. A packet
32 /// more than one hop from its destination keeps the transport header and takes
33 /// the path's next hop, and a packet one hop away loses it. The step writes a
34 /// reverse entry naming the carrier the packet arrived on and the carrier it
35 /// left on. A destination with no live path, or a path whose carrier sends
36 /// nothing, reports `unrouted` and sends no frame.
37 pub fn run(
38 node_owner: *node.Node,
39 value: wire.Packet,
40 raw: []const u8,
41 interface: carrier.Index,
42 hash: packet.Hash,
43 now: node.Seconds,
44 ) node.StepError!Outcome {
45 std.debug.assert(applies(node_owner, value));
46 std.debug.assert(raw.len >= wire.header_two_bytes);
47 const path = node_owner.transport.paths.find(value.destination, now) orelse return .unrouted;
48 std.debug.assert(path.hops >= 1);
49 const fanout: node.outbound.Fanout = .{ .one = path.carrier };
50 if (node.outbound.frameCount(node_owner, fanout) == 0) return .unrouted;
51 try node.outbound.reserve(node_owner, 1, 1);
52 const frame = if (path.hops > 1)
53 node.transport.rewrite.forward(raw, value.hops, path.next_hop, node_owner.scratch)
54 else
55 node.transport.rewrite.strip(raw, value.hops, node_owner.scratch);
56 node_owner.transport.reverse_entries.insert(.{
57 .truncated = hash[0..16].*,
58 .timestamp = now,
59 .receiving = interface,
60 .outbound = path.carrier,
61 });
62 path.timestamp = now;
63 try node.outbound.transmit(node_owner, fanout, frame);
64 return .relayed;
65 }
66
67 /// Relays a proof along its reverse entry, following Reticulum@1.5.0
68 /// RNS/Transport.py:2670-2679, so the proof travels back along the way its
69 /// packet came as the only route the sender is reachable by. A proof that
70 /// arrives on a carrier other than the one the packet left on drops the reverse
71 /// entry and reports `proof_relay_wrong_interface`. The reverse entry is
72 /// removed whether the proof goes on or stops here, so one relayed packet
73 /// carries one proof back.
74 pub fn proof(
75 node_owner: *node.Node,
76 value: wire.Packet,
77 raw: []const u8,
78 interface: carrier.Index,
79 hash: packet.Hash,
80 now: node.Seconds,
81 ) node.StepError!bool {
82 std.debug.assert(value.packet_type == .proof);
83 std.debug.assert(raw.len >= wire.header_one_bytes);
84 if (!node_owner.transport.enabled) return false;
85 const entry = node_owner.transport.reverse_entries.find(value.destination, now) orelse
86 return false;
87 if (entry.outbound != interface) {
88 try node.outbound.reserve(node_owner, 2, 0);
89 _ = node_owner.transport.reverse_entries.remove(value.destination);
90 node.inbound.diagnostic(node_owner, .proof_relay_wrong_interface, hash);
91 return false;
92 }
93 const fanout: node.outbound.Fanout = .{ .one = entry.receiving };
94 const carriers = node.outbound.frameCount(node_owner, fanout);
95 try node.outbound.reserve(node_owner, carriers + 1, carriers);
96 _ = node_owner.transport.reverse_entries.remove(value.destination);
97 if (carriers == 0) return false;
98 const frame = node.transport.rewrite.rehop(raw, value.hops, node_owner.scratch);
99 try node.outbound.transmit(node_owner, fanout, frame);
100 return true;
101 }