1use super::tables::{PathEntry, PathSet};
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 prefer_shorter_path: bool,
54) -> PathDecision {
55 if announce_hops > constants::PATHFINDER_M {
57 return PathDecision::Reject;
58 }
59
60 let existing = match existing {
61 None => return PathDecision::Add,
62 Some(e) => e,
63 };
64
65 let path_timebase = timebase_from_random_blobs(&existing.random_blobs);
66 let blob_is_new = !existing.random_blobs.contains(random_blob);
67
68 if announce_hops <= existing.hops {
69 if prefer_shorter_path && announce_hops < existing.hops {
71 return PathDecision::Add;
72 }
73 if blob_is_new && announce_emitted_ts > path_timebase {
75 return PathDecision::Add;
76 }
77 if announce_emitted_ts == path_timebase && path_is_unresponsive {
79 return PathDecision::Add;
80 }
81 PathDecision::Reject
82 } else {
83 let path_expired = now >= existing.expires;
85
86 if path_expired && blob_is_new {
87 return PathDecision::Add;
88 }
89
90 if announce_emitted_ts > path_timebase && blob_is_new {
91 return PathDecision::Add;
92 }
93
94 if announce_emitted_ts == path_timebase && path_is_unresponsive {
95 return PathDecision::Add;
96 }
97
98 PathDecision::Reject
99 }
100}
101
102#[derive(Debug, PartialEq, Eq)]
104pub enum MultiPathDecision {
105 ReplacePrimary,
107 AddAlternative,
109 Reject,
111}
112
113#[allow(clippy::too_many_arguments)]
124pub fn decide_announce_multipath(
125 existing_set: Option<&PathSet>,
126 announce_hops: u8,
127 announce_emitted_ts: u64,
128 random_blob: &[u8; 10],
129 next_hop: &[u8; 16],
130 path_is_unresponsive: bool,
131 now: f64,
132 prefer_shorter_path: bool,
133) -> MultiPathDecision {
134 if announce_hops > constants::PATHFINDER_M {
136 return MultiPathDecision::Reject;
137 }
138
139 let path_set = match existing_set {
140 None => return MultiPathDecision::ReplacePrimary,
141 Some(ps) if ps.is_empty() => return MultiPathDecision::ReplacePrimary,
142 Some(ps) => ps,
143 };
144
145 if let Some(existing_path) = path_set.find_by_next_hop(next_hop) {
147 let decision = should_update_path(
148 Some(existing_path),
149 announce_hops,
150 announce_emitted_ts,
151 random_blob,
152 path_is_unresponsive,
153 now,
154 prefer_shorter_path,
155 );
156 match decision {
157 PathDecision::Add => MultiPathDecision::ReplacePrimary,
158 PathDecision::Reject => MultiPathDecision::Reject,
159 }
160 } else {
161 let all_blobs = path_set.all_random_blobs();
163 let blob_is_new = !all_blobs.contains(random_blob);
164
165 if !blob_is_new {
166 return MultiPathDecision::Reject;
167 }
168
169 let max_timebase = timebase_from_random_blobs(&all_blobs);
170 if announce_emitted_ts >= max_timebase {
171 MultiPathDecision::AddAlternative
172 } else {
173 MultiPathDecision::Reject
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::super::types::InterfaceId;
181 use super::*;
182
183 fn make_blob(timebase: u64) -> [u8; 10] {
184 let mut blob = [0u8; 10];
185 let bytes = timebase.to_be_bytes();
186 blob[5..10].copy_from_slice(&bytes[3..8]);
188 blob
189 }
190
191 fn make_path_entry(hops: u8, blobs: &[[u8; 10]], expires: f64) -> PathEntry {
192 PathEntry {
193 timestamp: 1000.0,
194 next_hop: [0xAA; 16],
195 hops,
196 expires,
197 random_blobs: blobs.to_vec(),
198 receiving_interface: InterfaceId(1),
199 packet_hash: [0xBB; 32],
200 announce_raw: None,
201 }
202 }
203
204 #[test]
205 fn test_timebase_extraction() {
206 let blob = make_blob(12345);
207 assert_eq!(timebase_from_random_blob(&blob), 12345);
208 }
209
210 #[test]
211 fn test_timebase_from_multiple_blobs() {
212 let b1 = make_blob(100);
213 let b2 = make_blob(200);
214 let b3 = make_blob(50);
215 assert_eq!(timebase_from_random_blobs(&[b1, b2, b3]), 200);
216 }
217
218 #[test]
219 fn test_timebase_empty_blobs() {
220 assert_eq!(timebase_from_random_blobs(&[]), 0);
221 }
222
223 #[test]
224 fn test_extract_random_blob() {
225 let mut data = [0u8; 100];
227 data[74..84].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
229 let blob = extract_random_blob(&data).unwrap();
230 assert_eq!(blob, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
231 }
232
233 #[test]
234 fn test_extract_random_blob_too_short() {
235 let data = [0u8; 80]; assert!(extract_random_blob(&data).is_none());
237 }
238
239 #[test]
242 fn test_no_existing_path_always_add() {
243 let blob = make_blob(100);
244 assert_eq!(
245 should_update_path(None, 3, 100, &blob, false, 1000.0, false),
246 PathDecision::Add
247 );
248 }
249
250 #[test]
251 fn test_hop_limit_reject() {
252 let blob = make_blob(100);
253 assert_eq!(
254 should_update_path(None, 129, 100, &blob, false, 1000.0, false),
255 PathDecision::Reject
256 );
257 }
258
259 #[test]
260 fn test_fewer_hops_new_blob_newer_emission_add() {
261 let old_blob = make_blob(100);
262 let new_blob = make_blob(200);
263 let entry = make_path_entry(5, &[old_blob], 9999.0);
264 assert_eq!(
265 should_update_path(Some(&entry), 3, 200, &new_blob, false, 1000.0, false),
266 PathDecision::Add
267 );
268 }
269
270 #[test]
271 fn test_fewer_hops_duplicate_blob_reject() {
272 let blob = make_blob(100);
273 let entry = make_path_entry(5, &[blob], 9999.0);
274 assert_eq!(
275 should_update_path(Some(&entry), 3, 200, &blob, false, 1000.0, false),
276 PathDecision::Reject
277 );
278 }
279
280 #[test]
281 fn test_fewer_hops_same_emission_unresponsive_add() {
282 let old_blob = make_blob(100);
283 let mut different_blob = [0u8; 10];
284 different_blob[0] = 0xFF;
285 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
286
287 let entry = make_path_entry(5, &[old_blob], 9999.0);
288 assert_eq!(
289 should_update_path(Some(&entry), 3, 100, &different_blob, true, 1000.0, false),
290 PathDecision::Add
291 );
292 }
293
294 #[test]
295 fn test_fewer_hops_same_emission_responsive_reject() {
296 let old_blob = make_blob(100);
297 let mut different_blob = [0u8; 10];
298 different_blob[0] = 0xFF;
299 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
300
301 let entry = make_path_entry(5, &[old_blob], 9999.0);
302 assert_eq!(
303 should_update_path(Some(&entry), 3, 100, &different_blob, false, 1000.0, false),
304 PathDecision::Reject
305 );
306 }
307
308 #[test]
309 fn test_more_hops_expired_path_new_blob_add() {
310 let old_blob = make_blob(100);
311 let new_blob = make_blob(50); let entry = make_path_entry(2, &[old_blob], 500.0); assert_eq!(
315 should_update_path(Some(&entry), 5, 50, &new_blob, false, 600.0, false), PathDecision::Add
317 );
318 }
319
320 #[test]
321 fn test_more_hops_not_expired_older_emission_reject() {
322 let old_blob = make_blob(200);
323 let new_blob = make_blob(100);
324 let entry = make_path_entry(2, &[old_blob], 9999.0);
325
326 assert_eq!(
327 should_update_path(Some(&entry), 5, 100, &new_blob, false, 1000.0, false),
328 PathDecision::Reject
329 );
330 }
331
332 #[test]
333 fn test_more_hops_newer_emission_new_blob_add() {
334 let old_blob = make_blob(100);
335 let new_blob = make_blob(200);
336 let entry = make_path_entry(2, &[old_blob], 9999.0);
337
338 assert_eq!(
339 should_update_path(Some(&entry), 5, 200, &new_blob, false, 1000.0, false),
340 PathDecision::Add
341 );
342 }
343
344 #[test]
345 fn test_more_hops_same_emission_unresponsive_add() {
346 let old_blob = make_blob(100);
347 let mut different_blob = [0u8; 10];
348 different_blob[0] = 0xFF;
349 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
350
351 let entry = make_path_entry(2, &[old_blob], 9999.0);
352
353 assert_eq!(
354 should_update_path(Some(&entry), 5, 100, &different_blob, true, 1000.0, false),
355 PathDecision::Add
356 );
357 }
358
359 #[test]
360 fn test_more_hops_same_emission_responsive_reject() {
361 let old_blob = make_blob(100);
362 let mut different_blob = [0u8; 10];
363 different_blob[0] = 0xFF;
364 different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
365
366 let entry = make_path_entry(2, &[old_blob], 9999.0);
367
368 assert_eq!(
369 should_update_path(Some(&entry), 5, 100, &different_blob, false, 1000.0, false),
370 PathDecision::Reject
371 );
372 }
373
374 #[test]
375 fn test_more_hops_duplicate_blob_reject() {
376 let blob = make_blob(200);
377 let entry = make_path_entry(2, &[blob], 9999.0);
378
379 assert_eq!(
380 should_update_path(Some(&entry), 5, 200, &blob, false, 1000.0, false),
381 PathDecision::Reject
382 );
383 }
384
385 #[test]
386 fn test_equal_hops_new_blob_newer_emission_add() {
387 let old_blob = make_blob(100);
388 let new_blob = make_blob(200);
389 let entry = make_path_entry(3, &[old_blob], 9999.0);
390
391 assert_eq!(
392 should_update_path(Some(&entry), 3, 200, &new_blob, false, 1000.0, false),
393 PathDecision::Add
394 );
395 }
396
397 #[test]
400 fn test_prefer_shorter_path_strictly_fewer_hops_duplicate_blob_add() {
401 let blob = make_blob(100);
402 let entry = make_path_entry(5, &[blob], 9999.0);
403 assert_eq!(
404 should_update_path(Some(&entry), 3, 100, &blob, false, 1000.0, true),
405 PathDecision::Add
406 );
407 }
408
409 #[test]
410 fn test_prefer_shorter_path_equal_hops_duplicate_blob_reject() {
411 let blob = make_blob(100);
413 let entry = make_path_entry(3, &[blob], 9999.0);
414 assert_eq!(
415 should_update_path(Some(&entry), 3, 100, &blob, false, 1000.0, true),
416 PathDecision::Reject
417 );
418 }
419
420 #[test]
421 fn test_prefer_shorter_path_more_hops_duplicate_blob_reject() {
422 let blob = make_blob(100);
424 let entry = make_path_entry(2, &[blob], 9999.0);
425 assert_eq!(
426 should_update_path(Some(&entry), 5, 100, &blob, false, 1000.0, true),
427 PathDecision::Reject
428 );
429 }
430
431 #[test]
434 fn test_multipath_no_existing_set_replace_primary() {
435 let blob = make_blob(100);
436 assert_eq!(
437 decide_announce_multipath(None, 3, 100, &blob, &[0xBB; 16], false, 1000.0, false),
438 MultiPathDecision::ReplacePrimary
439 );
440 }
441
442 #[test]
443 fn test_multipath_same_nexthop_update() {
444 let blob_old = make_blob(100);
445 let blob_new = make_blob(200);
446 let entry = make_path_entry(3, &[blob_old], 9999.0);
447 let ps = PathSet::from_single(entry, 3);
448
449 assert_eq!(
451 decide_announce_multipath(
452 Some(&ps),
453 2,
454 200,
455 &blob_new,
456 &[0xAA; 16],
457 false,
458 1000.0,
459 false
460 ),
461 MultiPathDecision::ReplacePrimary
462 );
463 }
464
465 #[test]
466 fn test_multipath_same_nexthop_reject() {
467 let blob = make_blob(100);
468 let entry = make_path_entry(3, &[blob], 9999.0);
469 let ps = PathSet::from_single(entry, 3);
470
471 assert_eq!(
473 decide_announce_multipath(Some(&ps), 3, 100, &blob, &[0xAA; 16], false, 1000.0, false),
474 MultiPathDecision::Reject
475 );
476 }
477
478 #[test]
479 fn test_multipath_new_nexthop_novel_blob_add_alternative() {
480 let blob_existing = make_blob(100);
481 let blob_new = make_blob(200);
482 let entry = make_path_entry(3, &[blob_existing], 9999.0);
483 let ps = PathSet::from_single(entry, 3);
484
485 assert_eq!(
487 decide_announce_multipath(
488 Some(&ps),
489 4,
490 200,
491 &blob_new,
492 &[0xCC; 16],
493 false,
494 1000.0,
495 false
496 ),
497 MultiPathDecision::AddAlternative
498 );
499 }
500
501 #[test]
502 fn test_multipath_new_nexthop_known_blob_reject() {
503 let blob = make_blob(100);
504 let entry = make_path_entry(3, &[blob], 9999.0);
505 let ps = PathSet::from_single(entry, 3);
506
507 assert_eq!(
509 decide_announce_multipath(Some(&ps), 4, 100, &blob, &[0xCC; 16], false, 1000.0, false),
510 MultiPathDecision::Reject
511 );
512 }
513
514 #[test]
515 fn test_multipath_new_nexthop_older_emission_reject() {
516 let blob_existing = make_blob(200);
517 let blob_new = make_blob(100); let entry = make_path_entry(3, &[blob_existing], 9999.0);
519 let ps = PathSet::from_single(entry, 3);
520
521 assert_eq!(
523 decide_announce_multipath(
524 Some(&ps),
525 4,
526 100,
527 &blob_new,
528 &[0xCC; 16],
529 false,
530 1000.0,
531 false
532 ),
533 MultiPathDecision::Reject
534 );
535 }
536
537 #[test]
538 fn test_multipath_hop_limit_reject() {
539 let blob = make_blob(100);
540 assert_eq!(
541 decide_announce_multipath(None, 129, 100, &blob, &[0xBB; 16], false, 1000.0, false),
542 MultiPathDecision::Reject
543 );
544 }
545}