1use super::tables::PathEntry;
2use crate::constants;
3
4pub fn timebase_from_random_blob(blob: &[u8; 10]) -> u64 {
6 let mut bytes = [0u8; 8];
7 bytes[3..8].copy_from_slice(&blob[5..10]);
8 u64::from_be_bytes(bytes)
9}
10
11pub fn timebase_from_random_blobs(blobs: &[[u8; 10]]) -> u64 {
13 let mut timebase: u64 = 0;
14 for blob in blobs {
15 let emitted = timebase_from_random_blob(blob);
16 if emitted > timebase {
17 timebase = emitted;
18 }
19 }
20 timebase
21}
22
23pub fn extract_random_blob(packet_data: &[u8]) -> Option<[u8; 10]> {
28 let offset = constants::KEYSIZE / 8 + constants::NAME_HASH_LENGTH / 8;
29 if packet_data.len() < offset + 10 {
30 return None;
31 }
32 let mut blob = [0u8; 10];
33 blob.copy_from_slice(&packet_data[offset..offset + 10]);
34 Some(blob)
35}
36
37#[derive(Debug, PartialEq, Eq)]
38pub enum PathDecision {
39 Add,
40 Reject,
41}
42
43pub fn should_update_path(
47 existing: Option<&PathEntry>,
48 announce_hops: u8,
49 announce_emitted_ts: u64,
50 random_blob: &[u8; 10],
51 path_is_unresponsive: bool,
52 now: f64,
53) -> PathDecision {
54 if announce_hops >= constants::PATHFINDER_M + 1 {
56 return PathDecision::Reject;
57 }
58
59 let existing = match existing {
60 None => return PathDecision::Add,
61 Some(e) => e,
62 };
63
64 let path_timebase = timebase_from_random_blobs(&existing.random_blobs);
65 let blob_is_new = !existing.random_blobs.contains(random_blob);
66
67 if announce_hops <= existing.hops {
68 if blob_is_new && announce_emitted_ts > path_timebase {
70 return PathDecision::Add;
71 }
72 if announce_emitted_ts == path_timebase && path_is_unresponsive {
74 return PathDecision::Add;
75 }
76 PathDecision::Reject
77 } else {
78 let path_expired = now >= existing.expires;
80
81 if path_expired && blob_is_new {
82 return PathDecision::Add;
83 }
84
85 if announce_emitted_ts > path_timebase && blob_is_new {
86 return PathDecision::Add;
87 }
88
89 if announce_emitted_ts == path_timebase && path_is_unresponsive {
90 return PathDecision::Add;
91 }
92
93 PathDecision::Reject
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use super::super::types::InterfaceId;
101
102 fn make_blob(timebase: u64) -> [u8; 10] {
103 let mut blob = [0u8; 10];
104 let bytes = timebase.to_be_bytes();
105 blob[5..10].copy_from_slice(&bytes[3..8]);
107 blob
108 }
109
110 fn make_path_entry(hops: u8, blobs: &[[u8; 10]], expires: f64) -> PathEntry {
111 PathEntry {
112 timestamp: 1000.0,
113 next_hop: [0xAA; 16],
114 hops,
115 expires,
116 random_blobs: blobs.to_vec(),
117 receiving_interface: InterfaceId(1),
118 packet_hash: [0xBB; 32],
119 announce_raw: None,
120 }
121 }
122
123 #[test]
124 fn test_timebase_extraction() {
125 let blob = make_blob(12345);
126 assert_eq!(timebase_from_random_blob(&blob), 12345);
127 }
128
129 #[test]
130 fn test_timebase_from_multiple_blobs() {
131 let b1 = make_blob(100);
132 let b2 = make_blob(200);
133 let b3 = make_blob(50);
134 assert_eq!(timebase_from_random_blobs(&[b1, b2, b3]), 200);
135 }
136
137 #[test]
138 fn test_timebase_empty_blobs() {
139 assert_eq!(timebase_from_random_blobs(&[]), 0);
140 }
141
142 #[test]
143 fn test_extract_random_blob() {
144 let mut data = [0u8; 100];
146 data[74..84].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
148 let blob = extract_random_blob(&data).unwrap();
149 assert_eq!(blob, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
150 }
151
152 #[test]
153 fn test_extract_random_blob_too_short() {
154 let data = [0u8; 80]; assert!(extract_random_blob(&data).is_none());
156 }
157
158 #[test]
161 fn test_no_existing_path_always_add() {
162 let blob = make_blob(100);
163 assert_eq!(
164 should_update_path(None, 3, 100, &blob, false, 1000.0),
165 PathDecision::Add
166 );
167 }
168
169 #[test]
170 fn test_hop_limit_reject() {
171 let blob = make_blob(100);
172 assert_eq!(
173 should_update_path(None, 129, 100, &blob, false, 1000.0),
174 PathDecision::Reject
175 );
176 }
177
178 #[test]
179 fn test_fewer_hops_new_blob_newer_emission_add() {
180 let old_blob = make_blob(100);
181 let new_blob = make_blob(200);
182 let entry = make_path_entry(5, &[old_blob], 9999.0);
183 assert_eq!(
184 should_update_path(Some(&entry), 3, 200, &new_blob, false, 1000.0),
185 PathDecision::Add
186 );
187 }
188
189 #[test]
190 fn test_fewer_hops_duplicate_blob_reject() {
191 let blob = make_blob(100);
192 let entry = make_path_entry(5, &[blob], 9999.0);
193 assert_eq!(
194 should_update_path(Some(&entry), 3, 200, &blob, false, 1000.0),
195 PathDecision::Reject
196 );
197 }
198
199 #[test]
200 fn test_fewer_hops_same_emission_unresponsive_add() {
201 let old_blob = make_blob(100);
202 let mut different_blob = [0u8; 10];
203 different_blob[0] = 0xFF;
204 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
205
206 let entry = make_path_entry(5, &[old_blob], 9999.0);
207 assert_eq!(
208 should_update_path(Some(&entry), 3, 100, &different_blob, true, 1000.0),
209 PathDecision::Add
210 );
211 }
212
213 #[test]
214 fn test_fewer_hops_same_emission_responsive_reject() {
215 let old_blob = make_blob(100);
216 let mut different_blob = [0u8; 10];
217 different_blob[0] = 0xFF;
218 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
219
220 let entry = make_path_entry(5, &[old_blob], 9999.0);
221 assert_eq!(
222 should_update_path(Some(&entry), 3, 100, &different_blob, false, 1000.0),
223 PathDecision::Reject
224 );
225 }
226
227 #[test]
228 fn test_more_hops_expired_path_new_blob_add() {
229 let old_blob = make_blob(100);
230 let new_blob = make_blob(50); let entry = make_path_entry(2, &[old_blob], 500.0); assert_eq!(
234 should_update_path(Some(&entry), 5, 50, &new_blob, false, 600.0), PathDecision::Add
236 );
237 }
238
239 #[test]
240 fn test_more_hops_not_expired_older_emission_reject() {
241 let old_blob = make_blob(200);
242 let new_blob = make_blob(100);
243 let entry = make_path_entry(2, &[old_blob], 9999.0);
244
245 assert_eq!(
246 should_update_path(Some(&entry), 5, 100, &new_blob, false, 1000.0),
247 PathDecision::Reject
248 );
249 }
250
251 #[test]
252 fn test_more_hops_newer_emission_new_blob_add() {
253 let old_blob = make_blob(100);
254 let new_blob = make_blob(200);
255 let entry = make_path_entry(2, &[old_blob], 9999.0);
256
257 assert_eq!(
258 should_update_path(Some(&entry), 5, 200, &new_blob, false, 1000.0),
259 PathDecision::Add
260 );
261 }
262
263 #[test]
264 fn test_more_hops_same_emission_unresponsive_add() {
265 let old_blob = make_blob(100);
266 let mut different_blob = [0u8; 10];
267 different_blob[0] = 0xFF;
268 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
269
270 let entry = make_path_entry(2, &[old_blob], 9999.0);
271
272 assert_eq!(
273 should_update_path(Some(&entry), 5, 100, &different_blob, true, 1000.0),
274 PathDecision::Add
275 );
276 }
277
278 #[test]
279 fn test_more_hops_same_emission_responsive_reject() {
280 let old_blob = make_blob(100);
281 let mut different_blob = [0u8; 10];
282 different_blob[0] = 0xFF;
283 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
284
285 let entry = make_path_entry(2, &[old_blob], 9999.0);
286
287 assert_eq!(
288 should_update_path(Some(&entry), 5, 100, &different_blob, false, 1000.0),
289 PathDecision::Reject
290 );
291 }
292
293 #[test]
294 fn test_more_hops_duplicate_blob_reject() {
295 let blob = make_blob(200);
296 let entry = make_path_entry(2, &[blob], 9999.0);
297
298 assert_eq!(
299 should_update_path(Some(&entry), 5, 200, &blob, false, 1000.0),
300 PathDecision::Reject
301 );
302 }
303
304 #[test]
305 fn test_equal_hops_new_blob_newer_emission_add() {
306 let old_blob = make_blob(100);
307 let new_blob = make_blob(200);
308 let entry = make_path_entry(3, &[old_blob], 9999.0);
309
310 assert_eq!(
311 should_update_path(Some(&entry), 3, 200, &new_blob, false, 1000.0),
312 PathDecision::Add
313 );
314 }
315}