1use dashmap::DashMap;
47use parking_lot::RwLock;
48use std::collections::VecDeque;
49use std::net::SocketAddr;
50use std::sync::Arc;
51use std::time::{Duration, Instant};
52use thiserror::Error;
53use tracing::{debug, info, warn};
54
55#[derive(Debug, Error)]
57pub enum MultipathError {
58 #[error("No paths available")]
59 NoPathsAvailable,
60
61 #[error("Path not found: {0}")]
62 PathNotFound(u64),
63
64 #[error("Path quality too low: {0}")]
65 PathQualityTooLow(f64),
66
67 #[error("Maximum paths reached: {0}")]
68 MaxPathsReached(usize),
69
70 #[error("Invalid configuration: {0}")]
71 InvalidConfig(String),
72}
73
74pub type Result<T> = std::result::Result<T, MultipathError>;
76
77pub type PathId = u64;
79
80#[derive(Debug, Clone)]
82pub struct NetworkPath {
83 pub id: PathId,
85
86 pub local_addr: SocketAddr,
88
89 pub remote_addr: SocketAddr,
91
92 pub state: PathState,
94
95 pub quality: PathQuality,
97
98 pub created_at: Instant,
100
101 pub last_used: Instant,
103
104 pub bytes_sent: u64,
106
107 pub bytes_received: u64,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum PathState {
114 Validating,
116
117 Active,
119
120 Standby,
122
123 Degraded,
125
126 Failed,
128
129 Closing,
131}
132
133#[derive(Debug, Clone)]
135pub struct PathQuality {
136 pub rtt_ms: f64,
138
139 pub bandwidth_bps: u64,
141
142 pub loss_rate: f64,
144
145 pub jitter_ms: f64,
147
148 pub score: f64,
150
151 pub sample_count: usize,
153
154 pub last_updated: Instant,
156}
157
158impl Default for PathQuality {
159 fn default() -> Self {
160 Self {
161 rtt_ms: 0.0,
162 bandwidth_bps: 0,
163 loss_rate: 0.0,
164 jitter_ms: 0.0,
165 score: 0.5, sample_count: 0,
167 last_updated: Instant::now(),
168 }
169 }
170}
171
172impl PathQuality {
173 pub fn calculate_score(&mut self) {
175 const RTT_WEIGHT: f64 = 0.3;
177 const BANDWIDTH_WEIGHT: f64 = 0.3;
178 const LOSS_WEIGHT: f64 = 0.3;
179 const JITTER_WEIGHT: f64 = 0.1;
180
181 let rtt_score = (1.0 - (self.rtt_ms / 500.0).clamp(0.0, 1.0)).max(0.0);
183
184 let bandwidth_mbps = self.bandwidth_bps as f64 / 125_000.0;
186 let bandwidth_score = (bandwidth_mbps / 100.0).clamp(0.0, 1.0);
187
188 let loss_score = (1.0 - self.loss_rate).max(0.0);
190
191 let jitter_score = (1.0 - (self.jitter_ms / 50.0).clamp(0.0, 1.0)).max(0.0);
193
194 self.score = rtt_score * RTT_WEIGHT
196 + bandwidth_score * BANDWIDTH_WEIGHT
197 + loss_score * LOSS_WEIGHT
198 + jitter_score * JITTER_WEIGHT;
199 }
200
201 pub fn update(&mut self, rtt_ms: f64, bandwidth_bps: u64, loss_rate: f64, jitter_ms: f64) {
203 const ALPHA: f64 = 0.8; if self.sample_count == 0 {
206 self.rtt_ms = rtt_ms;
208 self.bandwidth_bps = bandwidth_bps;
209 self.loss_rate = loss_rate;
210 self.jitter_ms = jitter_ms;
211 } else {
212 self.rtt_ms = self.rtt_ms * (1.0 - ALPHA) + rtt_ms * ALPHA;
214 self.bandwidth_bps = ((self.bandwidth_bps as f64) * (1.0 - ALPHA)
215 + (bandwidth_bps as f64) * ALPHA) as u64;
216 self.loss_rate = self.loss_rate * (1.0 - ALPHA) + loss_rate * ALPHA;
217 self.jitter_ms = self.jitter_ms * (1.0 - ALPHA) + jitter_ms * ALPHA;
218 }
219
220 self.sample_count += 1;
221 self.last_updated = Instant::now();
222 self.calculate_score();
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum PathSelectionStrategy {
229 RoundRobin,
231
232 QualityBased,
234
235 LowestLatency,
237
238 HighestBandwidth,
240
241 Redundant,
243
244 WeightedRoundRobin,
246}
247
248#[derive(Debug, Clone)]
250pub struct MultipathConfig {
251 pub max_paths: usize,
253
254 pub strategy: PathSelectionStrategy,
256
257 pub enable_redundancy: bool,
259
260 pub min_quality_threshold: f64,
262
263 pub quality_check_interval: Duration,
265
266 pub path_idle_timeout: Duration,
268
269 pub enable_auto_migration: bool,
271
272 pub migration_quality_threshold: f64,
274}
275
276impl Default for MultipathConfig {
277 fn default() -> Self {
278 Self {
279 max_paths: 4,
280 strategy: PathSelectionStrategy::QualityBased,
281 enable_redundancy: false,
282 min_quality_threshold: 0.3,
283 quality_check_interval: Duration::from_secs(5),
284 path_idle_timeout: Duration::from_secs(60),
285 enable_auto_migration: true,
286 migration_quality_threshold: 0.2, }
288 }
289}
290
291impl MultipathConfig {
292 pub fn low_latency() -> Self {
294 Self {
295 max_paths: 2,
296 strategy: PathSelectionStrategy::LowestLatency,
297 enable_redundancy: false,
298 min_quality_threshold: 0.4,
299 quality_check_interval: Duration::from_secs(1),
300 path_idle_timeout: Duration::from_secs(30),
301 enable_auto_migration: true,
302 migration_quality_threshold: 0.15,
303 }
304 }
305
306 pub fn high_bandwidth() -> Self {
308 Self {
309 max_paths: 4,
310 strategy: PathSelectionStrategy::HighestBandwidth,
311 enable_redundancy: false,
312 min_quality_threshold: 0.3,
313 quality_check_interval: Duration::from_secs(5),
314 path_idle_timeout: Duration::from_secs(60),
315 enable_auto_migration: true,
316 migration_quality_threshold: 0.25,
317 }
318 }
319
320 pub fn high_reliability() -> Self {
322 Self {
323 max_paths: 4,
324 strategy: PathSelectionStrategy::Redundant,
325 enable_redundancy: true,
326 min_quality_threshold: 0.2,
327 quality_check_interval: Duration::from_secs(3),
328 path_idle_timeout: Duration::from_secs(90),
329 enable_auto_migration: false, migration_quality_threshold: 0.3,
331 }
332 }
333
334 pub fn mobile() -> Self {
336 Self {
337 max_paths: 2, strategy: PathSelectionStrategy::QualityBased,
339 enable_redundancy: false,
340 min_quality_threshold: 0.25,
341 quality_check_interval: Duration::from_secs(2),
342 path_idle_timeout: Duration::from_secs(45),
343 enable_auto_migration: true,
344 migration_quality_threshold: 0.2,
345 }
346 }
347}
348
349#[derive(Debug, Clone, Default)]
351pub struct MultipathStats {
352 pub paths_created: usize,
354
355 pub active_paths: usize,
357
358 pub total_bytes_sent: u64,
360
361 pub total_bytes_received: u64,
363
364 pub migrations_count: usize,
366
367 pub path_failures: usize,
369
370 pub avg_quality_score: f64,
372
373 pub best_path_quality: f64,
375
376 pub redundant_packets: usize,
378}
379
380pub struct MultipathQuicManager {
382 config: MultipathConfig,
384
385 paths: Arc<DashMap<PathId, NetworkPath>>,
387
388 next_path_id: Arc<RwLock<PathId>>,
390
391 round_robin_index: Arc<RwLock<usize>>,
393
394 stats: Arc<RwLock<MultipathStats>>,
396
397 quality_history: Arc<RwLock<VecDeque<(Instant, f64)>>>,
399}
400
401impl MultipathQuicManager {
402 pub fn new(config: MultipathConfig) -> Self {
404 info!(
405 "Creating multipath QUIC manager with strategy: {:?}",
406 config.strategy
407 );
408
409 Self {
410 config,
411 paths: Arc::new(DashMap::new()),
412 next_path_id: Arc::new(RwLock::new(0)),
413 round_robin_index: Arc::new(RwLock::new(0)),
414 stats: Arc::new(RwLock::new(MultipathStats::default())),
415 quality_history: Arc::new(RwLock::new(VecDeque::with_capacity(100))),
416 }
417 }
418
419 pub fn add_path(&self, local_addr: SocketAddr, remote_addr: SocketAddr) -> Result<PathId> {
421 if self.paths.len() >= self.config.max_paths {
422 warn!(
423 "Maximum paths reached: {}, cannot add new path",
424 self.config.max_paths
425 );
426 return Err(MultipathError::MaxPathsReached(self.config.max_paths));
427 }
428
429 let path_id = {
430 let mut id = self.next_path_id.write();
431 let current_id = *id;
432 *id += 1;
433 current_id
434 };
435
436 let path = NetworkPath {
437 id: path_id,
438 local_addr,
439 remote_addr,
440 state: PathState::Validating,
441 quality: PathQuality::default(),
442 created_at: Instant::now(),
443 last_used: Instant::now(),
444 bytes_sent: 0,
445 bytes_received: 0,
446 };
447
448 self.paths.insert(path_id, path);
449
450 let mut stats = self.stats.write();
451 stats.paths_created += 1;
452 stats.active_paths = self.paths.len();
453
454 info!(
455 "Added new path {}: {} -> {}",
456 path_id, local_addr, remote_addr
457 );
458
459 Ok(path_id)
460 }
461
462 pub fn remove_path(&self, path_id: PathId) -> Result<()> {
464 if self.paths.remove(&path_id).is_some() {
465 let mut stats = self.stats.write();
466 stats.active_paths = self.paths.len();
467
468 info!("Removed path {}", path_id);
469 Ok(())
470 } else {
471 Err(MultipathError::PathNotFound(path_id))
472 }
473 }
474
475 pub fn update_path_state(&self, path_id: PathId, state: PathState) -> Result<()> {
477 if let Some(mut path) = self.paths.get_mut(&path_id) {
478 let old_state = path.state;
479 path.state = state;
480
481 debug!(
482 "Path {} state changed: {:?} -> {:?}",
483 path_id, old_state, state
484 );
485
486 if state == PathState::Failed {
487 let mut stats = self.stats.write();
488 stats.path_failures += 1;
489 }
490
491 Ok(())
492 } else {
493 Err(MultipathError::PathNotFound(path_id))
494 }
495 }
496
497 pub fn update_path_quality(
499 &self,
500 path_id: PathId,
501 rtt_ms: f64,
502 bandwidth_bps: u64,
503 loss_rate: f64,
504 jitter_ms: f64,
505 ) -> Result<()> {
506 if let Some(mut path) = self.paths.get_mut(&path_id) {
507 path.quality
508 .update(rtt_ms, bandwidth_bps, loss_rate, jitter_ms);
509
510 debug!(
511 "Path {} quality updated: score={:.2}, rtt={:.1}ms, bandwidth={}Mbps, loss={:.2}%",
512 path_id,
513 path.quality.score,
514 path.quality.rtt_ms,
515 path.quality.bandwidth_bps / 125_000,
516 path.quality.loss_rate * 100.0
517 );
518
519 if path.quality.score < self.config.min_quality_threshold
521 && path.state == PathState::Active
522 {
523 warn!(
524 "Path {} quality too low: {:.2}, marking as degraded",
525 path_id, path.quality.score
526 );
527 path.state = PathState::Degraded;
528 }
529
530 let mut history = self.quality_history.write();
532 history.push_back((Instant::now(), path.quality.score));
533 if history.len() > 100 {
534 history.pop_front();
535 }
536
537 Ok(())
538 } else {
539 Err(MultipathError::PathNotFound(path_id))
540 }
541 }
542
543 pub fn select_path(&self) -> Result<PathId> {
545 let active_paths: Vec<_> = self
546 .paths
547 .iter()
548 .filter(|entry| entry.value().state == PathState::Active)
549 .collect();
550
551 if active_paths.is_empty() {
552 return Err(MultipathError::NoPathsAvailable);
553 }
554
555 match self.config.strategy {
556 PathSelectionStrategy::RoundRobin => {
557 let mut index = self.round_robin_index.write();
558 let selected = &active_paths[*index % active_paths.len()];
559 *index = (*index + 1) % active_paths.len();
560 Ok(selected.value().id)
561 }
562
563 PathSelectionStrategy::QualityBased | PathSelectionStrategy::WeightedRoundRobin => {
564 let best = active_paths
566 .iter()
567 .max_by(|a, b| {
568 a.value()
569 .quality
570 .score
571 .partial_cmp(&b.value().quality.score)
572 .unwrap_or(std::cmp::Ordering::Equal)
573 })
574 .ok_or(MultipathError::NoPathsAvailable)?;
575
576 Ok(best.value().id)
577 }
578
579 PathSelectionStrategy::LowestLatency => {
580 let best = active_paths
582 .iter()
583 .min_by(|a, b| {
584 a.value()
585 .quality
586 .rtt_ms
587 .partial_cmp(&b.value().quality.rtt_ms)
588 .unwrap_or(std::cmp::Ordering::Equal)
589 })
590 .ok_or(MultipathError::NoPathsAvailable)?;
591
592 Ok(best.value().id)
593 }
594
595 PathSelectionStrategy::HighestBandwidth => {
596 let best = active_paths
598 .iter()
599 .max_by(|a, b| {
600 a.value()
601 .quality
602 .bandwidth_bps
603 .cmp(&b.value().quality.bandwidth_bps)
604 })
605 .ok_or(MultipathError::NoPathsAvailable)?;
606
607 Ok(best.value().id)
608 }
609
610 PathSelectionStrategy::Redundant => {
611 Ok(active_paths[0].value().id)
614 }
615 }
616 }
617
618 pub fn select_all_paths(&self) -> Vec<PathId> {
620 self.paths
621 .iter()
622 .filter(|entry| entry.value().state == PathState::Active)
623 .map(|entry| entry.value().id)
624 .collect()
625 }
626
627 pub fn record_sent(&self, path_id: PathId, bytes: u64) {
629 if let Some(mut path) = self.paths.get_mut(&path_id) {
630 path.bytes_sent += bytes;
631 path.last_used = Instant::now();
632
633 let mut stats = self.stats.write();
634 stats.total_bytes_sent += bytes;
635 }
636 }
637
638 pub fn record_received(&self, path_id: PathId, bytes: u64) {
640 if let Some(mut path) = self.paths.get_mut(&path_id) {
641 path.bytes_received += bytes;
642 path.last_used = Instant::now();
643
644 let mut stats = self.stats.write();
645 stats.total_bytes_received += bytes;
646 }
647 }
648
649 pub fn get_path(&self, path_id: PathId) -> Option<NetworkPath> {
651 self.paths.get(&path_id).map(|entry| entry.value().clone())
652 }
653
654 pub fn get_active_paths(&self) -> Vec<NetworkPath> {
656 self.paths
657 .iter()
658 .filter(|entry| entry.value().state == PathState::Active)
659 .map(|entry| entry.value().clone())
660 .collect()
661 }
662
663 pub fn get_all_paths(&self) -> Vec<NetworkPath> {
665 self.paths
666 .iter()
667 .map(|entry| entry.value().clone())
668 .collect()
669 }
670
671 pub fn should_migrate(&self, current_path_id: PathId) -> Option<PathId> {
673 if !self.config.enable_auto_migration {
674 return None;
675 }
676
677 let current_path = self.paths.get(¤t_path_id)?;
678 let current_quality = current_path.quality.score;
679
680 let best_path = self
682 .paths
683 .iter()
684 .filter(|entry| {
685 entry.value().id != current_path_id && entry.value().state == PathState::Active
686 })
687 .max_by(|a, b| {
688 a.value()
689 .quality
690 .score
691 .partial_cmp(&b.value().quality.score)
692 .unwrap_or(std::cmp::Ordering::Equal)
693 })?;
694
695 let best_quality = best_path.quality.score;
696
697 if best_quality - current_quality >= self.config.migration_quality_threshold {
699 info!(
700 "Migration recommended: path {} (quality={:.2}) -> path {} (quality={:.2})",
701 current_path_id,
702 current_quality,
703 best_path.value().id,
704 best_quality
705 );
706
707 let mut stats = self.stats.write();
708 stats.migrations_count += 1;
709
710 return Some(best_path.value().id);
711 }
712
713 None
714 }
715
716 pub fn cleanup_stale_paths(&self) {
718 let now = Instant::now();
719 let timeout = self.config.path_idle_timeout;
720
721 let stale_paths: Vec<PathId> = self
722 .paths
723 .iter()
724 .filter(|entry| {
725 let path = entry.value();
726 now.duration_since(path.last_used) > timeout && path.state != PathState::Active
727 })
728 .map(|entry| entry.value().id)
729 .collect();
730
731 for path_id in stale_paths {
732 info!("Removing stale path {}", path_id);
733 let _ = self.remove_path(path_id);
734 }
735 }
736
737 pub fn stats(&self) -> MultipathStats {
739 let mut stats = self.stats.read().clone();
740
741 let active_paths: Vec<_> = self
743 .paths
744 .iter()
745 .filter(|entry| entry.value().state == PathState::Active)
746 .collect();
747
748 if !active_paths.is_empty() {
749 let total_quality: f64 = active_paths
750 .iter()
751 .map(|entry| entry.value().quality.score)
752 .sum();
753 stats.avg_quality_score = total_quality / active_paths.len() as f64;
754
755 stats.best_path_quality = active_paths
756 .iter()
757 .map(|entry| entry.value().quality.score)
758 .fold(0.0, f64::max);
759 }
760
761 stats.active_paths = active_paths.len();
762
763 stats
764 }
765
766 pub fn config(&self) -> &MultipathConfig {
768 &self.config
769 }
770}
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775
776 #[test]
777 fn test_path_quality_calculation() {
778 let mut quality = PathQuality::default();
779
780 quality.update(10.0, 10_000_000, 0.01, 2.0);
782
783 assert!(quality.score > 0.7, "Good quality should have high score");
784 assert_eq!(quality.sample_count, 1);
785 }
786
787 #[test]
788 fn test_path_quality_ema() {
789 let mut quality = PathQuality::default();
790
791 quality.update(100.0, 1_000_000, 0.1, 10.0);
793 let first_rtt = quality.rtt_ms;
794
795 quality.update(50.0, 1_000_000, 0.1, 10.0);
797
798 assert!(
799 quality.rtt_ms < first_rtt,
800 "RTT should decrease with better sample"
801 );
802 assert!(
803 quality.rtt_ms > 50.0,
804 "RTT should be smoothed with EMA, not exact"
805 );
806 assert_eq!(quality.sample_count, 2);
807 }
808
809 #[test]
810 fn test_manager_creation() {
811 let config = MultipathConfig::default();
812 let manager = MultipathQuicManager::new(config);
813
814 let stats = manager.stats();
815 assert_eq!(stats.active_paths, 0);
816 assert_eq!(stats.paths_created, 0);
817 }
818
819 #[test]
820 fn test_add_path() {
821 let config = MultipathConfig::default();
822 let manager = MultipathQuicManager::new(config);
823
824 let local = "127.0.0.1:8080"
825 .parse()
826 .expect("test: failed to parse local addr");
827 let remote = "192.168.1.1:9090"
828 .parse()
829 .expect("test: failed to parse remote addr");
830
831 let path_id = manager
832 .add_path(local, remote)
833 .expect("test: failed to add path");
834
835 assert_eq!(path_id, 0);
836
837 let stats = manager.stats();
839 assert_eq!(stats.active_paths, 0);
840 assert_eq!(stats.paths_created, 1);
841
842 manager
844 .update_path_state(path_id, PathState::Active)
845 .expect("test: failed to update path state to Active");
846
847 let stats = manager.stats();
849 assert_eq!(stats.active_paths, 1);
850 }
851
852 #[test]
853 fn test_max_paths_limit() {
854 let config = MultipathConfig {
855 max_paths: 2,
856 ..Default::default()
857 };
858 let manager = MultipathQuicManager::new(config);
859
860 let local = "127.0.0.1:8080"
861 .parse()
862 .expect("test: failed to parse local addr");
863 let remote = "192.168.1.1:9090"
864 .parse()
865 .expect("test: failed to parse remote addr");
866
867 manager
869 .add_path(local, remote)
870 .expect("test: failed to add path 1");
871 manager
872 .add_path(local, remote)
873 .expect("test: failed to add path 2");
874
875 let result = manager.add_path(local, remote);
877 assert!(result.is_err());
878 assert!(matches!(result, Err(MultipathError::MaxPathsReached(2))));
879 }
880
881 #[test]
882 fn test_remove_path() {
883 let config = MultipathConfig::default();
884 let manager = MultipathQuicManager::new(config);
885
886 let local = "127.0.0.1:8080"
887 .parse()
888 .expect("test: failed to parse local addr");
889 let remote = "192.168.1.1:9090"
890 .parse()
891 .expect("test: failed to parse remote addr");
892
893 let path_id = manager
894 .add_path(local, remote)
895 .expect("test: failed to add path");
896 manager
897 .remove_path(path_id)
898 .expect("test: failed to remove path");
899
900 let stats = manager.stats();
901 assert_eq!(stats.active_paths, 0);
902 }
903
904 #[test]
905 fn test_update_path_state() {
906 let config = MultipathConfig::default();
907 let manager = MultipathQuicManager::new(config);
908
909 let local = "127.0.0.1:8080"
910 .parse()
911 .expect("test: failed to parse local addr");
912 let remote = "192.168.1.1:9090"
913 .parse()
914 .expect("test: failed to parse remote addr");
915
916 let path_id = manager
917 .add_path(local, remote)
918 .expect("test: failed to add path");
919
920 manager
921 .update_path_state(path_id, PathState::Active)
922 .expect("test: failed to update path state to Active");
923
924 let path = manager.get_path(path_id).expect("test: path should exist");
925 assert_eq!(path.state, PathState::Active);
926 }
927
928 #[test]
929 fn test_path_selection_round_robin() {
930 let config = MultipathConfig {
931 strategy: PathSelectionStrategy::RoundRobin,
932 ..Default::default()
933 };
934 let manager = MultipathQuicManager::new(config);
935
936 let local = "127.0.0.1:8080"
937 .parse()
938 .expect("test: failed to parse local addr");
939 let remote = "192.168.1.1:9090"
940 .parse()
941 .expect("test: failed to parse remote addr");
942
943 let path1 = manager
944 .add_path(local, remote)
945 .expect("test: failed to add path 1");
946 let path2 = manager
947 .add_path(local, remote)
948 .expect("test: failed to add path 2");
949
950 manager
951 .update_path_state(path1, PathState::Active)
952 .expect("test: failed to activate path1");
953 manager
954 .update_path_state(path2, PathState::Active)
955 .expect("test: failed to activate path2");
956
957 let selected1 = manager
958 .select_path()
959 .expect("test: failed to select path (first)");
960 let selected2 = manager
961 .select_path()
962 .expect("test: failed to select path (second)");
963
964 assert_ne!(selected1, selected2, "Round robin should alternate paths");
965 }
966
967 #[test]
968 fn test_path_selection_quality_based() {
969 let config = MultipathConfig {
970 strategy: PathSelectionStrategy::QualityBased,
971 ..Default::default()
972 };
973 let manager = MultipathQuicManager::new(config);
974
975 let local = "127.0.0.1:8080"
976 .parse()
977 .expect("test: failed to parse local addr");
978 let remote = "192.168.1.1:9090"
979 .parse()
980 .expect("test: failed to parse remote addr");
981
982 let path1 = manager
983 .add_path(local, remote)
984 .expect("test: failed to add path 1");
985 let path2 = manager
986 .add_path(local, remote)
987 .expect("test: failed to add path 2");
988
989 manager
990 .update_path_state(path1, PathState::Active)
991 .expect("test: failed to activate path1");
992 manager
993 .update_path_state(path2, PathState::Active)
994 .expect("test: failed to activate path2");
995
996 manager
998 .update_path_quality(path1, 100.0, 1_000_000, 0.1, 10.0)
999 .expect("test: failed to update path1 quality");
1000 manager
1001 .update_path_quality(path2, 10.0, 10_000_000, 0.01, 2.0)
1002 .expect("test: failed to update path2 quality");
1003
1004 let selected = manager.select_path().expect("test: failed to select path");
1005 assert_eq!(selected, path2, "Should select higher quality path");
1006 }
1007
1008 #[test]
1009 fn test_path_selection_lowest_latency() {
1010 let config = MultipathConfig {
1011 strategy: PathSelectionStrategy::LowestLatency,
1012 ..Default::default()
1013 };
1014 let manager = MultipathQuicManager::new(config);
1015
1016 let local = "127.0.0.1:8080"
1017 .parse()
1018 .expect("test: failed to parse local addr");
1019 let remote = "192.168.1.1:9090"
1020 .parse()
1021 .expect("test: failed to parse remote addr");
1022
1023 let path1 = manager
1024 .add_path(local, remote)
1025 .expect("test: failed to add path 1");
1026 let path2 = manager
1027 .add_path(local, remote)
1028 .expect("test: failed to add path 2");
1029
1030 manager
1031 .update_path_state(path1, PathState::Active)
1032 .expect("test: failed to activate path1");
1033 manager
1034 .update_path_state(path2, PathState::Active)
1035 .expect("test: failed to activate path2");
1036
1037 manager
1039 .update_path_quality(path1, 10.0, 1_000_000, 0.1, 5.0)
1040 .expect("test: failed to update path1 quality");
1041 manager
1042 .update_path_quality(path2, 100.0, 10_000_000, 0.01, 2.0)
1043 .expect("test: failed to update path2 quality");
1044
1045 let selected = manager.select_path().expect("test: failed to select path");
1046 assert_eq!(selected, path1, "Should select lowest latency path");
1047 }
1048
1049 #[test]
1050 fn test_record_sent_received() {
1051 let config = MultipathConfig::default();
1052 let manager = MultipathQuicManager::new(config);
1053
1054 let local = "127.0.0.1:8080"
1055 .parse()
1056 .expect("test: failed to parse local addr");
1057 let remote = "192.168.1.1:9090"
1058 .parse()
1059 .expect("test: failed to parse remote addr");
1060
1061 let path_id = manager
1062 .add_path(local, remote)
1063 .expect("test: failed to add path");
1064
1065 manager.record_sent(path_id, 1000);
1066 manager.record_received(path_id, 500);
1067
1068 let path = manager
1069 .get_path(path_id)
1070 .expect("test: path should exist after recording");
1071 assert_eq!(path.bytes_sent, 1000);
1072 assert_eq!(path.bytes_received, 500);
1073
1074 let stats = manager.stats();
1075 assert_eq!(stats.total_bytes_sent, 1000);
1076 assert_eq!(stats.total_bytes_received, 500);
1077 }
1078
1079 #[test]
1080 fn test_auto_migration() {
1081 let config = MultipathConfig {
1082 enable_auto_migration: true,
1083 migration_quality_threshold: 0.2,
1084 ..Default::default()
1085 };
1086 let manager = MultipathQuicManager::new(config);
1087
1088 let local = "127.0.0.1:8080"
1089 .parse()
1090 .expect("test: failed to parse local addr");
1091 let remote = "192.168.1.1:9090"
1092 .parse()
1093 .expect("test: failed to parse remote addr");
1094
1095 let path1 = manager
1096 .add_path(local, remote)
1097 .expect("test: failed to add path 1");
1098 let path2 = manager
1099 .add_path(local, remote)
1100 .expect("test: failed to add path 2");
1101
1102 manager
1103 .update_path_state(path1, PathState::Active)
1104 .expect("test: failed to activate path1");
1105 manager
1106 .update_path_state(path2, PathState::Active)
1107 .expect("test: failed to activate path2");
1108
1109 manager
1111 .update_path_quality(path1, 200.0, 500_000, 0.2, 20.0)
1112 .expect("test: failed to update path1 quality");
1113 manager
1114 .update_path_quality(path2, 10.0, 10_000_000, 0.01, 2.0)
1115 .expect("test: failed to update path2 quality");
1116
1117 let migration = manager.should_migrate(path1);
1118 assert_eq!(migration, Some(path2), "Should recommend migration");
1119
1120 let stats = manager.stats();
1121 assert_eq!(stats.migrations_count, 1);
1122 }
1123
1124 #[test]
1125 fn test_config_presets() {
1126 let low_latency = MultipathConfig::low_latency();
1127 assert_eq!(low_latency.strategy, PathSelectionStrategy::LowestLatency);
1128 assert_eq!(low_latency.max_paths, 2);
1129
1130 let high_bandwidth = MultipathConfig::high_bandwidth();
1131 assert_eq!(
1132 high_bandwidth.strategy,
1133 PathSelectionStrategy::HighestBandwidth
1134 );
1135
1136 let high_reliability = MultipathConfig::high_reliability();
1137 assert_eq!(high_reliability.strategy, PathSelectionStrategy::Redundant);
1138 assert!(high_reliability.enable_redundancy);
1139
1140 let mobile = MultipathConfig::mobile();
1141 assert_eq!(mobile.max_paths, 2);
1142 assert!(mobile.enable_auto_migration);
1143 }
1144
1145 #[test]
1146 fn test_select_all_paths() {
1147 let config = MultipathConfig::default();
1148 let manager = MultipathQuicManager::new(config);
1149
1150 let local = "127.0.0.1:8080"
1151 .parse()
1152 .expect("test: failed to parse local addr");
1153 let remote = "192.168.1.1:9090"
1154 .parse()
1155 .expect("test: failed to parse remote addr");
1156
1157 let path1 = manager
1158 .add_path(local, remote)
1159 .expect("test: failed to add path 1");
1160 let path2 = manager
1161 .add_path(local, remote)
1162 .expect("test: failed to add path 2");
1163
1164 manager
1165 .update_path_state(path1, PathState::Active)
1166 .expect("test: failed to activate path1");
1167 manager
1168 .update_path_state(path2, PathState::Active)
1169 .expect("test: failed to activate path2");
1170
1171 let all_paths = manager.select_all_paths();
1172 assert_eq!(all_paths.len(), 2);
1173 assert!(all_paths.contains(&path1));
1174 assert!(all_paths.contains(&path2));
1175 }
1176
1177 #[test]
1178 fn test_quality_threshold_degradation() {
1179 let config = MultipathConfig {
1180 min_quality_threshold: 0.5,
1181 ..Default::default()
1182 };
1183 let manager = MultipathQuicManager::new(config);
1184
1185 let local = "127.0.0.1:8080"
1186 .parse()
1187 .expect("test: failed to parse local addr");
1188 let remote = "192.168.1.1:9090"
1189 .parse()
1190 .expect("test: failed to parse remote addr");
1191
1192 let path_id = manager
1193 .add_path(local, remote)
1194 .expect("test: failed to add path");
1195 manager
1196 .update_path_state(path_id, PathState::Active)
1197 .expect("test: failed to activate path");
1198
1199 manager
1201 .update_path_quality(path_id, 300.0, 100_000, 0.5, 50.0)
1202 .expect("test: failed to update path quality");
1203
1204 let path = manager
1205 .get_path(path_id)
1206 .expect("test: path should exist after quality update");
1207 assert_eq!(
1208 path.state,
1209 PathState::Degraded,
1210 "Path should be marked degraded due to low quality"
1211 );
1212 }
1213}