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 }