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 }