lib/reticulum/src/packet/filter.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const wire = @import("../wire/root.zig");
  3 
  4 pub const Verdict = enum(u1) {
  5     reject,
  6     admit,
  7 };
  8 
  9 fn contextPassthrough(context: wire.Context) bool {
 10     return switch (context) {
 11         .keepalive,
 12         .resource_req,
 13         .resource_prf,
 14         .resource,
 15         .cache_request,
 16         .channel,
 17         => true,
 18         else => false,
 19     };
 20 }
 21 
 22 /// Decides whether a node acts on an arriving packet, for a caller that runs
 23 /// every arriving packet past this check before doing anything with it. The
 24 /// call rejects a packet of any type but an announce that carries a transport
 25 /// id, unless that id is this node's own. The call admits a packet whatever
 26 /// else holds for six context bytes: keepalive, resource, resource request,
 27 /// resource proof, cache request, and channel, because a link or a resource
 28 /// transfer repeats them. The check rejects a packet addressed to a plain or
 29 /// group destination when it is an announce or its hop count exceeds one, and
 30 /// admits it otherwise. The call rejects a packet whose packet hash the node
 31 /// has already seen, and admits an announce to a single destination again so a
 32 /// fresher path can be taken. Reticulum 1.5.0 filters against its shared
 33 /// instance before reaching these rules, and this call leaves that to the
 34 /// caller, following Reticulum@1.5.0 RNS/Transport.py:1571-1628.
 35 pub fn admit(
 36     value: *const wire.Packet,
 37     seen: bool,
 38     own_transport_hash: ?[16]u8,
 39 ) Verdict {
 40     if (value.transport_id) |transport_id| {
 41         if (value.packet_type != .announce) {
 42             const own = own_transport_hash orelse return .reject;
 43             if (!std.mem.eql(u8, &transport_id, &own)) return .reject;
 44         }
 45     }
 46     if (contextPassthrough(value.context)) return .admit;
 47     switch (value.destination_type) {
 48         .plain, .group => {
 49             if (value.packet_type == .announce) return .reject;
 50             if (value.hops > 1) return .reject;
 51             return .admit;
 52         },
 53         else => {},
 54     }
 55     if (!seen) return .admit;
 56     if (value.packet_type == .announce and value.destination_type == .single) {
 57         return .admit;
 58     }
 59     return .reject;
 60 }
 61 
 62 fn packet() wire.Packet {
 63     return .{
 64         .ifac = 0,
 65         .header = .one,
 66         .context_flag = 0,
 67         .transport = .broadcast,
 68         .destination_type = .single,
 69         .packet_type = .data,
 70         .hops = 0,
 71         .transport_id = null,
 72         .destination = @splat(0),
 73         .context = .none,
 74         .payload = &.{},
 75     };
 76 }
 77 
 78 test "Reticulum@1.5.0 RNS/Transport.py:1576-1580 rejects another transport" {
 79     var value = packet();
 80     value.header = .two;
 81     value.transport_id = @splat(1);
 82     try @import("std").testing.expectEqual(Verdict.reject, admit(&value, false, @splat(2)));
 83 }
 84 
 85 test "Reticulum@1.5.0 RNS/Transport.py:1576-1580 admits its transport" {
 86     var value = packet();
 87     value.header = .two;
 88     value.transport_id = @splat(1);
 89     try @import("std").testing.expectEqual(Verdict.admit, admit(&value, false, @splat(1)));
 90 }
 91 
 92 test "Reticulum@1.5.0 RNS/Transport.py:1577 lets announces cross transports" {
 93     var value = packet();
 94     value.header = .two;
 95     value.transport_id = @splat(1);
 96     value.packet_type = .announce;
 97     try @import("std").testing.expectEqual(Verdict.admit, admit(&value, false, @splat(2)));
 98 }
 99 
100 fn expectContext(context: wire.Context) !void {
101     var value = packet();
102     value.context = context;
103     try std.testing.expectEqual(Verdict.admit, admit(&value, true, null));
104 }
105 
106 test "Reticulum@1.5.0 RNS/Transport.py:1582 passes keepalive context" {
107     try expectContext(.keepalive);
108 }
109 
110 test "Reticulum@1.5.0 RNS/Transport.py:1583 passes resource request context" {
111     try expectContext(.resource_req);
112 }
113 
114 test "Reticulum@1.5.0 RNS/Transport.py:1584 passes resource proof context" {
115     try expectContext(.resource_prf);
116 }
117 
118 test "Reticulum@1.5.0 RNS/Transport.py:1585 passes resource context" {
119     try expectContext(.resource);
120 }
121 
122 test "Reticulum@1.5.0 RNS/Transport.py:1586 passes cache request context" {
123     try expectContext(.cache_request);
124 }
125 
126 test "Reticulum@1.5.0 RNS/Transport.py:1587 passes channel context" {
127     try expectContext(.channel);
128 }
129 
130 test "Reticulum@1.5.0 RNS/Transport.py:1589-1596 admits local plain data" {
131     var value = packet();
132     value.destination_type = .plain;
133     value.hops = 1;
134     try std.testing.expectEqual(Verdict.admit, admit(&value, true, null));
135 }
136 
137 test "Reticulum@1.5.0 RNS/Transport.py:1591-1594 rejects transported plain data" {
138     var value = packet();
139     value.destination_type = .plain;
140     value.hops = 2;
141     try std.testing.expectEqual(Verdict.reject, admit(&value, false, null));
142 }
143 
144 test "Reticulum@1.5.0 RNS/Transport.py:1597-1600 rejects plain announces" {
145     var value = packet();
146     value.destination_type = .plain;
147     value.packet_type = .announce;
148     try std.testing.expectEqual(Verdict.reject, admit(&value, false, null));
149 }
150 
151 test "Reticulum@1.5.0 RNS/Transport.py:1602-1609 admits local group data" {
152     var value = packet();
153     value.destination_type = .group;
154     value.hops = 1;
155     try std.testing.expectEqual(Verdict.admit, admit(&value, true, null));
156 }
157 
158 test "Reticulum@1.5.0 RNS/Transport.py:1604-1607 rejects transported group data" {
159     var value = packet();
160     value.destination_type = .group;
161     value.hops = 2;
162     try std.testing.expectEqual(Verdict.reject, admit(&value, false, null));
163 }
164 
165 test "Reticulum@1.5.0 RNS/Transport.py:1610-1613 rejects group announces" {
166     var value = packet();
167     value.destination_type = .group;
168     value.packet_type = .announce;
169     try std.testing.expectEqual(Verdict.reject, admit(&value, false, null));
170 }
171 
172 test "Reticulum@1.5.0 RNS/Transport.py:1615 admits unseen packets" {
173     try @import("std").testing.expectEqual(Verdict.admit, admit(&packet(), false, null));
174 }
175 
176 test "Reticulum@1.5.0 RNS/Transport.py:1617-1619 readmits single announces" {
177     var value = packet();
178     value.packet_type = .announce;
179     try std.testing.expectEqual(Verdict.admit, admit(&value, true, null));
180 }
181 
182 test "Reticulum@1.5.0 RNS/Transport.py:1617-1623 rejects other duplicate announces" {
183     var value = packet();
184     value.packet_type = .announce;
185     value.destination_type = .link;
186     try std.testing.expectEqual(Verdict.reject, admit(&value, true, null));
187 }
188 
189 test "Reticulum@1.5.0 RNS/Transport.py:1625-1626 rejects other duplicates" {
190     try @import("std").testing.expectEqual(Verdict.reject, admit(&packet(), true, null));
191 }