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 }