lib/reticulum/src/node/transport/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const reticulum = @import("../../root.zig");
3
4 const pretty = @import("pretty");
5 const corpus = reticulum.conformance.transport;
6 const node = reticulum.node;
7 const wire = reticulum.wire;
8
9 const start: node.Seconds = 1_700_000_100;
10
11 const limits = node.Limits{
12 .interfaces_max = 3,
13 .destinations_max = 2,
14 .known_identities_max = 4,
15 .known_ratchets_max = 2,
16 .receipts_max = 2,
17 .duplicate_hashes_max = 16,
18 .timers_max = 3,
19 .effects_max = 8,
20 .effect_frames_max = 8,
21 .paths_max = 4,
22 .announces_max = 4,
23 .reverse_entries_max = 4,
24 .path_request_tags_max = 8,
25 .inflight_requests_max = 4,
26 .discoveries_max = 4,
27 .links_max = 2,
28 .link_entries_max = 2,
29 };
30 const transport_hash: [16]u8 = @splat(0xb0);
31 const neighbor_hash: [16]u8 = @splat(0x77);
32
33 fn Harness(comptime selected: node.Limits) type {
34 return struct {
35 const capacity = node.Capacity.derive(selected) catch unreachable;
36 pub const Bytes = [capacity.storage_bytes]u8;
37
38 pub fn init(storage: *align(8) Bytes, transport: bool) !node.Node {
39 var owner = try node.Node.init(storage[0..], selected);
40 owner.activate();
41 for (0..selected.interfaces_max) |index| {
42 try owner.registerCarrier(@intCast(index), true, null);
43 }
44 if (transport) owner.setTransport(transport_hash, true);
45 return owner;
46 }
47 };
48 }
49
50 const Wide = Harness(limits);
51 const Forgetful = Harness(blk: {
52 var selected = limits;
53 selected.known_identities_max = 1;
54 break :blk selected;
55 });
56 const SingleAnnounce = Harness(blk: {
57 var selected = limits;
58 selected.announces_max = 1;
59 break :blk selected;
60 });
61 const Storage = Wide.Bytes;
62
63 fn initNode(storage: *align(8) Storage) !node.Node {
64 return Wide.init(storage, false);
65 }
66
67 fn initTransportNode(storage: *align(8) Storage) !node.Node {
68 return Wide.init(storage, true);
69 }
70
71 fn packetVector(name: []const u8) reticulum.conformance.packet.Vector {
72 for (reticulum.conformance.packet.vectors) |vector| {
73 if (std.mem.eql(u8, vector.name, name)) return vector;
74 }
75 unreachable;
76 }
77
78 fn learn(
79 owner: *node.Node,
80 vector_name: []const u8,
81 hops: u8,
82 interface: reticulum.carrier.Index,
83 now: node.Seconds,
84 ) ![]const node.Effect {
85 return hear(owner, packetVector(vector_name).raw, hops, interface, now);
86 }
87
88 fn announceFrame(vector_name: []const u8) []const u8 {
89 for (reticulum.conformance.announce.vectors) |vector| {
90 if (std.mem.eql(u8, vector.name, vector_name)) return vector.raw;
91 }
92 unreachable;
93 }
94
95 fn hear(
96 owner: *node.Node,
97 raw: []const u8,
98 hops: u8,
99 interface: reticulum.carrier.Index,
100 now: node.Seconds,
101 ) ![]const node.Effect {
102 var frame: [wire.mtu]u8 = undefined;
103 @memcpy(frame[0..raw.len], raw);
104 frame[1] = hops;
105 return owner.step(.{ .carrier_frame = .{
106 .interface = interface,
107 .now = now,
108 .bytes = frame[0..raw.len],
109 .entropy = @splat(0),
110 } });
111 }
112
113 fn sweep(owner: *node.Node, now: node.Seconds) ![]const node.Effect {
114 return owner.step(.{ .timer_expired = .{
115 .id = .announces,
116 .now = now,
117 .entropy = @splat(0),
118 } });
119 }
120
121 fn rebroadcastFrame(
122 raw: []const u8,
123 transport_id: [16]u8,
124 hops: u8,
125 out: *[wire.mtu]u8,
126 ) ![]const u8 {
127 const original = try wire.decode(raw);
128 return wire.encode(.{
129 .ifac = 0,
130 .header = .two,
131 .context_flag = original.context_flag,
132 .transport = .transport,
133 .destination_type = .single,
134 .packet_type = .announce,
135 .hops = hops,
136 .transport_id = transport_id,
137 .destination = original.destination,
138 .context = original.context,
139 .payload = original.payload,
140 }, out);
141 }
142
143 fn transported(
144 transport_id: [16]u8,
145 destination: [16]u8,
146 payload: []const u8,
147 out: *[wire.mtu]u8,
148 ) []const u8 {
149 return wire.encode(.{
150 .ifac = 0,
151 .header = .two,
152 .context_flag = 1,
153 .transport = .transport,
154 .destination_type = .single,
155 .packet_type = .data,
156 .hops = 0,
157 .transport_id = transport_id,
158 .destination = destination,
159 .context = .none,
160 .payload = payload,
161 }, out) catch unreachable;
162 }
163
164 fn proofFrame(truncated: [16]u8, out: *[wire.mtu]u8) []const u8 {
165 const signature: [64]u8 = @splat(0x42);
166 return wire.encode(.{
167 .ifac = 0,
168 .header = .one,
169 .context_flag = 0,
170 .transport = .broadcast,
171 .destination_type = .single,
172 .packet_type = .proof,
173 .hops = 0,
174 .transport_id = null,
175 .destination = truncated,
176 .context = .none,
177 .payload = &signature,
178 }, out) catch unreachable;
179 }
180
181 fn relayData(
182 owner: *node.Node,
183 destination: [16]u8,
184 payload: []const u8,
185 now: node.Seconds,
186 ) !reticulum.packet.Hash {
187 var frame: [wire.mtu]u8 = undefined;
188 const input = transported(transport_hash, destination, payload, &frame);
189 const sent = collect(try owner.step(.{ .carrier_frame = .{
190 .interface = 0,
191 .now = now,
192 .bytes = input,
193 .entropy = @splat(0),
194 } }));
195 try std.testing.expectEqual(@as(usize, 1), sent.count);
196 return wire.hash.full(input);
197 }
198
199 fn sendData(
200 owner: *node.Node,
201 destination: [16]u8,
202 plaintext: []const u8,
203 now: node.Seconds,
204 ) node.StepError![]const node.Effect {
205 const data = packetVector("data-single-hop-0");
206 return owner.step(.{ .application_send = .{
207 .destination = destination,
208 .now = now,
209 .plaintext = plaintext,
210 .ephemeral_private = data.ephemeral_private_key[0..32].*,
211 .iv = data.iv[0..16].*,
212 } });
213 }
214
215 const Sends = struct {
216 count: usize = 0,
217 interface: reticulum.carrier.Index = 0,
218 frame: []const u8 = &.{},
219 deadline: ?node.Seconds = null,
220 };
221
222 fn collect(effects: []const node.Effect) Sends {
223 var sends = Sends{};
224 for (effects) |effect| switch (effect) {
225 .carrier_send => |send| {
226 sends.count += 1;
227 sends.interface = send.interface;
228 sends.frame = send.frame;
229 },
230 .schedule_timer => |scheduled| sends.deadline = scheduled.at,
231 else => {},
232 };
233 return sends;
234 }
235
236 test "Reticulum@1.5.0 RNS/Transport.py:1345-1356 inserts SINGLE data by a learned path" {
237 const vector = packetVector("announce-single-payload-max");
238 const destination = vector.destination_hash[0..16].*;
239 var storage: Storage align(8) = undefined;
240 var owner = try initNode(&storage);
241 defer _ = owner.deinit();
242 const learned = try learn(&owner, vector.name, 1, 2, start);
243 try std.testing.expectEqual(@as(usize, 1), learned.len);
244 try std.testing.expectEqual(@as(u8, 2), learned[0].announce_received.hops);
245 try std.testing.expect(!learned[0].announce_received.path_response);
246 const path = owner.transport.paths.find(destination, start).?;
247 try std.testing.expectEqual(@as(u8, 2), path.carrier);
248 try std.testing.expectEqualSlices(u8, vector.transport_id, &path.next_hop);
249 try std.testing.expectEqualSlices(u8, vector.plaintext, path.announcePayload().?);
250 const sent = collect(try sendData(&owner, destination, "routed", start + 10));
251 try std.testing.expectEqual(@as(usize, 1), sent.count);
252 try std.testing.expectEqual(@as(u8, 2), sent.interface);
253 const decoded = try wire.decode(sent.frame);
254 try std.testing.expectEqual(wire.HeaderType.two, decoded.header);
255 try std.testing.expectEqual(wire.TransportType.transport, decoded.transport);
256 try std.testing.expectEqual(@as(u8, 0), decoded.hops);
257 try std.testing.expectEqualSlices(u8, vector.transport_id, &decoded.transport_id.?);
258 try std.testing.expectEqual(start + 10 + 6 + 6 * 2 + 1, sent.deadline.?);
259 try std.testing.expectEqual(start + 10, path.timestamp);
260 try std.testing.expect(!owner.duplicate_hashes.contains(try wire.hash.full(sent.frame)));
261 }
262
263 test "Reticulum@1.5.0 RNS/Transport.py:1381-1386 sends one-hop data on the path carrier" {
264 const vector = packetVector("announce-single-header1-payload-max");
265 const destination = vector.destination_hash[0..16].*;
266 var storage: Storage align(8) = undefined;
267 var owner = try initNode(&storage);
268 defer _ = owner.deinit();
269 _ = try learn(&owner, vector.name, 0, 1, start);
270 const path = owner.transport.paths.find(destination, start).?;
271 try std.testing.expectEqual(@as(u8, 1), path.hops);
272 try std.testing.expect(path.announcePayload() == null);
273 const sent = collect(try sendData(&owner, destination, "direct", start + 3));
274 try std.testing.expectEqual(@as(usize, 1), sent.count);
275 try std.testing.expectEqual(@as(u8, 1), sent.interface);
276 try std.testing.expectEqual(wire.HeaderType.one, (try wire.decode(sent.frame)).header);
277 try std.testing.expectEqual(start + 3 + 6 + 6 + 1, sent.deadline.?);
278 try std.testing.expectEqual(start, path.timestamp);
279 try std.testing.expect(!owner.duplicate_hashes.contains(try wire.hash.full(sent.frame)));
280 }
281
282 test "Reticulum@1.5.0 RNS/Transport.py:940-943 routes by a path until a week passes" {
283 const vector = packetVector("announce-single-header1-payload-max");
284 const destination = vector.destination_hash[0..16].*;
285 var storage: Storage align(8) = undefined;
286 var owner = try initNode(&storage);
287 defer _ = owner.deinit();
288 _ = try learn(&owner, vector.name, 0, 1, start);
289 const lifetime = node.transport.path.lifetime;
290 const kept = collect(try sendData(&owner, destination, "kept", start + lifetime));
291 try std.testing.expectEqual(@as(usize, 1), kept.count);
292 const culled_at = start + lifetime + 1;
293 const culled = collect(try sendData(&owner, destination, "culled", culled_at));
294 try std.testing.expectEqual(@as(usize, limits.interfaces_max), culled.count);
295 try std.testing.expectEqual(culled_at + 6 + 6 * 128 + 1, culled.deadline.?);
296 }
297
298 test "Reticulum@1.5.0 RNS/Transport.py:1345-1354 inserts 499 bytes and rejects one more" {
299 const vector = packetVector("announce-single-payload-max");
300 const destination = vector.destination_hash[0..16].*;
301 var storage: Storage align(8) = undefined;
302 var owner = try initNode(&storage);
303 defer _ = owner.deinit();
304 _ = try learn(&owner, vector.name, 1, 0, start);
305 const fitting: [reticulum.destination.cipher.encrypted_mdu]u8 = @splat(0x33);
306 const sent = collect(try sendData(&owner, destination, &fitting, start + 1));
307 try std.testing.expectEqual(@as(usize, wire.mtu - 1), sent.frame.len);
308 const receipts = owner.receipts.count();
309 const timers = owner.timers.count();
310 const oversized: [reticulum.destination.cipher.encrypted_mdu + 1]u8 = @splat(0x34);
311 try std.testing.expectError(
312 error.PacketTooLarge,
313 sendData(&owner, destination, &oversized, start + 2),
314 );
315 try std.testing.expectEqual(@as(usize, 0), owner.effects.len);
316 try std.testing.expectEqual(receipts, owner.receipts.count());
317 try std.testing.expectEqual(timers, owner.timers.count());
318 const path = owner.transport.paths.find(destination, start + 2).?;
319 try std.testing.expectEqual(start + 1, path.timestamp);
320 }
321
322 test "Reticulum@1.5.0 RNS/Identity.py:943-954 proves only on the receiving carrier" {
323 const vector = packetVector("data-single-hop-0");
324 const destination = vector.destination_hash[0..16].*;
325 var storage: Storage align(8) = undefined;
326 var owner = try initNode(&storage);
327 defer _ = owner.deinit();
328 owner.identities[0] = reticulum.identity.Private.fromBytes(
329 vector.destination_private_key[0..64].*,
330 );
331 try owner.destinations.register(.{
332 .hash = destination,
333 .kind = .single,
334 .proof_strategy = .all,
335 .identity_index = 0,
336 });
337 const effects = try owner.step(.{ .carrier_frame = .{
338 .interface = 1,
339 .now = start,
340 .bytes = vector.raw,
341 .entropy = @splat(0),
342 } });
343 try std.testing.expectEqual(@as(u8, 1), effects[0].application_delivery.interface);
344 const proved = collect(effects);
345 try std.testing.expectEqual(@as(usize, 1), proved.count);
346 try std.testing.expectEqual(@as(u8, 1), proved.interface);
347 const again = collect(try owner.step(.{ .application_prove = .{
348 .destination = destination,
349 .packet_hash = vector.packet_hash[0..32].*,
350 .interface = 2,
351 .now = start + 1,
352 } }));
353 try std.testing.expectEqual(@as(usize, 1), again.count);
354 try std.testing.expectEqual(@as(u8, 2), again.interface);
355 try std.testing.expectError(error.NoOutgoingCarrier, owner.step(.{ .application_prove = .{
356 .destination = destination,
357 .packet_hash = vector.packet_hash[0..32].*,
358 .interface = limits.interfaces_max,
359 .now = start + 2,
360 } }));
361 }
362
363 test "announce_received fires only when the path table accepts an announce" {
364 const vector = packetVector("announce-single-payload-max");
365 const destination = vector.destination_hash[0..16].*;
366 var storage: Storage align(8) = undefined;
367 var owner = try initNode(&storage);
368 defer _ = owner.deinit();
369 const first = try learn(&owner, vector.name, 1, 0, start);
370 try std.testing.expectEqual(@as(usize, 1), first.len);
371 const replayed = try learn(&owner, vector.name, 1, 1, start + 1);
372 try std.testing.expectEqual(@as(usize, 0), replayed.len);
373 const shorter = try learn(&owner, vector.name, 0, 2, start + 2);
374 try std.testing.expectEqual(@as(usize, 0), shorter.len);
375 try std.testing.expect(owner.known_identities.recall(destination) != null);
376 const path = owner.transport.paths.find(destination, start + 2).?;
377 try std.testing.expectEqual(@as(u8, 2), path.hops);
378 try std.testing.expectEqual(@as(u8, 0), path.carrier);
379 }
380
381 test "Reticulum@1.5.0 RNS/Transport.py:737-799 rebroadcasts twice and then completes" {
382 var storage: Storage align(8) = undefined;
383 var owner = try initTransportNode(&storage);
384 defer _ = owner.deinit();
385 const raw = announceFrame("identity-no-app-data");
386 const heard = try hear(&owner, raw, 0, 0, start);
387 try std.testing.expectEqual(@as(usize, 2), heard.len);
388 try std.testing.expectEqual(start + 1, heard[0].schedule_timer.at);
389 try std.testing.expect(heard[1] == .announce_received);
390 const first = collect(try sweep(&owner, start + 1));
391 try std.testing.expectEqual(@as(usize, limits.interfaces_max), first.count);
392 try std.testing.expectEqual(start + 7, first.deadline.?);
393 var first_frame: [wire.mtu]u8 = undefined;
394 @memcpy(first_frame[0..first.frame.len], first.frame);
395 const copy = first_frame[0..first.frame.len];
396 const decoded = try wire.decode(copy);
397 try std.testing.expectEqual(wire.HeaderType.two, decoded.header);
398 try std.testing.expectEqualSlices(u8, &transport_hash, &decoded.transport_id.?);
399 try std.testing.expectEqual(@as(u8, 1), decoded.hops);
400 try std.testing.expectEqual(wire.Context.none, decoded.context);
401 try std.testing.expectEqualSlices(u8, raw[wire.header_one_bytes..], decoded.payload);
402 try std.testing.expectEqualSlices(u8, &try wire.hash.full(raw), &try wire.hash.full(copy));
403 const second = collect(try sweep(&owner, start + 7));
404 try std.testing.expectEqual(@as(usize, limits.interfaces_max), second.count);
405 try std.testing.expectEqual(start + 13, second.deadline.?);
406 try std.testing.expectEqualSlices(u8, copy, second.frame);
407 try std.testing.expectEqual(@as(usize, 1), owner.transport.announces.count());
408 try std.testing.expectEqual(@as(usize, 0), (try sweep(&owner, start + 13)).len);
409 try std.testing.expectEqual(@as(usize, 0), owner.transport.announces.count());
410 }
411
412 test "Reticulum@1.5.0 RNS/Transport.py:2102-2109 completes after two local rebroadcasts" {
413 var storage: Storage align(8) = undefined;
414 var owner = try initTransportNode(&storage);
415 defer _ = owner.deinit();
416 const raw = announceFrame("identity-no-app-data");
417 _ = try hear(&owner, raw, 0, 0, start);
418 _ = try sweep(&owner, start + 1);
419 var frame: [wire.mtu]u8 = undefined;
420 const echoed = try rebroadcastFrame(raw, neighbor_hash, 1, &frame);
421 const once = try owner.step(.{ .carrier_frame = .{
422 .interface = 1,
423 .now = start + 2,
424 .bytes = echoed,
425 .entropy = @splat(0),
426 } });
427 try std.testing.expectEqual(@as(usize, 0), once.len);
428 try std.testing.expectEqual(@as(usize, 1), owner.transport.announces.count());
429 _ = try owner.step(.{ .carrier_frame = .{
430 .interface = 2,
431 .now = start + 3,
432 .bytes = echoed,
433 .entropy = @splat(0),
434 } });
435 try std.testing.expectEqual(@as(usize, 0), owner.transport.announces.count());
436 try std.testing.expectEqual(@as(usize, 0), (try sweep(&owner, start + 7)).len);
437 }
438
439 test "Reticulum@1.5.0 RNS/Transport.py:2111-2116 completes when a neighbor passes it on" {
440 var storage: Storage align(8) = undefined;
441 var owner = try initTransportNode(&storage);
442 defer _ = owner.deinit();
443 const raw = announceFrame("identity-no-app-data");
444 _ = try hear(&owner, raw, 0, 0, start);
445 _ = try sweep(&owner, start + 1);
446 var frame: [wire.mtu]u8 = undefined;
447 const passed = try rebroadcastFrame(raw, neighbor_hash, 2, &frame);
448 _ = try owner.step(.{ .carrier_frame = .{
449 .interface = 1,
450 .now = start + 3,
451 .bytes = passed,
452 .entropy = @splat(0),
453 } });
454 try std.testing.expectEqual(@as(usize, 0), owner.transport.announces.count());
455 try std.testing.expectEqual(@as(usize, 0), (try sweep(&owner, start + 7)).len);
456 }
457
458 test "Reticulum@1.5.0 RNS/Transport.py:759-762 completes without sending for a forgotten identity" {
459 var storage: Forgetful.Bytes align(8) = undefined;
460 var owner = try Forgetful.init(&storage, true);
461 defer _ = owner.deinit();
462 const forgotten = announceFrame("identity-no-app-data");
463 const kept = announceFrame("identity-app-data");
464 _ = try hear(&owner, forgotten, 0, 0, start);
465 _ = try hear(&owner, kept, 0, 0, start);
466 try std.testing.expectEqual(@as(usize, 2), owner.transport.announces.count());
467 const sent = collect(try sweep(&owner, start + 1));
468 try std.testing.expectEqual(@as(usize, limits.interfaces_max), sent.count);
469 const kept_packet = try wire.decode(kept);
470 const sent_packet = try wire.decode(sent.frame);
471 try std.testing.expectEqualSlices(u8, &kept_packet.destination, &sent_packet.destination);
472 try std.testing.expectEqual(@as(usize, 1), owner.transport.announces.count());
473 }
474
475 test "Reticulum@1.5.0 RNS/Transport.py:1239-1240 sends due rebroadcasts in ascending hops" {
476 var storage: Storage align(8) = undefined;
477 var owner = try initTransportNode(&storage);
478 defer _ = owner.deinit();
479 const far = announceFrame("identity-no-app-data");
480 const near = announceFrame("identity-app-data");
481 _ = try hear(&owner, far, 2, 0, start);
482 _ = try hear(&owner, near, 0, 1, start);
483 const effects = try sweep(&owner, start + 1);
484 try std.testing.expectEqual(@as(usize, 2 * limits.interfaces_max + 1), effects.len);
485 const near_packet = try wire.decode(near);
486 const far_packet = try wire.decode(far);
487 const first = try wire.decode(effects[0].carrier_send.frame);
488 const last = try wire.decode(effects[2 * limits.interfaces_max - 1].carrier_send.frame);
489 try std.testing.expectEqualSlices(u8, &near_packet.destination, &first.destination);
490 try std.testing.expectEqual(@as(u8, 1), first.hops);
491 try std.testing.expectEqualSlices(u8, &far_packet.destination, &last.destination);
492 try std.testing.expectEqual(@as(u8, 3), last.hops);
493 }
494
495 test "an announce sweep stops before exceeding effect capacity and resumes at the same second" {
496 var storage: Storage align(8) = undefined;
497 var owner = try initTransportNode(&storage);
498 defer _ = owner.deinit();
499 const names = [_][]const u8{
500 "identity-no-app-data",
501 "identity-app-data",
502 "ratchet-no-app-data",
503 };
504 for (names) |name| _ = try hear(&owner, announceFrame(name), 0, 0, start);
505 const first = try sweep(&owner, start + 1);
506 try std.testing.expectEqual(@as(usize, 2 * limits.interfaces_max + 1), first.len);
507 try std.testing.expectEqual(start + 1, first[first.len - 1].schedule_timer.at);
508 const resumed = try sweep(&owner, start + 1);
509 try std.testing.expectEqual(@as(usize, limits.interfaces_max + 1), resumed.len);
510 try std.testing.expectEqual(start + 7, resumed[resumed.len - 1].schedule_timer.at);
511 }
512
513 test "Reticulum@1.5.0 RNS/Transport.py:1394-1415 sends no rebroadcast once the path is culled" {
514 var storage: Storage align(8) = undefined;
515 var owner = try initTransportNode(&storage);
516 defer _ = owner.deinit();
517 _ = try hear(&owner, announceFrame("identity-no-app-data"), 0, 0, start);
518 const culled_at = start + node.transport.path.lifetime + 1;
519 const first = try sweep(&owner, culled_at);
520 try std.testing.expectEqual(@as(usize, 1), first.len);
521 try std.testing.expectEqual(culled_at + 6, first[0].schedule_timer.at);
522 const second = try sweep(&owner, culled_at + 6);
523 try std.testing.expectEqual(@as(usize, 1), second.len);
524 try std.testing.expectEqual(culled_at + 12, second[0].schedule_timer.at);
525 try std.testing.expectEqual(@as(usize, 0), (try sweep(&owner, culled_at + 12)).len);
526 try std.testing.expectEqual(@as(usize, 0), owner.transport.announces.count());
527 }
528
529 test "Reticulum@1.5.0 RNS/Transport.py:791-794 sends a held announce at the next sweep" {
530 var storage: Storage align(8) = undefined;
531 var owner = try initTransportNode(&storage);
532 defer _ = owner.deinit();
533 const raw = announceFrame("identity-no-app-data");
534 _ = try hear(&owner, raw, 0, 0, start);
535 const entry = owner.transport.announces.find((try wire.decode(raw)).destination).?;
536 var response = entry.record;
537 response.retries = 1;
538 response.block_rebroadcasts = true;
539 response.attached = 2;
540 entry.hold(response);
541 const answered = collect(try sweep(&owner, start + 1));
542 try std.testing.expectEqual(@as(usize, 1), answered.count);
543 try std.testing.expectEqual(@as(u8, 2), answered.interface);
544 const answer = try wire.decode(answered.frame);
545 try std.testing.expectEqual(wire.Context.path_response, answer.context);
546 try std.testing.expectEqual(start + 2, answered.deadline.?);
547 const held = collect(try sweep(&owner, start + 2));
548 try std.testing.expectEqual(@as(usize, limits.interfaces_max), held.count);
549 try std.testing.expectEqual(wire.Context.none, (try wire.decode(held.frame)).context);
550 try std.testing.expectEqual(start + 8, held.deadline.?);
551 }
552
553 test "a HEADER_1 announce above 465 payload bytes skips its rebroadcast" {
554 const vector = packetVector("announce-single-header1-payload-max");
555 const destination = vector.destination_hash[0..16].*;
556 var storage: Storage align(8) = undefined;
557 var owner = try initTransportNode(&storage);
558 defer _ = owner.deinit();
559 const effects = try learn(&owner, vector.name, 0, 0, start);
560 try std.testing.expectEqual(@as(usize, 2), effects.len);
561 try std.testing.expectEqual(node.Code.announce_relay_too_large, effects[0].diagnostic.code);
562 try std.testing.expect(effects[1] == .announce_received);
563 try std.testing.expectEqual(@as(usize, 0), owner.transport.announces.count());
564 try std.testing.expect(owner.transport.paths.find(destination, start) != null);
565 }
566
567 test "a full announce table rejects a new destination and still learns its path" {
568 var storage: SingleAnnounce.Bytes align(8) = undefined;
569 var owner = try SingleAnnounce.init(&storage, true);
570 defer _ = owner.deinit();
571 _ = try hear(&owner, announceFrame("identity-no-app-data"), 0, 0, start);
572 const rejected = announceFrame("identity-app-data");
573 const effects = try hear(&owner, rejected, 0, 0, start);
574 try std.testing.expectEqual(@as(usize, 2), effects.len);
575 try std.testing.expectEqual(node.Code.announce_table_full, effects[0].diagnostic.code);
576 try std.testing.expect(effects[1] == .announce_received);
577 const destination = (try wire.decode(rejected)).destination;
578 try std.testing.expect(owner.transport.paths.find(destination, start) != null);
579 try std.testing.expectEqual(@as(usize, 1), owner.transport.announces.count());
580 }
581
582 test "Reticulum@1.5.0 RNS/Transport.py:1935-1940 forwards at two remaining hops" {
583 const announce = packetVector("announce-single-payload-max");
584 const destination = announce.destination_hash[0..16].*;
585 var storage: Storage align(8) = undefined;
586 var owner = try initTransportNode(&storage);
587 defer _ = owner.deinit();
588 _ = try learn(&owner, announce.name, 1, 2, start);
589 var frame: [wire.mtu]u8 = undefined;
590 const input = transported(transport_hash, destination, "relayed data", &frame);
591 const effects = try owner.step(.{ .carrier_frame = .{
592 .interface = 0,
593 .now = start + 5,
594 .bytes = input,
595 .entropy = @splat(0),
596 } });
597 try std.testing.expectEqual(@as(usize, 1), effects.len);
598 const sent = effects[0].carrier_send;
599 try std.testing.expectEqual(@as(u8, 2), sent.interface);
600 const forwarded = try wire.decode(sent.frame);
601 try std.testing.expectEqual(wire.HeaderType.two, forwarded.header);
602 try std.testing.expectEqual(@as(u8, 1), forwarded.hops);
603 try std.testing.expectEqual(@as(u1, 1), forwarded.context_flag);
604 try std.testing.expectEqualSlices(u8, announce.transport_id, &forwarded.transport_id.?);
605 const hash = try wire.hash.full(input);
606 try std.testing.expectEqualSlices(u8, &hash, &try wire.hash.full(sent.frame));
607 const reverse = owner.transport.reverse_entries.find(hash[0..16].*, start + 5).?;
608 try std.testing.expectEqual(@as(u8, 0), reverse.receiving);
609 try std.testing.expectEqual(@as(u8, 2), reverse.outbound);
610 const path = owner.transport.paths.find(destination, start + 5).?;
611 try std.testing.expectEqual(start + 5, path.timestamp);
612 }
613
614 test "Reticulum@1.5.0 RNS/Transport.py:1941-1946 strips at one remaining hop" {
615 const announce = packetVector("announce-single-header1-payload-max");
616 const destination = announce.destination_hash[0..16].*;
617 var storage: Storage align(8) = undefined;
618 var owner = try initTransportNode(&storage);
619 defer _ = owner.deinit();
620 _ = try learn(&owner, announce.name, 0, 1, start);
621 var frame: [wire.mtu]u8 = undefined;
622 const input = transported(transport_hash, destination, "last hop", &frame);
623 const sent = collect(try owner.step(.{ .carrier_frame = .{
624 .interface = 0,
625 .now = start + 5,
626 .bytes = input,
627 .entropy = @splat(0),
628 } }));
629 try std.testing.expectEqual(@as(usize, 1), sent.count);
630 try std.testing.expectEqual(@as(u8, 1), sent.interface);
631 const stripped = try wire.decode(sent.frame);
632 try std.testing.expectEqual(wire.HeaderType.one, stripped.header);
633 try std.testing.expectEqual(wire.TransportType.broadcast, stripped.transport);
634 try std.testing.expectEqual(@as(u1, 0), stripped.context_flag);
635 try std.testing.expectEqual(@as(u8, 1), stripped.hops);
636 try std.testing.expectEqualSlices(u8, "last hop", stripped.payload);
637 const hash = try wire.hash.full(input);
638 try std.testing.expectEqualSlices(u8, &hash, &try wire.hash.full(sent.frame));
639 }
640
641 test "Reticulum@1.5.0 RNS/Transport.py:1929,2028 filters a foreign id and reports no_path" {
642 var storage: Storage align(8) = undefined;
643 var owner = try initTransportNode(&storage);
644 defer _ = owner.deinit();
645 var frame: [wire.mtu]u8 = undefined;
646 const unrouted = transported(transport_hash, @splat(0x5c), "nowhere", &frame);
647 const effects = try owner.step(.{ .carrier_frame = .{
648 .interface = 0,
649 .now = start,
650 .bytes = unrouted,
651 .entropy = @splat(0),
652 } });
653 try std.testing.expectEqual(@as(usize, 1), effects.len);
654 try std.testing.expectEqual(node.Code.no_path, effects[0].diagnostic.code);
655 var foreign_frame: [wire.mtu]u8 = undefined;
656 const foreign = transported(@splat(0x99), @splat(0x5c), "elsewhere", &foreign_frame);
657 const filtered = try owner.step(.{ .carrier_frame = .{
658 .interface = 0,
659 .now = start + 1,
660 .bytes = foreign,
661 .entropy = @splat(0),
662 } });
663 try std.testing.expectEqual(@as(usize, 1), filtered.len);
664 try std.testing.expectEqual(node.Code.packet_filtered, filtered[0].diagnostic.code);
665 }
666
667 test "Reticulum@1.5.0 RNS/Transport.py:2670-2677 relays a proof on the receiving carrier" {
668 const announce = packetVector("announce-single-payload-max");
669 const destination = announce.destination_hash[0..16].*;
670 var storage: Storage align(8) = undefined;
671 var owner = try initTransportNode(&storage);
672 defer _ = owner.deinit();
673 _ = try learn(&owner, announce.name, 1, 2, start);
674 const hash = try relayData(&owner, destination, "proved data", start + 5);
675 var frame: [wire.mtu]u8 = undefined;
676 const proof = proofFrame(hash[0..16].*, &frame);
677 const effects = try owner.step(.{ .carrier_frame = .{
678 .interface = 2,
679 .now = start + 6,
680 .bytes = proof,
681 .entropy = @splat(0),
682 } });
683 try std.testing.expectEqual(@as(usize, 1), effects.len);
684 const relayed = effects[0].carrier_send;
685 try std.testing.expectEqual(@as(u8, 0), relayed.interface);
686 try std.testing.expectEqual(proof.len, relayed.frame.len);
687 try std.testing.expectEqual(proof[0], relayed.frame[0]);
688 try std.testing.expectEqual(@as(u8, 1), relayed.frame[1]);
689 try std.testing.expectEqualSlices(u8, proof[2..], relayed.frame[2..]);
690 try std.testing.expect(owner.transport.reverse_entries.find(hash[0..16].*, start + 6) == null);
691 }
692
693 test "proof relay drops a proof from the wrong carrier and expires at plus 481" {
694 const announce = packetVector("announce-single-payload-max");
695 const destination = announce.destination_hash[0..16].*;
696 var storage: Storage align(8) = undefined;
697 var owner = try initTransportNode(&storage);
698 defer _ = owner.deinit();
699 _ = try learn(&owner, announce.name, 1, 2, start);
700 var frame: [wire.mtu]u8 = undefined;
701 const wrong_hash = try relayData(&owner, destination, "wrong carrier", start + 5);
702 const wrong = try owner.step(.{ .carrier_frame = .{
703 .interface = 1,
704 .now = start + 6,
705 .bytes = proofFrame(wrong_hash[0..16].*, &frame),
706 .entropy = @splat(0),
707 } });
708 try std.testing.expectEqual(@as(usize, 2), wrong.len);
709 try std.testing.expectEqual(node.Code.proof_relay_wrong_interface, wrong[0].diagnostic.code);
710 try std.testing.expectEqual(node.Code.proof_rejected, wrong[1].diagnostic.code);
711 const late_hash = try relayData(&owner, destination, "late proof", start + 10);
712 const late = try owner.step(.{ .carrier_frame = .{
713 .interface = 2,
714 .now = start + 10 + node.transport.reverse.timeout + 1,
715 .bytes = proofFrame(late_hash[0..16].*, &frame),
716 .entropy = @splat(0),
717 } });
718 try std.testing.expectEqual(@as(usize, 1), late.len);
719 try std.testing.expectEqual(node.Code.proof_rejected, late[0].diagnostic.code);
720 const timely_hash = try relayData(&owner, destination, "timely proof", start + 20);
721 const timely = collect(try owner.step(.{ .carrier_frame = .{
722 .interface = 2,
723 .now = start + 20 + node.transport.reverse.timeout,
724 .bytes = proofFrame(timely_hash[0..16].*, &frame),
725 .entropy = @splat(0),
726 } }));
727 try std.testing.expectEqual(@as(usize, 1), timely.count);
728 try std.testing.expectEqual(@as(u8, 0), timely.interface);
729 }
730
731 const FrameFacts = struct {
732 transport_id: []const u8,
733 hops: u8,
734 context: u8,
735 context_flag: u1,
736 packet_hash: []const u8,
737 };
738
739 fn reportMismatch(
740 vector_name: []const u8,
741 field_name: []const u8,
742 expected: anytype,
743 actual: @TypeOf(expected),
744 ) !void {
745 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
746 defer arena.deinit();
747 var report = try pretty.diagnostic.Report.init(arena.allocator(), "Transport corpus mismatch");
748 defer report.deinit();
749 try report.field("vector", "{s}", .{vector_name});
750 try report.field("field", "{s}", .{field_name});
751 try report.field("expected", "{any}", .{expected});
752 try report.field("actual", "{any}", .{actual});
753 pretty.diagnostic.writeStderr(&report, .{ .width = 100 });
754 return error.ConformanceMismatch;
755 }
756
757 fn expectCorpusBytes(
758 vector_name: []const u8,
759 field_name: []const u8,
760 expected: []const u8,
761 actual: []const u8,
762 ) !void {
763 if (std.mem.eql(u8, expected, actual)) return;
764 return reportMismatch(vector_name, field_name, expected, actual);
765 }
766
767 fn expectCorpusValue(
768 vector_name: []const u8,
769 field_name: []const u8,
770 expected: u64,
771 actual: u64,
772 ) !void {
773 if (expected == actual) return;
774 return reportMismatch(vector_name, field_name, expected, actual);
775 }
776
777 fn initCorpusNode(
778 storage: *align(8) Storage,
779 interfaces: u8,
780 identity_hash: []const u8,
781 enabled: bool,
782 ) !node.Node {
783 std.debug.assert(interfaces <= limits.interfaces_max);
784 var owner = try node.Node.init(storage[0..], limits);
785 owner.activate();
786 for (0..interfaces) |index| try owner.registerCarrier(@intCast(index), true, null);
787 owner.setTransport(identity_hash[0..16].*, enabled);
788 return owner;
789 }
790
791 fn corpusStep(
792 owner: *node.Node,
793 raw: []const u8,
794 interface: u8,
795 now: node.Seconds,
796 ) ![]const node.Effect {
797 return owner.step(.{ .carrier_frame = .{
798 .interface = interface,
799 .now = now,
800 .bytes = raw,
801 .entropy = @splat(0),
802 } });
803 }
804
805 fn expectCorpusSends(
806 vector_name: []const u8,
807 field_name: []const u8,
808 interfaces: []const u8,
809 frames: []const []const u8,
810 effects: []const node.Effect,
811 ) !void {
812 std.debug.assert(interfaces.len == frames.len);
813 var count: usize = 0;
814 for (effects) |effect| switch (effect) {
815 .carrier_send => |send| {
816 if (count < frames.len) {
817 try expectCorpusValue(vector_name, field_name, interfaces[count], send.interface);
818 try expectCorpusBytes(vector_name, field_name, frames[count], send.frame);
819 }
820 count += 1;
821 },
822 else => {},
823 };
824 try expectCorpusValue(vector_name, field_name, frames.len, count);
825 }
826
827 fn expectFrameFacts(vector_name: []const u8, expected: FrameFacts, frame: []const u8) !void {
828 const decoded = try wire.decode(frame);
829 if (decoded.transport_id) |id| {
830 try expectCorpusBytes(vector_name, "transport_id", expected.transport_id, &id);
831 } else {
832 try expectCorpusValue(vector_name, "transport_id", expected.transport_id.len, 0);
833 }
834 try expectCorpusValue(vector_name, "hops", expected.hops, decoded.hops);
835 try expectCorpusValue(vector_name, "context", expected.context, decoded.context.encode());
836 try expectCorpusValue(vector_name, "context_flag", expected.context_flag, decoded.context_flag);
837 const hash = try wire.hash.full(frame);
838 try expectCorpusBytes(vector_name, "packet_hash", expected.packet_hash, &hash);
839 }
840
841 test "Reticulum@1.5.0 RNS/Transport.py:737-799 corpus rebroadcasts match node frames" {
842 try std.testing.expect(corpus.rebroadcast_vectors.len > 0);
843 for (corpus.rebroadcast_vectors) |vector| {
844 var storage: Storage align(8) = undefined;
845 var owner = try initCorpusNode(&storage, vector.interfaces, vector.identity_hash, true);
846 defer _ = owner.deinit();
847 _ = try corpusStep(&owner, vector.input_raw, vector.input_interface, vector.input_clock);
848 const first = try sweep(&owner, vector.first_clock);
849 try expectCorpusSends(
850 vector.name,
851 "first_frames",
852 vector.first_interfaces,
853 vector.first_frames,
854 first,
855 );
856 try expectFrameFacts(vector.name, .{
857 .transport_id = vector.transport_id,
858 .hops = vector.hops,
859 .context = vector.context,
860 .context_flag = vector.context_flag,
861 .packet_hash = vector.packet_hash,
862 }, collect(first).frame);
863 const second = try sweep(&owner, vector.second_clock);
864 try expectCorpusSends(
865 vector.name,
866 "second_frames",
867 vector.second_interfaces,
868 vector.second_frames,
869 second,
870 );
871 }
872 }
873
874 test "Reticulum@1.5.0 RNS/Transport.py:1345,1935,2670 corpus relays match node frames" {
875 var data_hash: ?[]const u8 = null;
876 for (corpus.relay_vectors) |vector| {
877 var storage: Storage align(8) = undefined;
878 var owner = try initCorpusNode(
879 &storage,
880 vector.interfaces,
881 vector.identity_hash,
882 vector.transport_enabled,
883 );
884 defer _ = owner.deinit();
885 _ = try corpusStep(
886 &owner,
887 vector.announce_raw,
888 vector.announce_interface,
889 vector.announce_clock,
890 );
891 if (vector.relay_raw.len > 0) {
892 _ = try corpusStep(
893 &owner,
894 vector.relay_raw,
895 vector.relay_interface,
896 vector.relay_clock,
897 );
898 }
899 const effects = if (vector.input_raw.len > 0)
900 try corpusStep(&owner, vector.input_raw, vector.input_interface, vector.input_clock)
901 else
902 try owner.step(.{ .application_send = .{
903 .destination = (try wire.decode(vector.announce_raw)).destination,
904 .now = vector.input_clock,
905 .plaintext = vector.plaintext,
906 .ephemeral_private = vector.ephemeral_private_key[0..32].*,
907 .iv = vector.iv[0..16].*,
908 } });
909 try expectCorpusSends(
910 vector.name,
911 "output_raw",
912 &.{vector.output_interface},
913 &.{vector.output_raw},
914 effects,
915 );
916 try expectFrameFacts(vector.name, .{
917 .transport_id = vector.transport_id,
918 .hops = vector.hops,
919 .context = vector.context,
920 .context_flag = vector.context_flag,
921 .packet_hash = vector.packet_hash,
922 }, collect(effects).frame);
923 if (std.mem.startsWith(u8, vector.name, "transport-data-")) {
924 const shared = data_hash orelse vector.packet_hash;
925 try expectCorpusBytes(vector.name, "packet_hash", shared, vector.packet_hash);
926 data_hash = shared;
927 }
928 }
929 try std.testing.expect(data_hash != null);
930 }
931
932 test "Reticulum@1.5.0 RNS/Transport.py:1968-2077 corpus link relays match node frames" {
933 for (link_corpus.relay_vectors) |vector| {
934 var storage: Storage align(8) = undefined;
935 const opens = vector.input_raw.len == 0;
936 var owner = try initCorpusNode(
937 &storage,
938 vector.interfaces,
939 vector.identity_hash,
940 !opens,
941 );
942 defer _ = owner.deinit();
943 _ = try corpusStep(
944 &owner,
945 vector.announce_raw,
946 vector.announce_interface,
947 vector.announce_clock,
948 );
949 if (vector.request_raw.len > 0) {
950 _ = try corpusStep(
951 &owner,
952 vector.request_raw,
953 vector.request_interface,
954 vector.request_clock,
955 );
956 }
957 if (vector.proof_raw.len > 0) {
958 _ = try corpusStep(
959 &owner,
960 vector.proof_raw,
961 vector.proof_interface,
962 vector.proof_clock,
963 );
964 }
965 const effects = if (opens)
966 try owner.step(.{ .application_link_open = .{
967 .destination = (try wire.decode(vector.announce_raw)).destination,
968 .encryption_private = vector.encryption_private_key[0..32].*,
969 .signing_private = vector.signing_private_key[0..32].*,
970 .now = vector.input_clock,
971 } })
972 else
973 try corpusStep(&owner, vector.input_raw, vector.input_interface, vector.input_clock);
974 try expectCorpusSends(
975 vector.name,
976 "output_raw",
977 &.{vector.output_interface},
978 &.{vector.output_raw},
979 effects,
980 );
981 try expectFrameFacts(vector.name, .{
982 .transport_id = vector.transport_id,
983 .hops = vector.hops,
984 .context = vector.context,
985 .context_flag = vector.context_flag,
986 .packet_hash = vector.packet_hash,
987 }, collect(effects).frame);
988 const sent = collect(effects).frame;
989 const decoded = try wire.decode(sent);
990 const carried = if (decoded.packet_type == .link_request)
991 try wire.link.linkId(sent)
992 else
993 decoded.destination;
994 try expectCorpusBytes(vector.name, "link_id", vector.link_id, &carried);
995 }
996 }
997
998 const requests = node.transport.requests;
999
1000 const SingleGate = Harness(blk: {
1001 var selected = limits;
1002 selected.inflight_requests_max = 1;
1003 break :blk selected;
1004 });
1005
1006 fn allCarriers(comptime effects_max: usize) node.Limits {
1007 return .{
1008 .interfaces_max = 256,
1009 .destinations_max = 1,
1010 .known_identities_max = 1,
1011 .known_ratchets_max = 1,
1012 .receipts_max = 1,
1013 .duplicate_hashes_max = 1,
1014 .timers_max = 1,
1015 .effects_max = effects_max,
1016 .effect_frames_max = node.effect_frames_per_event_max,
1017 .paths_max = 1,
1018 .announces_max = 1,
1019 .reverse_entries_max = 1,
1020 .path_request_tags_max = 256,
1021 .inflight_requests_max = 1,
1022 .discoveries_max = 1,
1023 .links_max = 1,
1024 .link_entries_max = 1,
1025 };
1026 }
1027 const AllCarriers = Harness(allCarriers(node.effects_per_event_max));
1028 const ShortEffects = Harness(allCarriers(node.effects_per_event_max - 1));
1029
1030 fn announceVector(name: []const u8) reticulum.conformance.announce.Vector {
1031 for (reticulum.conformance.announce.vectors) |vector| {
1032 if (std.mem.eql(u8, vector.name, name)) return vector;
1033 }
1034 unreachable;
1035 }
1036
1037 fn requestPayload(
1038 buffer: []u8,
1039 destination: [16]u8,
1040 requestor: ?[16]u8,
1041 tag: []const u8,
1042 ) []const u8 {
1043 std.debug.assert(buffer.len >= 32 + tag.len);
1044 @memcpy(buffer[0..16], &destination);
1045 var length: usize = 16;
1046 if (requestor) |id| {
1047 @memcpy(buffer[16..32], &id);
1048 length = 32;
1049 }
1050 @memcpy(buffer[length..][0..tag.len], tag);
1051 return buffer[0 .. length + tag.len];
1052 }
1053
1054 fn requestFrame(payload: []const u8, out: *[wire.mtu]u8) []const u8 {
1055 return wire.encode(.{
1056 .ifac = 0,
1057 .header = .one,
1058 .context_flag = 0,
1059 .transport = .broadcast,
1060 .destination_type = .plain,
1061 .packet_type = .data,
1062 .hops = 0,
1063 .transport_id = null,
1064 .destination = requests.destination_hash,
1065 .context = .none,
1066 .payload = payload,
1067 }, out) catch unreachable;
1068 }
1069
1070 fn ask(
1071 owner: *node.Node,
1072 destination: [16]u8,
1073 requestor: ?[16]u8,
1074 tag: []const u8,
1075 interface: reticulum.carrier.Index,
1076 now: node.Seconds,
1077 ) ![]const node.Effect {
1078 var payload: [64]u8 = undefined;
1079 var frame: [wire.mtu]u8 = undefined;
1080 const raw = requestFrame(requestPayload(&payload, destination, requestor, tag), &frame);
1081 return corpusStep(owner, raw, interface, now);
1082 }
1083
1084 fn diagnosis(effects: []const node.Effect) ?node.Code {
1085 for (effects) |effect| switch (effect) {
1086 .diagnostic => |value| return value.code,
1087 else => {},
1088 };
1089 return null;
1090 }
1091
1092 fn discoveringNode(storage: *align(8) Storage) !node.Node {
1093 var owner = try initTransportNode(storage);
1094 try owner.setPathDiscovery(0, true);
1095 return owner;
1096 }
1097
1098 fn expectPathResponse(frame: []const u8, hops: u8, payload: []const u8) !void {
1099 const answer = try wire.decode(frame);
1100 try std.testing.expectEqual(wire.HeaderType.two, answer.header);
1101 try std.testing.expectEqual(wire.Context.path_response, answer.context);
1102 try std.testing.expectEqualSlices(u8, &transport_hash, &answer.transport_id.?);
1103 try std.testing.expectEqual(hops, answer.hops);
1104 try std.testing.expectEqualSlices(u8, payload, answer.payload);
1105 }
1106
1107 test "Reticulum@1.5.0 RNS/Transport.py:1738-1750 drops short and tagless requests unstored" {
1108 var storage: Storage align(8) = undefined;
1109 var owner = try initNode(&storage);
1110 defer _ = owner.deinit();
1111 var payload: [64]u8 = undefined;
1112 for (&payload, 0..) |*byte, index| byte.* = @intCast(index);
1113 var frame: [wire.mtu]u8 = undefined;
1114 for ([_]usize{ 15, 16 }) |length| {
1115 const raw = requestFrame(payload[0..length], &frame);
1116 for (0..2) |_| {
1117 const effects = try corpusStep(&owner, raw, 0, start);
1118 try std.testing.expectEqual(@as(usize, 1), effects.len);
1119 try std.testing.expectEqual(node.Code.path_request_malformed, diagnosis(effects).?);
1120 }
1121 try std.testing.expect(!owner.duplicate_hashes.contains(try wire.hash.full(raw)));
1122 }
1123 try std.testing.expectEqual(@as(usize, 0), owner.transport.tags.count());
1124 try std.testing.expectEqual(@as(usize, 0), owner.transport.inflight_requests.count());
1125 for ([_]usize{ 17, 33, 64 }, 1..) |length, count| {
1126 payload[0] = @intCast(length);
1127 const raw = requestFrame(payload[0..length], &frame);
1128 try std.testing.expectEqual(@as(usize, 0), (try corpusStep(&owner, raw, 0, start)).len);
1129 try std.testing.expect(owner.duplicate_hashes.contains(try wire.hash.full(raw)));
1130 try std.testing.expectEqual(count, owner.transport.tags.count());
1131 try std.testing.expectEqual(count, owner.transport.inflight_requests.count());
1132 const replayed = try corpusStep(&owner, raw, 0, start);
1133 try std.testing.expectEqual(node.Code.path_request_duplicate, diagnosis(replayed).?);
1134 }
1135 }
1136
1137 test "Reticulum@1.5.0 RNS/Transport.py:1751-1764 drops a duplicate tag carried in different bytes" {
1138 var storage: Storage align(8) = undefined;
1139 var owner = try initNode(&storage);
1140 defer _ = owner.deinit();
1141 const wanted: [16]u8 = @splat(0x51);
1142 const tag: [16]u8 = @splat(0x7a);
1143 try std.testing.expectEqual(@as(usize, 0), (try ask(&owner, wanted, null, &tag, 0, start)).len);
1144 var long_tag: [32]u8 = @splat(0x7a);
1145 long_tag[20] = 0x01;
1146 const shapes = [_]struct { requestor: ?[16]u8, tag: []const u8 }{
1147 .{ .requestor = neighbor_hash, .tag = &tag },
1148 .{ .requestor = neighbor_hash, .tag = &long_tag },
1149 .{ .requestor = transport_hash, .tag = &tag },
1150 };
1151 for (shapes) |shape| {
1152 const effects = try ask(&owner, wanted, shape.requestor, shape.tag, 1, start + 50);
1153 try std.testing.expectEqual(node.Code.path_request_duplicate, diagnosis(effects).?);
1154 }
1155 try std.testing.expectEqual(@as(usize, 1), owner.transport.tags.count());
1156 }
1157
1158 test "Reticulum@1.5.0 RNS/Transport.py:3375-3382 answers a local request on its carrier" {
1159 const vector = announceVector("identity-no-app-data");
1160 const local = vector.destination_hash[0..16].*;
1161 var storage: Storage align(8) = undefined;
1162 var owner = try initNode(&storage);
1163 defer _ = owner.deinit();
1164 owner.identities[0] = reticulum.identity.Private.fromBytes(vector.private_key[0..64].*);
1165 try owner.destinations.register(.{
1166 .hash = local,
1167 .name_hash = vector.name_hash[0..10].*,
1168 .kind = .single,
1169 .proof_strategy = .all,
1170 .identity_index = 0,
1171 });
1172 const tag: [16]u8 = @splat(0x3c);
1173 const effects = try ask(&owner, local, null, &tag, 2, vector.clock);
1174 try std.testing.expectEqual(@as(usize, 1), effects.len);
1175 try std.testing.expectEqualSlices(u8, &local, &effects[0].path_request.destination);
1176 try std.testing.expectEqual(@as(reticulum.carrier.Index, 2), effects[0].path_request.interface);
1177 try std.testing.expectEqual(@as(usize, 0), owner.transport.inflight_requests.count());
1178 const sent = collect(try owner.step(.{ .application_announce = .{
1179 .destination = local,
1180 .app_data = vector.app_data,
1181 .random = vector.random_hash[0..5].*,
1182 .fresh_rotating_key = null,
1183 .now = vector.clock,
1184 .path_response = 2,
1185 } }));
1186 try std.testing.expectEqual(@as(usize, 1), sent.count);
1187 try std.testing.expectEqual(@as(reticulum.carrier.Index, 2), sent.interface);
1188 var expected: [wire.mtu]u8 = undefined;
1189 @memcpy(expected[0..vector.raw.len], vector.raw);
1190 expected[wire.header_one_bytes - 1] = wire.Context.path_response.encode();
1191 try std.testing.expectEqualSlices(u8, expected[0..vector.raw.len], sent.frame);
1192 }
1193
1194 test "Reticulum@1.5.0 RNS/Transport.py:3409-3444 answers a known path once on the asking carrier" {
1195 var storage: Storage align(8) = undefined;
1196 var owner = try initTransportNode(&storage);
1197 defer _ = owner.deinit();
1198 const raw = announceFrame("identity-no-app-data");
1199 const wanted = (try wire.decode(raw)).destination;
1200 _ = try hear(&owner, raw, 0, 0, start);
1201 const tag: [16]u8 = @splat(0x3d);
1202 const asked = try ask(&owner, wanted, neighbor_hash, &tag, 2, start + 2);
1203 try std.testing.expectEqual(@as(usize, 0), asked.len);
1204 try std.testing.expectEqual(@as(usize, 0), owner.transport.inflight_requests.count());
1205 const entry = owner.transport.announces.find(wanted).?;
1206 try std.testing.expect(entry.held != null);
1207 try std.testing.expectEqual(start + 3, entry.record.due);
1208 const answered = collect(try sweep(&owner, start + 3));
1209 try std.testing.expectEqual(@as(usize, 1), answered.count);
1210 try std.testing.expectEqual(@as(reticulum.carrier.Index, 2), answered.interface);
1211 try expectPathResponse(answered.frame, 1, raw[wire.header_one_bytes..]);
1212 const held = collect(try sweep(&owner, start + 4));
1213 try std.testing.expectEqual(@as(usize, limits.interfaces_max), held.count);
1214 try std.testing.expectEqual(wire.Context.none, (try wire.decode(held.frame)).context);
1215 }
1216
1217 test "Reticulum@1.5.0 RNS/Transport.py:3398-3404 does not answer the next hop and clears its gate" {
1218 var storage: Storage align(8) = undefined;
1219 var owner = try initTransportNode(&storage);
1220 defer _ = owner.deinit();
1221 const raw = announceFrame("identity-no-app-data");
1222 const wanted = (try wire.decode(raw)).destination;
1223 var frame: [wire.mtu]u8 = undefined;
1224 const relayed = try rebroadcastFrame(raw, neighbor_hash, 1, &frame);
1225 _ = try corpusStep(&owner, relayed, 1, start);
1226 const unknown: [16]u8 = @splat(0x52);
1227 const tag: [16]u8 = @splat(0x3e);
1228 _ = try ask(&owner, unknown, neighbor_hash, &tag, 0, start + 1);
1229 try std.testing.expectEqual(@as(usize, 1), owner.transport.inflight_requests.count());
1230 const asked = try ask(&owner, wanted, neighbor_hash, &tag, 0, start + 1);
1231 try std.testing.expectEqual(@as(usize, 0), asked.len);
1232 try std.testing.expectEqual(@as(usize, 1), owner.transport.inflight_requests.count());
1233 try std.testing.expect(owner.transport.inflight_requests.find(wanted, start + 1) == null);
1234 try std.testing.expect(owner.transport.announces.find(wanted).?.held == null);
1235 const other_tag: [16]u8 = @splat(0x3f);
1236 _ = try ask(&owner, wanted, @splat(0x99), &other_tag, 0, start + 2);
1237 try std.testing.expect(owner.transport.announces.find(wanted).?.held != null);
1238 }
1239
1240 test "Reticulum@1.5.0 RNS/Transport.py:968,1770-1797 batches to plus 45 and reopens at 46" {
1241 var storage: Storage align(8) = undefined;
1242 var owner = try initNode(&storage);
1243 defer _ = owner.deinit();
1244 const wanted: [16]u8 = @splat(0x53);
1245 const opened = try ask(&owner, wanted, null, &.{0x01}, 0, start);
1246 try std.testing.expectEqual(@as(usize, 0), opened.len);
1247 const batched = try ask(&owner, wanted, null, &.{0x02}, 1, start + 45);
1248 try std.testing.expectEqual(node.Code.path_request_batched, diagnosis(batched).?);
1249 const waiting = owner.transport.discoveries.find(wanted, start + 45).?;
1250 try std.testing.expect(!waiting.engaged);
1251 try std.testing.expect(waiting.requesters.isSet(1));
1252 const reopened = try ask(&owner, wanted, null, &.{0x03}, 2, start + 46);
1253 try std.testing.expectEqual(@as(usize, 0), reopened.len);
1254 const gate = owner.transport.inflight_requests.find(wanted, start + 46).?;
1255 try std.testing.expectEqual(start + 46, gate.timestamp);
1256 }
1257
1258 test "Reticulum@1.5.0 RNS/Transport.py:975-984,2347-2371 answers waiting carriers through plus 15" {
1259 const raw = announceFrame("identity-no-app-data");
1260 const wanted = (try wire.decode(raw)).destination;
1261 const tag: [16]u8 = @splat(0x40);
1262 for ([_]node.Seconds{ 15, 16 }) |delay| {
1263 var storage: Storage align(8) = undefined;
1264 var owner = try discoveringNode(&storage);
1265 defer _ = owner.deinit();
1266 const forwarded = collect(try ask(&owner, wanted, null, &tag, 0, start));
1267 try std.testing.expectEqual(@as(usize, limits.interfaces_max - 1), forwarded.count);
1268 const request = try wire.decode(forwarded.frame);
1269 try std.testing.expectEqualSlices(u8, &requests.destination_hash, &request.destination);
1270 try std.testing.expectEqual(@as(u8, 0), request.hops);
1271 var expected: [48]u8 = undefined;
1272 const payload = requestPayload(&expected, wanted, transport_hash, &tag);
1273 try std.testing.expectEqualSlices(u8, payload, request.payload);
1274 const sent = collect(try hear(&owner, raw, 0, 1, start + delay));
1275 if (delay == node.transport.discoveries.timeout) {
1276 try std.testing.expectEqual(@as(usize, 1), sent.count);
1277 try std.testing.expectEqual(@as(reticulum.carrier.Index, 0), sent.interface);
1278 try expectPathResponse(sent.frame, 1, raw[wire.header_one_bytes..]);
1279 } else {
1280 try std.testing.expectEqual(@as(usize, 0), sent.count);
1281 }
1282 try std.testing.expectEqual(@as(usize, 0), owner.transport.discoveries.count());
1283 try std.testing.expectEqual(@as(usize, 0), owner.transport.inflight_requests.count());
1284 }
1285 }
1286
1287 test "Reticulum@1.5.0 RNS/Transport.py:3459-3466 skips an engaged discovery" {
1288 var storage: SingleGate.Bytes align(8) = undefined;
1289 var owner = try SingleGate.init(&storage, true);
1290 defer _ = owner.deinit();
1291 try owner.setPathDiscovery(0, true);
1292 try owner.setPathDiscovery(1, true);
1293 const raw = announceFrame("identity-no-app-data");
1294 const wanted = (try wire.decode(raw)).destination;
1295 const other: [16]u8 = @splat(0x54);
1296 const first = collect(try ask(&owner, wanted, null, &.{0x01}, 0, start));
1297 try std.testing.expectEqual(@as(usize, 2), first.count);
1298 const second = collect(try ask(&owner, other, null, &.{0x02}, 0, start + 1));
1299 try std.testing.expectEqual(@as(usize, 2), second.count);
1300 try std.testing.expect(owner.transport.inflight_requests.find(wanted, start + 1) == null);
1301 const engaged = try ask(&owner, wanted, null, &.{0x03}, 1, start + 2);
1302 try std.testing.expectEqual(@as(usize, 0), engaged.len);
1303 const waiting = owner.transport.discoveries.find(wanted, start + 2).?;
1304 try std.testing.expect(waiting.engaged);
1305 try std.testing.expect(!waiting.requesters.isSet(1));
1306 const answered = collect(try hear(&owner, raw, 0, 2, start + 3));
1307 try std.testing.expectEqual(@as(usize, 1), answered.count);
1308 try std.testing.expectEqual(@as(reticulum.carrier.Index, 0), answered.interface);
1309 }
1310
1311 test "path request tags admit maximum and forget the oldest at maximum plus one" {
1312 var storage: Storage align(8) = undefined;
1313 var owner = try initNode(&storage);
1314 defer _ = owner.deinit();
1315 const wanted: [16]u8 = @splat(0x55);
1316 for (0..limits.path_request_tags_max) |index| {
1317 _ = try ask(&owner, wanted, null, &.{@intCast(index)}, 0, start);
1318 }
1319 try std.testing.expectEqual(limits.path_request_tags_max, owner.transport.tags.count());
1320 const remembered = try ask(&owner, wanted, neighbor_hash, &.{0}, 0, start);
1321 try std.testing.expectEqual(node.Code.path_request_duplicate, diagnosis(remembered).?);
1322 const fresh_tag: u8 = @intCast(limits.path_request_tags_max);
1323 const newest = try ask(&owner, wanted, null, &.{fresh_tag}, 0, start);
1324 try std.testing.expectEqual(node.Code.path_request_batched, diagnosis(newest).?);
1325 try std.testing.expectEqual(limits.path_request_tags_max, owner.transport.tags.count());
1326 const forgotten = try ask(&owner, wanted, transport_hash, &.{0}, 0, start);
1327 try std.testing.expectEqual(node.Code.path_request_batched, diagnosis(forgotten).?);
1328 }
1329
1330 test "discoveries admit maximum and report a full table at maximum plus one" {
1331 var storage: Storage align(8) = undefined;
1332 var owner = try discoveringNode(&storage);
1333 defer _ = owner.deinit();
1334 for (0..limits.discoveries_max) |index| {
1335 const wanted: [16]u8 = @splat(@intCast(0x60 + index));
1336 const forwarded = collect(try ask(&owner, wanted, null, &.{0x01}, 0, start));
1337 try std.testing.expectEqual(@as(usize, limits.interfaces_max - 1), forwarded.count);
1338 }
1339 try std.testing.expectEqual(limits.discoveries_max, owner.transport.discoveries.count());
1340 const rejected: [16]u8 = @splat(0x6f);
1341 const effects = try ask(&owner, rejected, null, &.{0x01}, 0, start);
1342 try std.testing.expectEqual(@as(usize, 1), effects.len);
1343 try std.testing.expectEqual(node.Code.discovery_table_full, diagnosis(effects).?);
1344 try std.testing.expect(owner.transport.discoveries.find(rejected, start) == null);
1345 }
1346
1347 fn waitOnEveryCarrier(owner: *node.Node, wanted: [16]u8) !void {
1348 try owner.setPathDiscovery(0, true);
1349 for (0..256) |index| {
1350 const tag: [16]u8 = @splat(@intCast(index));
1351 _ = try ask(owner, wanted, null, &tag, @intCast(index), start);
1352 }
1353 const waiting = owner.transport.discoveries.find(wanted, start).?;
1354 try std.testing.expectEqual(@as(usize, 256), waiting.requesters.count());
1355 }
1356
1357 test "Reticulum@1.5.0 RNS/Transport.py:2347-2371 answers every carrier within 258 effects" {
1358 const raw = announceFrame("identity-no-app-data");
1359 const wanted = (try wire.decode(raw)).destination;
1360 var storage: AllCarriers.Bytes align(8) = undefined;
1361 var owner = try AllCarriers.init(&storage, true);
1362 defer _ = owner.deinit();
1363 try waitOnEveryCarrier(&owner, wanted);
1364 const effects = try hear(&owner, raw, 0, 0, start + 1);
1365 try std.testing.expectEqual(@as(usize, 258), effects.len);
1366 try std.testing.expectEqual(@as(usize, 258), node.effects_per_event_max);
1367 try std.testing.expectEqual(@as(usize, 257), owner.effects.frames_used);
1368 try std.testing.expectEqual(@as(usize, 256), collect(effects).count);
1369 }
1370
1371 test "an announce that answers every carrier rejects 257 effects before mutation" {
1372 const raw = announceFrame("identity-no-app-data");
1373 const wanted = (try wire.decode(raw)).destination;
1374 var storage: ShortEffects.Bytes align(8) = undefined;
1375 var owner = try ShortEffects.init(&storage, true);
1376 defer _ = owner.deinit();
1377 try waitOnEveryCarrier(&owner, wanted);
1378 try std.testing.expectError(error.EffectsFull, hear(&owner, raw, 0, 0, start + 1));
1379 try std.testing.expect(owner.transport.paths.find(wanted, start + 1) == null);
1380 try std.testing.expectEqual(@as(usize, 1), owner.transport.discoveries.count());
1381 }
1382
1383 fn openLinkOnEveryCarrier(owner: *node.Node) ![]const node.Effect {
1384 const session = reticulum.conformance.link.session_vectors[0];
1385 var private = reticulum.identity.Private.fromBytes(
1386 session.destination_private_key[0..64].*,
1387 );
1388 defer private.zero();
1389 try owner.known_identities.remember(.{
1390 .destination_hash = session.destination_hash[0..16].*,
1391 .public_key = private.publicBytes(),
1392 .announce_packet_hash = @splat(0),
1393 .received = start,
1394 });
1395 return owner.step(.{ .application_link_open = .{
1396 .destination = session.destination_hash[0..16].*,
1397 .encryption_private = @splat(0x21),
1398 .signing_private = @splat(0x22),
1399 .now = start,
1400 } });
1401 }
1402
1403 test "a link open without a path fits 258 effects across every carrier" {
1404 var storage: AllCarriers.Bytes align(8) = undefined;
1405 var owner = try AllCarriers.init(&storage, false);
1406 defer _ = owner.deinit();
1407 const effects = try openLinkOnEveryCarrier(&owner);
1408 try std.testing.expectEqual(@as(usize, node.effects_per_event_max), effects.len);
1409 try std.testing.expectEqual(@as(usize, 256), owner.effects.frames_used);
1410 try std.testing.expectEqual(@as(usize, 256), collect(effects).count);
1411 try std.testing.expectEqual(@as(usize, 1), owner.transport.links.count());
1412 }
1413
1414 test "a link open across every carrier rejects 257 effects before mutation" {
1415 var storage: ShortEffects.Bytes align(8) = undefined;
1416 var owner = try ShortEffects.init(&storage, false);
1417 defer _ = owner.deinit();
1418 try std.testing.expectError(error.EffectsFull, openLinkOnEveryCarrier(&owner));
1419 try std.testing.expectEqual(@as(usize, 0), owner.transport.links.count());
1420 try std.testing.expectEqual(@as(usize, 0), owner.timers.count());
1421 try std.testing.expectEqual(@as(usize, 0), owner.duplicate_hashes.count());
1422 }
1423
1424 fn rememberDataDestination(owner: *node.Node) ![16]u8 {
1425 const vector = packetVector("data-single-hop-0");
1426 var private = reticulum.identity.Private.fromBytes(vector.destination_private_key[0..64].*);
1427 defer private.zero();
1428 const destination = vector.destination_hash[0..16].*;
1429 try owner.known_identities.remember(.{
1430 .destination_hash = destination,
1431 .public_key = private.publicBytes(),
1432 .announce_packet_hash = @splat(0),
1433 .received = start,
1434 });
1435 return destination;
1436 }
1437
1438 fn firstReceiptOnEveryCarrier(owner: *node.Node, destination: [16]u8) !reticulum.packet.Hash {
1439 const effects = try sendData(owner, destination, &.{0x01}, start);
1440 const sends = collect(effects);
1441 try std.testing.expectEqual(@as(usize, 256), sends.count);
1442 const hash = try wire.hash.full(sends.frame);
1443 try std.testing.expect(owner.receipts.find(hash) != null);
1444 return hash;
1445 }
1446
1447 test "an application send that culls a receipt fits 258 effects across every carrier" {
1448 var storage: AllCarriers.Bytes align(8) = undefined;
1449 var owner = try AllCarriers.init(&storage, false);
1450 defer _ = owner.deinit();
1451 const destination = try rememberDataDestination(&owner);
1452 const first = try firstReceiptOnEveryCarrier(&owner, destination);
1453 const effects = try sendData(&owner, destination, &.{0x02}, start + 1);
1454 try std.testing.expectEqual(@as(usize, node.effects_per_event_max), effects.len);
1455 try std.testing.expectEqual(@as(usize, 256), collect(effects).count);
1456 var timers: usize = 0;
1457 var culled: usize = 0;
1458 for (effects) |effect| switch (effect) {
1459 .schedule_timer => timers += 1,
1460 .receipt_update => |update| {
1461 try std.testing.expectEqualSlices(u8, &first, &update.packet_hash);
1462 try std.testing.expectEqual(reticulum.packet.receipt.Status.culled, update.status);
1463 try std.testing.expectEqual(@as(?u64, null), update.rtt);
1464 culled += 1;
1465 },
1466 else => {},
1467 };
1468 try std.testing.expectEqual(@as(usize, 1), timers);
1469 try std.testing.expectEqual(@as(usize, 1), culled);
1470 }
1471
1472 test "an application send that culls a receipt across every carrier rejects 257 effects" {
1473 var storage: ShortEffects.Bytes align(8) = undefined;
1474 var owner = try ShortEffects.init(&storage, false);
1475 defer _ = owner.deinit();
1476 const destination = try rememberDataDestination(&owner);
1477 const first = try firstReceiptOnEveryCarrier(&owner, destination);
1478 try std.testing.expectError(
1479 error.EffectsFull,
1480 sendData(&owner, destination, &.{0x02}, start + 1),
1481 );
1482 try std.testing.expectEqual(@as(usize, 1), owner.receipts.count());
1483 try std.testing.expect(owner.receipts.find(first) != null);
1484 try std.testing.expect(owner.timers.contains(.{ .receipt = first }));
1485 }
1486
1487 test "Reticulum@1.5.0 RNS/Transport.py:3219-3250 sends a tagged request on one or every carrier" {
1488 const wanted: [16]u8 = @splat(0x56);
1489 const tag: [16]u8 = @splat(0x41);
1490 var expected: [48]u8 = undefined;
1491 var storage: Storage align(8) = undefined;
1492 var owner = try initNode(&storage);
1493 defer _ = owner.deinit();
1494 const broadcast = collect(try owner.step(.{ .application_path_request = .{
1495 .destination = wanted,
1496 .tag = tag,
1497 .interface = null,
1498 .now = start,
1499 } }));
1500 try std.testing.expectEqual(@as(usize, limits.interfaces_max), broadcast.count);
1501 const endpoint = try wire.decode(broadcast.frame);
1502 try std.testing.expectEqualSlices(u8, &requests.destination_hash, &endpoint.destination);
1503 try std.testing.expectEqual(wire.DestinationType.plain, endpoint.destination_type);
1504 const endpoint_payload = requestPayload(&expected, wanted, null, &tag);
1505 try std.testing.expectEqualSlices(u8, endpoint_payload, endpoint.payload);
1506 try std.testing.expect(owner.duplicate_hashes.contains(try wire.hash.full(broadcast.frame)));
1507 try std.testing.expectError(error.NoOutgoingCarrier, owner.step(.{
1508 .application_path_request = .{
1509 .destination = wanted,
1510 .tag = tag,
1511 .interface = 3,
1512 .now = start,
1513 },
1514 }));
1515 var transport_storage: Storage align(8) = undefined;
1516 var transport_owner = try initTransportNode(&transport_storage);
1517 defer _ = transport_owner.deinit();
1518 const single = collect(try transport_owner.step(.{ .application_path_request = .{
1519 .destination = wanted,
1520 .tag = tag,
1521 .interface = 1,
1522 .now = start,
1523 } }));
1524 try std.testing.expectEqual(@as(usize, 1), single.count);
1525 try std.testing.expectEqual(@as(reticulum.carrier.Index, 1), single.interface);
1526 const transported_payload = requestPayload(&expected, wanted, transport_hash, &tag);
1527 const forwarded = try wire.decode(single.frame);
1528 try std.testing.expectEqualSlices(u8, transported_payload, forwarded.payload);
1529 }
1530
1531 test "Reticulum@1.5.0 RNS/Transport.py:3376-3382 drops a request for a local group" {
1532 var storage: Storage align(8) = undefined;
1533 var owner = try discoveringNode(&storage);
1534 defer _ = owner.deinit();
1535 const wanted: [16]u8 = @splat(0x57);
1536 try owner.destinations.register(.{
1537 .hash = wanted,
1538 .kind = .plain,
1539 .proof_strategy = .none,
1540 });
1541 const tag: [16]u8 = @splat(0x43);
1542 try std.testing.expectEqual(@as(usize, 0), (try ask(&owner, wanted, null, &tag, 0, start)).len);
1543 try std.testing.expect(owner.transport.discoveries.find(wanted, start) == null);
1544 try std.testing.expect(owner.transport.inflight_requests.find(wanted, start) != null);
1545 }
1546
1547 test "a known path above 465 payload bytes answers a request with announce_relay_too_large" {
1548 const vector = packetVector("announce-single-header1-payload-max");
1549 const wanted = vector.destination_hash[0..16].*;
1550 var storage: Storage align(8) = undefined;
1551 var owner = try initTransportNode(&storage);
1552 defer _ = owner.deinit();
1553 _ = try learn(&owner, vector.name, 0, 0, start);
1554 try std.testing.expect(owner.transport.paths.find(wanted, start).?.announcePayload() == null);
1555 const tag: [16]u8 = @splat(0x44);
1556 const effects = try ask(&owner, wanted, neighbor_hash, &tag, 1, start + 1);
1557 try std.testing.expectEqual(@as(usize, 1), effects.len);
1558 try std.testing.expectEqual(node.Code.announce_relay_too_large, diagnosis(effects).?);
1559 try std.testing.expectEqual(@as(usize, 0), owner.transport.announces.count());
1560 try std.testing.expectEqual(@as(usize, 0), owner.transport.inflight_requests.count());
1561 }
1562
1563 const broadcast_interface: u8 = 0xFF;
1564
1565 fn corpusRequestSend(
1566 owner: *node.Node,
1567 vector: reticulum.conformance.transport.RequestVector,
1568 ) ![]const node.Effect {
1569 const attached: ?reticulum.carrier.Index =
1570 if (vector.attached_interface == broadcast_interface) null else vector.attached_interface;
1571 return owner.step(.{ .application_path_request = .{
1572 .destination = vector.destination_hash[0..16].*,
1573 .tag = vector.tag[0..16].*,
1574 .interface = attached,
1575 .now = vector.input_clock,
1576 } });
1577 }
1578
1579 test "Reticulum@1.5.0 RNS/Transport.py:3219-3250,3478-3508 corpus requests match node frames" {
1580 try std.testing.expect(corpus.request_vectors.len > 0);
1581 for (corpus.request_vectors) |vector| {
1582 var storage: Storage align(8) = undefined;
1583 var owner = try initCorpusNode(
1584 &storage,
1585 vector.interfaces,
1586 vector.identity_hash,
1587 vector.transport_enabled,
1588 );
1589 defer _ = owner.deinit();
1590 const effects = if (vector.input_raw.len > 0) blk: {
1591 try owner.setPathDiscovery(vector.input_interface, true);
1592 break :blk try corpusStep(
1593 &owner,
1594 vector.input_raw,
1595 vector.input_interface,
1596 vector.input_clock,
1597 );
1598 } else try corpusRequestSend(&owner, vector);
1599 try expectCorpusSends(
1600 vector.name,
1601 "output_frames",
1602 vector.output_interfaces,
1603 vector.output_frames,
1604 effects,
1605 );
1606 const sent = try wire.decode(collect(effects).frame);
1607 try expectCorpusBytes(vector.name, "payload", vector.payload, sent.payload);
1608 try expectCorpusBytes(
1609 vector.name,
1610 "destination_hash",
1611 &requests.destination_hash,
1612 &sent.destination,
1613 );
1614 }
1615 }
1616
1617 test "Reticulum@1.5.0 RNS/Transport.py:2347-2371,3409-3444 corpus answers match node frames" {
1618 try std.testing.expect(corpus.answer_vectors.len > 0);
1619 for (corpus.answer_vectors) |vector| {
1620 var storage: Storage align(8) = undefined;
1621 var owner = try initCorpusNode(&storage, vector.interfaces, vector.identity_hash, true);
1622 defer _ = owner.deinit();
1623 if (vector.discovery) try owner.setPathDiscovery(vector.request_interface, true);
1624 const announce_first = vector.announce_clock < vector.request_clock;
1625 if (announce_first) {
1626 _ = try corpusStep(
1627 &owner,
1628 vector.announce_raw,
1629 vector.announce_interface,
1630 vector.announce_clock,
1631 );
1632 } else {
1633 _ = try corpusStep(
1634 &owner,
1635 vector.request_raw,
1636 vector.request_interface,
1637 vector.request_clock,
1638 );
1639 }
1640 var effects = if (announce_first) try corpusStep(
1641 &owner,
1642 vector.request_raw,
1643 vector.request_interface,
1644 vector.request_clock,
1645 ) else try corpusStep(
1646 &owner,
1647 vector.announce_raw,
1648 vector.announce_interface,
1649 vector.announce_clock,
1650 );
1651 const heard = @max(vector.announce_clock, vector.request_clock);
1652 if (vector.answer_clock > heard) effects = try sweep(&owner, vector.answer_clock);
1653 try expectCorpusSends(
1654 vector.name,
1655 "output_raw",
1656 &.{vector.output_interface},
1657 &.{vector.output_raw},
1658 effects,
1659 );
1660 try expectFrameFacts(vector.name, .{
1661 .transport_id = vector.transport_id,
1662 .hops = vector.hops,
1663 .context = vector.context,
1664 .context_flag = vector.context_flag,
1665 .packet_hash = vector.packet_hash,
1666 }, collect(effects).frame);
1667 }
1668 }
1669
1670 test "Reticulum@1.5.0 RNS/Destination.py:244-318 corpus local responses match node frames" {
1671 try std.testing.expect(corpus.local_vectors.len > 0);
1672 for (corpus.local_vectors) |vector| {
1673 var storage: Storage align(8) = undefined;
1674 var owner = try initNode(&storage);
1675 defer _ = owner.deinit();
1676 const local = vector.destination_hash[0..16].*;
1677 owner.identities[0] = reticulum.identity.Private.fromBytes(vector.private_key[0..64].*);
1678 try owner.destinations.register(.{
1679 .hash = local,
1680 .name_hash = vector.name_hash[0..10].*,
1681 .kind = .single,
1682 .proof_strategy = .all,
1683 .identity_index = 0,
1684 });
1685 const asked = try corpusStep(
1686 &owner,
1687 vector.request_raw,
1688 vector.request_interface,
1689 vector.clock,
1690 );
1691 try expectCorpusValue(vector.name, "request_raw", 1, asked.len);
1692 try expectCorpusBytes(
1693 vector.name,
1694 "destination_hash",
1695 &local,
1696 &asked[0].path_request.destination,
1697 );
1698 try expectCorpusValue(
1699 vector.name,
1700 "request_interface",
1701 vector.request_interface,
1702 asked[0].path_request.interface,
1703 );
1704 const answered = try owner.step(.{ .application_announce = .{
1705 .destination = local,
1706 .app_data = vector.app_data,
1707 .random = vector.random_hash[0..5].*,
1708 .fresh_rotating_key = null,
1709 .now = vector.clock,
1710 .path_response = vector.output_interface,
1711 } });
1712 try expectCorpusSends(
1713 vector.name,
1714 "output_raw",
1715 &.{vector.output_interface},
1716 &.{vector.output_raw},
1717 answered,
1718 );
1719 const sent = try wire.decode(vector.output_raw);
1720 try expectCorpusValue(vector.name, "context", vector.context, sent.context.encode());
1721 try expectCorpusValue(vector.name, "hops", 0, sent.hops);
1722 }
1723 }
1724
1725 fn expectCorpusParse(
1726 vector: reticulum.conformance.transport.ParseVector,
1727 effects: []const node.Effect,
1728 ) !void {
1729 if (std.mem.eql(u8, vector.verdict, "accepted")) {
1730 const parsed = try requests.parse(vector.payload);
1731 try expectCorpusBytes(
1732 vector.name,
1733 "destination_hash",
1734 vector.destination_hash,
1735 &parsed.destination,
1736 );
1737 try expectCorpusBytes(vector.name, "tag", vector.tag, parsed.tag);
1738 const requestor: []const u8 = if (parsed.requestor) |id| &id else &.{};
1739 try expectCorpusBytes(vector.name, "requestor", vector.requestor, requestor);
1740 try expectCorpusValue(vector.name, "verdict", 0, effects.len);
1741 return;
1742 }
1743 const code = diagnosis(effects) orelse return error.TestUnexpectedResult;
1744 if (std.mem.eql(u8, vector.verdict, "duplicate")) {
1745 try std.testing.expectEqual(node.Code.path_request_duplicate, code);
1746 _ = try requests.parse(vector.payload);
1747 return;
1748 }
1749 try std.testing.expectEqual(node.Code.path_request_malformed, code);
1750 if (std.mem.eql(u8, vector.verdict, "short")) {
1751 try std.testing.expectError(error.Short, requests.parse(vector.payload));
1752 } else {
1753 try expectCorpusBytes(vector.name, "verdict", "tagless", vector.verdict);
1754 try std.testing.expectError(error.Tagless, requests.parse(vector.payload));
1755 }
1756 }
1757
1758 test "Reticulum@1.5.0 RNS/Transport.py:1738-1764 corpus verdicts match node path request codes" {
1759 try std.testing.expect(corpus.parse_vectors.len > 0);
1760 var storage: Storage align(8) = undefined;
1761 var owner = try initTransportNode(&storage);
1762 defer _ = owner.deinit();
1763 for (corpus.parse_vectors) |vector| {
1764 try expectCorpusParse(vector, try corpusStep(&owner, vector.raw, 0, start));
1765 }
1766 }
1767
1768 test "Reticulum@1.5.0 RNS/Transport.py:347-351 corpus names the path request destination" {
1769 for (reticulum.conformance.destination.vectors) |vector| {
1770 if (!std.mem.eql(u8, vector.name, "plain-path-request")) continue;
1771 try expectCorpusBytes(
1772 vector.name,
1773 "destination_hash",
1774 vector.destination_hash,
1775 &requests.destination_hash,
1776 );
1777 return;
1778 }
1779 return error.TestUnexpectedResult;
1780 }
1781
1782 const link_corpus = reticulum.conformance.link;
1783 const link_entries = node.transport.link.entries;
1784 const link_session = link_corpus.session_vectors[0];
1785
1786 fn linkVector(name: []const u8) link_corpus.Vector {
1787 for (link_corpus.vectors) |vector| {
1788 if (std.mem.eql(u8, vector.name, name)) return vector;
1789 }
1790 unreachable;
1791 }
1792
1793 fn linkDestination() [16]u8 {
1794 return link_session.destination_hash[0..16].*;
1795 }
1796
1797 fn linkId() [16]u8 {
1798 return linkVector("link-request").link_id[0..16].*;
1799 }
1800
1801 /// Feeds a transport node the announce from the recorded exchange of a link
1802 /// session, so a link relay test starts from a node that already knows the
1803 /// destination and its key. The call returns a node with a path to the link
1804 /// destination over carrier 1, and with the signing identity behind the link
1805 /// request proof already recalled.
1806 fn linkPathNode(storage: *align(8) Storage, hops: u8) !node.Node {
1807 var owner = try initTransportNode(storage);
1808 const heard = try hear(&owner, link_session.announce_raw, hops, 1, start);
1809 try std.testing.expectEqual(@as(u8, hops + 1), owner.transport.paths.hopsTo(
1810 linkDestination(),
1811 start,
1812 ));
1813 try std.testing.expect(heard.len > 0);
1814 return owner;
1815 }
1816
1817 /// Produces the link request in the form it takes after one node has passed it
1818 /// on, under a header carrying that node hash, HEADER_2, so a relay case starts
1819 /// from a request that has already crossed one node.
1820 fn relayedRequest(out: *[wire.mtu]u8, key_byte: ?u8) ![]const u8 {
1821 var plain: [wire.mtu]u8 = undefined;
1822 const raw = linkVector("link-request").output_raw;
1823 @memcpy(plain[0..raw.len], raw);
1824 if (key_byte) |byte| plain[wire.header_one_bytes] = byte;
1825 return node.transport.rewrite.insert(plain[0..raw.len], transport_hash, out);
1826 }
1827
1828 fn relayRequest(owner: *node.Node, now: node.Seconds) ![]const node.Effect {
1829 var out: [wire.mtu]u8 = undefined;
1830 return hear(owner, try relayedRequest(&out, null), 0, 0, now);
1831 }
1832
1833 fn relayProof(
1834 owner: *node.Node,
1835 hops: u8,
1836 interface: reticulum.carrier.Index,
1837 now: node.Seconds,
1838 ) ![]const node.Effect {
1839 return hear(owner, linkVector("link-proof").output_raw, hops, interface, now);
1840 }
1841
1842 fn sweepLinkEntries(owner: *node.Node, now: node.Seconds) ![]const node.Effect {
1843 return owner.step(.{ .timer_expired = .{
1844 .id = .link_entries,
1845 .now = now,
1846 .entropy = @splat(0),
1847 } });
1848 }
1849
1850 test "Reticulum@1.5.0 RNS/Transport.py:1968-2010 forwards a link request over one hop" {
1851 var storage: Storage align(8) = undefined;
1852 var owner = try linkPathNode(&storage, 0);
1853 defer _ = owner.deinit();
1854 const sent = collect(try relayRequest(&owner, start + 1));
1855 try std.testing.expectEqual(@as(usize, 1), sent.count);
1856 try std.testing.expectEqual(@as(reticulum.carrier.Index, 1), sent.interface);
1857 try std.testing.expectEqual(start + 8, sent.deadline.?);
1858 const forwarded = try wire.decode(sent.frame);
1859 try std.testing.expectEqual(wire.HeaderType.one, forwarded.header);
1860 try std.testing.expectEqual(wire.PacketType.link_request, forwarded.packet_type);
1861 try std.testing.expectEqual(@as(u8, 1), forwarded.hops);
1862 try std.testing.expectEqualSlices(u8, &linkDestination(), &forwarded.destination);
1863 try std.testing.expectEqual(@as(usize, wire.link.request_bytes), forwarded.payload.len);
1864 const request = linkVector("link-request").output_raw;
1865 try std.testing.expectEqualSlices(
1866 u8,
1867 request[wire.header_one_bytes..][0..wire.link.request_bytes],
1868 forwarded.payload,
1869 );
1870 const entry = owner.transport.link_entries.find(linkId()) orelse
1871 return error.TestExpectedRelay;
1872 try std.testing.expect(!entry.validated);
1873 try std.testing.expectEqual(start + 1, entry.timestamp);
1874 try std.testing.expectEqual(start + 7, entry.proof_deadline);
1875 try std.testing.expectEqual(@as(u8, 1), entry.remaining_hops);
1876 try std.testing.expectEqual(@as(u8, 1), entry.taken_hops);
1877 try std.testing.expectEqual(@as(reticulum.carrier.Index, 1), entry.next_hop_carrier);
1878 try std.testing.expectEqual(@as(reticulum.carrier.Index, 0), entry.receiving_carrier);
1879 try std.testing.expectEqual(start + 8, owner.timers.scheduledAt(.link_entries).?);
1880 const path = owner.transport.paths.find(linkDestination(), start) orelse
1881 return error.TestExpectedPath;
1882 try std.testing.expectEqual(start + 1, path.timestamp);
1883 }
1884
1885 test "Reticulum@1.5.0 RNS/Transport.py:1935-1940,1971 keeps the header over two hops" {
1886 var storage: Storage align(8) = undefined;
1887 var owner = try linkPathNode(&storage, 1);
1888 defer _ = owner.deinit();
1889 const sent = collect(try relayRequest(&owner, start + 1));
1890 try std.testing.expectEqual(@as(usize, 1), sent.count);
1891 const forwarded = try wire.decode(sent.frame);
1892 try std.testing.expectEqual(wire.HeaderType.two, forwarded.header);
1893 try std.testing.expectEqual(@as(usize, wire.link.request_bytes), forwarded.payload.len);
1894 try std.testing.expectEqualSlices(
1895 u8,
1896 &linkDestination(),
1897 &forwarded.transport_id.?,
1898 );
1899 const entry = owner.transport.link_entries.find(linkId()) orelse
1900 return error.TestExpectedRelay;
1901 try std.testing.expectEqual(start + 13, entry.proof_deadline);
1902 try std.testing.expectEqual(@as(u8, 2), entry.remaining_hops);
1903 }
1904
1905 test "Reticulum@1.5.0 RNS/Transport.py:886-889,908-915 keeps a distant path responsive" {
1906 var storage: Storage align(8) = undefined;
1907 var owner = try linkPathNode(&storage, 1);
1908 defer _ = owner.deinit();
1909 var out: [wire.mtu]u8 = undefined;
1910 const sent = collect(try hear(&owner, try relayedRequest(&out, null), 1, 0, start + 1));
1911 try std.testing.expectEqual(@as(usize, 1), sent.count);
1912 const entry = owner.transport.link_entries.find(linkId()) orelse
1913 return error.TestExpectedRelay;
1914 try std.testing.expectEqual(@as(u8, 2), entry.taken_hops);
1915 try std.testing.expectEqual(@as(usize, 0), (try sweepLinkEntries(&owner, start + 14)).len);
1916 try std.testing.expectEqual(@as(usize, 0), owner.transport.link_entries.count());
1917 const path = owner.transport.paths.find(linkDestination(), start + 14).?;
1918 try std.testing.expectEqual(node.transport.path.State.unknown, path.state);
1919 }
1920
1921 test "Reticulum@1.5.0 RNS/Transport.py:2569-2590 relays a link proof and validates the relay" {
1922 var storage: Storage align(8) = undefined;
1923 var owner = try linkPathNode(&storage, 0);
1924 defer _ = owner.deinit();
1925 _ = try relayRequest(&owner, start + 1);
1926 const sent = collect(try relayProof(&owner, 0, 1, start + 2));
1927 try std.testing.expectEqual(@as(usize, 1), sent.count);
1928 try std.testing.expectEqual(@as(reticulum.carrier.Index, 0), sent.interface);
1929 const forwarded = try wire.decode(sent.frame);
1930 try std.testing.expectEqual(wire.Context.lrproof, forwarded.context);
1931 try std.testing.expectEqual(@as(u8, 1), forwarded.hops);
1932 try std.testing.expectEqualSlices(u8, &linkId(), &forwarded.destination);
1933 const entry = owner.transport.link_entries.find(linkId()) orelse
1934 return error.TestExpectedRelay;
1935 try std.testing.expect(entry.validated);
1936 try std.testing.expectEqual(start + 1, entry.timestamp);
1937 try std.testing.expectEqual(
1938 start + 2 + link_entries.validated_timeout,
1939 owner.timers.scheduledAt(.link_entries).?,
1940 );
1941 }
1942
1943 test "Reticulum@1.5.0 RNS/Transport.py:2570,2584 drops a proof on the wrong carrier or signature" {
1944 var storage: Storage align(8) = undefined;
1945 var owner = try linkPathNode(&storage, 0);
1946 defer _ = owner.deinit();
1947 _ = try relayRequest(&owner, start + 1);
1948 const wrong_carrier = try relayProof(&owner, 0, 2, start + 2);
1949 try std.testing.expectEqual(node.Code.proof_relay_wrong_interface, diagnosis(wrong_carrier).?);
1950 var forged: [wire.mtu]u8 = undefined;
1951 const raw = linkVector("link-proof").output_raw;
1952 @memcpy(forged[0..raw.len], raw);
1953 forged[wire.header_one_bytes] ^= 0x01;
1954 const rejected = try hear(&owner, forged[0..raw.len], 0, 1, start + 2);
1955 try std.testing.expectEqual(node.Code.proof_rejected, diagnosis(rejected).?);
1956 const strayed = try hear(&owner, forged[0..raw.len], 3, 1, start + 2);
1957 try std.testing.expectEqual(node.Code.link_relay_no_direction, diagnosis(strayed).?);
1958 const entry = owner.transport.link_entries.find(linkId()) orelse
1959 return error.TestExpectedRelay;
1960 try std.testing.expect(!entry.validated);
1961 try std.testing.expectEqual(@as(u8, 1), entry.remaining_hops);
1962 }
1963
1964 test "Reticulum@1.5.0 RNS/Transport.py:2540-2562 rebalances a relay to the proof hop count" {
1965 var storage: Storage align(8) = undefined;
1966 var owner = try linkPathNode(&storage, 0);
1967 defer _ = owner.deinit();
1968 _ = try relayRequest(&owner, start + 1);
1969 const sent = collect(try relayProof(&owner, 3, 1, start + 2));
1970 try std.testing.expectEqual(@as(usize, 1), sent.count);
1971 try std.testing.expectEqual(@as(reticulum.carrier.Index, 0), sent.interface);
1972 try std.testing.expectEqual(@as(u8, 4), (try wire.decode(sent.frame)).hops);
1973 const entry = owner.transport.link_entries.find(linkId()) orelse
1974 return error.TestExpectedRelay;
1975 try std.testing.expect(entry.validated);
1976 try std.testing.expectEqual(@as(u8, 4), entry.remaining_hops);
1977 const hops = owner.transport.paths.hopsTo(linkDestination(), start + 2);
1978 try std.testing.expectEqual(@as(u8, 4), hops);
1979 }
1980
1981 test "Reticulum@1.5.0 RNS/Transport.py:2030-2071 carries link traffic both ways" {
1982 var storage: Storage align(8) = undefined;
1983 var owner = try linkPathNode(&storage, 0);
1984 defer _ = owner.deinit();
1985 _ = try relayRequest(&owner, start + 1);
1986 _ = try relayProof(&owner, 0, 1, start + 2);
1987 const data = linkVector("link-data").output_raw;
1988 const outward = collect(try hear(&owner, data, 0, 0, start + 3));
1989 try std.testing.expectEqual(@as(usize, 1), outward.count);
1990 try std.testing.expectEqual(@as(reticulum.carrier.Index, 1), outward.interface);
1991 const carried = try wire.decode(outward.frame);
1992 try std.testing.expectEqual(@as(u8, 1), carried.hops);
1993 try std.testing.expectEqualSlices(u8, data[2..], outward.frame[2..]);
1994 try std.testing.expectEqual(start + 3, owner.transport.link_entries.find(linkId()).?.timestamp);
1995 const replay = try hear(&owner, data, 0, 0, start + 4);
1996 try std.testing.expectEqual(node.Code.duplicate_packet, diagnosis(replay).?);
1997 const answer = linkVector("link-data-proof").output_raw;
1998 const inward = collect(try hear(&owner, answer, 0, 1, start + 5));
1999 try std.testing.expectEqual(@as(usize, 1), inward.count);
2000 try std.testing.expectEqual(@as(reticulum.carrier.Index, 0), inward.interface);
2001 try std.testing.expectEqual(
2002 start + 6 + link_entries.validated_timeout,
2003 owner.timers.scheduledAt(.link_entries).?,
2004 );
2005 }
2006
2007 test "Reticulum@1.5.0 RNS/Transport.py:2035-2037,2073 refuses traffic with no direction" {
2008 var storage: Storage align(8) = undefined;
2009 var owner = try linkPathNode(&storage, 0);
2010 defer _ = owner.deinit();
2011 _ = try relayRequest(&owner, start + 1);
2012 const data = linkVector("link-data").output_raw;
2013 const early = try hear(&owner, data, 0, 0, start + 2);
2014 try std.testing.expectEqual(node.Code.link_relay_early, diagnosis(early).?);
2015 _ = try relayProof(&owner, 0, 1, start + 3);
2016 const strayed = try hear(&owner, data, 0, 2, start + 4);
2017 try std.testing.expectEqual(node.Code.link_relay_no_direction, diagnosis(strayed).?);
2018 const mismatched = try hear(&owner, data, 3, 1, start + 5);
2019 try std.testing.expectEqual(node.Code.link_relay_no_direction, diagnosis(mismatched).?);
2020 try std.testing.expectEqual(start + 1, owner.transport.link_entries.find(linkId()).?.timestamp);
2021 const carried = collect(try hear(&owner, data, 0, 0, start + 6));
2022 try std.testing.expectEqual(@as(usize, 1), carried.count);
2023 try std.testing.expectEqual(@as(reticulum.carrier.Index, 1), carried.interface);
2024 }
2025
2026 test "Reticulum@1.5.0 RNS/Transport.py:854-856,902 expires an unproved relay unresponsive" {
2027 var storage: Storage align(8) = undefined;
2028 var owner = try linkPathNode(&storage, 0);
2029 defer _ = owner.deinit();
2030 _ = try relayRequest(&owner, start + 1);
2031 const early = try sweepLinkEntries(&owner, start + 7);
2032 try std.testing.expectEqual(@as(usize, 1), early.len);
2033 try std.testing.expectEqual(start + 8, collect(early).deadline.?);
2034 try std.testing.expectEqual(@as(usize, 1), owner.transport.link_entries.count());
2035 const swept = try sweepLinkEntries(&owner, start + 8);
2036 try std.testing.expectEqual(@as(usize, 0), swept.len);
2037 try std.testing.expectEqual(@as(usize, 0), owner.transport.link_entries.count());
2038 try std.testing.expect(!owner.timers.contains(.link_entries));
2039 const path = owner.transport.paths.find(linkDestination(), start + 7).?;
2040 try std.testing.expectEqual(node.transport.path.State.unresponsive, path.state);
2041 }
2042
2043 test "Reticulum@1.5.0 RNS/Transport.py:849-850 keeps a validated relay for 900 seconds" {
2044 var storage: Storage align(8) = undefined;
2045 var owner = try linkPathNode(&storage, 0);
2046 defer _ = owner.deinit();
2047 _ = try relayRequest(&owner, start + 1);
2048 _ = try relayProof(&owner, 0, 1, start + 2);
2049 const timeout = start + 1 + link_entries.validated_timeout;
2050 try std.testing.expectEqual(@as(usize, 1), (try sweepLinkEntries(&owner, timeout)).len);
2051 try std.testing.expectEqual(@as(usize, 1), owner.transport.link_entries.count());
2052 try std.testing.expectEqual(@as(usize, 0), (try sweepLinkEntries(&owner, timeout + 1)).len);
2053 try std.testing.expectEqual(@as(usize, 0), owner.transport.link_entries.count());
2054 const path = owner.transport.paths.find(linkDestination(), timeout).?;
2055 try std.testing.expectEqual(node.transport.path.State.unknown, path.state);
2056 }
2057
2058 test "Reticulum@1.5.0 RNS/Transport.py:1998-2009 rejects a request that no relay slot holds" {
2059 var storage: Storage align(8) = undefined;
2060 var owner = try linkPathNode(&storage, 0);
2061 defer _ = owner.deinit();
2062 var out: [wire.mtu]u8 = undefined;
2063 for ([_]u8{ 0x11, 0x22 }) |byte| {
2064 const raw = try relayedRequest(&out, byte);
2065 const sent = collect(try hear(&owner, raw, 0, 0, start + 1));
2066 try std.testing.expectEqual(@as(usize, 1), sent.count);
2067 }
2068 try std.testing.expectEqual(@as(usize, 2), owner.transport.link_entries.count());
2069 const raw = try relayedRequest(&out, 0x33);
2070 const refused = try hear(&owner, raw, 0, 0, start + 2);
2071 try std.testing.expectEqual(node.Code.link_entries_full, diagnosis(refused).?);
2072 try std.testing.expectEqual(@as(usize, 2), owner.transport.link_entries.count());
2073 const repeat = try relayedRequest(&out, 0x11);
2074 const duplicate = try hear(&owner, repeat, 0, 0, start + 3);
2075 try std.testing.expectEqual(node.Code.duplicate_packet, diagnosis(duplicate).?);
2076 }