use super::*;
#[test]
fn test_routing_tree_fallback() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
let link_id = LinkId::new(1);
let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000);
let peer_addr = *id.node_addr();
node.add_connection(conn).unwrap();
node.promote_connection(link_id, id, 2000).unwrap();
let peer_coords = TreeCoordinate::from_addrs(vec![peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(peer_addr, my_addr, 1, 1000),
peer_coords,
);
let dest = make_node_addr(99);
let dest_coords = TreeCoordinate::from_addrs(vec![dest, peer_addr, my_addr]).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
let result = node.find_next_hop(&dest);
assert!(result.is_some());
assert_eq!(result.unwrap().node_addr(), &peer_addr);
}
#[test]
fn test_routing_bloom_hit_not_closer_falls_through_to_tree() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
let tree_link = LinkId::new(1);
let (tree_conn, tree_id) = make_completed_connection(&mut node, tree_link, transport_id, 1000);
let tree_peer_addr = *tree_id.node_addr();
node.add_connection(tree_conn).unwrap();
node.promote_connection(tree_link, tree_id, 2000).unwrap();
let bloom_link = LinkId::new(2);
let (bloom_conn, bloom_id) =
make_completed_connection(&mut node, bloom_link, transport_id, 1000);
let bloom_peer_addr = *bloom_id.node_addr();
node.add_connection(bloom_conn).unwrap();
node.promote_connection(bloom_link, bloom_id, 2000).unwrap();
let tree_peer_coords = TreeCoordinate::from_addrs(vec![tree_peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(tree_peer_addr, my_addr, 1, 1000),
tree_peer_coords,
);
let bloom_peer_coords = TreeCoordinate::from_addrs(vec![bloom_peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(bloom_peer_addr, my_addr, 1, 1000),
bloom_peer_coords,
);
let dest = make_node_addr(99);
let dest_coords = TreeCoordinate::from_addrs(vec![dest, tree_peer_addr, my_addr]).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
let bloom_peer = node.get_peer_mut(&bloom_peer_addr).unwrap();
let mut filter = BloomFilter::new();
filter.insert(&dest);
bloom_peer.update_filter(filter, 1, 3000);
let result = node.find_next_hop(&dest);
assert!(
result.is_some(),
"find_next_hop must fall through to tree routing when bloom \
candidates exist but none are strictly closer than self"
);
let next_hop = result.unwrap().node_addr();
assert_eq!(
next_hop, &tree_peer_addr,
"tree-routing winner expected (tree_peer), got {:?}",
next_hop,
);
assert_ne!(
next_hop, &bloom_peer_addr,
"bloom_peer must be excluded by the self-distance check",
);
}
#[test]
fn test_routing_tree_no_coords_in_cache() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let link_id = LinkId::new(1);
let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000);
node.add_connection(conn).unwrap();
node.promote_connection(link_id, id, 2000).unwrap();
let dest = make_node_addr(99);
assert!(node.find_next_hop(&dest).is_none());
}
#[test]
fn test_reply_learned_mode_uses_observed_route_without_coords() {
let mut config = Config::new();
config.node.routing.mode = RoutingMode::ReplyLearned;
let mut node = Node::new(config).unwrap();
let transport_id = TransportId::new(1);
let link_id1 = LinkId::new(1);
let (conn1, id1) = make_completed_connection(&mut node, link_id1, transport_id, 1000);
let peer1_addr = *id1.node_addr();
node.add_connection(conn1).unwrap();
node.promote_connection(link_id1, id1, 2000).unwrap();
let link_id2 = LinkId::new(2);
let (conn2, id2) = make_completed_connection(&mut node, link_id2, transport_id, 1000);
let peer2_addr = *id2.node_addr();
node.add_connection(conn2).unwrap();
node.promote_connection(link_id2, id2, 2000).unwrap();
let dest = make_node_addr(99);
node.learn_reverse_route(dest, peer2_addr);
let result = node.find_next_hop(&dest);
assert!(result.is_some(), "learned route should not require coords");
assert_eq!(result.unwrap().node_addr(), &peer2_addr);
assert_ne!(peer1_addr, peer2_addr);
}
#[test]
fn test_reply_learned_mode_multipaths_observed_routes() {
let mut config = Config::new();
config.node.routing.mode = RoutingMode::ReplyLearned;
let mut node = Node::new(config).unwrap();
let transport_id = TransportId::new(1);
let link_id1 = LinkId::new(1);
let (conn1, id1) = make_completed_connection(&mut node, link_id1, transport_id, 1000);
let peer1_addr = *id1.node_addr();
node.add_connection(conn1).unwrap();
node.promote_connection(link_id1, id1, 2000).unwrap();
let link_id2 = LinkId::new(2);
let (conn2, id2) = make_completed_connection(&mut node, link_id2, transport_id, 1000);
let peer2_addr = *id2.node_addr();
node.add_connection(conn2).unwrap();
node.promote_connection(link_id2, id2, 2000).unwrap();
let dest = make_node_addr(99);
node.learn_reverse_route(dest, peer1_addr);
for _ in 0..4 {
node.learn_reverse_route(dest, peer2_addr);
}
let mut selected = Vec::new();
for _ in 0..20 {
selected.push(
*node
.find_next_hop(&dest)
.expect("learned route")
.node_addr(),
);
}
let peer1_count = selected.iter().filter(|addr| **addr == peer1_addr).count();
let peer2_count = selected.iter().filter(|addr| **addr == peer2_addr).count();
assert!(
peer1_count > 0,
"lower-score learned route should remain in exploratory rotation"
);
assert!(
peer2_count > peer1_count,
"higher-score learned route should carry most packets"
);
}
#[test]
fn test_reply_learned_mode_periodically_explores_coordinate_route() {
let mut config = Config::new();
config.node.routing.mode = RoutingMode::ReplyLearned;
config.node.routing.learned_fallback_explore_interval = 2;
let mut node = Node::new(config).unwrap();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
let tree_link = LinkId::new(1);
let (tree_conn, tree_id) = make_completed_connection(&mut node, tree_link, transport_id, 1000);
let tree_peer_addr = *tree_id.node_addr();
node.add_connection(tree_conn).unwrap();
node.promote_connection(tree_link, tree_id, 2000).unwrap();
let learned_link = LinkId::new(2);
let (learned_conn, learned_id) =
make_completed_connection(&mut node, learned_link, transport_id, 1000);
let learned_peer_addr = *learned_id.node_addr();
node.add_connection(learned_conn).unwrap();
node.promote_connection(learned_link, learned_id, 2000)
.unwrap();
let tree_peer_coords = TreeCoordinate::from_addrs(vec![tree_peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(tree_peer_addr, my_addr, 1, 1000),
tree_peer_coords,
);
let learned_peer_coords = TreeCoordinate::from_addrs(vec![learned_peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(learned_peer_addr, my_addr, 1, 1000),
learned_peer_coords,
);
let dest = make_node_addr(99);
let dest_coords = TreeCoordinate::from_addrs(vec![dest, tree_peer_addr, my_addr]).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
node.learn_reverse_route(dest, learned_peer_addr);
let first = *node
.find_next_hop(&dest)
.expect("learned route")
.node_addr();
let second = *node
.find_next_hop(&dest)
.expect("learned route")
.node_addr();
let third = *node
.find_next_hop(&dest)
.expect("coordinate exploration route")
.node_addr();
assert_eq!(first, learned_peer_addr);
assert_eq!(second, learned_peer_addr);
assert_eq!(
third, tree_peer_addr,
"fallback exploration should periodically try the coordinate route"
);
}
#[test]
fn test_tree_mode_ignores_learned_route_without_coords() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let link_id = LinkId::new(1);
let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000);
let peer_addr = *id.node_addr();
node.add_connection(conn).unwrap();
node.promote_connection(link_id, id, 2000).unwrap();
let dest = make_node_addr(99);
node.learn_reverse_route(dest, peer_addr);
assert!(
node.find_next_hop(&dest).is_none(),
"default tree mode must preserve current no-coords behavior"
);
}
#[test]
fn test_routing_refreshes_coord_cache_ttl() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
let link_id = LinkId::new(1);
let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000);
let peer_addr = *id.node_addr();
node.add_connection(conn).unwrap();
node.promote_connection(link_id, id, 2000).unwrap();
let dest = make_node_addr(99);
let dest_coords = TreeCoordinate::from_addrs(vec![dest, peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(peer_addr, my_addr, 1, 1000),
TreeCoordinate::from_addrs(vec![peer_addr, my_addr]).unwrap(),
);
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
let short_ttl = 10_000; node.coord_cache_mut()
.insert_with_ttl(dest, dest_coords, now_ms, short_ttl);
let original_expiry = node.coord_cache().get_entry(&dest).unwrap().expires_at();
assert!(node.find_next_hop(&dest).is_some());
let new_expiry = node.coord_cache().get_entry(&dest).unwrap().expires_at();
assert!(
new_expiry > original_expiry,
"find_next_hop should refresh the coord_cache TTL: original={}, new={}",
original_expiry,
new_expiry,
);
}
#[test]
fn test_routing_bloom_hit_without_coords_returns_none() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let link_id1 = LinkId::new(1);
let (conn1, id1) = make_completed_connection(&mut node, link_id1, transport_id, 1000);
let peer1_addr = *id1.node_addr();
node.add_connection(conn1).unwrap();
node.promote_connection(link_id1, id1, 2000).unwrap();
let link_id2 = LinkId::new(2);
let (conn2, id2) = make_completed_connection(&mut node, link_id2, transport_id, 1000);
let peer2_addr = *id2.node_addr();
node.add_connection(conn2).unwrap();
node.promote_connection(link_id2, id2, 2000).unwrap();
let dest = make_node_addr(99);
for &addr in &[peer1_addr, peer2_addr] {
let peer = node.get_peer_mut(&addr).unwrap();
let mut filter = BloomFilter::new();
filter.insert(&dest);
peer.update_filter(filter, 1, 3000);
}
assert!(node.find_next_hop(&dest).is_none());
}
#[test]
fn test_routing_discovery_coord_cache() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
let link_id = LinkId::new(1);
let (conn, id) = make_completed_connection(&mut node, link_id, transport_id, 1000);
let peer_addr = *id.node_addr();
node.add_connection(conn).unwrap();
node.promote_connection(link_id, id, 2000).unwrap();
let peer_coords = TreeCoordinate::from_addrs(vec![peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(peer_addr, my_addr, 1, 1000),
peer_coords,
);
let dest = make_node_addr(99);
let dest_coords = TreeCoordinate::from_addrs(vec![dest, peer_addr, my_addr]).unwrap();
let peer = node.get_peer_mut(&peer_addr).unwrap();
let mut filter = BloomFilter::new();
filter.insert(&dest);
peer.update_filter(filter, 1, 3000);
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
assert!(node.coord_cache().get(&dest, now_ms).is_none());
assert!(node.find_next_hop(&dest).is_none());
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
let result = node.find_next_hop(&dest);
assert!(result.is_some(), "Should route via coord_cache");
assert_eq!(
result.unwrap().node_addr(),
&peer_addr,
"Should pick peer with bloom filter hit"
);
}