lib/reticulum/src/node/transport/rewrite.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const wire = @import("../../wire/root.zig");
  3 
  4 pub const Error = error{PacketTooLarge};
  5 
  6 const transport_header: u8 = 0b0101_0000;
  7 const low_flags: u8 = 0b0000_1111;
  8 
  9 /// Inserts a packet under HEADER_1 into transport, following Reticulum@1.5.0
 10 /// RNS/Transport.py:1345-1354, so a node sending its own packet through a
 11 /// neighbor adds the header field that names that neighbor. HEADER_1 is the
 12 /// 19-byte header shape carried by a packet that names no node it is passing
 13 /// through. Writing into the buffer the packet came in is allowed, because
 14 /// every byte ends up at or above where it started. A packet that would pass
 15 /// the 500-byte maximum returns `error.PacketTooLarge`.
 16 pub fn insert(raw: []const u8, next_hop: [16]u8, out: []u8) Error![]u8 {
 17     std.debug.assert(raw.len >= wire.header_one_bytes);
 18     std.debug.assert(raw.len <= wire.mtu);
 19     std.debug.assert(wire.Flags.decode(raw[0]).header == .one);
 20     const total = raw.len + wire.truncated_hash_bytes;
 21     if (total > wire.mtu) return error.PacketTooLarge;
 22     std.debug.assert(out.len >= total);
 23     const flags = raw[0];
 24     const hops = raw[1];
 25     std.mem.copyBackwards(u8, out[18..total], raw[2..]);
 26     out[0] = transport_header | (flags & low_flags);
 27     out[1] = hops;
 28     @memcpy(out[2..18], &next_hop);
 29     return out[0..total];
 30 }
 31 
 32 /// Names a new next hop in a packet already under HEADER_2, as Reticulum@1.5.0
 33 /// RNS/Transport.py:1935-1940 does. A node carrying a packet that still has
 34 /// hops to go calls this function to name the next neighbor. The output and
 35 /// the input do not overlap.
 36 pub fn forward(raw: []const u8, hops: u8, next_hop: [16]u8, out: []u8) []u8 {
 37     std.debug.assert(raw.len >= wire.header_two_bytes);
 38     std.debug.assert(raw.len <= wire.mtu);
 39     std.debug.assert(wire.Flags.decode(raw[0]).header == .two);
 40     std.debug.assert(out.len >= raw.len);
 41     out[0] = raw[0];
 42     out[1] = hops;
 43     @memcpy(out[2..18], &next_hop);
 44     @memcpy(out[18..raw.len], raw[18..]);
 45     return out[0..raw.len];
 46 }
 47 
 48 /// Strips transport for the last hop, following Reticulum@1.5.0
 49 /// RNS/Transport.py:1941-1946, so a node one hop from the destination takes the
 50 /// transport header off, because the destination expects the shorter shape. The
 51 /// output and the input do not overlap.
 52 pub fn strip(raw: []const u8, hops: u8, out: []u8) []u8 {
 53     std.debug.assert(raw.len >= wire.header_two_bytes);
 54     std.debug.assert(raw.len <= wire.mtu);
 55     std.debug.assert(wire.Flags.decode(raw[0]).header == .two);
 56     const total = raw.len - wire.truncated_hash_bytes;
 57     std.debug.assert(out.len >= total);
 58     out[0] = raw[0] & low_flags;
 59     out[1] = hops;
 60     @memcpy(out[2..total], raw[18..]);
 61     return out[0..total];
 62 }
 63 
 64 /// Writes a new hop count in and copies the rest through, as Reticulum@1.5.0
 65 /// RNS/Transport.py:2674-2676 does for a proof it passes on. A node passing a
 66 /// proof or link traffic on calls this function, because the hop count is the
 67 /// only field that may change. The output and the input do not overlap.
 68 pub fn rehop(raw: []const u8, hops: u8, out: []u8) []u8 {
 69     std.debug.assert(raw.len >= 2);
 70     std.debug.assert(raw.len <= wire.mtu);
 71     std.debug.assert(out.len >= raw.len);
 72     out[0] = raw[0];
 73     out[1] = hops;
 74     @memcpy(out[2..raw.len], raw[2..]);
 75     return out[0..raw.len];
 76 }
 77 
 78 fn encodeData(payload: []const u8, out: []u8) ![]u8 {
 79     return wire.encode(.{
 80         .ifac = 0,
 81         .header = .one,
 82         .context_flag = 1,
 83         .transport = .broadcast,
 84         .destination_type = .single,
 85         .packet_type = .data,
 86         .hops = 5,
 87         .transport_id = null,
 88         .destination = @splat(0x44),
 89         .context = .none,
 90         .payload = payload,
 91     }, out);
 92 }
 93 
 94 test "Reticulum@1.5.0 RNS/Transport.py:1345-1354 insertion keeps the packet hash" {
 95     var raw_storage: [wire.mtu]u8 = undefined;
 96     const raw = try encodeData("routed payload", &raw_storage);
 97     const hash = try wire.hash.full(raw);
 98     var out: [wire.mtu]u8 = undefined;
 99     const inserted = try insert(raw, @splat(0xb7), &out);
100     const decoded = try wire.decode(inserted);
101     try std.testing.expectEqual(wire.HeaderType.two, decoded.header);
102     try std.testing.expectEqual(wire.TransportType.transport, decoded.transport);
103     try std.testing.expectEqual(@as(u1, 0), decoded.context_flag);
104     try std.testing.expectEqual(@as(u8, 5), decoded.hops);
105     const next_hop: [16]u8 = @splat(0xb7);
106     try std.testing.expectEqualSlices(u8, &next_hop, &decoded.transport_id.?);
107     try std.testing.expectEqualSlices(u8, "routed payload", decoded.payload);
108     try std.testing.expectEqualSlices(u8, &hash, &try wire.hash.full(inserted));
109     const in_place = try insert(raw, next_hop, &raw_storage);
110     try std.testing.expectEqualSlices(u8, inserted, in_place);
111 }
112 
113 test "transport insertion admits the MTU and rejects one byte more" {
114     var raw_storage: [wire.mtu]u8 = undefined;
115     const payload: [wire.mtu - wire.header_one_bytes - wire.truncated_hash_bytes + 1]u8 =
116         @splat(0x61);
117     const raw = try encodeData(&payload, &raw_storage);
118     var out: [wire.mtu]u8 = undefined;
119     try std.testing.expectError(error.PacketTooLarge, insert(raw, @splat(0), &out));
120     const fitting = try insert(raw[0 .. raw.len - 1], @splat(0), &out);
121     try std.testing.expectEqual(@as(usize, wire.mtu), fitting.len);
122 }
123 
124 fn encodeTransported(payload: []const u8, out: []u8) ![]u8 {
125     return wire.encode(.{
126         .ifac = 0,
127         .header = .two,
128         .context_flag = 1,
129         .transport = .transport,
130         .destination_type = .single,
131         .packet_type = .data,
132         .hops = 1,
133         .transport_id = @splat(0xb7),
134         .destination = @splat(0x44),
135         .context = .none,
136         .payload = payload,
137     }, out);
138 }
139 
140 test "Reticulum@1.5.0 RNS/Transport.py:1935-1946 forwarding and stripping keep the packet hash" {
141     var raw_storage: [wire.mtu]u8 = undefined;
142     const raw = try encodeTransported("relayed payload", &raw_storage);
143     const hash = try wire.hash.full(raw);
144     const next_hop: [16]u8 = @splat(0xc3);
145     var forwarded_storage: [wire.mtu]u8 = undefined;
146     const forwarded_raw = forward(raw, 2, next_hop, &forwarded_storage);
147     const forwarded = try wire.decode(forwarded_raw);
148     try std.testing.expectEqual(wire.HeaderType.two, forwarded.header);
149     try std.testing.expectEqual(@as(u1, 1), forwarded.context_flag);
150     try std.testing.expectEqual(@as(u8, 2), forwarded.hops);
151     try std.testing.expectEqualSlices(u8, &next_hop, &forwarded.transport_id.?);
152     try std.testing.expectEqualSlices(u8, &hash, &try wire.hash.full(forwarded_raw));
153     var stripped_storage: [wire.mtu]u8 = undefined;
154     const stripped_raw = strip(raw, 2, &stripped_storage);
155     const stripped = try wire.decode(stripped_raw);
156     try std.testing.expectEqual(wire.HeaderType.one, stripped.header);
157     try std.testing.expectEqual(wire.TransportType.broadcast, stripped.transport);
158     try std.testing.expectEqual(@as(u1, 0), stripped.context_flag);
159     try std.testing.expectEqual(@as(u8, 2), stripped.hops);
160     try std.testing.expect(stripped.transport_id == null);
161     try std.testing.expectEqualSlices(u8, "relayed payload", stripped.payload);
162     try std.testing.expectEqualSlices(u8, &hash, &try wire.hash.full(stripped_raw));
163 }
164 
165 test "Reticulum@1.5.0 RNS/Transport.py:2674-2676 proof relay rewrites only the hops byte" {
166     var raw_storage: [wire.mtu]u8 = undefined;
167     const signature: [64]u8 = @splat(0x42);
168     const raw = try wire.encode(.{
169         .ifac = 0,
170         .header = .one,
171         .context_flag = 0,
172         .transport = .broadcast,
173         .destination_type = .single,
174         .packet_type = .proof,
175         .hops = 0,
176         .transport_id = null,
177         .destination = @splat(0x3d),
178         .context = .none,
179         .payload = &signature,
180     }, &raw_storage);
181     var out: [wire.mtu]u8 = undefined;
182     const relayed = rehop(raw, 4, &out);
183     try std.testing.expectEqual(raw.len, relayed.len);
184     try std.testing.expectEqual(raw[0], relayed[0]);
185     try std.testing.expectEqual(@as(u8, 4), relayed[1]);
186     try std.testing.expectEqualSlices(u8, raw[2..], relayed[2..]);
187 }