use super::tables::{PathEntry, PathSet};
use crate::constants;
pub fn timebase_from_random_blob(blob: &[u8; 10]) -> u64 {
let mut bytes = [0u8; 8];
bytes[3..8].copy_from_slice(&blob[5..10]);
u64::from_be_bytes(bytes)
}
pub fn timebase_from_random_blobs(blobs: &[[u8; 10]]) -> u64 {
let mut timebase: u64 = 0;
for blob in blobs {
let emitted = timebase_from_random_blob(blob);
if emitted > timebase {
timebase = emitted;
}
}
timebase
}
pub fn extract_random_blob(packet_data: &[u8]) -> Option<[u8; 10]> {
let offset = constants::KEYSIZE / 8 + constants::NAME_HASH_LENGTH / 8;
if packet_data.len() < offset + 10 {
return None;
}
let mut blob = [0u8; 10];
blob.copy_from_slice(&packet_data[offset..offset + 10]);
Some(blob)
}
#[derive(Debug, PartialEq, Eq)]
pub enum PathDecision {
Add,
Reject,
}
pub fn should_update_path(
existing: Option<&PathEntry>,
announce_hops: u8,
announce_emitted_ts: u64,
random_blob: &[u8; 10],
path_is_unresponsive: bool,
now: f64,
prefer_shorter_path: bool,
) -> PathDecision {
if announce_hops > constants::PATHFINDER_M {
return PathDecision::Reject;
}
let existing = match existing {
None => return PathDecision::Add,
Some(e) => e,
};
let path_timebase = timebase_from_random_blobs(&existing.random_blobs);
let blob_is_new = !existing.random_blobs.contains(random_blob);
if announce_hops <= existing.hops {
if prefer_shorter_path && announce_hops < existing.hops {
return PathDecision::Add;
}
if blob_is_new && announce_emitted_ts > path_timebase {
return PathDecision::Add;
}
if announce_emitted_ts == path_timebase && path_is_unresponsive {
return PathDecision::Add;
}
PathDecision::Reject
} else {
let path_expired = now >= existing.expires;
if path_expired && blob_is_new {
return PathDecision::Add;
}
if announce_emitted_ts > path_timebase && blob_is_new {
return PathDecision::Add;
}
if announce_emitted_ts == path_timebase && path_is_unresponsive {
return PathDecision::Add;
}
PathDecision::Reject
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum MultiPathDecision {
ReplacePrimary,
AddAlternative,
Reject,
}
#[allow(clippy::too_many_arguments)]
pub fn decide_announce_multipath(
existing_set: Option<&PathSet>,
announce_hops: u8,
announce_emitted_ts: u64,
random_blob: &[u8; 10],
next_hop: &[u8; 16],
path_is_unresponsive: bool,
now: f64,
prefer_shorter_path: bool,
) -> MultiPathDecision {
if announce_hops > constants::PATHFINDER_M {
return MultiPathDecision::Reject;
}
let path_set = match existing_set {
None => return MultiPathDecision::ReplacePrimary,
Some(ps) if ps.is_empty() => return MultiPathDecision::ReplacePrimary,
Some(ps) => ps,
};
if let Some(existing_path) = path_set.find_by_next_hop(next_hop) {
let decision = should_update_path(
Some(existing_path),
announce_hops,
announce_emitted_ts,
random_blob,
path_is_unresponsive,
now,
prefer_shorter_path,
);
match decision {
PathDecision::Add => MultiPathDecision::ReplacePrimary,
PathDecision::Reject => MultiPathDecision::Reject,
}
} else {
let all_blobs = path_set.all_random_blobs();
let blob_is_new = !all_blobs.contains(random_blob);
if !blob_is_new {
return MultiPathDecision::Reject;
}
let max_timebase = timebase_from_random_blobs(&all_blobs);
if announce_emitted_ts >= max_timebase {
MultiPathDecision::AddAlternative
} else {
MultiPathDecision::Reject
}
}
}
#[cfg(test)]
mod tests {
use super::super::types::InterfaceId;
use super::*;
fn make_blob(timebase: u64) -> [u8; 10] {
let mut blob = [0u8; 10];
let bytes = timebase.to_be_bytes();
blob[5..10].copy_from_slice(&bytes[3..8]);
blob
}
fn make_path_entry(hops: u8, blobs: &[[u8; 10]], expires: f64) -> PathEntry {
PathEntry {
timestamp: 1000.0,
next_hop: [0xAA; 16],
hops,
expires,
random_blobs: blobs.to_vec(),
receiving_interface: InterfaceId(1),
packet_hash: [0xBB; 32],
announce_raw: None,
}
}
#[test]
fn test_timebase_extraction() {
let blob = make_blob(12345);
assert_eq!(timebase_from_random_blob(&blob), 12345);
}
#[test]
fn test_timebase_from_multiple_blobs() {
let b1 = make_blob(100);
let b2 = make_blob(200);
let b3 = make_blob(50);
assert_eq!(timebase_from_random_blobs(&[b1, b2, b3]), 200);
}
#[test]
fn test_timebase_empty_blobs() {
assert_eq!(timebase_from_random_blobs(&[]), 0);
}
#[test]
fn test_extract_random_blob() {
let mut data = [0u8; 100];
data[74..84].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
let blob = extract_random_blob(&data).unwrap();
assert_eq!(blob, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
#[test]
fn test_extract_random_blob_too_short() {
let data = [0u8; 80]; assert!(extract_random_blob(&data).is_none());
}
#[test]
fn test_no_existing_path_always_add() {
let blob = make_blob(100);
assert_eq!(
should_update_path(None, 3, 100, &blob, false, 1000.0, false),
PathDecision::Add
);
}
#[test]
fn test_hop_limit_reject() {
let blob = make_blob(100);
assert_eq!(
should_update_path(None, 129, 100, &blob, false, 1000.0, false),
PathDecision::Reject
);
}
#[test]
fn test_fewer_hops_new_blob_newer_emission_add() {
let old_blob = make_blob(100);
let new_blob = make_blob(200);
let entry = make_path_entry(5, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 200, &new_blob, false, 1000.0, false),
PathDecision::Add
);
}
#[test]
fn test_fewer_hops_duplicate_blob_reject() {
let blob = make_blob(100);
let entry = make_path_entry(5, &[blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 200, &blob, false, 1000.0, false),
PathDecision::Reject
);
}
#[test]
fn test_fewer_hops_same_emission_unresponsive_add() {
let old_blob = make_blob(100);
let mut different_blob = [0u8; 10];
different_blob[0] = 0xFF;
different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
let entry = make_path_entry(5, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 100, &different_blob, true, 1000.0, false),
PathDecision::Add
);
}
#[test]
fn test_fewer_hops_same_emission_responsive_reject() {
let old_blob = make_blob(100);
let mut different_blob = [0u8; 10];
different_blob[0] = 0xFF;
different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
let entry = make_path_entry(5, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 100, &different_blob, false, 1000.0, false),
PathDecision::Reject
);
}
#[test]
fn test_more_hops_expired_path_new_blob_add() {
let old_blob = make_blob(100);
let new_blob = make_blob(50); let entry = make_path_entry(2, &[old_blob], 500.0);
assert_eq!(
should_update_path(Some(&entry), 5, 50, &new_blob, false, 600.0, false), PathDecision::Add
);
}
#[test]
fn test_more_hops_not_expired_older_emission_reject() {
let old_blob = make_blob(200);
let new_blob = make_blob(100);
let entry = make_path_entry(2, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 5, 100, &new_blob, false, 1000.0, false),
PathDecision::Reject
);
}
#[test]
fn test_more_hops_newer_emission_new_blob_add() {
let old_blob = make_blob(100);
let new_blob = make_blob(200);
let entry = make_path_entry(2, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 5, 200, &new_blob, false, 1000.0, false),
PathDecision::Add
);
}
#[test]
fn test_more_hops_same_emission_unresponsive_add() {
let old_blob = make_blob(100);
let mut different_blob = [0u8; 10];
different_blob[0] = 0xFF;
different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
let entry = make_path_entry(2, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 5, 100, &different_blob, true, 1000.0, false),
PathDecision::Add
);
}
#[test]
fn test_more_hops_same_emission_responsive_reject() {
let old_blob = make_blob(100);
let mut different_blob = [0u8; 10];
different_blob[0] = 0xFF;
different_blob[5..10].copy_from_slice(&100u64.to_be_bytes()[3..8]);
let entry = make_path_entry(2, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 5, 100, &different_blob, false, 1000.0, false),
PathDecision::Reject
);
}
#[test]
fn test_more_hops_duplicate_blob_reject() {
let blob = make_blob(200);
let entry = make_path_entry(2, &[blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 5, 200, &blob, false, 1000.0, false),
PathDecision::Reject
);
}
#[test]
fn test_equal_hops_new_blob_newer_emission_add() {
let old_blob = make_blob(100);
let new_blob = make_blob(200);
let entry = make_path_entry(3, &[old_blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 200, &new_blob, false, 1000.0, false),
PathDecision::Add
);
}
#[test]
fn test_prefer_shorter_path_strictly_fewer_hops_duplicate_blob_add() {
let blob = make_blob(100);
let entry = make_path_entry(5, &[blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 100, &blob, false, 1000.0, true),
PathDecision::Add
);
}
#[test]
fn test_prefer_shorter_path_equal_hops_duplicate_blob_reject() {
let blob = make_blob(100);
let entry = make_path_entry(3, &[blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 3, 100, &blob, false, 1000.0, true),
PathDecision::Reject
);
}
#[test]
fn test_prefer_shorter_path_more_hops_duplicate_blob_reject() {
let blob = make_blob(100);
let entry = make_path_entry(2, &[blob], 9999.0);
assert_eq!(
should_update_path(Some(&entry), 5, 100, &blob, false, 1000.0, true),
PathDecision::Reject
);
}
#[test]
fn test_multipath_no_existing_set_replace_primary() {
let blob = make_blob(100);
assert_eq!(
decide_announce_multipath(None, 3, 100, &blob, &[0xBB; 16], false, 1000.0, false),
MultiPathDecision::ReplacePrimary
);
}
#[test]
fn test_multipath_same_nexthop_update() {
let blob_old = make_blob(100);
let blob_new = make_blob(200);
let entry = make_path_entry(3, &[blob_old], 9999.0);
let ps = PathSet::from_single(entry, 3);
assert_eq!(
decide_announce_multipath(
Some(&ps),
2,
200,
&blob_new,
&[0xAA; 16],
false,
1000.0,
false
),
MultiPathDecision::ReplacePrimary
);
}
#[test]
fn test_multipath_same_nexthop_reject() {
let blob = make_blob(100);
let entry = make_path_entry(3, &[blob], 9999.0);
let ps = PathSet::from_single(entry, 3);
assert_eq!(
decide_announce_multipath(Some(&ps), 3, 100, &blob, &[0xAA; 16], false, 1000.0, false),
MultiPathDecision::Reject
);
}
#[test]
fn test_multipath_new_nexthop_novel_blob_add_alternative() {
let blob_existing = make_blob(100);
let blob_new = make_blob(200);
let entry = make_path_entry(3, &[blob_existing], 9999.0);
let ps = PathSet::from_single(entry, 3);
assert_eq!(
decide_announce_multipath(
Some(&ps),
4,
200,
&blob_new,
&[0xCC; 16],
false,
1000.0,
false
),
MultiPathDecision::AddAlternative
);
}
#[test]
fn test_multipath_new_nexthop_known_blob_reject() {
let blob = make_blob(100);
let entry = make_path_entry(3, &[blob], 9999.0);
let ps = PathSet::from_single(entry, 3);
assert_eq!(
decide_announce_multipath(Some(&ps), 4, 100, &blob, &[0xCC; 16], false, 1000.0, false),
MultiPathDecision::Reject
);
}
#[test]
fn test_multipath_new_nexthop_older_emission_reject() {
let blob_existing = make_blob(200);
let blob_new = make_blob(100); let entry = make_path_entry(3, &[blob_existing], 9999.0);
let ps = PathSet::from_single(entry, 3);
assert_eq!(
decide_announce_multipath(
Some(&ps),
4,
100,
&blob_new,
&[0xCC; 16],
false,
1000.0,
false
),
MultiPathDecision::Reject
);
}
#[test]
fn test_multipath_hop_limit_reject() {
let blob = make_blob(100);
assert_eq!(
decide_announce_multipath(None, 129, 100, &blob, &[0xBB; 16], false, 1000.0, false),
MultiPathDecision::Reject
);
}
}