1use std::collections::{HashMap, HashSet, VecDeque};
19
20use thiserror::Error;
21
22#[derive(Debug, Error, Clone, PartialEq, Eq)]
28pub enum OverlayError {
29 #[error("node not found: {0}")]
31 NodeNotFound(u64),
32
33 #[error("overlay network is full")]
35 NetworkFull,
36
37 #[error("no route found from {from} to {to}")]
39 RouteNotFound {
40 from: u64,
42 to: u64,
44 },
45
46 #[error("cannot route a node to itself")]
48 SelfRoute,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum OverlayTopology {
58 Chord {
60 fingers: usize,
62 },
63 Pastry {
65 leaf_set_size: usize,
67 routing_table_rows: usize,
69 },
70 Kademlia {
72 k: usize,
74 alpha: usize,
76 },
77 FullMesh,
79 Tree {
81 branching_factor: usize,
83 },
84}
85
86impl OverlayTopology {
87 pub fn name(&self) -> &'static str {
89 match self {
90 OverlayTopology::Chord { .. } => "Chord",
91 OverlayTopology::Pastry { .. } => "Pastry",
92 OverlayTopology::Kademlia { .. } => "Kademlia",
93 OverlayTopology::FullMesh => "FullMesh",
94 OverlayTopology::Tree { .. } => "Tree",
95 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct OverlayNode {
106 pub node_id: u64,
108 pub address: String,
110 pub joined_at: u64,
112 pub last_heartbeat: u64,
114 pub metadata: HashMap<String, String>,
116}
117
118impl OverlayNode {
119 pub fn new(node_id: u64, address: impl Into<String>, now: u64) -> Self {
121 Self {
122 node_id,
123 address: address.into(),
124 joined_at: now,
125 last_heartbeat: now,
126 metadata: HashMap::new(),
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq)]
137pub struct OverlayRoute {
138 pub hops: Vec<u64>,
140 pub latency_estimate_ms: f64,
142 pub hop_count: usize,
144}
145
146impl OverlayRoute {
147 fn new(hops: Vec<u64>) -> Self {
148 let hop_count = hops.len().saturating_sub(1);
151 let latency_estimate_ms = hop_count as f64 * 20.0;
152 Self {
153 hops,
154 latency_estimate_ms,
155 hop_count,
156 }
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct OverlayMessage {
167 pub id: u64,
169 pub from: u64,
171 pub to: u64,
173 pub ttl: u8,
175 pub payload_size: usize,
177 pub created_at: u64,
179}
180
181impl OverlayMessage {
182 pub fn new(from: u64, to: u64, ttl: u8, payload_size: usize, created_at: u64) -> Self {
184 let id = fnv1a_hash(from ^ to ^ created_at);
185 Self {
186 id,
187 from,
188 to,
189 ttl,
190 payload_size,
191 created_at,
192 }
193 }
194}
195
196#[inline]
198fn fnv1a_hash(value: u64) -> u64 {
199 const FNV_OFFSET_BASIS: u64 = 14_695_981_039_346_656_037;
200 const FNV_PRIME: u64 = 1_099_511_628_211;
201 let bytes = value.to_le_bytes();
202 let mut hash = FNV_OFFSET_BASIS;
203 for b in bytes {
204 hash ^= b as u64;
205 hash = hash.wrapping_mul(FNV_PRIME);
206 }
207 hash
208}
209
210#[derive(Debug, Clone, PartialEq)]
216pub struct OverlayStats {
217 pub node_count: usize,
219 pub avg_heartbeat_age_ms: f64,
221 pub estimated_diameter: f64,
223 pub topology_name: String,
225}
226
227#[derive(Debug)]
249pub struct OverlayNetworkManager {
250 topology: OverlayTopology,
252 nodes: HashMap<u64, OverlayNode>,
254 local_id: u64,
256 max_nodes: usize,
258}
259
260impl OverlayNetworkManager {
261 pub fn new(local_id: u64, topology: OverlayTopology, max_nodes: usize) -> Self {
269 Self {
270 topology,
271 nodes: HashMap::new(),
272 local_id,
273 max_nodes,
274 }
275 }
276
277 pub fn join(&mut self, node: OverlayNode) -> Result<(), OverlayError> {
286 if let std::collections::hash_map::Entry::Occupied(mut e) = self.nodes.entry(node.node_id) {
288 e.insert(node);
289 return Ok(());
290 }
291 if self.nodes.len() >= self.max_nodes {
292 return Err(OverlayError::NetworkFull);
293 }
294 self.nodes.insert(node.node_id, node);
295 Ok(())
296 }
297
298 pub fn leave(&mut self, node_id: u64) -> Result<(), OverlayError> {
302 self.nodes
303 .remove(&node_id)
304 .map(|_| ())
305 .ok_or(OverlayError::NodeNotFound(node_id))
306 }
307
308 pub fn heartbeat(&mut self, node_id: u64, now: u64) -> Result<(), OverlayError> {
310 self.nodes
311 .get_mut(&node_id)
312 .map(|n| n.last_heartbeat = now)
313 .ok_or(OverlayError::NodeNotFound(node_id))
314 }
315
316 pub fn evict_stale(&mut self, max_age_ms: u64, now: u64) -> usize {
320 let cutoff = now.saturating_sub(max_age_ms);
321 let stale: Vec<u64> = self
322 .nodes
323 .values()
324 .filter(|n| n.last_heartbeat < cutoff)
325 .map(|n| n.node_id)
326 .collect();
327 let count = stale.len();
328 for id in stale {
329 self.nodes.remove(&id);
330 }
331 count
332 }
333
334 pub fn node_count(&self) -> usize {
336 self.nodes.len()
337 }
338
339 pub fn route(&self, from: u64, to: u64, now: u64) -> Result<OverlayRoute, OverlayError> {
348 let _ = now; if from == to {
350 return Err(OverlayError::SelfRoute);
351 }
352 if !self.nodes.contains_key(&from) {
353 return Err(OverlayError::NodeNotFound(from));
354 }
355 if !self.nodes.contains_key(&to) {
356 return Err(OverlayError::NodeNotFound(to));
357 }
358
359 match &self.topology {
360 OverlayTopology::FullMesh => self.route_full_mesh(from, to),
361 OverlayTopology::Chord { fingers } => self.route_chord(from, to, *fingers),
362 OverlayTopology::Tree { branching_factor } => {
363 self.route_tree(from, to, *branching_factor)
364 }
365 OverlayTopology::Pastry { .. } | OverlayTopology::Kademlia { .. } => {
366 self.route_greedy_proximity(from, to)
367 }
368 }
369 }
370
371 fn route_full_mesh(&self, from: u64, to: u64) -> Result<OverlayRoute, OverlayError> {
373 Ok(OverlayRoute::new(vec![from, to]))
374 }
375
376 fn route_chord(
383 &self,
384 from: u64,
385 to: u64,
386 fingers: usize,
387 ) -> Result<OverlayRoute, OverlayError> {
388 const MAX_HOPS: usize = 64;
389 let mut path = vec![from];
390 let mut current = from;
391
392 let all_ids: Vec<u64> = self.nodes.keys().copied().collect();
394
395 let ring_distance = |base: u64, target: u64| -> u64 {
397 if target >= base {
398 target - base
399 } else {
400 u64::MAX - base + target + 1
401 }
402 };
403
404 let best_finger = |node: u64, ideal: u64| -> u64 {
406 all_ids
407 .iter()
408 .copied()
409 .min_by_key(|&id| ring_distance(ideal, id))
410 .unwrap_or(node)
411 };
412
413 while current != to && path.len() <= MAX_HOPS {
414 let mut best_next = current;
416 let current_dist = ring_distance(current, to);
417
418 let effective_fingers = fingers.max(1);
419 for i in 0..effective_fingers {
420 let ideal = current.wrapping_add(1u64 << (i.min(63)));
422 let finger_node = best_finger(current, ideal);
423 let finger_dist = ring_distance(finger_node, to);
424 if finger_dist < current_dist || finger_node == to {
425 let best_dist = ring_distance(best_next, to);
427 if best_next == current || finger_dist < best_dist {
428 best_next = finger_node;
429 }
430 }
431 }
432
433 if best_next == current || path.contains(&best_next) {
434 if self.nodes.contains_key(&to) && !path.contains(&to) {
436 path.push(to);
437 break;
438 }
439 return Err(OverlayError::RouteNotFound { from, to });
440 }
441 path.push(best_next);
442 current = best_next;
443 }
444
445 if current != to {
446 return Err(OverlayError::RouteNotFound { from, to });
447 }
448 Ok(OverlayRoute::new(path))
449 }
450
451 fn route_tree(
457 &self,
458 from: u64,
459 to: u64,
460 branching_factor: usize,
461 ) -> Result<OverlayRoute, OverlayError> {
462 let mut sorted: Vec<&OverlayNode> = self.nodes.values().collect();
465 sorted.sort_by_key(|n| (n.joined_at, n.node_id));
466
467 let bf = branching_factor.max(1);
468
469 let mut children: HashMap<u64, Vec<u64>> = HashMap::new();
471 let mut parent: HashMap<u64, Option<u64>> = HashMap::new();
473
474 for (idx, node) in sorted.iter().enumerate() {
475 if idx == 0 {
476 parent.insert(node.node_id, None);
477 } else {
478 let parent_idx = (idx - 1) / bf;
480 let parent_id = sorted[parent_idx].node_id;
481 parent.insert(node.node_id, Some(parent_id));
482 children.entry(parent_id).or_default().push(node.node_id);
483 }
484 }
485
486 let mut visited: HashSet<u64> = HashSet::new();
488 let mut queue: VecDeque<(u64, Vec<u64>)> = VecDeque::new();
489 queue.push_back((from, vec![from]));
490 visited.insert(from);
491
492 while let Some((current, path)) = queue.pop_front() {
493 if current == to {
494 return Ok(OverlayRoute::new(path));
495 }
496 if let Some(kids) = children.get(¤t) {
498 for &child in kids {
499 if !visited.contains(&child) {
500 visited.insert(child);
501 let mut new_path = path.clone();
502 new_path.push(child);
503 queue.push_back((child, new_path));
504 }
505 }
506 }
507 if let Some(Some(par)) = parent.get(¤t) {
509 if !visited.contains(par) {
510 visited.insert(*par);
511 let mut new_path = path.clone();
512 new_path.push(*par);
513 queue.push_back((*par, new_path));
514 }
515 }
516 }
517
518 Err(OverlayError::RouteNotFound { from, to })
519 }
520
521 fn route_greedy_proximity(&self, from: u64, to: u64) -> Result<OverlayRoute, OverlayError> {
527 const MAX_HOPS: usize = 32;
528 let mut path = vec![from];
529 let mut current = from;
530 let mut visited: HashSet<u64> = HashSet::new();
531 visited.insert(from);
532
533 while current != to && path.len() <= MAX_HOPS {
534 let nbrs = self.neighbors_of(current);
535 let best = nbrs
537 .into_iter()
538 .filter(|id| !visited.contains(id))
539 .min_by_key(|&id| xor_distance(id, to));
540
541 match best {
542 None => {
543 if self.nodes.contains_key(&to) && !path.contains(&to) {
545 path.push(to);
546 return Ok(OverlayRoute::new(path));
547 }
548 return Err(OverlayError::RouteNotFound { from, to });
549 }
550 Some(next) => {
551 if next == to {
552 path.push(to);
553 return Ok(OverlayRoute::new(path));
554 }
555 let current_dist = xor_distance(current, to);
557 let next_dist = xor_distance(next, to);
558 if next_dist >= current_dist {
559 if self.nodes.contains_key(&to) {
561 path.push(to);
562 return Ok(OverlayRoute::new(path));
563 }
564 return Err(OverlayError::RouteNotFound { from, to });
565 }
566 visited.insert(next);
567 path.push(next);
568 current = next;
569 }
570 }
571 }
572
573 if current == to {
574 Ok(OverlayRoute::new(path))
575 } else {
576 Err(OverlayError::RouteNotFound { from, to })
577 }
578 }
579
580 pub fn neighbors(&self, node_id: u64) -> Vec<u64> {
588 if !self.nodes.contains_key(&node_id) {
589 return Vec::new();
590 }
591 self.neighbors_of(node_id)
592 }
593
594 fn neighbors_of(&self, node_id: u64) -> Vec<u64> {
596 match &self.topology {
597 OverlayTopology::FullMesh => {
598 self.nodes
600 .keys()
601 .copied()
602 .filter(|&id| id != node_id)
603 .collect()
604 }
605 OverlayTopology::Chord { fingers } => self.chord_fingers(node_id, *fingers),
606 OverlayTopology::Tree { branching_factor } => {
607 self.tree_neighbors(node_id, *branching_factor)
608 }
609 OverlayTopology::Pastry { leaf_set_size, .. } => {
610 self.closest_n_neighbors(node_id, *leaf_set_size)
611 }
612 OverlayTopology::Kademlia { k, .. } => self.closest_n_neighbors(node_id, *k),
613 }
614 }
615
616 fn chord_fingers(&self, node_id: u64, fingers: usize) -> Vec<u64> {
618 let effective = fingers.max(1);
619 let all_ids: Vec<u64> = self
620 .nodes
621 .keys()
622 .copied()
623 .filter(|&id| id != node_id)
624 .collect();
625 let mut seen: HashSet<u64> = HashSet::new();
626 let mut result = Vec::new();
627
628 for i in 0..effective {
629 let ideal = node_id.wrapping_add(1u64 << (i.min(63)));
630 let best = all_ids.iter().copied().min_by_key(|&id| {
632 if id >= ideal {
633 id - ideal
634 } else {
635 u64::MAX - ideal + id + 1
636 }
637 });
638 if let Some(id) = best {
639 if seen.insert(id) {
640 result.push(id);
641 }
642 }
643 }
644 result
645 }
646
647 fn closest_n_neighbors(&self, node_id: u64, n: usize) -> Vec<u64> {
649 let mut candidates: Vec<u64> = self
650 .nodes
651 .keys()
652 .copied()
653 .filter(|&id| id != node_id)
654 .collect();
655 candidates.sort_by_key(|&id| xor_distance(id, node_id));
656 candidates.truncate(n);
657 candidates
658 }
659
660 fn tree_neighbors(&self, node_id: u64, branching_factor: usize) -> Vec<u64> {
662 let mut sorted: Vec<&OverlayNode> = self.nodes.values().collect();
663 sorted.sort_by_key(|n| (n.joined_at, n.node_id));
664 let bf = branching_factor.max(1);
665
666 let pos = match sorted.iter().position(|n| n.node_id == node_id) {
667 Some(p) => p,
668 None => return Vec::new(),
669 };
670
671 let mut neighbors = Vec::new();
672
673 if pos > 0 {
675 let parent_idx = (pos - 1) / bf;
676 neighbors.push(sorted[parent_idx].node_id);
677 }
678
679 let first_child = pos * bf + 1;
681 for c in first_child..first_child + bf {
682 if c < sorted.len() {
683 neighbors.push(sorted[c].node_id);
684 }
685 }
686
687 neighbors
688 }
689
690 pub fn diameter_estimate(&self) -> f64 {
702 let n = self.nodes.len().max(2) as f64;
703 match &self.topology {
704 OverlayTopology::FullMesh => 1.0,
705 OverlayTopology::Chord { .. }
706 | OverlayTopology::Kademlia { .. }
707 | OverlayTopology::Pastry { .. } => n.log2(),
708 OverlayTopology::Tree { branching_factor } => {
709 let bf = (*branching_factor).max(2) as f64;
710 n.log(bf)
711 }
712 }
713 }
714
715 pub fn stats(&self, now: u64) -> OverlayStats {
721 let node_count = self.nodes.len();
722 let avg_heartbeat_age_ms = if node_count == 0 {
723 0.0
724 } else {
725 let total_age: u64 = self
726 .nodes
727 .values()
728 .map(|n| now.saturating_sub(n.last_heartbeat))
729 .sum();
730 total_age as f64 / node_count as f64
731 };
732 OverlayStats {
733 node_count,
734 avg_heartbeat_age_ms,
735 estimated_diameter: self.diameter_estimate(),
736 topology_name: self.topology.name().to_string(),
737 }
738 }
739
740 pub fn get_node(&self, node_id: u64) -> Option<&OverlayNode> {
746 self.nodes.get(&node_id)
747 }
748
749 pub fn local_id(&self) -> u64 {
751 self.local_id
752 }
753
754 pub fn topology(&self) -> &OverlayTopology {
756 &self.topology
757 }
758}
759
760#[inline]
766fn xor_distance(a: u64, b: u64) -> u64 {
767 a ^ b
768}
769
770#[cfg(test)]
775mod tests {
776 use super::*;
777
778 fn make_node(id: u64, joined_at: u64) -> OverlayNode {
783 OverlayNode::new(id, format!("127.0.0.1:{}", 4000 + id), joined_at)
784 }
785
786 fn populate(mgr: &mut OverlayNetworkManager, ids: &[u64], base_time: u64) {
787 for (i, &id) in ids.iter().enumerate() {
788 mgr.join(make_node(id, base_time + i as u64))
789 .expect("test: join should succeed during populate");
790 }
791 }
792
793 #[test]
798 fn test_overlay_node_new() {
799 let node = make_node(42, 1000);
800 assert_eq!(node.node_id, 42);
801 assert_eq!(node.joined_at, 1000);
802 assert_eq!(node.last_heartbeat, 1000);
803 assert!(node.metadata.is_empty());
804 }
805
806 #[test]
807 fn test_overlay_node_address() {
808 let node = OverlayNode::new(7, "/ip4/1.2.3.4/tcp/4001", 0);
809 assert_eq!(node.address, "/ip4/1.2.3.4/tcp/4001");
810 }
811
812 #[test]
817 fn test_overlay_message_id_deterministic() {
818 let m1 = OverlayMessage::new(1, 2, 10, 100, 999);
819 let m2 = OverlayMessage::new(1, 2, 10, 100, 999);
820 assert_eq!(m1.id, m2.id);
821 }
822
823 #[test]
824 fn test_overlay_message_id_changes_with_inputs() {
825 let m1 = OverlayMessage::new(1, 2, 10, 100, 999);
826 let m2 = OverlayMessage::new(1, 3, 10, 100, 999);
827 assert_ne!(m1.id, m2.id);
828 }
829
830 #[test]
831 fn test_overlay_message_fields() {
832 let msg = OverlayMessage::new(10, 20, 5, 256, 12345);
833 assert_eq!(msg.from, 10);
834 assert_eq!(msg.to, 20);
835 assert_eq!(msg.ttl, 5);
836 assert_eq!(msg.payload_size, 256);
837 assert_eq!(msg.created_at, 12345);
838 }
839
840 #[test]
845 fn test_overlay_route_direct() {
846 let r = OverlayRoute::new(vec![1, 2]);
847 assert_eq!(r.hop_count, 1);
848 assert!((r.latency_estimate_ms - 20.0).abs() < 1e-9);
849 }
850
851 #[test]
852 fn test_overlay_route_multi_hop() {
853 let r = OverlayRoute::new(vec![1, 2, 3, 4]);
854 assert_eq!(r.hop_count, 3);
855 assert!((r.latency_estimate_ms - 60.0).abs() < 1e-9);
856 }
857
858 #[test]
859 fn test_overlay_route_single_node() {
860 let r = OverlayRoute::new(vec![5]);
861 assert_eq!(r.hop_count, 0);
862 assert!((r.latency_estimate_ms - 0.0).abs() < 1e-9);
863 }
864
865 #[test]
870 fn test_join_success() {
871 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 10);
872 assert!(mgr.join(make_node(1, 0)).is_ok());
873 assert_eq!(mgr.node_count(), 1);
874 }
875
876 #[test]
877 fn test_join_network_full() {
878 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 2);
879 mgr.join(make_node(1, 0))
880 .expect("test: first join should succeed");
881 mgr.join(make_node(2, 0))
882 .expect("test: second join should succeed");
883 let err = mgr.join(make_node(3, 0)).unwrap_err();
884 assert_eq!(err, OverlayError::NetworkFull);
885 }
886
887 #[test]
888 fn test_join_updates_existing_node() {
889 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 10);
890 mgr.join(make_node(1, 0))
891 .expect("test: initial join should succeed");
892 let mut updated = make_node(1, 0);
894 updated.last_heartbeat = 9999;
895 mgr.join(updated).expect("test: update join should succeed");
896 assert_eq!(mgr.node_count(), 1);
897 assert_eq!(
898 mgr.get_node(1)
899 .expect("test: node 1 should be present")
900 .last_heartbeat,
901 9999
902 );
903 }
904
905 #[test]
906 fn test_leave_success() {
907 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 10);
908 mgr.join(make_node(1, 0))
909 .expect("test: join should succeed");
910 assert!(mgr.leave(1).is_ok());
911 assert_eq!(mgr.node_count(), 0);
912 }
913
914 #[test]
915 fn test_leave_not_found() {
916 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 10);
917 let err = mgr.leave(99).unwrap_err();
918 assert_eq!(err, OverlayError::NodeNotFound(99));
919 }
920
921 #[test]
922 fn test_heartbeat_updates_timestamp() {
923 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 10);
924 mgr.join(make_node(1, 0))
925 .expect("test: join should succeed");
926 mgr.heartbeat(1, 5000)
927 .expect("test: heartbeat should succeed");
928 assert_eq!(
929 mgr.get_node(1)
930 .expect("test: node 1 should be present after join")
931 .last_heartbeat,
932 5000
933 );
934 }
935
936 #[test]
937 fn test_heartbeat_not_found() {
938 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 10);
939 let err = mgr.heartbeat(99, 0).unwrap_err();
940 assert_eq!(err, OverlayError::NodeNotFound(99));
941 }
942
943 #[test]
948 fn test_evict_stale_removes_old_nodes() {
949 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
950 mgr.join(make_node(1, 0))
951 .expect("test: join node 1 should succeed");
952 mgr.join(make_node(2, 0))
953 .expect("test: join node 2 should succeed");
954 mgr.heartbeat(1, 190)
955 .expect("test: heartbeat for node 1 should succeed"); let removed = mgr.evict_stale(50, 200); assert_eq!(removed, 1);
959 assert!(mgr.get_node(1).is_some());
960 assert!(mgr.get_node(2).is_none());
961 }
962
963 #[test]
964 fn test_evict_stale_none_removed() {
965 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
966 mgr.join(make_node(1, 1000))
967 .expect("test: join should succeed");
968 let removed = mgr.evict_stale(500, 1200);
969 assert_eq!(removed, 0); }
971
972 #[test]
973 fn test_evict_all() {
974 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
975 populate(&mut mgr, &[1, 2, 3, 4, 5], 0);
976 let removed = mgr.evict_stale(0, 9999); assert_eq!(removed, 5);
978 assert_eq!(mgr.node_count(), 0);
979 }
980
981 #[test]
986 fn test_full_mesh_route_direct() {
987 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
988 populate(&mut mgr, &[1, 2, 3], 0);
989 let route = mgr
990 .route(1, 2, 0)
991 .expect("test: full mesh route should succeed");
992 assert_eq!(route.hop_count, 1);
993 assert_eq!(route.hops, vec![1, 2]);
994 }
995
996 #[test]
997 fn test_full_mesh_route_self_error() {
998 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
999 mgr.join(make_node(1, 0))
1000 .expect("test: join should succeed");
1001 assert_eq!(mgr.route(1, 1, 0).unwrap_err(), OverlayError::SelfRoute);
1002 }
1003
1004 #[test]
1005 fn test_full_mesh_route_unknown_source() {
1006 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1007 mgr.join(make_node(2, 0))
1008 .expect("test: join should succeed");
1009 assert_eq!(
1010 mgr.route(99, 2, 0).unwrap_err(),
1011 OverlayError::NodeNotFound(99)
1012 );
1013 }
1014
1015 #[test]
1016 fn test_full_mesh_route_unknown_dest() {
1017 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1018 mgr.join(make_node(1, 0))
1019 .expect("test: join should succeed");
1020 assert_eq!(
1021 mgr.route(1, 99, 0).unwrap_err(),
1022 OverlayError::NodeNotFound(99)
1023 );
1024 }
1025
1026 #[test]
1031 fn test_chord_route_two_nodes() {
1032 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 4 }, 100);
1033 mgr.join(make_node(0, 0))
1034 .expect("test: join node 0 should succeed");
1035 mgr.join(make_node(100, 0))
1036 .expect("test: join node 100 should succeed");
1037 let route = mgr
1038 .route(0, 100, 0)
1039 .expect("test: chord route should succeed");
1040 assert!(route.hop_count >= 1);
1041 assert_eq!(
1042 *route
1043 .hops
1044 .first()
1045 .expect("test: route hops should be non-empty"),
1046 0
1047 );
1048 assert_eq!(
1049 *route
1050 .hops
1051 .last()
1052 .expect("test: route hops should have last element"),
1053 100
1054 );
1055 }
1056
1057 #[test]
1058 fn test_chord_route_self_error() {
1059 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 8 }, 100);
1060 mgr.join(make_node(5, 0))
1061 .expect("test: join should succeed");
1062 assert_eq!(mgr.route(5, 5, 0).unwrap_err(), OverlayError::SelfRoute);
1063 }
1064
1065 #[test]
1066 fn test_chord_route_many_nodes() {
1067 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 8 }, 100);
1068 let ids: Vec<u64> = (0..16).map(|i: u64| i * (u64::MAX / 16)).collect();
1070 populate(&mut mgr, &ids, 0);
1071 let route = mgr
1072 .route(ids[0], ids[8], 0)
1073 .expect("test: chord route across many nodes should succeed");
1074 assert_eq!(
1075 *route
1076 .hops
1077 .first()
1078 .expect("test: route hops should be non-empty"),
1079 ids[0]
1080 );
1081 assert_eq!(
1082 *route
1083 .hops
1084 .last()
1085 .expect("test: route hops should have last element"),
1086 ids[8]
1087 );
1088 assert!(route.hop_count <= 16);
1090 }
1091
1092 #[test]
1097 fn test_tree_route_parent_to_child() {
1098 let mut mgr = OverlayNetworkManager::new(
1099 0,
1100 OverlayTopology::Tree {
1101 branching_factor: 2,
1102 },
1103 100,
1104 );
1105 mgr.join(make_node(10, 10))
1108 .expect("test: join node 10 should succeed");
1109 mgr.join(make_node(20, 20))
1110 .expect("test: join node 20 should succeed");
1111 mgr.join(make_node(30, 30))
1112 .expect("test: join node 30 should succeed");
1113 mgr.join(make_node(40, 40))
1114 .expect("test: join node 40 should succeed");
1115 let route = mgr
1117 .route(10, 40, 0)
1118 .expect("test: tree route from root to grandchild should succeed");
1119 assert_eq!(
1120 *route
1121 .hops
1122 .first()
1123 .expect("test: route hops should be non-empty"),
1124 10
1125 );
1126 assert_eq!(
1127 *route
1128 .hops
1129 .last()
1130 .expect("test: route hops should have last element"),
1131 40
1132 );
1133 assert_eq!(route.hop_count, 2);
1134 }
1135
1136 #[test]
1137 fn test_tree_route_sibling_via_parent() {
1138 let mut mgr = OverlayNetworkManager::new(
1139 0,
1140 OverlayTopology::Tree {
1141 branching_factor: 2,
1142 },
1143 100,
1144 );
1145 mgr.join(make_node(1, 1))
1146 .expect("test: join node 1 should succeed");
1147 mgr.join(make_node(2, 2))
1148 .expect("test: join node 2 should succeed");
1149 mgr.join(make_node(3, 3))
1150 .expect("test: join node 3 should succeed");
1151 let route = mgr
1154 .route(2, 3, 0)
1155 .expect("test: tree route between siblings should succeed");
1156 assert_eq!(
1157 *route
1158 .hops
1159 .first()
1160 .expect("test: route hops should be non-empty"),
1161 2
1162 );
1163 assert_eq!(
1164 *route
1165 .hops
1166 .last()
1167 .expect("test: route hops should have last element"),
1168 3
1169 );
1170 assert_eq!(route.hop_count, 2); }
1172
1173 #[test]
1174 fn test_tree_route_self_error() {
1175 let mut mgr = OverlayNetworkManager::new(
1176 0,
1177 OverlayTopology::Tree {
1178 branching_factor: 3,
1179 },
1180 100,
1181 );
1182 mgr.join(make_node(5, 0))
1183 .expect("test: join should succeed");
1184 assert_eq!(mgr.route(5, 5, 0).unwrap_err(), OverlayError::SelfRoute);
1185 }
1186
1187 #[test]
1192 fn test_pastry_route_direct() {
1193 let mut mgr = OverlayNetworkManager::new(
1194 0,
1195 OverlayTopology::Pastry {
1196 leaf_set_size: 4,
1197 routing_table_rows: 4,
1198 },
1199 100,
1200 );
1201 mgr.join(make_node(1, 0))
1202 .expect("test: join node 1 should succeed");
1203 mgr.join(make_node(2, 0))
1204 .expect("test: join node 2 should succeed");
1205 let route = mgr
1206 .route(1, 2, 0)
1207 .expect("test: pastry route should succeed");
1208 assert_eq!(
1209 *route
1210 .hops
1211 .first()
1212 .expect("test: route hops should be non-empty"),
1213 1
1214 );
1215 assert_eq!(
1216 *route
1217 .hops
1218 .last()
1219 .expect("test: route hops should have last element"),
1220 2
1221 );
1222 }
1223
1224 #[test]
1225 fn test_kademlia_route_multi_hop() {
1226 let mut mgr =
1227 OverlayNetworkManager::new(0, OverlayTopology::Kademlia { k: 4, alpha: 3 }, 100);
1228 populate(&mut mgr, &[0, 4, 6, 7], 0);
1231 let route = mgr
1232 .route(0, 7, 0)
1233 .expect("test: kademlia route should succeed");
1234 assert_eq!(
1235 *route
1236 .hops
1237 .first()
1238 .expect("test: route hops should be non-empty"),
1239 0
1240 );
1241 assert_eq!(
1242 *route
1243 .hops
1244 .last()
1245 .expect("test: route hops should have last element"),
1246 7
1247 );
1248 }
1249
1250 #[test]
1251 fn test_kademlia_route_self_error() {
1252 let mut mgr =
1253 OverlayNetworkManager::new(0, OverlayTopology::Kademlia { k: 8, alpha: 3 }, 100);
1254 mgr.join(make_node(10, 0))
1255 .expect("test: join should succeed");
1256 assert_eq!(mgr.route(10, 10, 0).unwrap_err(), OverlayError::SelfRoute);
1257 }
1258
1259 #[test]
1264 fn test_neighbors_full_mesh_all() {
1265 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1266 populate(&mut mgr, &[1, 2, 3, 4], 0);
1267 let mut nbrs = mgr.neighbors(1);
1268 nbrs.sort();
1269 assert_eq!(nbrs, vec![2, 3, 4]);
1270 }
1271
1272 #[test]
1273 fn test_neighbors_unknown_node_empty() {
1274 let mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1275 assert!(mgr.neighbors(999).is_empty());
1276 }
1277
1278 #[test]
1279 fn test_neighbors_kademlia_limited_to_k() {
1280 let mut mgr =
1281 OverlayNetworkManager::new(0, OverlayTopology::Kademlia { k: 3, alpha: 1 }, 100);
1282 populate(&mut mgr, &[0, 1, 2, 3, 4, 5, 6], 0);
1283 let nbrs = mgr.neighbors(0);
1284 assert!(nbrs.len() <= 3);
1285 }
1286
1287 #[test]
1288 fn test_neighbors_tree_root_has_only_children() {
1289 let mut mgr = OverlayNetworkManager::new(
1290 0,
1291 OverlayTopology::Tree {
1292 branching_factor: 2,
1293 },
1294 100,
1295 );
1296 mgr.join(make_node(1, 1))
1298 .expect("test: join node 1 should succeed");
1299 mgr.join(make_node(2, 2))
1300 .expect("test: join node 2 should succeed");
1301 mgr.join(make_node(3, 3))
1302 .expect("test: join node 3 should succeed");
1303 let mut nbrs = mgr.neighbors(1);
1304 nbrs.sort();
1305 assert_eq!(nbrs, vec![2, 3]);
1306 }
1307
1308 #[test]
1309 fn test_neighbors_chord_finger_count() {
1310 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 4 }, 100);
1311 populate(&mut mgr, &[0, 10, 20, 30, 40, 50, 60, 70], 0);
1312 let nbrs = mgr.neighbors(0);
1313 assert!(nbrs.len() <= 4);
1315 }
1316
1317 #[test]
1322 fn test_diameter_full_mesh() {
1323 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1324 populate(&mut mgr, &[1, 2, 3, 4, 5], 0);
1325 assert!((mgr.diameter_estimate() - 1.0).abs() < 1e-9);
1326 }
1327
1328 #[test]
1329 fn test_diameter_chord_log2() {
1330 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 8 }, 1000);
1331 let ids: Vec<u64> = (1..=16).collect();
1332 populate(&mut mgr, &ids, 0);
1333 let d = mgr.diameter_estimate();
1335 assert!((d - 4.0).abs() < 1e-9);
1336 }
1337
1338 #[test]
1339 fn test_diameter_tree() {
1340 let mut mgr = OverlayNetworkManager::new(
1341 0,
1342 OverlayTopology::Tree {
1343 branching_factor: 4,
1344 },
1345 1000,
1346 );
1347 let ids: Vec<u64> = (1..=16).collect();
1349 populate(&mut mgr, &ids, 0);
1350 let d = mgr.diameter_estimate();
1351 assert!((d - 2.0).abs() < 1e-9);
1352 }
1353
1354 #[test]
1355 fn test_diameter_kademlia_log2() {
1356 let mut mgr =
1357 OverlayNetworkManager::new(0, OverlayTopology::Kademlia { k: 8, alpha: 3 }, 1000);
1358 let ids: Vec<u64> = (1..=8).collect();
1359 populate(&mut mgr, &ids, 0);
1360 let d = mgr.diameter_estimate();
1362 assert!((d - 3.0).abs() < 1e-9);
1363 }
1364
1365 #[test]
1370 fn test_stats_empty_overlay() {
1371 let mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1372 let s = mgr.stats(1000);
1373 assert_eq!(s.node_count, 0);
1374 assert!((s.avg_heartbeat_age_ms - 0.0).abs() < 1e-9);
1375 assert_eq!(s.topology_name, "FullMesh");
1376 }
1377
1378 #[test]
1379 fn test_stats_heartbeat_age() {
1380 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1381 mgr.join(make_node(1, 0))
1382 .expect("test: join node 1 should succeed"); mgr.join(make_node(2, 0))
1384 .expect("test: join node 2 should succeed"); let s = mgr.stats(1000); assert!((s.avg_heartbeat_age_ms - 1000.0).abs() < 1e-9);
1388 }
1389
1390 #[test]
1391 fn test_stats_topology_name_chord() {
1392 let mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 4 }, 100);
1393 assert_eq!(mgr.stats(0).topology_name, "Chord");
1394 }
1395
1396 #[test]
1397 fn test_stats_topology_name_pastry() {
1398 let mgr = OverlayNetworkManager::new(
1399 0,
1400 OverlayTopology::Pastry {
1401 leaf_set_size: 4,
1402 routing_table_rows: 4,
1403 },
1404 100,
1405 );
1406 assert_eq!(mgr.stats(0).topology_name, "Pastry");
1407 }
1408
1409 #[test]
1414 fn test_local_id() {
1415 let mgr = OverlayNetworkManager::new(42, OverlayTopology::FullMesh, 100);
1416 assert_eq!(mgr.local_id(), 42);
1417 }
1418
1419 #[test]
1420 fn test_topology_accessor() {
1421 let topo = OverlayTopology::Chord { fingers: 3 };
1422 let mgr = OverlayNetworkManager::new(0, topo.clone(), 100);
1423 assert_eq!(mgr.topology(), &topo);
1424 }
1425
1426 #[test]
1427 fn test_get_node_present() {
1428 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1429 mgr.join(make_node(7, 42))
1430 .expect("test: join should succeed");
1431 let node = mgr
1432 .get_node(7)
1433 .expect("test: node 7 should be present after join");
1434 assert_eq!(node.node_id, 7);
1435 assert_eq!(node.joined_at, 42);
1436 }
1437
1438 #[test]
1439 fn test_get_node_absent() {
1440 let mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1441 assert!(mgr.get_node(999).is_none());
1442 }
1443
1444 #[test]
1449 fn test_fnv1a_hash_different_inputs() {
1450 assert_ne!(fnv1a_hash(0), fnv1a_hash(1));
1451 assert_ne!(fnv1a_hash(u64::MAX), fnv1a_hash(0));
1452 }
1453
1454 #[test]
1455 fn test_fnv1a_hash_deterministic() {
1456 assert_eq!(fnv1a_hash(12345), fnv1a_hash(12345));
1457 }
1458
1459 #[test]
1464 fn test_xor_distance_identity() {
1465 assert_eq!(xor_distance(7, 7), 0);
1466 }
1467
1468 #[test]
1469 fn test_xor_distance_symmetric() {
1470 assert_eq!(xor_distance(3, 5), xor_distance(5, 3));
1471 }
1472
1473 #[test]
1478 fn test_evict_stale_overflow_safe() {
1479 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::FullMesh, 100);
1480 mgr.join(make_node(1, 0))
1482 .expect("test: join should succeed");
1483 let removed = mgr.evict_stale(u64::MAX, 0);
1484 assert_eq!(removed, 0);
1485 }
1486
1487 #[test]
1488 fn test_chord_route_two_equal_nodes_no_panic() {
1489 let mut mgr = OverlayNetworkManager::new(0, OverlayTopology::Chord { fingers: 1 }, 100);
1490 mgr.join(make_node(1, 0))
1491 .expect("test: join node 1 should succeed");
1492 mgr.join(make_node(2, 0))
1493 .expect("test: join node 2 should succeed");
1494 let result = mgr.route(1, 2, 0);
1496 assert!(result.is_ok() || matches!(result, Err(OverlayError::RouteNotFound { .. })));
1497 }
1498
1499 #[test]
1500 fn test_pastry_route_self_error() {
1501 let mut mgr = OverlayNetworkManager::new(
1502 0,
1503 OverlayTopology::Pastry {
1504 leaf_set_size: 4,
1505 routing_table_rows: 4,
1506 },
1507 100,
1508 );
1509 mgr.join(make_node(5, 0))
1510 .expect("test: join should succeed");
1511 assert_eq!(mgr.route(5, 5, 0).unwrap_err(), OverlayError::SelfRoute);
1512 }
1513
1514 #[test]
1515 fn test_tree_single_branching_factor_one() {
1516 let mut mgr = OverlayNetworkManager::new(
1517 0,
1518 OverlayTopology::Tree {
1519 branching_factor: 1,
1520 },
1521 100,
1522 );
1523 mgr.join(make_node(1, 1))
1525 .expect("test: join node 1 should succeed");
1526 mgr.join(make_node(2, 2))
1527 .expect("test: join node 2 should succeed");
1528 mgr.join(make_node(3, 3))
1529 .expect("test: join node 3 should succeed");
1530 let route = mgr
1532 .route(1, 3, 0)
1533 .expect("test: tree route with bf=1 should succeed");
1534 assert_eq!(route.hop_count, 2);
1535 }
1536
1537 #[test]
1538 fn test_overlay_route_latency_proportional_to_hops() {
1539 let r2 = OverlayRoute::new(vec![1, 2, 3]);
1540 let r4 = OverlayRoute::new(vec![1, 2, 3, 4, 5]);
1541 assert!((r4.latency_estimate_ms - 2.0 * r2.latency_estimate_ms).abs() < 1e-9);
1542 }
1543}