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 = (message_size as u64 * 1000)
246 .checked_div(self.max_bandwidth_bps)
247 .map(|t| t as u32)
248 .unwrap_or(0);
249 self.typical_latency_ms + transfer_time
250 }
251}
252
253impl Default for TransportCapabilities {
254 fn default() -> Self {
255 Self::quic()
256 }
257}
258
259#[derive(
267 Debug,
268 Clone,
269 Copy,
270 PartialEq,
271 Eq,
272 PartialOrd,
273 Ord,
274 Default,
275 Hash,
276 serde::Serialize,
277 serde::Deserialize,
278)]
279#[serde(rename_all = "snake_case")]
280pub enum MessagePriority {
281 Background = 0,
283 #[default]
285 Normal = 1,
286 High = 2,
288 Critical = 3,
290}
291
292impl std::fmt::Display for MessagePriority {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 match self {
295 MessagePriority::Background => write!(f, "background"),
296 MessagePriority::Normal => write!(f, "normal"),
297 MessagePriority::High => write!(f, "high"),
298 MessagePriority::Critical => write!(f, "critical"),
299 }
300 }
301}
302
303#[derive(Debug, Clone, Default)]
330pub struct MessageRequirements {
331 pub min_bandwidth_bps: u64,
333
334 pub max_latency_ms: Option<u32>,
336
337 pub message_size: usize,
339
340 pub reliable: bool,
342
343 pub priority: MessagePriority,
345
346 pub power_sensitive: bool,
348
349 pub bypass_sync: bool,
361
362 pub ttl: Option<std::time::Duration>,
368}
369
370impl MessageRequirements {
371 pub fn bypass(max_latency_ms: u32) -> Self {
373 Self {
374 bypass_sync: true,
375 reliable: false,
376 max_latency_ms: Some(max_latency_ms),
377 ..Default::default()
378 }
379 }
380
381 pub fn bypass_with_ttl(max_latency_ms: u32, ttl: std::time::Duration) -> Self {
383 Self {
384 bypass_sync: true,
385 reliable: false,
386 max_latency_ms: Some(max_latency_ms),
387 ttl: Some(ttl),
388 ..Default::default()
389 }
390 }
391
392 pub fn with_bypass(mut self, bypass: bool) -> Self {
394 self.bypass_sync = bypass;
395 if bypass {
396 self.reliable = false; }
398 self
399 }
400
401 pub fn with_ttl(mut self, ttl: std::time::Duration) -> Self {
403 self.ttl = Some(ttl);
404 self
405 }
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
417pub enum RangeMode {
418 #[default]
420 Standard,
421 Extended,
423 Maximum,
425 Custom(u8),
427}
428
429impl std::fmt::Display for RangeMode {
430 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431 match self {
432 RangeMode::Standard => write!(f, "standard"),
433 RangeMode::Extended => write!(f, "extended"),
434 RangeMode::Maximum => write!(f, "maximum"),
435 RangeMode::Custom(val) => write!(f, "custom({})", val),
436 }
437 }
438}
439
440#[derive(Debug, Clone)]
442pub struct RangeModeConfig {
443 pub available_modes: Vec<RangeMode>,
445 pub current_mode: RangeMode,
447 pub mode_capabilities: HashMap<RangeMode, TransportCapabilities>,
449}
450
451impl RangeModeConfig {
452 pub fn new(modes: Vec<(RangeMode, TransportCapabilities)>) -> Self {
454 let available_modes: Vec<_> = modes.iter().map(|(m, _)| *m).collect();
455 let current_mode = available_modes
456 .first()
457 .copied()
458 .unwrap_or(RangeMode::Standard);
459 let mode_capabilities = modes.into_iter().collect();
460
461 Self {
462 available_modes,
463 current_mode,
464 mode_capabilities,
465 }
466 }
467
468 pub fn current_capabilities(&self) -> Option<&TransportCapabilities> {
470 self.mode_capabilities.get(&self.current_mode)
471 }
472
473 pub fn recommend_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
475 self.mode_capabilities
477 .iter()
478 .filter(|(_, caps)| {
479 caps.max_range_meters >= distance_meters || caps.max_range_meters == 0
480 })
481 .max_by_key(|(_, caps)| caps.max_bandwidth_bps)
482 .map(|(mode, _)| *mode)
483 }
484}
485
486#[derive(Debug, Clone)]
492pub enum DistanceSource {
493 Gps {
495 confidence_meters: u32,
497 },
498 Rssi {
500 estimated_meters: u32,
502 variance: u32,
504 },
505 Tof {
507 precision_ns: u32,
509 },
510 Configured,
512 Unknown,
514}
515
516#[derive(Debug, Clone)]
518pub struct PeerDistance {
519 pub peer_id: NodeId,
521 pub distance_meters: u32,
523 pub source: DistanceSource,
525 pub last_updated: Instant,
527}
528
529pub type TransportId = String;
538
539#[derive(Debug, Clone)]
543pub struct TransportInstance {
544 pub id: TransportId,
546 pub transport_type: TransportType,
548 pub description: String,
550 pub interface: Option<String>,
552 pub capabilities: TransportCapabilities,
554 pub available: bool,
556}
557
558impl TransportInstance {
559 pub fn new(
561 id: impl Into<String>,
562 transport_type: TransportType,
563 capabilities: TransportCapabilities,
564 ) -> Self {
565 Self {
566 id: id.into(),
567 transport_type,
568 description: String::new(),
569 interface: None,
570 capabilities,
571 available: true,
572 }
573 }
574
575 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
577 self.description = desc.into();
578 self
579 }
580
581 pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
583 self.interface = Some(iface.into());
584 self
585 }
586}
587
588#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
592pub enum PaceLevel {
593 #[default]
595 Primary = 0,
596 Alternate = 1,
598 Contingency = 2,
600 Emergency = 3,
602 None = 4,
604}
605
606impl std::fmt::Display for PaceLevel {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 match self {
609 PaceLevel::Primary => write!(f, "PRIMARY"),
610 PaceLevel::Alternate => write!(f, "ALTERNATE"),
611 PaceLevel::Contingency => write!(f, "CONTINGENCY"),
612 PaceLevel::Emergency => write!(f, "EMERGENCY"),
613 PaceLevel::None => write!(f, "NONE"),
614 }
615 }
616}
617
618#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
635pub struct TransportPolicy {
636 pub name: String,
638 pub primary: Vec<TransportId>,
640 pub alternate: Vec<TransportId>,
642 pub contingency: Vec<TransportId>,
644 pub emergency: Vec<TransportId>,
646}
647
648impl TransportPolicy {
649 pub fn new(name: impl Into<String>) -> Self {
651 Self {
652 name: name.into(),
653 ..Default::default()
654 }
655 }
656
657 pub fn primary(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
659 self.primary = transports.into_iter().map(Into::into).collect();
660 self
661 }
662
663 pub fn alternate(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
665 self.alternate = transports.into_iter().map(Into::into).collect();
666 self
667 }
668
669 pub fn contingency(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
671 self.contingency = transports.into_iter().map(Into::into).collect();
672 self
673 }
674
675 pub fn emergency(mut self, transports: Vec<impl Into<TransportId>>) -> Self {
677 self.emergency = transports.into_iter().map(Into::into).collect();
678 self
679 }
680
681 pub fn ordered(&self) -> impl Iterator<Item = &TransportId> {
683 self.primary
684 .iter()
685 .chain(self.alternate.iter())
686 .chain(self.contingency.iter())
687 .chain(self.emergency.iter())
688 }
689
690 pub fn current_level(&self, available: &std::collections::HashSet<TransportId>) -> PaceLevel {
692 if self.primary.iter().any(|t| available.contains(t)) {
693 PaceLevel::Primary
694 } else if self.alternate.iter().any(|t| available.contains(t)) {
695 PaceLevel::Alternate
696 } else if self.contingency.iter().any(|t| available.contains(t)) {
697 PaceLevel::Contingency
698 } else if self.emergency.iter().any(|t| available.contains(t)) {
699 PaceLevel::Emergency
700 } else {
701 PaceLevel::None
702 }
703 }
704
705 pub fn at_level(&self, level: PaceLevel) -> Vec<&TransportId> {
707 match level {
708 PaceLevel::Primary => self.primary.iter().collect(),
709 PaceLevel::Alternate => self.primary.iter().chain(self.alternate.iter()).collect(),
710 PaceLevel::Contingency => self
711 .primary
712 .iter()
713 .chain(self.alternate.iter())
714 .chain(self.contingency.iter())
715 .collect(),
716 PaceLevel::Emergency | PaceLevel::None => self.ordered().collect(),
717 }
718 }
719}
720
721#[derive(Debug, Clone, Default)]
723pub enum TransportMode {
724 #[default]
726 Single,
727
728 Redundant {
731 min_paths: u8,
733 max_paths: Option<u8>,
735 },
736
737 Bonded,
740
741 LoadBalanced {
743 weights: Option<HashMap<TransportId, u8>>,
745 },
746}
747
748impl TransportMode {
749 pub fn redundant(min_paths: u8) -> Self {
751 Self::Redundant {
752 min_paths,
753 max_paths: None,
754 }
755 }
756
757 pub fn redundant_bounded(min_paths: u8, max_paths: u8) -> Self {
759 Self::Redundant {
760 min_paths,
761 max_paths: Some(max_paths),
762 }
763 }
764
765 pub fn load_balanced_weighted(weights: HashMap<TransportId, u8>) -> Self {
767 Self::LoadBalanced {
768 weights: Some(weights),
769 }
770 }
771}
772
773#[async_trait]
800pub trait Transport: MeshTransport {
801 fn capabilities(&self) -> &TransportCapabilities;
803
804 fn is_available(&self) -> bool;
806
807 fn signal_quality(&self) -> Option<u8> {
811 None
812 }
813
814 fn can_reach(&self, peer_id: &NodeId) -> bool;
816
817 fn estimate_delivery_ms(&self, message_size: usize) -> u32 {
819 self.capabilities().estimate_delivery_ms(message_size)
820 }
821
822 fn calculate_score(&self, requirements: &MessageRequirements, preference_bonus: i32) -> i32 {
826 let caps = self.capabilities();
827 let mut score = 100i32;
828
829 if requirements.priority >= MessagePriority::High {
831 score += 50 - (caps.typical_latency_ms.min(50) as i32);
832 }
833
834 if requirements.power_sensitive {
836 score -= caps.battery_impact as i32;
837 }
838
839 score += preference_bonus;
841
842 if let Some(quality) = self.signal_quality() {
844 score += (quality / 10) as i32;
845 }
846
847 score
848 }
849}
850
851#[async_trait]
856pub trait ConfigurableTransport: Transport {
857 fn range_modes(&self) -> Option<&RangeModeConfig> {
859 None
860 }
861
862 async fn set_range_mode(&self, _mode: RangeMode) -> Result<TransportCapabilities> {
864 Err(TransportError::Other(
865 "Range mode not supported".to_string().into(),
866 ))
867 }
868
869 fn recommend_mode_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
871 self.range_modes()?.recommend_for_distance(distance_meters)
872 }
873}
874
875#[cfg(test)]
880mod tests {
881 use super::*;
882
883 #[test]
884 fn test_transport_type_display() {
885 assert_eq!(TransportType::Quic.to_string(), "QUIC");
886 assert_eq!(TransportType::BluetoothLE.to_string(), "Bluetooth LE");
887 assert_eq!(TransportType::LoRa.to_string(), "LoRa");
888 assert_eq!(TransportType::Custom(42).to_string(), "Custom(42)");
889 }
890
891 #[test]
892 fn test_quic_capabilities() {
893 let caps = TransportCapabilities::quic();
894 assert_eq!(caps.transport_type, TransportType::Quic);
895 assert!(caps.reliable);
896 assert!(caps.bidirectional);
897 assert_eq!(caps.max_range_meters, 0); }
899
900 #[test]
901 fn test_ble_capabilities() {
902 let caps = TransportCapabilities::bluetooth_le();
903 assert_eq!(caps.transport_type, TransportType::BluetoothLE);
904 assert_eq!(caps.max_range_meters, 100);
905 assert_eq!(caps.max_message_size, 512);
906 assert!(caps.supports_broadcast);
907 }
908
909 #[test]
910 fn test_lora_capabilities() {
911 let caps_sf7 = TransportCapabilities::lora(7);
912 let caps_sf12 = TransportCapabilities::lora(12);
913
914 assert!(caps_sf7.max_bandwidth_bps > caps_sf12.max_bandwidth_bps);
916 assert!(caps_sf7.max_range_meters < caps_sf12.max_range_meters);
917 }
918
919 #[test]
920 fn test_meets_requirements_reliable() {
921 let caps = TransportCapabilities::lora(7);
922 assert!(!caps.reliable);
923
924 let requirements = MessageRequirements {
925 reliable: true,
926 ..Default::default()
927 };
928
929 assert!(!caps.meets_requirements(&requirements));
930 }
931
932 #[test]
933 fn test_meets_requirements_bandwidth() {
934 let caps = TransportCapabilities::lora(12); let low_bandwidth = MessageRequirements {
937 min_bandwidth_bps: 500,
938 ..Default::default()
939 };
940
941 let high_bandwidth = MessageRequirements {
942 min_bandwidth_bps: 1_000_000,
943 ..Default::default()
944 };
945
946 assert!(caps.meets_requirements(&low_bandwidth));
947 assert!(!caps.meets_requirements(&high_bandwidth));
948 }
949
950 #[test]
951 fn test_meets_requirements_message_size() {
952 let caps = TransportCapabilities::lora(7); let small_message = MessageRequirements {
955 message_size: 100,
956 ..Default::default()
957 };
958
959 let large_message = MessageRequirements {
960 message_size: 1000,
961 ..Default::default()
962 };
963
964 assert!(caps.meets_requirements(&small_message));
965 assert!(!caps.meets_requirements(&large_message));
966 }
967
968 #[test]
969 fn test_estimate_delivery_ms() {
970 let caps = TransportCapabilities::quic();
971 let estimate = caps.estimate_delivery_ms(1_000_000);
973 assert!(estimate >= 10);
974 assert!(estimate < 200);
975 }
976
977 #[test]
978 fn test_message_priority_ordering() {
979 assert!(MessagePriority::Critical > MessagePriority::High);
980 assert!(MessagePriority::High > MessagePriority::Normal);
981 assert!(MessagePriority::Normal > MessagePriority::Background);
982 }
983
984 #[test]
985 fn test_range_mode_config() {
986 let modes = vec![
987 (RangeMode::Standard, TransportCapabilities::bluetooth_le()),
988 (
989 RangeMode::Extended,
990 TransportCapabilities {
991 max_bandwidth_bps: 125_000,
992 max_range_meters: 200,
993 ..TransportCapabilities::bluetooth_le()
994 },
995 ),
996 (
997 RangeMode::Maximum,
998 TransportCapabilities {
999 max_bandwidth_bps: 62_500,
1000 max_range_meters: 400,
1001 ..TransportCapabilities::bluetooth_le()
1002 },
1003 ),
1004 ];
1005
1006 let config = RangeModeConfig::new(modes);
1007
1008 assert_eq!(config.recommend_for_distance(50), Some(RangeMode::Standard));
1010
1011 assert_eq!(
1013 config.recommend_for_distance(150),
1014 Some(RangeMode::Extended)
1015 );
1016
1017 assert_eq!(config.recommend_for_distance(300), Some(RangeMode::Maximum));
1019 }
1020
1021 #[test]
1022 fn test_distance_source() {
1023 let gps = DistanceSource::Gps {
1024 confidence_meters: 10,
1025 };
1026 let rssi = DistanceSource::Rssi {
1027 estimated_meters: 50,
1028 variance: 20,
1029 };
1030
1031 let _ = format!("{:?}", gps);
1033 let _ = format!("{:?}", rssi);
1034 }
1035
1036 #[test]
1037 fn test_message_requirements_bypass() {
1038 let req = MessageRequirements::bypass(5);
1039 assert!(req.bypass_sync);
1040 assert!(!req.reliable);
1041 assert_eq!(req.max_latency_ms, Some(5));
1042 }
1043
1044 #[test]
1045 fn test_message_requirements_bypass_with_ttl() {
1046 use std::time::Duration;
1047
1048 let req = MessageRequirements::bypass_with_ttl(5, Duration::from_millis(200));
1049 assert!(req.bypass_sync);
1050 assert!(!req.reliable);
1051 assert_eq!(req.max_latency_ms, Some(5));
1052 assert_eq!(req.ttl, Some(Duration::from_millis(200)));
1053 }
1054
1055 #[test]
1056 fn test_message_requirements_builder() {
1057 use std::time::Duration;
1058
1059 let req = MessageRequirements::default()
1060 .with_bypass(true)
1061 .with_ttl(Duration::from_secs(1));
1062
1063 assert!(req.bypass_sync);
1064 assert!(!req.reliable); assert_eq!(req.ttl, Some(Duration::from_secs(1)));
1066 }
1067
1068 #[test]
1069 fn test_message_requirements_default() {
1070 let req = MessageRequirements::default();
1071 assert!(!req.bypass_sync);
1072 assert!(req.ttl.is_none());
1073 }
1074
1075 #[test]
1080 fn test_transport_instance_creation() {
1081 let instance = TransportInstance::new(
1082 "iroh-eth0",
1083 TransportType::Quic,
1084 TransportCapabilities::quic(),
1085 )
1086 .with_description("Primary ethernet")
1087 .with_interface("eth0");
1088
1089 assert_eq!(instance.id, "iroh-eth0");
1090 assert_eq!(instance.transport_type, TransportType::Quic);
1091 assert_eq!(instance.description, "Primary ethernet");
1092 assert_eq!(instance.interface, Some("eth0".to_string()));
1093 assert!(instance.available);
1094 }
1095
1096 #[test]
1097 fn test_pace_level_ordering() {
1098 assert!(PaceLevel::Primary < PaceLevel::Alternate);
1099 assert!(PaceLevel::Alternate < PaceLevel::Contingency);
1100 assert!(PaceLevel::Contingency < PaceLevel::Emergency);
1101 assert!(PaceLevel::Emergency < PaceLevel::None);
1102 }
1103
1104 #[test]
1105 fn test_pace_level_display() {
1106 assert_eq!(PaceLevel::Primary.to_string(), "PRIMARY");
1107 assert_eq!(PaceLevel::Alternate.to_string(), "ALTERNATE");
1108 assert_eq!(PaceLevel::Contingency.to_string(), "CONTINGENCY");
1109 assert_eq!(PaceLevel::Emergency.to_string(), "EMERGENCY");
1110 assert_eq!(PaceLevel::None.to_string(), "NONE");
1111 }
1112
1113 #[test]
1114 fn test_transport_policy_builder() {
1115 let policy = TransportPolicy::new("tactical-standard")
1116 .primary(vec!["iroh-eth0", "iroh-wlan0"])
1117 .alternate(vec!["iroh-starlink"])
1118 .contingency(vec!["lora-primary"])
1119 .emergency(vec!["ble-mesh"]);
1120
1121 assert_eq!(policy.name, "tactical-standard");
1122 assert_eq!(policy.primary.len(), 2);
1123 assert_eq!(policy.alternate.len(), 1);
1124 assert_eq!(policy.contingency.len(), 1);
1125 assert_eq!(policy.emergency.len(), 1);
1126 }
1127
1128 #[test]
1129 fn test_transport_policy_ordered() {
1130 let policy = TransportPolicy::new("test")
1131 .primary(vec!["p1", "p2"])
1132 .alternate(vec!["a1"])
1133 .contingency(vec!["c1"])
1134 .emergency(vec!["e1"]);
1135
1136 let ordered: Vec<_> = policy.ordered().collect();
1137 assert_eq!(ordered, vec!["p1", "p2", "a1", "c1", "e1"]);
1138 }
1139
1140 #[test]
1141 fn test_transport_policy_current_level() {
1142 let policy = TransportPolicy::new("test")
1143 .primary(vec!["p1", "p2"])
1144 .alternate(vec!["a1"])
1145 .contingency(vec!["c1"])
1146 .emergency(vec!["e1"]);
1147
1148 let mut available = std::collections::HashSet::new();
1150 available.insert("p1".to_string());
1151 assert_eq!(policy.current_level(&available), PaceLevel::Primary);
1152
1153 available.clear();
1155 available.insert("a1".to_string());
1156 assert_eq!(policy.current_level(&available), PaceLevel::Alternate);
1157
1158 available.clear();
1160 available.insert("c1".to_string());
1161 assert_eq!(policy.current_level(&available), PaceLevel::Contingency);
1162
1163 available.clear();
1165 available.insert("e1".to_string());
1166 assert_eq!(policy.current_level(&available), PaceLevel::Emergency);
1167
1168 available.clear();
1170 assert_eq!(policy.current_level(&available), PaceLevel::None);
1171 }
1172
1173 #[test]
1174 fn test_transport_policy_at_level() {
1175 let policy = TransportPolicy::new("test")
1176 .primary(vec!["p1"])
1177 .alternate(vec!["a1"])
1178 .contingency(vec!["c1"])
1179 .emergency(vec!["e1"]);
1180
1181 assert_eq!(policy.at_level(PaceLevel::Primary).len(), 1);
1183
1184 assert_eq!(policy.at_level(PaceLevel::Alternate).len(), 2);
1186
1187 assert_eq!(policy.at_level(PaceLevel::Contingency).len(), 3);
1189
1190 assert_eq!(policy.at_level(PaceLevel::Emergency).len(), 4);
1192 }
1193
1194 #[test]
1195 fn test_transport_mode_single() {
1196 let mode = TransportMode::Single;
1197 assert!(matches!(mode, TransportMode::Single));
1198 }
1199
1200 #[test]
1201 fn test_transport_mode_redundant() {
1202 let mode = TransportMode::redundant(2);
1203 assert!(matches!(
1204 mode,
1205 TransportMode::Redundant {
1206 min_paths: 2,
1207 max_paths: None
1208 }
1209 ));
1210
1211 let bounded = TransportMode::redundant_bounded(2, 4);
1212 assert!(matches!(
1213 bounded,
1214 TransportMode::Redundant {
1215 min_paths: 2,
1216 max_paths: Some(4)
1217 }
1218 ));
1219 }
1220
1221 #[test]
1222 fn test_wifi_direct_capabilities() {
1223 let caps = TransportCapabilities::wifi_direct();
1224 assert_eq!(caps.transport_type, TransportType::WifiDirect);
1225 assert_eq!(caps.max_range_meters, 200);
1226 assert!(caps.reliable);
1227 assert!(caps.supports_broadcast);
1228 assert!(caps.requires_pairing);
1229 assert_eq!(caps.battery_impact, 50);
1230 }
1231
1232 #[test]
1233 fn test_lora_all_spreading_factors() {
1234 for sf in [7, 8, 9, 10, 11, 12] {
1236 let caps = TransportCapabilities::lora(sf);
1237 assert_eq!(caps.transport_type, TransportType::LoRa);
1238 assert!(!caps.reliable);
1239 assert!(caps.supports_broadcast);
1240 assert_eq!(caps.max_message_size, 255);
1241 }
1242
1243 let caps_default = TransportCapabilities::lora(6);
1245 assert_eq!(caps_default.max_bandwidth_bps, 5_000);
1246 assert_eq!(caps_default.max_range_meters, 10_000);
1247 }
1248
1249 #[test]
1250 fn test_transport_capabilities_default() {
1251 let caps = TransportCapabilities::default();
1252 assert_eq!(caps.transport_type, TransportType::Quic);
1253 }
1254
1255 #[test]
1256 fn test_transport_type_display_all() {
1257 assert_eq!(
1258 TransportType::BluetoothClassic.to_string(),
1259 "Bluetooth Classic"
1260 );
1261 assert_eq!(TransportType::WifiDirect.to_string(), "WiFi Direct");
1262 assert_eq!(TransportType::TacticalRadio.to_string(), "Tactical Radio");
1263 assert_eq!(TransportType::Satellite.to_string(), "Satellite");
1264 }
1265
1266 #[test]
1267 fn test_range_mode_display() {
1268 assert_eq!(RangeMode::Standard.to_string(), "standard");
1269 assert_eq!(RangeMode::Extended.to_string(), "extended");
1270 assert_eq!(RangeMode::Maximum.to_string(), "maximum");
1271 assert_eq!(RangeMode::Custom(42).to_string(), "custom(42)");
1272 }
1273
1274 #[test]
1275 fn test_range_mode_default() {
1276 assert_eq!(RangeMode::default(), RangeMode::Standard);
1277 }
1278
1279 #[test]
1280 fn test_message_priority_display() {
1281 assert_eq!(MessagePriority::Background.to_string(), "background");
1282 assert_eq!(MessagePriority::Normal.to_string(), "normal");
1283 assert_eq!(MessagePriority::High.to_string(), "high");
1284 assert_eq!(MessagePriority::Critical.to_string(), "critical");
1285 }
1286
1287 #[test]
1288 fn test_message_priority_default() {
1289 assert_eq!(MessagePriority::default(), MessagePriority::Normal);
1290 }
1291
1292 #[test]
1293 fn test_pace_level_default() {
1294 assert_eq!(PaceLevel::default(), PaceLevel::Primary);
1295 }
1296
1297 #[test]
1298 fn test_transport_mode_default() {
1299 let mode = TransportMode::default();
1300 assert!(matches!(mode, TransportMode::Single));
1301 }
1302
1303 #[test]
1304 fn test_transport_mode_bonded() {
1305 let mode = TransportMode::Bonded;
1306 let _ = format!("{:?}", mode);
1307 }
1308
1309 #[test]
1310 fn test_distance_source_all_variants() {
1311 let tof = DistanceSource::Tof { precision_ns: 100 };
1312 let configured = DistanceSource::Configured;
1313 let unknown = DistanceSource::Unknown;
1314 let _ = format!("{:?}", tof);
1315 let _ = format!("{:?}", configured);
1316 let _ = format!("{:?}", unknown);
1317 }
1318
1319 #[test]
1320 fn test_peer_distance_construction() {
1321 let pd = PeerDistance {
1322 peer_id: NodeId::new("test-peer".to_string()),
1323 distance_meters: 500,
1324 source: DistanceSource::Gps {
1325 confidence_meters: 5,
1326 },
1327 last_updated: Instant::now(),
1328 };
1329 assert_eq!(pd.distance_meters, 500);
1330 let _ = format!("{:?}", pd);
1331 }
1332
1333 #[test]
1334 fn test_range_mode_config_current_capabilities() {
1335 let modes = vec![
1336 (RangeMode::Standard, TransportCapabilities::bluetooth_le()),
1337 (
1338 RangeMode::Extended,
1339 TransportCapabilities {
1340 max_bandwidth_bps: 125_000,
1341 max_range_meters: 200,
1342 ..TransportCapabilities::bluetooth_le()
1343 },
1344 ),
1345 ];
1346 let config = RangeModeConfig::new(modes);
1347 let caps = config.current_capabilities();
1348 assert!(caps.is_some());
1349 assert_eq!(caps.unwrap().transport_type, TransportType::BluetoothLE);
1350 }
1351
1352 #[test]
1353 fn test_range_mode_config_no_match_for_distance() {
1354 let modes = vec![(
1355 RangeMode::Standard,
1356 TransportCapabilities {
1357 max_range_meters: 50,
1358 ..TransportCapabilities::bluetooth_le()
1359 },
1360 )];
1361 let config = RangeModeConfig::new(modes);
1362 let result = config.recommend_for_distance(1000);
1364 assert!(result.is_none());
1365 }
1366
1367 #[test]
1368 fn test_with_bypass_false() {
1369 let req = MessageRequirements::default().with_bypass(false);
1370 assert!(!req.bypass_sync);
1371 assert!(!req.reliable); }
1374
1375 #[test]
1376 fn test_message_priority_serde() {
1377 let priority = MessagePriority::Critical;
1378 let json = serde_json::to_string(&priority).unwrap();
1379 let deserialized: MessagePriority = serde_json::from_str(&json).unwrap();
1380 assert_eq!(deserialized, MessagePriority::Critical);
1381 }
1382
1383 #[test]
1384 fn test_transport_policy_at_level_none() {
1385 let policy = TransportPolicy::new("test")
1386 .primary(vec!["p1"])
1387 .alternate(vec!["a1"])
1388 .contingency(vec!["c1"])
1389 .emergency(vec!["e1"]);
1390
1391 assert_eq!(policy.at_level(PaceLevel::None).len(), 4);
1393 }
1394
1395 #[test]
1396 fn test_estimate_delivery_zero_bandwidth() {
1397 let caps = TransportCapabilities {
1398 max_bandwidth_bps: 0,
1399 typical_latency_ms: 50,
1400 ..TransportCapabilities::quic()
1401 };
1402 assert_eq!(caps.estimate_delivery_ms(1_000_000), 50);
1404 }
1405
1406 #[test]
1407 fn test_meets_requirements_all_pass() {
1408 let caps = TransportCapabilities::quic();
1409 let req = MessageRequirements {
1410 reliable: true,
1411 min_bandwidth_bps: 1_000,
1412 message_size: 100,
1413 ..Default::default()
1414 };
1415 assert!(caps.meets_requirements(&req));
1417 }
1418
1419 #[test]
1420 fn test_transport_mode_load_balanced() {
1421 let mut weights = std::collections::HashMap::new();
1422 weights.insert("t1".to_string(), 3);
1423 weights.insert("t2".to_string(), 1);
1424
1425 let mode = TransportMode::load_balanced_weighted(weights.clone());
1426 if let TransportMode::LoadBalanced { weights: Some(w) } = mode {
1427 assert_eq!(w.get("t1"), Some(&3));
1428 assert_eq!(w.get("t2"), Some(&1));
1429 } else {
1430 panic!("Expected LoadBalanced with weights");
1431 }
1432 }
1433}