1use std::collections::HashMap;
8use std::time::{SystemTime, UNIX_EPOCH};
9
10#[inline]
13fn xorshift64(state: &mut u64) -> u64 {
14 let mut x = *state;
15 x ^= x << 13;
16 x ^= x >> 7;
17 x ^= x << 17;
18 *state = x;
19 x
20}
21
22#[inline]
23fn fnv1a_64(data: &[u8]) -> u64 {
24 let mut h: u64 = 14_695_981_039_346_656_037;
25 for &b in data {
26 h ^= b as u64;
27 h = h.wrapping_mul(1_099_511_628_211);
28 }
29 h
30}
31
32pub type AreRouteKey = RouteKey;
36pub type AreRouteEntry = RouteEntry;
38pub type AreRoutingPolicy = RoutingPolicy;
40pub type AreRoutingConfig = AdaptiveRoutingConfig;
42pub type AreRoutingStats = AdaptiveRoutingStats;
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub struct RouteKey {
50 pub src: [u8; 32],
52 pub dst: [u8; 32],
54}
55
56impl RouteKey {
57 pub fn new(src: [u8; 32], dst: [u8; 32]) -> Self {
59 Self { src, dst }
60 }
61
62 pub fn hash_u64(&self) -> u64 {
64 let mut buf = [0u8; 64];
65 buf[..32].copy_from_slice(&self.src);
66 buf[32..].copy_from_slice(&self.dst);
67 fnv1a_64(&buf)
68 }
69}
70
71#[derive(Debug, Clone)]
75pub struct RouteEntry {
76 pub next_hop: [u8; 32],
78 pub weight: f64,
80 pub rtt_ms: f64,
82 pub loss_rate: f64,
84 pub last_updated: u64,
86 pub bandwidth_kbps: f64,
88}
89
90impl RouteEntry {
91 pub fn new(next_hop: [u8; 32]) -> Self {
93 Self {
94 next_hop,
95 weight: 1.0,
96 rtt_ms: 100.0,
97 loss_rate: 0.0,
98 last_updated: now_secs(),
99 bandwidth_kbps: 1000.0,
100 }
101 }
102
103 pub fn is_healthy(&self) -> bool {
105 self.loss_rate < 0.5 && self.rtt_ms < 5_000.0
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
113pub enum RoutingPolicy {
114 #[default]
116 ShortestPath,
117 LowestLatency,
119 HighestBandwidth,
121 LoadBalanced,
123 QoSAware,
125}
126
127impl RoutingPolicy {
128 pub fn label(&self) -> &'static str {
130 match self {
131 RoutingPolicy::ShortestPath => "shortest_path",
132 RoutingPolicy::LowestLatency => "lowest_latency",
133 RoutingPolicy::HighestBandwidth => "highest_bandwidth",
134 RoutingPolicy::LoadBalanced => "load_balanced",
135 RoutingPolicy::QoSAware => "qos_aware",
136 }
137 }
138}
139
140#[derive(Debug, Clone)]
144pub struct AdaptiveRoutingConfig {
145 pub policy: RoutingPolicy,
147 pub alpha: f64,
149 pub max_routes_per_key: usize,
151 pub probe_interval_secs: u64,
153}
154
155impl Default for AdaptiveRoutingConfig {
156 fn default() -> Self {
157 Self {
158 policy: RoutingPolicy::LowestLatency,
159 alpha: 0.125,
160 max_routes_per_key: 8,
161 probe_interval_secs: 30,
162 }
163 }
164}
165
166#[derive(Debug, Clone, Default)]
170pub struct AdaptiveRoutingStats {
171 pub total_routes: usize,
173 pub avg_rtt_ms: f64,
175 pub avg_loss_rate: f64,
177 pub policy_switches: u64,
179 pub pruned_last_cycle: usize,
181 pub total_probe_rounds: u64,
183}
184
185pub struct AdaptiveRoutingEngine {
193 route_table: HashMap<RouteKey, Vec<RouteEntry>>,
195 config: AdaptiveRoutingConfig,
197 active_policy: RoutingPolicy,
199 stats: AdaptiveRoutingStats,
201 prng_state: u64,
203}
204
205impl AdaptiveRoutingEngine {
206 pub fn new(config: AdaptiveRoutingConfig) -> Self {
210 let active_policy = config.policy;
211 let seed = fnv1a_64(active_policy.label().as_bytes());
213 Self {
214 route_table: HashMap::new(),
215 config,
216 active_policy,
217 stats: AdaptiveRoutingStats::default(),
218 prng_state: seed | 1, }
220 }
221
222 pub fn with_defaults() -> Self {
224 Self::new(AdaptiveRoutingConfig::default())
225 }
226
227 pub fn add_route(&mut self, key: RouteKey, entry: RouteEntry) {
234 let entries = self.route_table.entry(key).or_default();
235 if let Some(existing) = entries.iter_mut().find(|e| e.next_hop == entry.next_hop) {
237 *existing = entry;
238 return;
239 }
240 if entries.len() >= self.config.max_routes_per_key {
241 if let Some(idx) = worst_entry_index(entries) {
243 entries.remove(idx);
244 }
245 }
246 entries.push(entry);
247 }
248
249 pub fn remove_route(&mut self, key: &RouteKey, next_hop: &[u8; 32]) {
251 if let Some(entries) = self.route_table.get_mut(key) {
252 entries.retain(|e| &e.next_hop != next_hop);
253 }
254 }
255
256 pub fn clear_routes(&mut self, key: &RouteKey) {
258 self.route_table.remove(key);
259 }
260
261 pub fn routes_for(&self, key: &RouteKey) -> Option<&[RouteEntry]> {
263 self.route_table.get(key).map(|v| v.as_slice())
264 }
265
266 pub fn update_metrics(
274 &mut self,
275 key: &RouteKey,
276 next_hop: &[u8; 32],
277 rtt_ms: f64,
278 loss: f64,
279 ) -> Result<(), RoutingEngineError> {
280 let entries = self
281 .route_table
282 .get_mut(key)
283 .ok_or(RoutingEngineError::KeyNotFound)?;
284 let entry = entries
285 .iter_mut()
286 .find(|e| &e.next_hop == next_hop)
287 .ok_or(RoutingEngineError::NextHopNotFound)?;
288
289 let alpha = self.config.alpha;
290 entry.rtt_ms = alpha * rtt_ms + (1.0 - alpha) * entry.rtt_ms;
291 entry.loss_rate = (alpha * loss + (1.0 - alpha) * entry.loss_rate).clamp(0.0, 1.0);
292 entry.last_updated = now_secs();
293 recompute_weight(entry);
294 Ok(())
295 }
296
297 pub fn update_bandwidth(
299 &mut self,
300 key: &RouteKey,
301 next_hop: &[u8; 32],
302 bandwidth_kbps: f64,
303 ) -> Result<(), RoutingEngineError> {
304 let entries = self
305 .route_table
306 .get_mut(key)
307 .ok_or(RoutingEngineError::KeyNotFound)?;
308 let entry = entries
309 .iter_mut()
310 .find(|e| &e.next_hop == next_hop)
311 .ok_or(RoutingEngineError::NextHopNotFound)?;
312 let alpha = self.config.alpha;
313 entry.bandwidth_kbps = alpha * bandwidth_kbps + (1.0 - alpha) * entry.bandwidth_kbps;
314 entry.last_updated = now_secs();
315 recompute_weight(entry);
316 Ok(())
317 }
318
319 pub fn select_next_hop(&self, key: &RouteKey, policy: RoutingPolicy) -> Option<[u8; 32]> {
326 let entries = self.route_table.get(key)?;
327 let healthy: Vec<&RouteEntry> = entries.iter().filter(|e| e.is_healthy()).collect();
328 if healthy.is_empty() {
329 return entries.first().map(|e| e.next_hop);
331 }
332 match policy {
333 RoutingPolicy::ShortestPath => healthy
334 .iter()
335 .max_by(|a, b| {
336 a.weight
337 .partial_cmp(&b.weight)
338 .unwrap_or(std::cmp::Ordering::Equal)
339 })
340 .map(|e| e.next_hop),
341 RoutingPolicy::LowestLatency => healthy
342 .iter()
343 .min_by(|a, b| {
344 a.rtt_ms
345 .partial_cmp(&b.rtt_ms)
346 .unwrap_or(std::cmp::Ordering::Equal)
347 })
348 .map(|e| e.next_hop),
349 RoutingPolicy::HighestBandwidth => healthy
350 .iter()
351 .max_by(|a, b| {
352 a.bandwidth_kbps
353 .partial_cmp(&b.bandwidth_kbps)
354 .unwrap_or(std::cmp::Ordering::Equal)
355 })
356 .map(|e| e.next_hop),
357 RoutingPolicy::LoadBalanced => self.select_load_balanced(&healthy),
358 RoutingPolicy::QoSAware => healthy
359 .iter()
360 .max_by(|a, b| {
361 qos_score(a)
362 .partial_cmp(&qos_score(b))
363 .unwrap_or(std::cmp::Ordering::Equal)
364 })
365 .map(|e| e.next_hop),
366 }
367 }
368
369 pub fn select_next_hop_default(&self, key: &RouteKey) -> Option<[u8; 32]> {
371 self.select_next_hop(key, self.active_policy)
372 }
373
374 fn select_load_balanced(&self, entries: &[&RouteEntry]) -> Option<[u8; 32]> {
376 if entries.is_empty() {
377 return None;
378 }
379 let total_weight: f64 = entries.iter().map(|e| e.weight.max(0.0)).sum();
380 if total_weight <= 0.0 {
381 return entries.first().map(|e| e.next_hop);
382 }
383 let mut state = fnv1a_64(&now_secs().to_le_bytes()) | 1;
385 let r = (xorshift64(&mut state) as f64 / u64::MAX as f64) * total_weight;
386 let mut acc = 0.0;
387 for entry in entries {
388 acc += entry.weight.max(0.0);
389 if acc >= r {
390 return Some(entry.next_hop);
391 }
392 }
393 entries.last().map(|e| e.next_hop)
394 }
395
396 pub fn set_policy(&mut self, policy: RoutingPolicy) {
400 if policy != self.active_policy {
401 self.stats.policy_switches += 1;
402 self.active_policy = policy;
403 }
404 }
405
406 pub fn active_policy(&self) -> RoutingPolicy {
408 self.active_policy
409 }
410
411 pub fn probe_routes(&mut self) {
419 let alpha = self.config.alpha;
420 for entries in self.route_table.values_mut() {
421 for entry in entries.iter_mut() {
422 let r = xorshift64(&mut self.prng_state);
424 let jitter_fraction = (r as f64 / u64::MAX as f64) * 0.20 - 0.10; let synthetic_rtt = (entry.rtt_ms * (1.0 + jitter_fraction)).max(0.5);
426 let probe_alpha = alpha * 0.5;
428 entry.rtt_ms = probe_alpha * synthetic_rtt + (1.0 - probe_alpha) * entry.rtt_ms;
429 entry.last_updated = now_secs();
430 recompute_weight(entry);
431 }
432 }
433 self.stats.total_probe_rounds += 1;
434 }
435
436 pub fn run_adaptation_cycle(&mut self) {
443 let stale_threshold = self.config.probe_interval_secs.saturating_mul(3);
444 let now = now_secs();
445 let mut pruned = 0usize;
446
447 for entries in self.route_table.values_mut() {
448 let before = entries.len();
449 entries.retain(|e| {
450 let age = now.saturating_sub(e.last_updated);
451 age < stale_threshold
452 });
453 pruned += before - entries.len();
454 for entry in entries.iter_mut() {
455 recompute_weight(entry);
456 }
457 }
458 self.route_table.retain(|_, v| !v.is_empty());
460
461 self.stats.pruned_last_cycle = pruned;
462 }
463
464 pub fn routing_stats(&self) -> AdaptiveRoutingStats {
468 let all_entries: Vec<&RouteEntry> =
469 self.route_table.values().flat_map(|v| v.iter()).collect();
470 let total_routes = all_entries.len();
471 let (avg_rtt_ms, avg_loss_rate) = if total_routes == 0 {
472 (0.0, 0.0)
473 } else {
474 let sum_rtt: f64 = all_entries.iter().map(|e| e.rtt_ms).sum();
475 let sum_loss: f64 = all_entries.iter().map(|e| e.loss_rate).sum();
476 (
477 sum_rtt / total_routes as f64,
478 sum_loss / total_routes as f64,
479 )
480 };
481
482 AdaptiveRoutingStats {
483 total_routes,
484 avg_rtt_ms,
485 avg_loss_rate,
486 policy_switches: self.stats.policy_switches,
487 pruned_last_cycle: self.stats.pruned_last_cycle,
488 total_probe_rounds: self.stats.total_probe_rounds,
489 }
490 }
491
492 pub fn key_count(&self) -> usize {
494 self.route_table.len()
495 }
496
497 pub fn entry_count(&self) -> usize {
499 self.route_table.values().map(|v| v.len()).sum()
500 }
501
502 pub fn has_route(&self, key: &RouteKey, next_hop: &[u8; 32]) -> bool {
504 self.route_table
505 .get(key)
506 .map(|entries| entries.iter().any(|e| &e.next_hop == next_hop))
507 .unwrap_or(false)
508 }
509
510 pub fn next_hops_for(&self, key: &RouteKey) -> Vec<[u8; 32]> {
512 self.route_table
513 .get(key)
514 .map(|entries| entries.iter().map(|e| e.next_hop).collect())
515 .unwrap_or_default()
516 }
517
518 pub fn best_entry(&self, key: &RouteKey, policy: RoutingPolicy) -> Option<RouteEntry> {
520 let next_hop = self.select_next_hop(key, policy)?;
521 self.route_table
522 .get(key)?
523 .iter()
524 .find(|e| e.next_hop == next_hop)
525 .cloned()
526 }
527
528 pub fn route_table(&self) -> &HashMap<RouteKey, Vec<RouteEntry>> {
530 &self.route_table
531 }
532
533 pub fn merge_from(&mut self, other: &AdaptiveRoutingEngine) {
535 for (key, entries) in &other.route_table {
536 for entry in entries {
537 self.add_route(key.clone(), entry.clone());
538 }
539 }
540 }
541
542 pub fn resize_max_routes(&mut self, new_max: usize) {
544 self.config.max_routes_per_key = new_max;
545 for entries in self.route_table.values_mut() {
546 while entries.len() > new_max {
547 if let Some(idx) = worst_entry_index(entries) {
548 entries.remove(idx);
549 } else {
550 break;
551 }
552 }
553 }
554 }
555
556 pub fn set_alpha(&mut self, alpha: f64) -> Result<(), RoutingEngineError> {
562 if alpha <= 0.0 || alpha > 1.0 {
563 return Err(RoutingEngineError::InvalidAlpha(alpha));
564 }
565 self.config.alpha = alpha;
566 Ok(())
567 }
568
569 pub fn set_probe_interval(&mut self, secs: u64) {
571 self.config.probe_interval_secs = secs;
572 }
573
574 pub fn reset(&mut self) {
576 self.route_table.clear();
577 self.stats = AdaptiveRoutingStats::default();
578 }
579}
580
581#[derive(Debug, Clone, PartialEq)]
585pub enum RoutingEngineError {
586 KeyNotFound,
588 NextHopNotFound,
590 InvalidAlpha(f64),
592}
593
594impl std::fmt::Display for RoutingEngineError {
595 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596 match self {
597 RoutingEngineError::KeyNotFound => write!(f, "route key not found"),
598 RoutingEngineError::NextHopNotFound => write!(f, "next-hop not found for key"),
599 RoutingEngineError::InvalidAlpha(a) => {
600 write!(f, "invalid EWMA alpha {a}: must be in (0, 1]")
601 }
602 }
603 }
604}
605
606impl std::error::Error for RoutingEngineError {}
607
608fn now_secs() -> u64 {
612 SystemTime::now()
613 .duration_since(UNIX_EPOCH)
614 .map(|d| d.as_secs())
615 .unwrap_or(0)
616}
617
618fn recompute_weight(entry: &mut RouteEntry) {
622 let rtt = entry.rtt_ms.max(1.0);
623 let loss_factor = (1.0 + entry.loss_rate).powi(2);
624 entry.weight = entry.bandwidth_kbps / (rtt * loss_factor);
625}
626
627fn qos_score(entry: &RouteEntry) -> f64 {
631 let availability = (1.0 - entry.loss_rate).max(0.0);
632 availability * entry.bandwidth_kbps / entry.rtt_ms.max(1.0)
633}
634
635fn worst_entry_index(entries: &[RouteEntry]) -> Option<usize> {
637 entries
638 .iter()
639 .enumerate()
640 .min_by(|(_, a), (_, b)| {
641 a.weight
642 .partial_cmp(&b.weight)
643 .unwrap_or(std::cmp::Ordering::Equal)
644 })
645 .map(|(i, _)| i)
646}
647
648#[cfg(test)]
651mod tests {
652 use super::*;
653
654 fn make_key(src_byte: u8, dst_byte: u8) -> RouteKey {
657 let mut src = [0u8; 32];
658 let mut dst = [0u8; 32];
659 src[0] = src_byte;
660 dst[0] = dst_byte;
661 RouteKey::new(src, dst)
662 }
663
664 fn make_hop(byte: u8) -> [u8; 32] {
665 let mut h = [0u8; 32];
666 h[0] = byte;
667 h
668 }
669
670 fn entry_with_metrics(hop_byte: u8, rtt: f64, loss: f64, bw: f64) -> RouteEntry {
671 let mut e = RouteEntry::new(make_hop(hop_byte));
672 e.rtt_ms = rtt;
673 e.loss_rate = loss;
674 e.bandwidth_kbps = bw;
675 recompute_weight(&mut e);
676 e
677 }
678
679 fn engine_default() -> AdaptiveRoutingEngine {
680 AdaptiveRoutingEngine::with_defaults()
681 }
682
683 #[test]
686 fn test_xorshift64_non_zero_output() {
687 let mut state = 12345u64;
688 let v = xorshift64(&mut state);
689 assert_ne!(v, 0);
690 }
691
692 #[test]
693 fn test_xorshift64_state_changes() {
694 let mut state = 99u64;
695 let before = state;
696 xorshift64(&mut state);
697 assert_ne!(state, before);
698 }
699
700 #[test]
701 fn test_xorshift64_sequence_unique() {
702 let mut state = 1u64;
703 let a = xorshift64(&mut state);
704 let b = xorshift64(&mut state);
705 let c = xorshift64(&mut state);
706 assert_ne!(a, b);
707 assert_ne!(b, c);
708 }
709
710 #[test]
711 fn test_xorshift64_deterministic() {
712 let mut s1 = 42u64;
713 let mut s2 = 42u64;
714 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
715 }
716
717 #[test]
720 fn test_fnv1a_64_empty() {
721 let h = fnv1a_64(&[]);
722 assert_eq!(h, 14_695_981_039_346_656_037u64);
723 }
724
725 #[test]
726 fn test_fnv1a_64_known_value() {
727 let h = fnv1a_64(b"foobar");
729 assert_eq!(h, 0x85944171f73967e8);
730 }
731
732 #[test]
733 fn test_fnv1a_64_different_inputs() {
734 assert_ne!(fnv1a_64(b"abc"), fnv1a_64(b"xyz"));
735 }
736
737 #[test]
738 fn test_fnv1a_64_single_byte() {
739 let h = fnv1a_64(&[0xAB]);
740 assert_ne!(h, 0);
741 }
742
743 #[test]
746 fn test_route_key_equality() {
747 let k1 = make_key(1, 2);
748 let k2 = make_key(1, 2);
749 assert_eq!(k1, k2);
750 }
751
752 #[test]
753 fn test_route_key_inequality() {
754 let k1 = make_key(1, 2);
755 let k2 = make_key(1, 3);
756 assert_ne!(k1, k2);
757 }
758
759 #[test]
760 fn test_route_key_hash_deterministic() {
761 let k = make_key(5, 10);
762 assert_eq!(k.hash_u64(), k.hash_u64());
763 }
764
765 #[test]
766 fn test_route_key_hash_distinct() {
767 assert_ne!(make_key(1, 2).hash_u64(), make_key(2, 1).hash_u64());
768 }
769
770 #[test]
771 fn test_route_key_hashmap_usage() {
772 let mut map: HashMap<RouteKey, u32> = HashMap::new();
773 let k = make_key(7, 8);
774 map.insert(k.clone(), 42);
775 assert_eq!(map.get(&k), Some(&42));
776 }
777
778 #[test]
781 fn test_route_entry_defaults() {
782 let e = RouteEntry::new(make_hop(1));
783 assert_eq!(e.rtt_ms, 100.0);
784 assert_eq!(e.loss_rate, 0.0);
785 assert_eq!(e.bandwidth_kbps, 1000.0);
786 assert!(e.weight > 0.0);
787 }
788
789 #[test]
790 fn test_route_entry_is_healthy_nominal() {
791 let e = RouteEntry::new(make_hop(1));
792 assert!(e.is_healthy());
793 }
794
795 #[test]
796 fn test_route_entry_unhealthy_high_loss() {
797 let mut e = RouteEntry::new(make_hop(1));
798 e.loss_rate = 0.9;
799 assert!(!e.is_healthy());
800 }
801
802 #[test]
803 fn test_route_entry_unhealthy_high_rtt() {
804 let mut e = RouteEntry::new(make_hop(1));
805 e.rtt_ms = 6000.0;
806 assert!(!e.is_healthy());
807 }
808
809 #[test]
810 fn test_recompute_weight_high_bw_low_rtt() {
811 let e = entry_with_metrics(1, 10.0, 0.0, 10_000.0);
812 let w1 = e.weight;
813 let mut e2 = entry_with_metrics(2, 100.0, 0.0, 10_000.0);
814 let w2 = e2.weight;
815 assert!(w1 > w2, "w1={w1} w2={w2}");
817 let _ = &mut e2;
819 let _ = e.weight;
820 }
821
822 #[test]
823 fn test_recompute_weight_increases_with_bandwidth() {
824 let mut e1 = entry_with_metrics(1, 50.0, 0.0, 500.0);
825 let mut e2 = entry_with_metrics(2, 50.0, 0.0, 5000.0);
826 assert!(e2.weight > e1.weight);
827 let _ = &mut e1;
829 let _ = &mut e2;
830 }
831
832 #[test]
835 fn test_routing_policy_labels_unique() {
836 let policies = [
837 RoutingPolicy::ShortestPath,
838 RoutingPolicy::LowestLatency,
839 RoutingPolicy::HighestBandwidth,
840 RoutingPolicy::LoadBalanced,
841 RoutingPolicy::QoSAware,
842 ];
843 let labels: std::collections::HashSet<_> = policies.iter().map(|p| p.label()).collect();
844 assert_eq!(labels.len(), 5);
845 }
846
847 #[test]
848 fn test_routing_policy_default_is_shortest_path() {
849 assert_eq!(RoutingPolicy::default(), RoutingPolicy::ShortestPath);
850 }
851
852 #[test]
855 fn test_config_defaults_valid() {
856 let cfg = AdaptiveRoutingConfig::default();
857 assert!(cfg.alpha > 0.0 && cfg.alpha <= 1.0);
858 assert!(cfg.max_routes_per_key > 0);
859 assert!(cfg.probe_interval_secs > 0);
860 }
861
862 #[test]
865 fn test_engine_new_empty() {
866 let e = engine_default();
867 assert_eq!(e.key_count(), 0);
868 assert_eq!(e.entry_count(), 0);
869 }
870
871 #[test]
872 fn test_engine_active_policy_matches_config() {
873 let cfg = AdaptiveRoutingConfig {
874 policy: RoutingPolicy::QoSAware,
875 ..AdaptiveRoutingConfig::default()
876 };
877 let eng = AdaptiveRoutingEngine::new(cfg);
878 assert_eq!(eng.active_policy(), RoutingPolicy::QoSAware);
879 }
880
881 #[test]
884 fn test_add_route_increases_count() {
885 let mut eng = engine_default();
886 let key = make_key(1, 2);
887 eng.add_route(key, RouteEntry::new(make_hop(10)));
888 assert_eq!(eng.entry_count(), 1);
889 }
890
891 #[test]
892 fn test_add_route_duplicate_hop_overwrites() {
893 let mut eng = engine_default();
894 let key = make_key(1, 2);
895 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
896 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
897 assert_eq!(eng.entry_count(), 1);
898 }
899
900 #[test]
901 fn test_add_multiple_hops() {
902 let mut eng = engine_default();
903 let key = make_key(1, 2);
904 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
905 eng.add_route(key.clone(), RouteEntry::new(make_hop(11)));
906 assert_eq!(eng.entry_count(), 2);
907 }
908
909 #[test]
910 fn test_remove_route() {
911 let mut eng = engine_default();
912 let key = make_key(1, 2);
913 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
914 eng.remove_route(&key, &make_hop(10));
915 assert_eq!(eng.entry_count(), 0);
916 }
917
918 #[test]
919 fn test_remove_nonexistent_route_no_panic() {
920 let mut eng = engine_default();
921 let key = make_key(1, 2);
922 eng.remove_route(&key, &make_hop(99));
924 }
925
926 #[test]
927 fn test_max_routes_per_key_evicts_worst() {
928 let cfg = AdaptiveRoutingConfig {
929 max_routes_per_key: 2,
930 ..AdaptiveRoutingConfig::default()
931 };
932 let mut eng = AdaptiveRoutingEngine::new(cfg);
933 let key = make_key(0, 1);
934 eng.add_route(key.clone(), entry_with_metrics(1, 50.0, 0.0, 1000.0));
936 eng.add_route(key.clone(), entry_with_metrics(2, 100.0, 0.2, 1000.0));
937 eng.add_route(key.clone(), entry_with_metrics(3, 30.0, 0.0, 2000.0));
938 assert_eq!(eng.routes_for(&key).map(|s| s.len()), Some(2));
939 }
940
941 #[test]
942 fn test_clear_routes() {
943 let mut eng = engine_default();
944 let key = make_key(1, 2);
945 eng.add_route(key.clone(), RouteEntry::new(make_hop(1)));
946 eng.add_route(key.clone(), RouteEntry::new(make_hop(2)));
947 eng.clear_routes(&key);
948 assert!(eng.routes_for(&key).is_none());
949 }
950
951 #[test]
952 fn test_has_route_true() {
953 let mut eng = engine_default();
954 let key = make_key(1, 2);
955 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
956 assert!(eng.has_route(&key, &make_hop(10)));
957 }
958
959 #[test]
960 fn test_has_route_false() {
961 let eng = engine_default();
962 let key = make_key(1, 2);
963 assert!(!eng.has_route(&key, &make_hop(10)));
964 }
965
966 #[test]
969 fn test_update_metrics_ok() {
970 let mut eng = engine_default();
971 let key = make_key(1, 2);
972 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
973 let result = eng.update_metrics(&key, &make_hop(10), 20.0, 0.01);
974 assert!(result.is_ok());
975 }
976
977 #[test]
978 fn test_update_metrics_rtt_decreases_with_low_sample() {
979 let mut eng = engine_default();
980 let key = make_key(1, 2);
981 let mut entry = RouteEntry::new(make_hop(10));
982 entry.rtt_ms = 200.0;
983 eng.add_route(key.clone(), entry);
984 eng.update_metrics(&key, &make_hop(10), 10.0, 0.0).ok();
985 let rtt = eng
986 .routes_for(&key)
987 .and_then(|s| s.first())
988 .map(|e| e.rtt_ms)
989 .unwrap_or(0.0);
990 assert!(rtt < 200.0, "rtt should decrease: {rtt}");
991 }
992
993 #[test]
994 fn test_update_metrics_loss_clamped() {
995 let mut eng = engine_default();
996 let key = make_key(1, 2);
997 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
998 eng.update_metrics(&key, &make_hop(10), 50.0, 2.0).ok();
999 let loss = eng
1000 .routes_for(&key)
1001 .and_then(|s| s.first())
1002 .map(|e| e.loss_rate)
1003 .unwrap_or(1.1);
1004 assert!(loss <= 1.0, "loss clamped to 1.0: {loss}");
1005 }
1006
1007 #[test]
1008 fn test_update_metrics_key_not_found() {
1009 let mut eng = engine_default();
1010 let key = make_key(9, 9);
1011 let res = eng.update_metrics(&key, &make_hop(1), 10.0, 0.0);
1012 assert_eq!(res, Err(RoutingEngineError::KeyNotFound));
1013 }
1014
1015 #[test]
1016 fn test_update_metrics_hop_not_found() {
1017 let mut eng = engine_default();
1018 let key = make_key(1, 2);
1019 eng.add_route(key.clone(), RouteEntry::new(make_hop(1)));
1020 let res = eng.update_metrics(&key, &make_hop(99), 10.0, 0.0);
1021 assert_eq!(res, Err(RoutingEngineError::NextHopNotFound));
1022 }
1023
1024 #[test]
1027 fn test_select_next_hop_no_routes_returns_none() {
1028 let eng = engine_default();
1029 let key = make_key(1, 2);
1030 assert!(eng
1031 .select_next_hop(&key, RoutingPolicy::LowestLatency)
1032 .is_none());
1033 }
1034
1035 #[test]
1036 fn test_select_next_hop_single_entry() {
1037 let mut eng = engine_default();
1038 let key = make_key(1, 2);
1039 eng.add_route(key.clone(), RouteEntry::new(make_hop(7)));
1040 let hop = eng.select_next_hop(&key, RoutingPolicy::LowestLatency);
1041 assert_eq!(hop, Some(make_hop(7)));
1042 }
1043
1044 #[test]
1045 fn test_select_lowest_latency_picks_best() {
1046 let mut eng = engine_default();
1047 let key = make_key(1, 2);
1048 eng.add_route(key.clone(), entry_with_metrics(1, 200.0, 0.0, 1000.0));
1049 eng.add_route(key.clone(), entry_with_metrics(2, 50.0, 0.0, 1000.0));
1050 eng.add_route(key.clone(), entry_with_metrics(3, 500.0, 0.0, 1000.0));
1051 let hop = eng.select_next_hop(&key, RoutingPolicy::LowestLatency);
1052 assert_eq!(hop, Some(make_hop(2)));
1053 }
1054
1055 #[test]
1056 fn test_select_highest_bandwidth_picks_best() {
1057 let mut eng = engine_default();
1058 let key = make_key(1, 2);
1059 eng.add_route(key.clone(), entry_with_metrics(1, 50.0, 0.0, 500.0));
1060 eng.add_route(key.clone(), entry_with_metrics(2, 50.0, 0.0, 5000.0));
1061 eng.add_route(key.clone(), entry_with_metrics(3, 50.0, 0.0, 100.0));
1062 let hop = eng.select_next_hop(&key, RoutingPolicy::HighestBandwidth);
1063 assert_eq!(hop, Some(make_hop(2)));
1064 }
1065
1066 #[test]
1067 fn test_select_shortest_path_picks_highest_weight() {
1068 let mut eng = engine_default();
1069 let key = make_key(1, 2);
1070 eng.add_route(key.clone(), entry_with_metrics(1, 100.0, 0.0, 1000.0));
1072 eng.add_route(key.clone(), entry_with_metrics(2, 10.0, 0.0, 10_000.0));
1073 let hop = eng.select_next_hop(&key, RoutingPolicy::ShortestPath);
1074 assert_eq!(hop, Some(make_hop(2)));
1075 }
1076
1077 #[test]
1078 fn test_select_qos_aware_picks_best_composite() {
1079 let mut eng = engine_default();
1080 let key = make_key(1, 2);
1081 eng.add_route(key.clone(), entry_with_metrics(1, 30.0, 0.01, 5000.0));
1083 eng.add_route(key.clone(), entry_with_metrics(2, 30.0, 0.8, 50_000.0));
1085 let hop = eng.select_next_hop(&key, RoutingPolicy::QoSAware);
1086 assert_eq!(hop, Some(make_hop(1)));
1087 }
1088
1089 #[test]
1090 fn test_select_load_balanced_returns_valid_hop() {
1091 let mut eng = engine_default();
1092 let key = make_key(1, 2);
1093 eng.add_route(key.clone(), entry_with_metrics(1, 50.0, 0.0, 1000.0));
1094 eng.add_route(key.clone(), entry_with_metrics(2, 60.0, 0.0, 2000.0));
1095 let hop = eng.select_next_hop(&key, RoutingPolicy::LoadBalanced);
1096 assert!(hop == Some(make_hop(1)) || hop == Some(make_hop(2)));
1097 }
1098
1099 #[test]
1100 fn test_select_default_uses_active_policy() {
1101 let mut eng = engine_default();
1102 eng.set_policy(RoutingPolicy::HighestBandwidth);
1103 let key = make_key(1, 2);
1104 eng.add_route(key.clone(), entry_with_metrics(1, 50.0, 0.0, 100.0));
1105 eng.add_route(key.clone(), entry_with_metrics(2, 50.0, 0.0, 9000.0));
1106 let hop = eng.select_next_hop_default(&key);
1107 assert_eq!(hop, Some(make_hop(2)));
1108 }
1109
1110 #[test]
1111 fn test_select_unhealthy_all_falls_back_to_first() {
1112 let mut eng = engine_default();
1113 let key = make_key(1, 2);
1114 eng.add_route(key.clone(), entry_with_metrics(1, 6000.0, 0.9, 1000.0));
1116 eng.add_route(key.clone(), entry_with_metrics(2, 6000.0, 0.9, 1000.0));
1117 let hop = eng.select_next_hop(&key, RoutingPolicy::LowestLatency);
1118 assert!(hop.is_some());
1119 }
1120
1121 #[test]
1124 fn test_set_policy_increments_switch_count() {
1125 let mut eng = engine_default();
1126 eng.set_policy(RoutingPolicy::LowestLatency); eng.set_policy(RoutingPolicy::QoSAware);
1129 assert_eq!(eng.routing_stats().policy_switches, 1);
1130 }
1131
1132 #[test]
1133 fn test_set_policy_same_no_increment() {
1134 let mut eng = engine_default();
1135 let p = eng.active_policy();
1136 eng.set_policy(p);
1137 assert_eq!(eng.routing_stats().policy_switches, 0);
1138 }
1139
1140 #[test]
1143 fn test_probe_routes_increments_counter() {
1144 let mut eng = engine_default();
1145 let key = make_key(1, 2);
1146 eng.add_route(key, RouteEntry::new(make_hop(1)));
1147 eng.probe_routes();
1148 assert_eq!(eng.routing_stats().total_probe_rounds, 1);
1149 }
1150
1151 #[test]
1152 fn test_probe_routes_modifies_rtt() {
1153 let mut eng = engine_default();
1154 let key = make_key(1, 2);
1155 let mut entry = RouteEntry::new(make_hop(1));
1156 entry.rtt_ms = 100.0;
1157 eng.add_route(key.clone(), entry);
1158 eng.probe_routes();
1159 let rtt = eng
1160 .routes_for(&key)
1161 .and_then(|s| s.first())
1162 .map(|e| e.rtt_ms)
1163 .unwrap_or(100.0);
1164 assert!(rtt > 0.0);
1167 }
1168
1169 #[test]
1170 fn test_probe_routes_empty_table_no_panic() {
1171 let mut eng = engine_default();
1172 eng.probe_routes(); assert_eq!(eng.routing_stats().total_probe_rounds, 1);
1174 }
1175
1176 #[test]
1179 fn test_adaptation_cycle_prunes_stale() {
1180 let cfg = AdaptiveRoutingConfig {
1181 probe_interval_secs: 1,
1182 ..AdaptiveRoutingConfig::default()
1183 };
1184 let mut eng = AdaptiveRoutingEngine::new(cfg);
1186 let key = make_key(1, 2);
1187 let mut entry = RouteEntry::new(make_hop(1));
1188 entry.last_updated = 0;
1190 eng.route_table.entry(key.clone()).or_default().push(entry);
1191
1192 eng.run_adaptation_cycle();
1193 assert_eq!(eng.entry_count(), 0, "stale entry should be pruned");
1194 }
1195
1196 #[test]
1197 fn test_adaptation_cycle_keeps_fresh() {
1198 let mut eng = engine_default();
1199 let key = make_key(1, 2);
1200 eng.add_route(key.clone(), RouteEntry::new(make_hop(1)));
1201 eng.run_adaptation_cycle();
1202 assert_eq!(eng.entry_count(), 1, "fresh entry must survive");
1203 }
1204
1205 #[test]
1206 fn test_adaptation_cycle_prune_count_recorded() {
1207 let cfg = AdaptiveRoutingConfig {
1208 probe_interval_secs: 1,
1209 ..AdaptiveRoutingConfig::default()
1210 };
1211 let mut eng = AdaptiveRoutingEngine::new(cfg);
1212 let key = make_key(3, 4);
1213 let mut e = RouteEntry::new(make_hop(5));
1214 e.last_updated = 0;
1215 eng.route_table.entry(key).or_default().push(e);
1216 eng.run_adaptation_cycle();
1217 assert_eq!(eng.routing_stats().pruned_last_cycle, 1);
1218 }
1219
1220 #[test]
1223 fn test_routing_stats_empty_engine() {
1224 let eng = engine_default();
1225 let s = eng.routing_stats();
1226 assert_eq!(s.total_routes, 0);
1227 assert_eq!(s.avg_rtt_ms, 0.0);
1228 assert_eq!(s.avg_loss_rate, 0.0);
1229 }
1230
1231 #[test]
1232 fn test_routing_stats_avg_rtt() {
1233 let mut eng = engine_default();
1234 let key = make_key(1, 2);
1235 eng.add_route(key.clone(), entry_with_metrics(1, 100.0, 0.0, 1000.0));
1236 eng.add_route(key.clone(), entry_with_metrics(2, 200.0, 0.0, 1000.0));
1237 let s = eng.routing_stats();
1238 assert!(
1239 (s.avg_rtt_ms - 150.0).abs() < 1.0,
1240 "avg_rtt={}",
1241 s.avg_rtt_ms
1242 );
1243 }
1244
1245 #[test]
1246 fn test_routing_stats_total_routes() {
1247 let mut eng = engine_default();
1248 for i in 0u8..5 {
1249 let key = make_key(i, i + 1);
1250 eng.add_route(key, RouteEntry::new(make_hop(i)));
1251 }
1252 assert_eq!(eng.routing_stats().total_routes, 5);
1253 }
1254
1255 #[test]
1258 fn test_next_hops_for_empty() {
1259 let eng = engine_default();
1260 let key = make_key(1, 2);
1261 assert!(eng.next_hops_for(&key).is_empty());
1262 }
1263
1264 #[test]
1265 fn test_next_hops_for_multiple() {
1266 let mut eng = engine_default();
1267 let key = make_key(1, 2);
1268 eng.add_route(key.clone(), RouteEntry::new(make_hop(10)));
1269 eng.add_route(key.clone(), RouteEntry::new(make_hop(20)));
1270 let hops = eng.next_hops_for(&key);
1271 assert_eq!(hops.len(), 2);
1272 assert!(hops.contains(&make_hop(10)));
1273 assert!(hops.contains(&make_hop(20)));
1274 }
1275
1276 #[test]
1279 fn test_best_entry_returns_lowest_latency() {
1280 let mut eng = engine_default();
1281 let key = make_key(1, 2);
1282 eng.add_route(key.clone(), entry_with_metrics(1, 300.0, 0.0, 1000.0));
1283 eng.add_route(key.clone(), entry_with_metrics(2, 20.0, 0.0, 1000.0));
1284 let best = eng.best_entry(&key, RoutingPolicy::LowestLatency);
1285 assert!(best.is_some());
1286 assert_eq!(
1287 best.expect("test: best_entry must return Some for LowestLatency with two routes")
1288 .next_hop,
1289 make_hop(2)
1290 );
1291 }
1292
1293 #[test]
1294 fn test_best_entry_missing_key_none() {
1295 let eng = engine_default();
1296 assert!(eng
1297 .best_entry(&make_key(9, 9), RoutingPolicy::ShortestPath)
1298 .is_none());
1299 }
1300
1301 #[test]
1304 fn test_set_alpha_valid() {
1305 let mut eng = engine_default();
1306 assert!(eng.set_alpha(0.5).is_ok());
1307 }
1308
1309 #[test]
1310 fn test_set_alpha_zero_is_error() {
1311 let mut eng = engine_default();
1312 assert!(eng.set_alpha(0.0).is_err());
1313 }
1314
1315 #[test]
1316 fn test_set_alpha_negative_is_error() {
1317 let mut eng = engine_default();
1318 assert!(eng.set_alpha(-0.1).is_err());
1319 }
1320
1321 #[test]
1322 fn test_set_alpha_one_is_valid() {
1323 let mut eng = engine_default();
1324 assert!(eng.set_alpha(1.0).is_ok());
1325 }
1326
1327 #[test]
1330 fn test_update_bandwidth_ok() {
1331 let mut eng = engine_default();
1332 let key = make_key(1, 2);
1333 eng.add_route(key.clone(), RouteEntry::new(make_hop(1)));
1334 assert!(eng.update_bandwidth(&key, &make_hop(1), 5000.0).is_ok());
1335 }
1336
1337 #[test]
1338 fn test_update_bandwidth_increases_bw() {
1339 let mut eng = engine_default();
1340 let key = make_key(1, 2);
1341 let mut e = RouteEntry::new(make_hop(1));
1342 e.bandwidth_kbps = 100.0;
1343 eng.add_route(key.clone(), e);
1344 eng.update_bandwidth(&key, &make_hop(1), 10_000.0).ok();
1345 let bw = eng
1346 .routes_for(&key)
1347 .and_then(|s| s.first())
1348 .map(|e| e.bandwidth_kbps)
1349 .unwrap_or(0.0);
1350 assert!(bw > 100.0, "bw should increase: {bw}");
1351 }
1352
1353 #[test]
1354 fn test_update_bandwidth_key_not_found() {
1355 let mut eng = engine_default();
1356 let res = eng.update_bandwidth(&make_key(9, 9), &make_hop(1), 5000.0);
1357 assert_eq!(res, Err(RoutingEngineError::KeyNotFound));
1358 }
1359
1360 #[test]
1363 fn test_merge_from_adds_routes() {
1364 let mut eng1 = engine_default();
1365 let mut eng2 = engine_default();
1366 let key = make_key(1, 2);
1367 eng2.add_route(key.clone(), RouteEntry::new(make_hop(42)));
1368 eng1.merge_from(&eng2);
1369 assert!(eng1.has_route(&key, &make_hop(42)));
1370 }
1371
1372 #[test]
1373 fn test_merge_from_no_duplicates_for_same_hop() {
1374 let mut eng1 = engine_default();
1375 let mut eng2 = engine_default();
1376 let key = make_key(1, 2);
1377 eng1.add_route(key.clone(), RouteEntry::new(make_hop(1)));
1378 eng2.add_route(key.clone(), RouteEntry::new(make_hop(1)));
1379 eng1.merge_from(&eng2);
1380 assert_eq!(eng1.entry_count(), 1);
1381 }
1382
1383 #[test]
1386 fn test_resize_max_routes_evicts_when_shrinking() {
1387 let cfg = AdaptiveRoutingConfig {
1388 max_routes_per_key: 5,
1389 ..AdaptiveRoutingConfig::default()
1390 };
1391 let mut eng = AdaptiveRoutingEngine::new(cfg);
1392 let key = make_key(1, 2);
1393 for i in 0u8..5 {
1394 eng.add_route(key.clone(), RouteEntry::new(make_hop(i + 1)));
1395 }
1396 assert_eq!(eng.entry_count(), 5);
1397 eng.resize_max_routes(2);
1398 assert!(eng.entry_count() <= 2, "count={}", eng.entry_count());
1399 }
1400
1401 #[test]
1404 fn test_reset_clears_routes() {
1405 let mut eng = engine_default();
1406 let key = make_key(1, 2);
1407 eng.add_route(key, RouteEntry::new(make_hop(1)));
1408 eng.reset();
1409 assert_eq!(eng.entry_count(), 0);
1410 }
1411
1412 #[test]
1413 fn test_reset_clears_stats() {
1414 let mut eng = engine_default();
1415 eng.set_policy(RoutingPolicy::QoSAware);
1416 eng.reset();
1417 assert_eq!(eng.routing_stats().policy_switches, 0);
1418 }
1419
1420 #[test]
1423 fn test_set_probe_interval() {
1424 let mut eng = engine_default();
1425 eng.set_probe_interval(60);
1426 assert_eq!(eng.config.probe_interval_secs, 60);
1427 }
1428
1429 #[test]
1432 fn test_are_type_aliases_usable() {
1433 let key: AreRouteKey = make_key(1, 2);
1434 let entry: AreRouteEntry = RouteEntry::new(make_hop(5));
1435 let policy: AreRoutingPolicy = RoutingPolicy::QoSAware;
1436 let config: AreRoutingConfig = AdaptiveRoutingConfig::default();
1437 let stats: AreRoutingStats = AdaptiveRoutingStats::default();
1438
1439 assert_eq!(key.src[0], 1);
1440 assert!(entry.rtt_ms > 0.0);
1441 assert_eq!(policy, RoutingPolicy::QoSAware);
1442 assert!(config.alpha > 0.0);
1443 assert_eq!(stats.total_routes, 0);
1444 }
1445
1446 #[test]
1449 fn test_many_keys() {
1450 let mut eng = engine_default();
1451 for i in 0u8..200 {
1452 let key = make_key(i, 255 - i);
1453 eng.add_route(key, RouteEntry::new(make_hop(i)));
1454 }
1455 assert_eq!(eng.key_count(), 200);
1456 }
1457
1458 #[test]
1459 fn test_multiple_probe_rounds() {
1460 let mut eng = engine_default();
1461 let key = make_key(1, 2);
1462 eng.add_route(key, RouteEntry::new(make_hop(1)));
1463 for _ in 0..10 {
1464 eng.probe_routes();
1465 }
1466 assert_eq!(eng.routing_stats().total_probe_rounds, 10);
1467 }
1468
1469 #[test]
1470 fn test_update_metrics_ewma_convergence() {
1471 let mut eng = engine_default();
1472 let key = make_key(1, 2);
1473 let mut e = RouteEntry::new(make_hop(1));
1474 e.rtt_ms = 1000.0;
1475 eng.add_route(key.clone(), e);
1476 for _ in 0..100 {
1478 eng.update_metrics(&key, &make_hop(1), 10.0, 0.0).ok();
1479 }
1480 let rtt = eng
1481 .routes_for(&key)
1482 .and_then(|s| s.first())
1483 .map(|e| e.rtt_ms)
1484 .unwrap_or(1000.0);
1485 assert!(rtt < 50.0, "EWMA should converge: {rtt}");
1486 }
1487
1488 #[test]
1489 fn test_qos_score_better_for_low_loss() {
1490 let e_low_loss = entry_with_metrics(1, 50.0, 0.01, 1000.0);
1491 let e_high_loss = entry_with_metrics(2, 50.0, 0.9, 1000.0);
1492 assert!(qos_score(&e_low_loss) > qos_score(&e_high_loss));
1493 }
1494
1495 #[test]
1496 fn test_routing_engine_error_display() {
1497 let e = RoutingEngineError::InvalidAlpha(0.0);
1498 assert!(e.to_string().contains("alpha"));
1499 let e2 = RoutingEngineError::KeyNotFound;
1500 assert!(e2.to_string().contains("key"));
1501 }
1502}