lib/reticulum/src/properties/wire.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const hypothesis = @import("hypothesis");
  3 const wire = @import("reticulum").wire;
  4 
  5 fn settings(seed: u64) hypothesis.Settings {
  6     return hypothesis.Settings.quick()
  7         .withSeed(seed)
  8         .withDatabase("zig-out/hypothesis-failures/reticulum");
  9 }
 10 
 11 fn drawArray(data: *hypothesis.ConjectureData, comptime length: usize) ![length]u8 {
 12     return (try data.drawBytes(length, length))[0..length].*;
 13 }
 14 
 15 fn drawPacket(data: *hypothesis.ConjectureData) !wire.Packet {
 16     const header: wire.HeaderType = @fromBackingInt(@intCast(
 17         @as(u1, @intCast(try data.drawInteger(0, 1, 0))),
 18     ));
 19     const context_byte: u8 = @intCast(try data.drawInteger(0, 255, 0));
 20     const destination_type: wire.DestinationType = if (context_byte == 0xff)
 21         .link
 22     else
 23         @fromBackingInt(@intCast(@as(u2, @intCast(try data.drawInteger(0, 3, 0)))));
 24     const payload_max: u16 = switch (header) {
 25         .one => wire.mtu - wire.header_one_bytes,
 26         .two => wire.mtu - wire.header_two_bytes,
 27     };
 28     const payload_len: usize = @intCast(try data.drawInteger(0, payload_max, 0));
 29     return .{
 30         .ifac = @intCast(try data.drawInteger(0, 1, 0)),
 31         .header = header,
 32         .context_flag = @intCast(try data.drawInteger(0, 1, 0)),
 33         .transport = @fromBackingInt(@intCast(@as(u1, @intCast(try data.drawInteger(0, 1, 0))))),
 34         .destination_type = destination_type,
 35         .packet_type = @fromBackingInt(@intCast(@as(u2, @intCast(try data.drawInteger(0, 3, 0))))),
 36         .hops = @intCast(try data.drawInteger(0, 127, 0)),
 37         .transport_id = if (header == .two) try drawArray(data, 16) else null,
 38         .destination = try drawArray(data, 16),
 39         .context = wire.Context.decode(context_byte),
 40         .payload = try data.drawBytes(payload_len, payload_len),
 41     };
 42 }
 43 
 44 fn expectPacket(expected: wire.Packet, actual: wire.Packet) !void {
 45     try std.testing.expectEqual(expected.ifac, actual.ifac);
 46     try std.testing.expectEqual(expected.header, actual.header);
 47     try std.testing.expectEqual(expected.context_flag, actual.context_flag);
 48     try std.testing.expectEqual(expected.transport, actual.transport);
 49     try std.testing.expectEqual(expected.destination_type, actual.destination_type);
 50     try std.testing.expectEqual(expected.packet_type, actual.packet_type);
 51     try std.testing.expectEqual(expected.hops, actual.hops);
 52     try std.testing.expectEqual(expected.transport_id, actual.transport_id);
 53     try std.testing.expectEqual(expected.destination, actual.destination);
 54     try std.testing.expectEqual(expected.context, actual.context);
 55     try std.testing.expectEqualSlices(u8, expected.payload, actual.payload);
 56 }
 57 
 58 const PacketRoundTrip = struct {
 59     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
 60         const packet = try drawPacket(data);
 61         var out: [wire.mtu]u8 = undefined;
 62         const encoded = try wire.encode(packet, &out);
 63         try std.testing.expectEqual(@as(usize, try wire.encodedLength(packet)), encoded.len);
 64         try expectPacket(packet, try wire.decode(encoded));
 65     }
 66 };
 67 
 68 test "property: Reticulum@1.5.0 RNS/Packet.py:178-271 packet round trip" {
 69     try hypothesis.checkNamed(
 70         PacketRoundTrip,
 71         "reticulum-wire-packet-round-trip",
 72         settings(0x5245_5449_4355_4c51),
 73     );
 74 }
 75 
 76 const DecoderTotal = struct {
 77     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
 78         const bytes = try data.drawBytes(0, 600);
 79         const packet: ?wire.Packet = wire.decode(bytes) catch null;
 80         const full: ?[32]u8 = wire.hash.full(bytes) catch null;
 81         const proof: ?wire.proof.Proof = wire.proof.decode(bytes) catch null;
 82         _ = packet;
 83         _ = full;
 84         _ = proof;
 85     }
 86 };
 87 
 88 test "property: arbitrary byte slices never panic Reticulum wire decoders" {
 89     try hypothesis.checkNamed(
 90         DecoderTotal,
 91         "reticulum-wire-decoders-total",
 92         settings(0x5245_5449_4355_4c52),
 93     );
 94 }
 95 
 96 fn boundaryPacket(header: wire.HeaderType, payload: []const u8) wire.Packet {
 97     return .{
 98         .ifac = 0,
 99         .header = header,
100         .context_flag = 0,
101         .transport = if (header == .two) .transport else .broadcast,
102         .destination_type = .plain,
103         .packet_type = .data,
104         .hops = 127,
105         .transport_id = if (header == .two) @as([16]u8, @splat(0xa5)) else null,
106         .destination = @splat(0x5a),
107         .context = .none,
108         .payload = payload,
109     };
110 }
111 
112 const PacketBoundaries = struct {
113     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
114         const payload = try data.drawBytes(482, 482);
115         var out: [wire.mtu]u8 = undefined;
116         var overflow: [wire.mtu + 1]u8 = undefined;
117 
118         var header_one = boundaryPacket(.one, payload[0..481]);
119         const one_max = try wire.encode(header_one, &out);
120         try std.testing.expectEqual(@as(usize, 500), one_max.len);
121         _ = try wire.decode(one_max);
122         header_one.payload = payload[0..482];
123         try std.testing.expectError(error.PacketTooLarge, wire.encode(header_one, &overflow));
124 
125         var header_two = boundaryPacket(.two, payload[0..465]);
126         const two_max = try wire.encode(header_two, &out);
127         try std.testing.expectEqual(@as(usize, 500), two_max.len);
128         _ = try wire.decode(two_max);
129         std.mem.copyForwards(u8, overflow[0..two_max.len], two_max);
130         overflow[wire.mtu] = 0;
131         try std.testing.expectError(error.PacketTooLarge, wire.decode(&overflow));
132         header_two.payload = payload[0..466];
133         try std.testing.expectError(error.PacketTooLarge, wire.encode(header_two, &overflow));
134 
135         var hops = boundaryPacket(.one, &.{});
136         try std.testing.expectEqual(@as(usize, 19), (try wire.encode(hops, &out)).len);
137         hops.hops = 128;
138         try std.testing.expectError(error.InvalidHops, wire.encode(hops, &out));
139     }
140 };
141 
142 test "property: Reticulum@1.5.0 packet hop and MTU boundaries" {
143     try hypothesis.checkNamed(
144         PacketBoundaries,
145         "reticulum-wire-packet-boundaries",
146         settings(0x5245_5449_4355_4c53),
147     );
148 }