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 }