Skip to main content

peat_mesh/transport/
capabilities.rs

1//! Transport capabilities and multi-transport abstractions
2//!
3//! This module provides the pluggable transport abstraction layer for supporting
4//! multiple transport types (QUIC, Bluetooth LE, LoRa, WiFi Direct, etc.)
5//!
6//! ## Architecture
7//!
8//! - **TransportCapabilities**: Declares what a transport can do
9//! - **Transport**: Extended trait with capability advertisement
10//! - **MessageRequirements**: Requirements for message delivery
11//! - **TransportManager**: Coordinates multiple transports
12//!
13//! ## Design (ADR-032)
14//!
15//! The design follows a pluggable architecture where:
16//! 1. Each transport declares its capabilities (bandwidth, latency, range, power)
17//! 2. Messages declare their requirements (reliability, latency, priority)
18//! 3. TransportManager selects the best transport for each message
19//!
20//! ## Example
21//!
22//! ```ignore
23//! use peat_mesh::transport::{TransportManager, MessageRequirements, MessagePriority};
24//!
25//! // Register transports
26//! let mut manager = TransportManager::new(config);
27//! manager.register(quic_transport);
28//! manager.register(ble_transport);
29//!
30//! // Send with requirements
31//! let requirements = MessageRequirements {
32//!     reliable: true,
33//!     priority: MessagePriority::High,
34//!     ..Default::default()
35//! };
36//! manager.send(&peer_id, &data, requirements).await?;
37//! ```
38
39use async_trait::async_trait;
40use std::collections::HashMap;
41use std::time::Instant;
42
43use super::{MeshTransport, NodeId, Result, TransportError};
44
45// =============================================================================
46// Transport Type
47// =============================================================================
48
49/// Type of transport technology
50///
51/// Used to identify and categorize transports for selection and configuration.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub enum TransportType {
55    /// QUIC over IP (Iroh) - primary mesh transport
56    Quic,
57    /// Classic Bluetooth (RFCOMM)
58    BluetoothClassic,
59    /// Bluetooth Low Energy (GATT)
60    BluetoothLE,
61    /// WiFi Direct (P2P)
62    WifiDirect,
63    /// LoRa (long range, low power)
64    LoRa,
65    /// Tactical radio (MANET)
66    TacticalRadio,
67    /// Satellite (Starlink, Iridium)
68    Satellite,
69    /// Custom/vendor-specific transport
70    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// =============================================================================
89// Transport Capabilities
90// =============================================================================
91
92/// Declares the capabilities of a transport
93///
94/// Each transport advertises what it can do, allowing the TransportManager
95/// to select the best transport for each message based on requirements.
96///
97/// # Example
98///
99/// ```
100/// use peat_mesh::transport::{TransportCapabilities, TransportType};
101///
102/// let quic_caps = TransportCapabilities {
103///     transport_type: TransportType::Quic,
104///     max_bandwidth_bps: 100_000_000,  // 100 Mbps
105///     typical_latency_ms: 10,
106///     max_range_meters: 0,  // Unlimited (IP)
107///     bidirectional: true,
108///     reliable: true,
109///     battery_impact: 20,
110///     supports_broadcast: false,
111///     requires_pairing: false,
112///     max_message_size: 0,  // Unlimited (stream-based)
113/// };
114/// ```
115#[derive(Debug, Clone)]
116pub struct TransportCapabilities {
117    /// Transport type identifier
118    pub transport_type: TransportType,
119
120    /// Maximum bandwidth in bytes/second (0 = unknown/unlimited)
121    pub max_bandwidth_bps: u64,
122
123    /// Typical latency in milliseconds
124    pub typical_latency_ms: u32,
125
126    /// Maximum practical range in meters (0 = unlimited/IP-based)
127    pub max_range_meters: u32,
128
129    /// Supports bidirectional communication
130    pub bidirectional: bool,
131
132    /// Supports reliable delivery (vs best-effort)
133    pub reliable: bool,
134
135    /// Battery impact score (0-100, higher = more power consumption)
136    pub battery_impact: u8,
137
138    /// Supports broadcast/multicast
139    pub supports_broadcast: bool,
140
141    /// Requires pairing/bonding before use
142    pub requires_pairing: bool,
143
144    /// Maximum message size in bytes (0 = unlimited/stream-based)
145    pub max_message_size: usize,
146}
147
148impl TransportCapabilities {
149    /// Create capabilities for QUIC/Iroh transport
150    pub fn quic() -> Self {
151        Self {
152            transport_type: TransportType::Quic,
153            max_bandwidth_bps: 100_000_000, // ~100 Mbps typical
154            typical_latency_ms: 10,
155            max_range_meters: 0, // Unlimited (IP-based)
156            bidirectional: true,
157            reliable: true,
158            battery_impact: 20,
159            supports_broadcast: false,
160            requires_pairing: false,
161            max_message_size: 0, // Unlimited (stream-based)
162        }
163    }
164
165    /// Create capabilities for Bluetooth LE transport
166    pub fn bluetooth_le() -> Self {
167        Self {
168            transport_type: TransportType::BluetoothLE,
169            max_bandwidth_bps: 250_000, // ~2 Mbps theoretical, ~250 KB/s practical
170            typical_latency_ms: 30,
171            max_range_meters: 100,
172            bidirectional: true,
173            reliable: true,
174            battery_impact: 15,       // BLE is efficient
175            supports_broadcast: true, // Advertising
176            requires_pairing: false,  // Can use just-works
177            max_message_size: 512,    // MTU limit
178        }
179    }
180
181    /// Create capabilities for LoRa transport with given spreading factor
182    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), // Default
191        };
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, // Best-effort by default
200            battery_impact: 10,
201            supports_broadcast: true,
202            requires_pairing: false,
203            max_message_size: 255, // LoRa packet limit
204        }
205    }
206
207    /// Create capabilities for WiFi Direct transport
208    pub fn wifi_direct() -> Self {
209        Self {
210            transport_type: TransportType::WifiDirect,
211            max_bandwidth_bps: 250_000_000, // ~250 Mbps
212            typical_latency_ms: 10,
213            max_range_meters: 200,
214            bidirectional: true,
215            reliable: true,
216            battery_impact: 50, // WiFi uses more power
217            supports_broadcast: true,
218            requires_pairing: true, // GO negotiation required
219            max_message_size: 0,    // Unlimited (TCP/UDP)
220        }
221    }
222
223    /// Check if this transport can meet the given requirements
224    pub fn meets_requirements(&self, requirements: &MessageRequirements) -> bool {
225        // Check reliability requirement
226        if requirements.reliable && !self.reliable {
227            return false;
228        }
229
230        // Check bandwidth requirement
231        if self.max_bandwidth_bps > 0 && self.max_bandwidth_bps < requirements.min_bandwidth_bps {
232            return false;
233        }
234
235        // Check message size
236        if self.max_message_size > 0 && self.max_message_size < requirements.message_size {
237            return false;
238        }
239
240        true
241    }
242
243    /// Estimate delivery time for a message of given size
244    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// =============================================================================
261// Message Requirements
262// =============================================================================
263
264/// Priority level for message delivery
265///
266/// Higher priority messages will be routed via faster/more reliable transports.
267#[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 sync, can use any available transport
283    Background = 0,
284    /// Normal operational messages
285    #[default]
286    Normal = 1,
287    /// Time-sensitive, prefer low-latency transports
288    High = 2,
289    /// Emergency/critical, use fastest available path
290    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/// Requirements for message delivery
305///
306/// Used by TransportManager to select the best transport for a message.
307///
308/// # Example
309///
310/// ```
311/// use peat_mesh::transport::{MessageRequirements, MessagePriority};
312///
313/// // High-priority reliable message
314/// let requirements = MessageRequirements {
315///     reliable: true,
316///     priority: MessagePriority::High,
317///     max_latency_ms: Some(100),
318///     message_size: 1024,
319///     ..Default::default()
320/// };
321///
322/// // Bypass message for low-latency delivery
323/// let bypass_req = MessageRequirements {
324///     bypass_sync: true,
325///     reliable: false,  // UDP is unreliable
326///     max_latency_ms: Some(5),
327///     ..Default::default()
328/// };
329/// ```
330#[derive(Debug, Clone, Default)]
331pub struct MessageRequirements {
332    /// Minimum required bandwidth (bytes/second)
333    pub min_bandwidth_bps: u64,
334
335    /// Maximum acceptable latency (ms)
336    pub max_latency_ms: Option<u32>,
337
338    /// Message size in bytes (for capacity checking)
339    pub message_size: usize,
340
341    /// Requires reliable delivery
342    pub reliable: bool,
343
344    /// Priority level (higher = more important)
345    pub priority: MessagePriority,
346
347    /// Prefer low power consumption
348    pub power_sensitive: bool,
349
350    /// Use UDP bypass channel instead of CRDT sync (ADR-042)
351    ///
352    /// When `true`, the message should be sent via the low-latency UDP
353    /// bypass channel instead of the normal CRDT synchronization path.
354    /// This bypasses:
355    /// - Automerge encoding/decoding
356    /// - CRDT conflict resolution
357    /// - Iroh/QUIC transport overhead
358    ///
359    /// Use for ephemeral data like position updates, sensor telemetry,
360    /// and time-critical commands.
361    pub bypass_sync: bool,
362
363    /// Time-to-live for bypass messages
364    ///
365    /// Messages older than this will be dropped by receivers.
366    /// Only applies when `bypass_sync` is `true`.
367    /// Default: None (use collection config or 5 seconds)
368    pub ttl: Option<std::time::Duration>,
369}
370
371impl MessageRequirements {
372    /// Create requirements for bypass mode with specified latency
373    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    /// Create requirements for bypass mode with TTL
383    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    /// Set bypass_sync flag
394    pub fn with_bypass(mut self, bypass: bool) -> Self {
395        self.bypass_sync = bypass;
396        if bypass {
397            self.reliable = false; // UDP bypass is unreliable
398        }
399        self
400    }
401
402    /// Set TTL for bypass messages
403    pub fn with_ttl(mut self, ttl: std::time::Duration) -> Self {
404        self.ttl = Some(ttl);
405        self
406    }
407}
408
409// =============================================================================
410// Range Mode (Dynamic Range/Bandwidth Tradeoff)
411// =============================================================================
412
413/// Available range modes for configurable transports
414///
415/// Many radio technologies allow trading bandwidth for range. This enum
416/// represents standard operating modes.
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
418pub enum RangeMode {
419    /// Default/balanced mode
420    #[default]
421    Standard,
422    /// Extended range at cost of bandwidth
423    Extended,
424    /// Maximum range (lowest bandwidth)
425    Maximum,
426    /// Custom configuration (transport-specific value)
427    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/// Range mode configuration for a transport
442#[derive(Debug, Clone)]
443pub struct RangeModeConfig {
444    /// Available modes for this transport
445    pub available_modes: Vec<RangeMode>,
446    /// Current active mode
447    pub current_mode: RangeMode,
448    /// Capabilities per mode
449    pub mode_capabilities: HashMap<RangeMode, TransportCapabilities>,
450}
451
452impl RangeModeConfig {
453    /// Create a new range mode configuration
454    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    /// Get capabilities for the current mode
470    pub fn current_capabilities(&self) -> Option<&TransportCapabilities> {
471        self.mode_capabilities.get(&self.current_mode)
472    }
473
474    /// Find the best mode for a target distance
475    pub fn recommend_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
476        // Find mode with sufficient range and best bandwidth
477        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// =============================================================================
488// Distance Estimation
489// =============================================================================
490
491/// How peer distance was determined
492#[derive(Debug, Clone)]
493pub enum DistanceSource {
494    /// GPS coordinates from both peers
495    Gps {
496        /// Confidence in meters
497        confidence_meters: u32,
498    },
499    /// Signal strength (RSSI) estimation
500    Rssi {
501        /// Estimated distance
502        estimated_meters: u32,
503        /// Variance in estimate
504        variance: u32,
505    },
506    /// Time-of-flight measurement
507    Tof {
508        /// Measurement precision in nanoseconds
509        precision_ns: u32,
510    },
511    /// Manual/configured distance
512    Configured,
513    /// Unknown distance
514    Unknown,
515}
516
517/// Peer distance information
518#[derive(Debug, Clone)]
519pub struct PeerDistance {
520    /// Peer node ID
521    pub peer_id: NodeId,
522    /// Estimated distance in meters
523    pub distance_meters: u32,
524    /// How distance was determined
525    pub source: DistanceSource,
526    /// When this estimate was made
527    pub last_updated: Instant,
528}
529
530// =============================================================================
531// PACE Transport Policy (ADR-032)
532// =============================================================================
533
534/// Unique identifier for a transport instance
535///
536/// Format: "{type}-{interface}" or custom string
537/// Examples: "iroh-eth0", "iroh-wlan0", "lora-915mhz", "ble-hci0"
538pub type TransportId = String;
539
540/// Transport instance metadata
541///
542/// Represents a registered transport with its unique ID and current state.
543#[derive(Debug, Clone)]
544pub struct TransportInstance {
545    /// Unique instance identifier
546    pub id: TransportId,
547    /// Transport type (for capability grouping)
548    pub transport_type: TransportType,
549    /// Human-readable description
550    pub description: String,
551    /// Physical interface name (if applicable)
552    pub interface: Option<String>,
553    /// Current capabilities (may change with range mode)
554    pub capabilities: TransportCapabilities,
555    /// Is this transport currently available?
556    pub available: bool,
557}
558
559impl TransportInstance {
560    /// Create a new transport instance
561    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    /// Set the description
577    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
578        self.description = desc.into();
579        self
580    }
581
582    /// Set the interface name
583    pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
584        self.interface = Some(iface.into());
585        self
586    }
587}
588
589/// PACE level for transport availability
590///
591/// Military PACE planning: Primary, Alternate, Contingency, Emergency
592#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
593pub enum PaceLevel {
594    /// Primary transports available
595    #[default]
596    Primary = 0,
597    /// Alternate transports (primary unavailable)
598    Alternate = 1,
599    /// Contingency transports (degraded operation)
600    Contingency = 2,
601    /// Emergency transports (last resort)
602    Emergency = 3,
603    /// No transports available
604    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/// PACE-style transport policy
620///
621/// Defines transport selection order following military PACE planning:
622/// Primary → Alternate → Contingency → Emergency
623///
624/// # Example
625///
626/// ```
627/// use peat_mesh::transport::TransportPolicy;
628///
629/// let policy = TransportPolicy::new("tactical-standard")
630///     .primary(vec!["iroh-eth0", "iroh-wlan0"])
631///     .alternate(vec!["iroh-starlink"])
632///     .contingency(vec!["lora-primary"])
633///     .emergency(vec!["ble-mesh"]);
634/// ```
635#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
636pub struct TransportPolicy {
637    /// Policy name for reference
638    pub name: String,
639    /// Primary transports - use when available
640    pub primary: Vec<TransportId>,
641    /// Alternate - if all primary unavailable
642    pub alternate: Vec<TransportId>,
643    /// Contingency - degraded but functional
644    pub contingency: Vec<TransportId>,
645    /// Emergency - last resort
646    pub emergency: Vec<TransportId>,
647}
648
649impl TransportPolicy {
650    /// Create a new transport policy
651    pub fn new(name: impl Into<String>) -> Self {
652        Self {
653            name: name.into(),
654            ..Default::default()
655        }
656    }
657
658    /// Set primary transports
659    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    /// Set alternate transports
665    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    /// Set contingency transports
671    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    /// Set emergency transports
677    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    /// Get transports in PACE order (primary first, then alternate, etc.)
683    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    /// Get current PACE level based on available transports
692    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    /// Get available transports at or above a minimum PACE level
707    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/// How to use multiple available transports
723#[derive(Debug, Clone, Default)]
724pub enum TransportMode {
725    /// Use single best transport from policy (PACE failover)
726    #[default]
727    Single,
728
729    /// Send on multiple transports simultaneously for reliability
730    /// Receiver deduplicates by message ID
731    Redundant {
732        /// Minimum transports to send on (default: 2)
733        min_paths: u8,
734        /// Maximum transports to send on (None = all available)
735        max_paths: Option<u8>,
736    },
737
738    /// Aggregate bandwidth across transports (for large transfers)
739    /// Splits message across transports, receiver reassembles
740    Bonded,
741
742    /// Distribute messages across transports (round-robin or weighted)
743    LoadBalanced {
744        /// Weight per transport (higher = more traffic)
745        weights: Option<HashMap<TransportId, u8>>,
746    },
747}
748
749impl TransportMode {
750    /// Create redundant mode with minimum paths
751    pub fn redundant(min_paths: u8) -> Self {
752        Self::Redundant {
753            min_paths,
754            max_paths: None,
755        }
756    }
757
758    /// Create redundant mode with min and max paths
759    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    /// Create load balanced mode with weights
767    pub fn load_balanced_weighted(weights: HashMap<TransportId, u8>) -> Self {
768        Self::LoadBalanced {
769            weights: Some(weights),
770        }
771    }
772}
773
774// =============================================================================
775// Transport Trait (Extended)
776// =============================================================================
777
778/// Extended transport trait with capability advertisement
779///
780/// This trait extends `MeshTransport` with capability declaration and
781/// selection support. All pluggable transports should implement this.
782///
783/// # Example Implementation
784///
785/// ```ignore
786/// impl Transport for MyTransport {
787///     fn capabilities(&self) -> &TransportCapabilities {
788///         &self.caps
789///     }
790///
791///     fn is_available(&self) -> bool {
792///         self.hardware.is_ready()
793///     }
794///
795///     fn can_reach(&self, peer_id: &NodeId) -> bool {
796///         self.known_peers.contains(peer_id)
797///     }
798/// }
799/// ```
800#[async_trait]
801pub trait Transport: MeshTransport {
802    /// Get transport capabilities
803    fn capabilities(&self) -> &TransportCapabilities;
804
805    /// Check if transport is currently available/enabled
806    fn is_available(&self) -> bool;
807
808    /// Get current signal quality (0-100, for wireless transports)
809    ///
810    /// Returns `None` for wired/IP transports.
811    fn signal_quality(&self) -> Option<u8> {
812        None
813    }
814
815    /// Estimate if peer is reachable via this transport
816    fn can_reach(&self, peer_id: &NodeId) -> bool;
817
818    /// Get estimated delivery time for message of given size
819    fn estimate_delivery_ms(&self, message_size: usize) -> u32 {
820        self.capabilities().estimate_delivery_ms(message_size)
821    }
822
823    /// Calculate selection score for this transport
824    ///
825    /// Higher scores are better. Used by TransportManager for selection.
826    fn calculate_score(&self, requirements: &MessageRequirements, preference_bonus: i32) -> i32 {
827        let caps = self.capabilities();
828        let mut score = 100i32;
829
830        // Latency bonus for high-priority messages
831        if requirements.priority >= MessagePriority::High {
832            score += 50 - (caps.typical_latency_ms.min(50) as i32);
833        }
834
835        // Power penalty if power-sensitive
836        if requirements.power_sensitive {
837            score -= caps.battery_impact as i32;
838        }
839
840        // Add preference bonus
841        score += preference_bonus;
842
843        // Signal quality bonus for wireless
844        if let Some(quality) = self.signal_quality() {
845            score += (quality / 10) as i32;
846        }
847
848        score
849    }
850}
851
852/// Extended transport trait with range mode configuration
853///
854/// Transports that support dynamic range/bandwidth tradeoffs should
855/// implement this trait.
856#[async_trait]
857pub trait ConfigurableTransport: Transport {
858    /// Get available range modes
859    fn range_modes(&self) -> Option<&RangeModeConfig> {
860        None
861    }
862
863    /// Set range mode (returns new capabilities)
864    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    /// Get recommended mode for target distance
871    fn recommend_mode_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
872        self.range_modes()?.recommend_for_distance(distance_meters)
873    }
874}
875
876// =============================================================================
877// Tests
878// =============================================================================
879
880#[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); // Unlimited
899    }
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        // SF7 has more bandwidth but less range
916        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); // ~1.1 kbps
936
937        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); // 255 byte limit
954
955        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        // 1MB message at 100 Mbps = ~80ms transfer + 10ms latency
973        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        // Should recommend Standard for short range
1010        assert_eq!(config.recommend_for_distance(50), Some(RangeMode::Standard));
1011
1012        // Should recommend Extended for medium range
1013        assert_eq!(
1014            config.recommend_for_distance(150),
1015            Some(RangeMode::Extended)
1016        );
1017
1018        // Should recommend Maximum for long range
1019        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        // Just ensure these compile and can be debugged
1033        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); // auto-set to false when bypass=true
1066        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    // =========================================================================
1077    // PACE Policy Tests (ADR-032)
1078    // =========================================================================
1079
1080    #[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        // Primary available
1150        let mut available = std::collections::HashSet::new();
1151        available.insert("p1".to_string());
1152        assert_eq!(policy.current_level(&available), PaceLevel::Primary);
1153
1154        // Only alternate available
1155        available.clear();
1156        available.insert("a1".to_string());
1157        assert_eq!(policy.current_level(&available), PaceLevel::Alternate);
1158
1159        // Only contingency available
1160        available.clear();
1161        available.insert("c1".to_string());
1162        assert_eq!(policy.current_level(&available), PaceLevel::Contingency);
1163
1164        // Only emergency available
1165        available.clear();
1166        available.insert("e1".to_string());
1167        assert_eq!(policy.current_level(&available), PaceLevel::Emergency);
1168
1169        // Nothing available
1170        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        // At Primary level - only primary
1183        assert_eq!(policy.at_level(PaceLevel::Primary).len(), 1);
1184
1185        // At Alternate level - primary + alternate
1186        assert_eq!(policy.at_level(PaceLevel::Alternate).len(), 2);
1187
1188        // At Contingency level - primary + alternate + contingency
1189        assert_eq!(policy.at_level(PaceLevel::Contingency).len(), 3);
1190
1191        // At Emergency level - all
1192        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        // Test all known spreading factors
1236        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        // Test default/unknown SF
1245        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        // 1000m exceeds the only mode's 50m range
1364        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        // reliable shouldn't be forced to false when bypass is false
1373        assert!(!req.reliable); // default is false
1374    }
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        // PaceLevel::None should return all
1393        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        // Should just return latency when bandwidth is 0/unlimited
1404        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        // QUIC caps: max_bandwidth_bps=100M (>1000), reliable=true, max_message_size=0 (unlimited)
1417        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}