1use async_trait::async_trait;
40use std::collections::HashMap;
41use std::time::Instant;
42
43use super::{MeshTransport, NodeId, Result, TransportError};
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub enum TransportType {
55 Quic,
57 BluetoothClassic,
59 BluetoothLE,
61 WifiDirect,
63 LoRa,
65 TacticalRadio,
67 Satellite,
69 Custom(u32),
71}
72
73impl std::fmt::Display for TransportType {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 TransportType::Quic => write!(f, "QUIC"),
77 TransportType::BluetoothClassic => write!(f, "Bluetooth Classic"),
78 TransportType::BluetoothLE => write!(f, "Bluetooth LE"),
79 TransportType::WifiDirect => write!(f, "WiFi Direct"),
80 TransportType::LoRa => write!(f, "LoRa"),
81 TransportType::TacticalRadio => write!(f, "Tactical Radio"),
82 TransportType::Satellite => write!(f, "Satellite"),
83 TransportType::Custom(id) => write!(f, "Custom({})", id),
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
116pub struct TransportCapabilities {
117 pub transport_type: TransportType,
119
120 pub max_bandwidth_bps: u64,
122
123 pub typical_latency_ms: u32,
125
126 pub max_range_meters: u32,
128
129 pub bidirectional: bool,
131
132 pub reliable: bool,
134
135 pub battery_impact: u8,
137
138 pub supports_broadcast: bool,
140
141 pub requires_pairing: bool,
143
144 pub max_message_size: usize,
146}
147
148impl TransportCapabilities {
149 pub fn quic() -> Self {
151 Self {
152 transport_type: TransportType::Quic,
153 max_bandwidth_bps: 100_000_000, typical_latency_ms: 10,
155 max_range_meters: 0, bidirectional: true,
157 reliable: true,
158 battery_impact: 20,
159 supports_broadcast: false,
160 requires_pairing: false,
161 max_message_size: 0, }
163 }
164
165 pub fn bluetooth_le() -> Self {
167 Self {
168 transport_type: TransportType::BluetoothLE,
169 max_bandwidth_bps: 250_000, typical_latency_ms: 30,
171 max_range_meters: 100,
172 bidirectional: true,
173 reliable: true,
174 battery_impact: 15, supports_broadcast: true, requires_pairing: false, max_message_size: 512, }
179 }
180
181 pub fn lora(spreading_factor: u8) -> Self {
183 let (bandwidth, range, latency) = match spreading_factor {
184 7 => (21_900, 6_000, 100),
185 8 => (12_500, 8_000, 150),
186 9 => (7_000, 10_000, 200),
187 10 => (3_900, 12_000, 300),
188 11 => (2_100, 14_000, 500),
189 12 => (1_100, 15_000, 1000),
190 _ => (5_000, 10_000, 300), };
192
193 Self {
194 transport_type: TransportType::LoRa,
195 max_bandwidth_bps: bandwidth,
196 typical_latency_ms: latency,
197 max_range_meters: range,
198 bidirectional: true,
199 reliable: false, battery_impact: 10,
201 supports_broadcast: true,
202 requires_pairing: false,
203 max_message_size: 255, }
205 }
206
207 pub fn wifi_direct() -> Self {
209 Self {
210 transport_type: TransportType::WifiDirect,
211 max_bandwidth_bps: 250_000_000, typical_latency_ms: 10,
213 max_range_meters: 200,
214 bidirectional: true,
215 reliable: true,
216 battery_impact: 50, supports_broadcast: true,
218 requires_pairing: true, max_message_size: 0, }
221 }
222
223 pub fn meets_requirements(&self, requirements: &MessageRequirements) -> bool {
225 if requirements.reliable && !self.reliable {
227 return false;
228 }
229
230 if self.max_bandwidth_bps > 0 && self.max_bandwidth_bps < requirements.min_bandwidth_bps {
232 return false;
233 }
234
235 if self.max_message_size > 0 && self.max_message_size < requirements.message_size {
237 return false;
238 }
239
240 true
241 }
242
243 pub fn estimate_delivery_ms(&self, message_size: usize) -> u32 {
245 let transfer_time = if self.max_bandwidth_bps > 0 {
246 (message_size as u64 * 1000 / self.max_bandwidth_bps) as u32
247 } else {
248 0
249 };
250 self.typical_latency_ms + transfer_time
251 }
252}
253
254impl Default for TransportCapabilities {
255 fn default() -> Self {
256 Self::quic()
257 }
258}
259
260#[derive(
268 Debug,
269 Clone,
270 Copy,
271 PartialEq,
272 Eq,
273 PartialOrd,
274 Ord,
275 Default,
276 Hash,
277 serde::Serialize,
278 serde::Deserialize,
279)]
280#[serde(rename_all = "snake_case")]
281pub enum MessagePriority {
282 Background = 0,
284 #[default]
286 Normal = 1,
287 High = 2,
289 Critical = 3,
291}
292
293impl std::fmt::Display for MessagePriority {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 match self {
296 MessagePriority::Background => write!(f, "background"),
297 MessagePriority::Normal => write!(f, "normal"),
298 MessagePriority::High => write!(f, "high"),
299 MessagePriority::Critical => write!(f, "critical"),
300 }
301 }
302}
303
304#[derive(Debug, Clone, Default)]
331pub struct MessageRequirements {
332 pub min_bandwidth_bps: u64,
334
335 pub max_latency_ms: Option<u32>,
337
338 pub message_size: usize,
340
341 pub reliable: bool,
343
344 pub priority: MessagePriority,
346
347 pub power_sensitive: bool,
349
350 pub bypass_sync: bool,
362
363 pub ttl: Option<std::time::Duration>,
369}
370
371impl MessageRequirements {
372 pub fn bypass(max_latency_ms: u32) -> Self {
374 Self {
375 bypass_sync: true,
376 reliable: false,
377 max_latency_ms: Some(max_latency_ms),
378 ..Default::default()
379 }
380 }
381
382 pub fn bypass_with_ttl(max_latency_ms: u32, ttl: std::time::Duration) -> Self {
384 Self {
385 bypass_sync: true,
386 reliable: false,
387 max_latency_ms: Some(max_latency_ms),
388 ttl: Some(ttl),
389 ..Default::default()
390 }
391 }
392
393 pub fn with_bypass(mut self, bypass: bool) -> Self {
395 self.bypass_sync = bypass;
396 if bypass {
397 self.reliable = false; }
399 self
400 }
401
402 pub fn with_ttl(mut self, ttl: std::time::Duration) -> Self {
404 self.ttl = Some(ttl);
405 self
406 }
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
418pub enum RangeMode {
419 #[default]
421 Standard,
422 Extended,
424 Maximum,
426 Custom(u8),
428}
429
430impl std::fmt::Display for RangeMode {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432 match self {
433 RangeMode::Standard => write!(f, "standard"),
434 RangeMode::Extended => write!(f, "extended"),
435 RangeMode::Maximum => write!(f, "maximum"),
436 RangeMode::Custom(val) => write!(f, "custom({})", val),
437 }
438 }
439}
440
441#[derive(Debug, Clone)]
443pub struct RangeModeConfig {
444 pub available_modes: Vec<RangeMode>,
446 pub current_mode: RangeMode,
448 pub mode_capabilities: HashMap<RangeMode, TransportCapabilities>,
450}
451
452impl RangeModeConfig {
453 pub fn new(modes: Vec<(RangeMode, TransportCapabilities)>) -> Self {
455 let available_modes: Vec<_> = modes.iter().map(|(m, _)| *m).collect();
456 let current_mode = available_modes
457 .first()
458 .copied()
459 .unwrap_or(RangeMode::Standard);
460 let mode_capabilities = modes.into_iter().collect();
461
462 Self {
463 available_modes,
464 current_mode,
465 mode_capabilities,
466 }
467 }
468
469 pub fn current_capabilities(&self) -> Option<&TransportCapabilities> {
471 self.mode_capabilities.get(&self.current_mode)
472 }
473
474 pub fn recommend_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
476 self.mode_capabilities
478 .iter()
479 .filter(|(_, caps)| {
480 caps.max_range_meters >= distance_meters || caps.max_range_meters == 0
481 })
482 .max_by_key(|(_, caps)| caps.max_bandwidth_bps)
483 .map(|(mode, _)| *mode)
484 }
485}
486
487#[derive(Debug, Clone)]
493pub enum DistanceSource {
494 Gps {
496 confidence_meters: u32,
498 },
499 Rssi {
501 estimated_meters: u32,
503 variance: u32,
505 },
506 Tof {
508 precision_ns: u32,
510 },
511 Configured,
513 Unknown,
515}
516
517#[derive(Debug, Clone)]
519pub struct PeerDistance {
520 pub peer_id: NodeId,
522 pub distance_meters: u32,
524 pub source: DistanceSource,
526 pub last_updated: Instant,
528}
529
530pub type TransportId = String;
539
540#[derive(Debug, Clone)]
544pub struct TransportInstance {
545 pub id: TransportId,
547 pub transport_type: TransportType,
549 pub description: String,
551 pub interface: Option<String>,
553 pub capabilities: TransportCapabilities,
555 pub available: bool,
557}
558
559impl TransportInstance {
560 pub fn new(
562 id: impl Into<String>,
563 transport_type: TransportType,
564 capabilities: TransportCapabilities,
565 ) -> Self {
566 Self {
567 id: id.into(),
568 transport_type,
569 description: String::new(),
570 interface: None,
571 capabilities,
572 available: true,
573 }
574 }
575
576 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
578 self.description = desc.into();
579 self
580 }
581
582 pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
584 self.interface = Some(iface.into());
585 self
586 }
587}
588
589#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
593pub enum PaceLevel {
594 #[default]
596 Primary = 0,
597 Alternate = 1,
599 Contingency = 2,
601 Emergency = 3,
603 None = 4,
605}
606
607impl std::fmt::Display for PaceLevel {
608 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
609 match self {
610 PaceLevel::Primary => write!(f, "PRIMARY"),
611 PaceLevel::Alternate => write!(f, "ALTERNATE"),
612 PaceLevel::Contingency => write!(f, "CONTINGENCY"),
613 PaceLevel::Emergency => write!(f, "EMERGENCY"),
614 PaceLevel::None => write!(f, "NONE"),
615 }
616 }
617}
618
619#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
636pub struct TransportPolicy {
637 pub name: String,
639 pub primary: Vec<TransportId>,
641 pub alternate: Vec<TransportId>,
643 pub contingency: Vec<TransportId>,
645 pub emergency: Vec<TransportId>,
647}
648
649impl TransportPolicy {
650 pub fn new(name: impl Into<String>) -> Self {
652 Self {
653 name: name.into(),
654 ..Default::default()
655 }
656 }
657
658 pub fn primary(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
660 self.primary = transports.into_iter().map(Into::into).collect();
661 self
662 }
663
664 pub fn alternate(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
666 self.alternate = transports.into_iter().map(Into::into).collect();
667 self
668 }
669
670 pub fn contingency(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
672 self.contingency = transports.into_iter().map(Into::into).collect();
673 self
674 }
675
676 pub fn emergency(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
678 self.emergency = transports.into_iter().map(Into::into).collect();
679 self
680 }
681
682 pub fn ordered(&self) -> impl Iterator<Item = &TransportId> {
684 self.primary
685 .iter()
686 .chain(self.alternate.iter())
687 .chain(self.contingency.iter())
688 .chain(self.emergency.iter())
689 }
690
691 pub fn current_level(&self, available: &std::collections::HashSet<TransportId>) -> PaceLevel {
693 if self.primary.iter().any(|t| available.contains(t)) {
694 PaceLevel::Primary
695 } else if self.alternate.iter().any(|t| available.contains(t)) {
696 PaceLevel::Alternate
697 } else if self.contingency.iter().any(|t| available.contains(t)) {
698 PaceLevel::Contingency
699 } else if self.emergency.iter().any(|t| available.contains(t)) {
700 PaceLevel::Emergency
701 } else {
702 PaceLevel::None
703 }
704 }
705
706 pub fn at_level(&self, level: PaceLevel) -> Vec<&TransportId> {
708 match level {
709 PaceLevel::Primary => self.primary.iter().collect(),
710 PaceLevel::Alternate => self.primary.iter().chain(self.alternate.iter()).collect(),
711 PaceLevel::Contingency => self
712 .primary
713 .iter()
714 .chain(self.alternate.iter())
715 .chain(self.contingency.iter())
716 .collect(),
717 PaceLevel::Emergency | PaceLevel::None => self.ordered().collect(),
718 }
719 }
720}
721
722#[derive(Debug, Clone, Default)]
724pub enum TransportMode {
725 #[default]
727 Single,
728
729 Redundant {
732 min_paths: u8,
734 max_paths: Option<u8>,
736 },
737
738 Bonded,
741
742 LoadBalanced {
744 weights: Option<HashMap<TransportId, u8>>,
746 },
747}
748
749impl TransportMode {
750 pub fn redundant(min_paths: u8) -> Self {
752 Self::Redundant {
753 min_paths,
754 max_paths: None,
755 }
756 }
757
758 pub fn redundant_bounded(min_paths: u8, max_paths: u8) -> Self {
760 Self::Redundant {
761 min_paths,
762 max_paths: Some(max_paths),
763 }
764 }
765
766 pub fn load_balanced_weighted(weights: HashMap<TransportId, u8>) -> Self {
768 Self::LoadBalanced {
769 weights: Some(weights),
770 }
771 }
772}
773
774#[async_trait]
801pub trait Transport: MeshTransport {
802 fn capabilities(&self) -> &TransportCapabilities;
804
805 fn is_available(&self) -> bool;
807
808 fn signal_quality(&self) -> Option<u8> {
812 None
813 }
814
815 fn can_reach(&self, peer_id: &NodeId) -> bool;
817
818 fn estimate_delivery_ms(&self, message_size: usize) -> u32 {
820 self.capabilities().estimate_delivery_ms(message_size)
821 }
822
823 fn calculate_score(&self, requirements: &MessageRequirements, preference_bonus: i32) -> i32 {
827 let caps = self.capabilities();
828 let mut score = 100i32;
829
830 if requirements.priority >= MessagePriority::High {
832 score += 50 - (caps.typical_latency_ms.min(50) as i32);
833 }
834
835 if requirements.power_sensitive {
837 score -= caps.battery_impact as i32;
838 }
839
840 score += preference_bonus;
842
843 if let Some(quality) = self.signal_quality() {
845 score += (quality / 10) as i32;
846 }
847
848 score
849 }
850}
851
852#[async_trait]
857pub trait ConfigurableTransport: Transport {
858 fn range_modes(&self) -> Option<&RangeModeConfig> {
860 None
861 }
862
863 async fn set_range_mode(&self, _mode: RangeMode) -> Result<TransportCapabilities> {
865 Err(TransportError::Other(
866 "Range mode not supported".to_string().into(),
867 ))
868 }
869
870 fn recommend_mode_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
872 self.range_modes()?.recommend_for_distance(distance_meters)
873 }
874}
875
876#[cfg(test)]
881mod tests {
882 use super::*;
883
884 #[test]
885 fn test_transport_type_display() {
886 assert_eq!(TransportType::Quic.to_string(), "QUIC");
887 assert_eq!(TransportType::BluetoothLE.to_string(), "Bluetooth LE");
888 assert_eq!(TransportType::LoRa.to_string(), "LoRa");
889 assert_eq!(TransportType::Custom(42).to_string(), "Custom(42)");
890 }
891
892 #[test]
893 fn test_quic_capabilities() {
894 let caps = TransportCapabilities::quic();
895 assert_eq!(caps.transport_type, TransportType::Quic);
896 assert!(caps.reliable);
897 assert!(caps.bidirectional);
898 assert_eq!(caps.max_range_meters, 0); }
900
901 #[test]
902 fn test_ble_capabilities() {
903 let caps = TransportCapabilities::bluetooth_le();
904 assert_eq!(caps.transport_type, TransportType::BluetoothLE);
905 assert_eq!(caps.max_range_meters, 100);
906 assert_eq!(caps.max_message_size, 512);
907 assert!(caps.supports_broadcast);
908 }
909
910 #[test]
911 fn test_lora_capabilities() {
912 let caps_sf7 = TransportCapabilities::lora(7);
913 let caps_sf12 = TransportCapabilities::lora(12);
914
915 assert!(caps_sf7.max_bandwidth_bps > caps_sf12.max_bandwidth_bps);
917 assert!(caps_sf7.max_range_meters < caps_sf12.max_range_meters);
918 }
919
920 #[test]
921 fn test_meets_requirements_reliable() {
922 let caps = TransportCapabilities::lora(7);
923 assert!(!caps.reliable);
924
925 let requirements = MessageRequirements {
926 reliable: true,
927 ..Default::default()
928 };
929
930 assert!(!caps.meets_requirements(&requirements));
931 }
932
933 #[test]
934 fn test_meets_requirements_bandwidth() {
935 let caps = TransportCapabilities::lora(12); let low_bandwidth = MessageRequirements {
938 min_bandwidth_bps: 500,
939 ..Default::default()
940 };
941
942 let high_bandwidth = MessageRequirements {
943 min_bandwidth_bps: 1_000_000,
944 ..Default::default()
945 };
946
947 assert!(caps.meets_requirements(&low_bandwidth));
948 assert!(!caps.meets_requirements(&high_bandwidth));
949 }
950
951 #[test]
952 fn test_meets_requirements_message_size() {
953 let caps = TransportCapabilities::lora(7); let small_message = MessageRequirements {
956 message_size: 100,
957 ..Default::default()
958 };
959
960 let large_message = MessageRequirements {
961 message_size: 1000,
962 ..Default::default()
963 };
964
965 assert!(caps.meets_requirements(&small_message));
966 assert!(!caps.meets_requirements(&large_message));
967 }
968
969 #[test]
970 fn test_estimate_delivery_ms() {
971 let caps = TransportCapabilities::quic();
972 let estimate = caps.estimate_delivery_ms(1_000_000);
974 assert!(estimate >= 10);
975 assert!(estimate < 200);
976 }
977
978 #[test]
979 fn test_message_priority_ordering() {
980 assert!(MessagePriority::Critical > MessagePriority::High);
981 assert!(MessagePriority::High > MessagePriority::Normal);
982 assert!(MessagePriority::Normal > MessagePriority::Background);
983 }
984
985 #[test]
986 fn test_range_mode_config() {
987 let modes = vec![
988 (RangeMode::Standard, TransportCapabilities::bluetooth_le()),
989 (
990 RangeMode::Extended,
991 TransportCapabilities {
992 max_bandwidth_bps: 125_000,
993 max_range_meters: 200,
994 ..TransportCapabilities::bluetooth_le()
995 },
996 ),
997 (
998 RangeMode::Maximum,
999 TransportCapabilities {
1000 max_bandwidth_bps: 62_500,
1001 max_range_meters: 400,
1002 ..TransportCapabilities::bluetooth_le()
1003 },
1004 ),
1005 ];
1006
1007 let config = RangeModeConfig::new(modes);
1008
1009 assert_eq!(config.recommend_for_distance(50), Some(RangeMode::Standard));
1011
1012 assert_eq!(
1014 config.recommend_for_distance(150),
1015 Some(RangeMode::Extended)
1016 );
1017
1018 assert_eq!(config.recommend_for_distance(300), Some(RangeMode::Maximum));
1020 }
1021
1022 #[test]
1023 fn test_distance_source() {
1024 let gps = DistanceSource::Gps {
1025 confidence_meters: 10,
1026 };
1027 let rssi = DistanceSource::Rssi {
1028 estimated_meters: 50,
1029 variance: 20,
1030 };
1031
1032 let _ = format!("{:?}", gps);
1034 let _ = format!("{:?}", rssi);
1035 }
1036
1037 #[test]
1038 fn test_message_requirements_bypass() {
1039 let req = MessageRequirements::bypass(5);
1040 assert!(req.bypass_sync);
1041 assert!(!req.reliable);
1042 assert_eq!(req.max_latency_ms, Some(5));
1043 }
1044
1045 #[test]
1046 fn test_message_requirements_bypass_with_ttl() {
1047 use std::time::Duration;
1048
1049 let req = MessageRequirements::bypass_with_ttl(5, Duration::from_millis(200));
1050 assert!(req.bypass_sync);
1051 assert!(!req.reliable);
1052 assert_eq!(req.max_latency_ms, Some(5));
1053 assert_eq!(req.ttl, Some(Duration::from_millis(200)));
1054 }
1055
1056 #[test]
1057 fn test_message_requirements_builder() {
1058 use std::time::Duration;
1059
1060 let req = MessageRequirements::default()
1061 .with_bypass(true)
1062 .with_ttl(Duration::from_secs(1));
1063
1064 assert!(req.bypass_sync);
1065 assert!(!req.reliable); assert_eq!(req.ttl, Some(Duration::from_secs(1)));
1067 }
1068
1069 #[test]
1070 fn test_message_requirements_default() {
1071 let req = MessageRequirements::default();
1072 assert!(!req.bypass_sync);
1073 assert!(req.ttl.is_none());
1074 }
1075
1076 #[test]
1081 fn test_transport_instance_creation() {
1082 let instance = TransportInstance::new(
1083 "iroh-eth0",
1084 TransportType::Quic,
1085 TransportCapabilities::quic(),
1086 )
1087 .with_description("Primary ethernet")
1088 .with_interface("eth0");
1089
1090 assert_eq!(instance.id, "iroh-eth0");
1091 assert_eq!(instance.transport_type, TransportType::Quic);
1092 assert_eq!(instance.description, "Primary ethernet");
1093 assert_eq!(instance.interface, Some("eth0".to_string()));
1094 assert!(instance.available);
1095 }
1096
1097 #[test]
1098 fn test_pace_level_ordering() {
1099 assert!(PaceLevel::Primary < PaceLevel::Alternate);
1100 assert!(PaceLevel::Alternate < PaceLevel::Contingency);
1101 assert!(PaceLevel::Contingency < PaceLevel::Emergency);
1102 assert!(PaceLevel::Emergency < PaceLevel::None);
1103 }
1104
1105 #[test]
1106 fn test_pace_level_display() {
1107 assert_eq!(PaceLevel::Primary.to_string(), "PRIMARY");
1108 assert_eq!(PaceLevel::Alternate.to_string(), "ALTERNATE");
1109 assert_eq!(PaceLevel::Contingency.to_string(), "CONTINGENCY");
1110 assert_eq!(PaceLevel::Emergency.to_string(), "EMERGENCY");
1111 assert_eq!(PaceLevel::None.to_string(), "NONE");
1112 }
1113
1114 #[test]
1115 fn test_transport_policy_builder() {
1116 let policy = TransportPolicy::new("tactical-standard")
1117 .primary(vec!["iroh-eth0", "iroh-wlan0"])
1118 .alternate(vec!["iroh-starlink"])
1119 .contingency(vec!["lora-primary"])
1120 .emergency(vec!["ble-mesh"]);
1121
1122 assert_eq!(policy.name, "tactical-standard");
1123 assert_eq!(policy.primary.len(), 2);
1124 assert_eq!(policy.alternate.len(), 1);
1125 assert_eq!(policy.contingency.len(), 1);
1126 assert_eq!(policy.emergency.len(), 1);
1127 }
1128
1129 #[test]
1130 fn test_transport_policy_ordered() {
1131 let policy = TransportPolicy::new("test")
1132 .primary(vec!["p1", "p2"])
1133 .alternate(vec!["a1"])
1134 .contingency(vec!["c1"])
1135 .emergency(vec!["e1"]);
1136
1137 let ordered: Vec<_> = policy.ordered().collect();
1138 assert_eq!(ordered, vec!["p1", "p2", "a1", "c1", "e1"]);
1139 }
1140
1141 #[test]
1142 fn test_transport_policy_current_level() {
1143 let policy = TransportPolicy::new("test")
1144 .primary(vec!["p1", "p2"])
1145 .alternate(vec!["a1"])
1146 .contingency(vec!["c1"])
1147 .emergency(vec!["e1"]);
1148
1149 let mut available = std::collections::HashSet::new();
1151 available.insert("p1".to_string());
1152 assert_eq!(policy.current_level(&available), PaceLevel::Primary);
1153
1154 available.clear();
1156 available.insert("a1".to_string());
1157 assert_eq!(policy.current_level(&available), PaceLevel::Alternate);
1158
1159 available.clear();
1161 available.insert("c1".to_string());
1162 assert_eq!(policy.current_level(&available), PaceLevel::Contingency);
1163
1164 available.clear();
1166 available.insert("e1".to_string());
1167 assert_eq!(policy.current_level(&available), PaceLevel::Emergency);
1168
1169 available.clear();
1171 assert_eq!(policy.current_level(&available), PaceLevel::None);
1172 }
1173
1174 #[test]
1175 fn test_transport_policy_at_level() {
1176 let policy = TransportPolicy::new("test")
1177 .primary(vec!["p1"])
1178 .alternate(vec!["a1"])
1179 .contingency(vec!["c1"])
1180 .emergency(vec!["e1"]);
1181
1182 assert_eq!(policy.at_level(PaceLevel::Primary).len(), 1);
1184
1185 assert_eq!(policy.at_level(PaceLevel::Alternate).len(), 2);
1187
1188 assert_eq!(policy.at_level(PaceLevel::Contingency).len(), 3);
1190
1191 assert_eq!(policy.at_level(PaceLevel::Emergency).len(), 4);
1193 }
1194
1195 #[test]
1196 fn test_transport_mode_single() {
1197 let mode = TransportMode::Single;
1198 assert!(matches!(mode, TransportMode::Single));
1199 }
1200
1201 #[test]
1202 fn test_transport_mode_redundant() {
1203 let mode = TransportMode::redundant(2);
1204 assert!(matches!(
1205 mode,
1206 TransportMode::Redundant {
1207 min_paths: 2,
1208 max_paths: None
1209 }
1210 ));
1211
1212 let bounded = TransportMode::redundant_bounded(2, 4);
1213 assert!(matches!(
1214 bounded,
1215 TransportMode::Redundant {
1216 min_paths: 2,
1217 max_paths: Some(4)
1218 }
1219 ));
1220 }
1221
1222 #[test]
1223 fn test_wifi_direct_capabilities() {
1224 let caps = TransportCapabilities::wifi_direct();
1225 assert_eq!(caps.transport_type, TransportType::WifiDirect);
1226 assert_eq!(caps.max_range_meters, 200);
1227 assert!(caps.reliable);
1228 assert!(caps.supports_broadcast);
1229 assert!(caps.requires_pairing);
1230 assert_eq!(caps.battery_impact, 50);
1231 }
1232
1233 #[test]
1234 fn test_lora_all_spreading_factors() {
1235 for sf in [7, 8, 9, 10, 11, 12] {
1237 let caps = TransportCapabilities::lora(sf);
1238 assert_eq!(caps.transport_type, TransportType::LoRa);
1239 assert!(!caps.reliable);
1240 assert!(caps.supports_broadcast);
1241 assert_eq!(caps.max_message_size, 255);
1242 }
1243
1244 let caps_default = TransportCapabilities::lora(6);
1246 assert_eq!(caps_default.max_bandwidth_bps, 5_000);
1247 assert_eq!(caps_default.max_range_meters, 10_000);
1248 }
1249
1250 #[test]
1251 fn test_transport_capabilities_default() {
1252 let caps = TransportCapabilities::default();
1253 assert_eq!(caps.transport_type, TransportType::Quic);
1254 }
1255
1256 #[test]
1257 fn test_transport_type_display_all() {
1258 assert_eq!(
1259 TransportType::BluetoothClassic.to_string(),
1260 "Bluetooth Classic"
1261 );
1262 assert_eq!(TransportType::WifiDirect.to_string(), "WiFi Direct");
1263 assert_eq!(TransportType::TacticalRadio.to_string(), "Tactical Radio");
1264 assert_eq!(TransportType::Satellite.to_string(), "Satellite");
1265 }
1266
1267 #[test]
1268 fn test_range_mode_display() {
1269 assert_eq!(RangeMode::Standard.to_string(), "standard");
1270 assert_eq!(RangeMode::Extended.to_string(), "extended");
1271 assert_eq!(RangeMode::Maximum.to_string(), "maximum");
1272 assert_eq!(RangeMode::Custom(42).to_string(), "custom(42)");
1273 }
1274
1275 #[test]
1276 fn test_range_mode_default() {
1277 assert_eq!(RangeMode::default(), RangeMode::Standard);
1278 }
1279
1280 #[test]
1281 fn test_message_priority_display() {
1282 assert_eq!(MessagePriority::Background.to_string(), "background");
1283 assert_eq!(MessagePriority::Normal.to_string(), "normal");
1284 assert_eq!(MessagePriority::High.to_string(), "high");
1285 assert_eq!(MessagePriority::Critical.to_string(), "critical");
1286 }
1287
1288 #[test]
1289 fn test_message_priority_default() {
1290 assert_eq!(MessagePriority::default(), MessagePriority::Normal);
1291 }
1292
1293 #[test]
1294 fn test_pace_level_default() {
1295 assert_eq!(PaceLevel::default(), PaceLevel::Primary);
1296 }
1297
1298 #[test]
1299 fn test_transport_mode_default() {
1300 let mode = TransportMode::default();
1301 assert!(matches!(mode, TransportMode::Single));
1302 }
1303
1304 #[test]
1305 fn test_transport_mode_bonded() {
1306 let mode = TransportMode::Bonded;
1307 let _ = format!("{:?}", mode);
1308 }
1309
1310 #[test]
1311 fn test_distance_source_all_variants() {
1312 let tof = DistanceSource::Tof { precision_ns: 100 };
1313 let configured = DistanceSource::Configured;
1314 let unknown = DistanceSource::Unknown;
1315 let _ = format!("{:?}", tof);
1316 let _ = format!("{:?}", configured);
1317 let _ = format!("{:?}", unknown);
1318 }
1319
1320 #[test]
1321 fn test_peer_distance_construction() {
1322 let pd = PeerDistance {
1323 peer_id: NodeId::new("test-peer".to_string()),
1324 distance_meters: 500,
1325 source: DistanceSource::Gps {
1326 confidence_meters: 5,
1327 },
1328 last_updated: Instant::now(),
1329 };
1330 assert_eq!(pd.distance_meters, 500);
1331 let _ = format!("{:?}", pd);
1332 }
1333
1334 #[test]
1335 fn test_range_mode_config_current_capabilities() {
1336 let modes = vec![
1337 (RangeMode::Standard, TransportCapabilities::bluetooth_le()),
1338 (
1339 RangeMode::Extended,
1340 TransportCapabilities {
1341 max_bandwidth_bps: 125_000,
1342 max_range_meters: 200,
1343 ..TransportCapabilities::bluetooth_le()
1344 },
1345 ),
1346 ];
1347 let config = RangeModeConfig::new(modes);
1348 let caps = config.current_capabilities();
1349 assert!(caps.is_some());
1350 assert_eq!(caps.unwrap().transport_type, TransportType::BluetoothLE);
1351 }
1352
1353 #[test]
1354 fn test_range_mode_config_no_match_for_distance() {
1355 let modes = vec![(
1356 RangeMode::Standard,
1357 TransportCapabilities {
1358 max_range_meters: 50,
1359 ..TransportCapabilities::bluetooth_le()
1360 },
1361 )];
1362 let config = RangeModeConfig::new(modes);
1363 let result = config.recommend_for_distance(1000);
1365 assert!(result.is_none());
1366 }
1367
1368 #[test]
1369 fn test_with_bypass_false() {
1370 let req = MessageRequirements::default().with_bypass(false);
1371 assert!(!req.bypass_sync);
1372 assert!(!req.reliable); }
1375
1376 #[test]
1377 fn test_message_priority_serde() {
1378 let priority = MessagePriority::Critical;
1379 let json = serde_json::to_string(&priority).unwrap();
1380 let deserialized: MessagePriority = serde_json::from_str(&json).unwrap();
1381 assert_eq!(deserialized, MessagePriority::Critical);
1382 }
1383
1384 #[test]
1385 fn test_transport_policy_at_level_none() {
1386 let policy = TransportPolicy::new("test")
1387 .primary(vec!["p1"])
1388 .alternate(vec!["a1"])
1389 .contingency(vec!["c1"])
1390 .emergency(vec!["e1"]);
1391
1392 assert_eq!(policy.at_level(PaceLevel::None).len(), 4);
1394 }
1395
1396 #[test]
1397 fn test_estimate_delivery_zero_bandwidth() {
1398 let caps = TransportCapabilities {
1399 max_bandwidth_bps: 0,
1400 typical_latency_ms: 50,
1401 ..TransportCapabilities::quic()
1402 };
1403 assert_eq!(caps.estimate_delivery_ms(1_000_000), 50);
1405 }
1406
1407 #[test]
1408 fn test_meets_requirements_all_pass() {
1409 let caps = TransportCapabilities::quic();
1410 let req = MessageRequirements {
1411 reliable: true,
1412 min_bandwidth_bps: 1_000,
1413 message_size: 100,
1414 ..Default::default()
1415 };
1416 assert!(caps.meets_requirements(&req));
1418 }
1419
1420 #[test]
1421 fn test_transport_mode_load_balanced() {
1422 let mut weights = std::collections::HashMap::new();
1423 weights.insert("t1".to_string(), 3);
1424 weights.insert("t2".to_string(), 1);
1425
1426 let mode = TransportMode::load_balanced_weighted(weights.clone());
1427 if let TransportMode::LoadBalanced { weights: Some(w) } = mode {
1428 assert_eq!(w.get("t1"), Some(&3));
1429 assert_eq!(w.get("t2"), Some(&1));
1430 } else {
1431 panic!("Expected LoadBalanced with weights");
1432 }
1433 }
1434}