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 = (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// =============================================================================
260// Message Requirements
261// =============================================================================
262
263/// Priority level for message delivery
264///
265/// Higher priority messages will be routed via faster/more reliable transports.
266#[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 sync, can use any available transport
282    Background = 0,
283    /// Normal operational messages
284    #[default]
285    Normal = 1,
286    /// Time-sensitive, prefer low-latency transports
287    High = 2,
288    /// Emergency/critical, use fastest available path
289    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/// Requirements for message delivery
304///
305/// Used by TransportManager to select the best transport for a message.
306///
307/// # Example
308///
309/// ```
310/// use peat_mesh::transport::{MessageRequirements, MessagePriority};
311///
312/// // High-priority reliable message
313/// let requirements = MessageRequirements {
314///     reliable: true,
315///     priority: MessagePriority::High,
316///     max_latency_ms: Some(100),
317///     message_size: 1024,
318///     ..Default::default()
319/// };
320///
321/// // Bypass message for low-latency delivery
322/// let bypass_req = MessageRequirements {
323///     bypass_sync: true,
324///     reliable: false,  // UDP is unreliable
325///     max_latency_ms: Some(5),
326///     ..Default::default()
327/// };
328/// ```
329#[derive(Debug, Clone, Default)]
330pub struct MessageRequirements {
331    /// Minimum required bandwidth (bytes/second)
332    pub min_bandwidth_bps: u64,
333
334    /// Maximum acceptable latency (ms)
335    pub max_latency_ms: Option<u32>,
336
337    /// Message size in bytes (for capacity checking)
338    pub message_size: usize,
339
340    /// Requires reliable delivery
341    pub reliable: bool,
342
343    /// Priority level (higher = more important)
344    pub priority: MessagePriority,
345
346    /// Prefer low power consumption
347    pub power_sensitive: bool,
348
349    /// Use UDP bypass channel instead of CRDT sync (ADR-042)
350    ///
351    /// When `true`, the message should be sent via the low-latency UDP
352    /// bypass channel instead of the normal CRDT synchronization path.
353    /// This bypasses:
354    /// - Automerge encoding/decoding
355    /// - CRDT conflict resolution
356    /// - Iroh/QUIC transport overhead
357    ///
358    /// Use for ephemeral data like position updates, sensor telemetry,
359    /// and time-critical commands.
360    pub bypass_sync: bool,
361
362    /// Time-to-live for bypass messages
363    ///
364    /// Messages older than this will be dropped by receivers.
365    /// Only applies when `bypass_sync` is `true`.
366    /// Default: None (use collection config or 5 seconds)
367    pub ttl: Option<std::time::Duration>,
368}
369
370impl MessageRequirements {
371    /// Create requirements for bypass mode with specified latency
372    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    /// Create requirements for bypass mode with TTL
382    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    /// Set bypass_sync flag
393    pub fn with_bypass(mut self, bypass: bool) -> Self {
394        self.bypass_sync = bypass;
395        if bypass {
396            self.reliable = false; // UDP bypass is unreliable
397        }
398        self
399    }
400
401    /// Set TTL for bypass messages
402    pub fn with_ttl(mut self, ttl: std::time::Duration) -> Self {
403        self.ttl = Some(ttl);
404        self
405    }
406}
407
408// =============================================================================
409// Range Mode (Dynamic Range/Bandwidth Tradeoff)
410// =============================================================================
411
412/// Available range modes for configurable transports
413///
414/// Many radio technologies allow trading bandwidth for range. This enum
415/// represents standard operating modes.
416#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
417pub enum RangeMode {
418    /// Default/balanced mode
419    #[default]
420    Standard,
421    /// Extended range at cost of bandwidth
422    Extended,
423    /// Maximum range (lowest bandwidth)
424    Maximum,
425    /// Custom configuration (transport-specific value)
426    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/// Range mode configuration for a transport
441#[derive(Debug, Clone)]
442pub struct RangeModeConfig {
443    /// Available modes for this transport
444    pub available_modes: Vec<RangeMode>,
445    /// Current active mode
446    pub current_mode: RangeMode,
447    /// Capabilities per mode
448    pub mode_capabilities: HashMap<RangeMode, TransportCapabilities>,
449}
450
451impl RangeModeConfig {
452    /// Create a new range mode configuration
453    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    /// Get capabilities for the current mode
469    pub fn current_capabilities(&self) -> Option<&TransportCapabilities> {
470        self.mode_capabilities.get(&self.current_mode)
471    }
472
473    /// Find the best mode for a target distance
474    pub fn recommend_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
475        // Find mode with sufficient range and best bandwidth
476        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// =============================================================================
487// Distance Estimation
488// =============================================================================
489
490/// How peer distance was determined
491#[derive(Debug, Clone)]
492pub enum DistanceSource {
493    /// GPS coordinates from both peers
494    Gps {
495        /// Confidence in meters
496        confidence_meters: u32,
497    },
498    /// Signal strength (RSSI) estimation
499    Rssi {
500        /// Estimated distance
501        estimated_meters: u32,
502        /// Variance in estimate
503        variance: u32,
504    },
505    /// Time-of-flight measurement
506    Tof {
507        /// Measurement precision in nanoseconds
508        precision_ns: u32,
509    },
510    /// Manual/configured distance
511    Configured,
512    /// Unknown distance
513    Unknown,
514}
515
516/// Peer distance information
517#[derive(Debug, Clone)]
518pub struct PeerDistance {
519    /// Peer node ID
520    pub peer_id: NodeId,
521    /// Estimated distance in meters
522    pub distance_meters: u32,
523    /// How distance was determined
524    pub source: DistanceSource,
525    /// When this estimate was made
526    pub last_updated: Instant,
527}
528
529// =============================================================================
530// PACE Transport Policy (ADR-032)
531// =============================================================================
532
533/// Unique identifier for a transport instance
534///
535/// Format: "{type}-{interface}" or custom string
536/// Examples: "iroh-eth0", "iroh-wlan0", "lora-915mhz", "ble-hci0"
537pub type TransportId = String;
538
539/// Transport instance metadata
540///
541/// Represents a registered transport with its unique ID and current state.
542#[derive(Debug, Clone)]
543pub struct TransportInstance {
544    /// Unique instance identifier
545    pub id: TransportId,
546    /// Transport type (for capability grouping)
547    pub transport_type: TransportType,
548    /// Human-readable description
549    pub description: String,
550    /// Physical interface name (if applicable)
551    pub interface: Option<String>,
552    /// Current capabilities (may change with range mode)
553    pub capabilities: TransportCapabilities,
554    /// Is this transport currently available?
555    pub available: bool,
556}
557
558impl TransportInstance {
559    /// Create a new transport instance
560    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    /// Set the description
576    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
577        self.description = desc.into();
578        self
579    }
580
581    /// Set the interface name
582    pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
583        self.interface = Some(iface.into());
584        self
585    }
586}
587
588/// PACE level for transport availability
589///
590/// Military PACE planning: Primary, Alternate, Contingency, Emergency
591#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
592pub enum PaceLevel {
593    /// Primary transports available
594    #[default]
595    Primary = 0,
596    /// Alternate transports (primary unavailable)
597    Alternate = 1,
598    /// Contingency transports (degraded operation)
599    Contingency = 2,
600    /// Emergency transports (last resort)
601    Emergency = 3,
602    /// No transports available
603    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/// PACE-style transport policy
619///
620/// Defines transport selection order following military PACE planning:
621/// Primary → Alternate → Contingency → Emergency
622///
623/// # Example
624///
625/// ```
626/// use peat_mesh::transport::TransportPolicy;
627///
628/// let policy = TransportPolicy::new("tactical-standard")
629///     .primary(vec!["iroh-eth0", "iroh-wlan0"])
630///     .alternate(vec!["iroh-starlink"])
631///     .contingency(vec!["lora-primary"])
632///     .emergency(vec!["ble-mesh"]);
633/// ```
634#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
635pub struct TransportPolicy {
636    /// Policy name for reference
637    pub name: String,
638    /// Primary transports - use when available
639    pub primary: Vec<TransportId>,
640    /// Alternate - if all primary unavailable
641    pub alternate: Vec<TransportId>,
642    /// Contingency - degraded but functional
643    pub contingency: Vec<TransportId>,
644    /// Emergency - last resort
645    pub emergency: Vec<TransportId>,
646}
647
648impl TransportPolicy {
649    /// Create a new transport policy
650    pub fn new(name: impl Into<String>) -> Self {
651        Self {
652            name: name.into(),
653            ..Default::default()
654        }
655    }
656
657    /// Set primary transports
658    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    /// Set alternate transports
664    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    /// Set contingency transports
670    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    /// Set emergency transports
676    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    /// Get transports in PACE order (primary first, then alternate, etc.)
682    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    /// Get current PACE level based on available transports
691    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    /// Get available transports at or above a minimum PACE level
706    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/// How to use multiple available transports
722#[derive(Debug, Clone, Default)]
723pub enum TransportMode {
724    /// Use single best transport from policy (PACE failover)
725    #[default]
726    Single,
727
728    /// Send on multiple transports simultaneously for reliability
729    /// Receiver deduplicates by message ID
730    Redundant {
731        /// Minimum transports to send on (default: 2)
732        min_paths: u8,
733        /// Maximum transports to send on (None = all available)
734        max_paths: Option<u8>,
735    },
736
737    /// Aggregate bandwidth across transports (for large transfers)
738    /// Splits message across transports, receiver reassembles
739    Bonded,
740
741    /// Distribute messages across transports (round-robin or weighted)
742    LoadBalanced {
743        /// Weight per transport (higher = more traffic)
744        weights: Option<HashMap<TransportId, u8>>,
745    },
746}
747
748impl TransportMode {
749    /// Create redundant mode with minimum paths
750    pub fn redundant(min_paths: u8) -> Self {
751        Self::Redundant {
752            min_paths,
753            max_paths: None,
754        }
755    }
756
757    /// Create redundant mode with min and max paths
758    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    /// Create load balanced mode with weights
766    pub fn load_balanced_weighted(weights: HashMap<TransportId, u8>) -> Self {
767        Self::LoadBalanced {
768            weights: Some(weights),
769        }
770    }
771}
772
773// =============================================================================
774// Transport Trait (Extended)
775// =============================================================================
776
777/// Extended transport trait with capability advertisement
778///
779/// This trait extends `MeshTransport` with capability declaration and
780/// selection support. All pluggable transports should implement this.
781///
782/// # Example Implementation
783///
784/// ```ignore
785/// impl Transport for MyTransport {
786///     fn capabilities(&self) -> &TransportCapabilities {
787///         &self.caps
788///     }
789///
790///     fn is_available(&self) -> bool {
791///         self.hardware.is_ready()
792///     }
793///
794///     fn can_reach(&self, peer_id: &NodeId) -> bool {
795///         self.known_peers.contains(peer_id)
796///     }
797/// }
798/// ```
799#[async_trait]
800pub trait Transport: MeshTransport {
801    /// Get transport capabilities
802    fn capabilities(&self) -> &TransportCapabilities;
803
804    /// Check if transport is currently available/enabled
805    fn is_available(&self) -> bool;
806
807    /// Get current signal quality (0-100, for wireless transports)
808    ///
809    /// Returns `None` for wired/IP transports.
810    fn signal_quality(&self) -> Option<u8> {
811        None
812    }
813
814    /// Estimate if peer is reachable via this transport
815    fn can_reach(&self, peer_id: &NodeId) -> bool;
816
817    /// Get estimated delivery time for message of given size
818    fn estimate_delivery_ms(&self, message_size: usize) -> u32 {
819        self.capabilities().estimate_delivery_ms(message_size)
820    }
821
822    /// Calculate selection score for this transport
823    ///
824    /// Higher scores are better. Used by TransportManager for selection.
825    fn calculate_score(&self, requirements: &MessageRequirements, preference_bonus: i32) -> i32 {
826        let caps = self.capabilities();
827        let mut score = 100i32;
828
829        // Latency bonus for high-priority messages
830        if requirements.priority >= MessagePriority::High {
831            score += 50 - (caps.typical_latency_ms.min(50) as i32);
832        }
833
834        // Power penalty if power-sensitive
835        if requirements.power_sensitive {
836            score -= caps.battery_impact as i32;
837        }
838
839        // Add preference bonus
840        score += preference_bonus;
841
842        // Signal quality bonus for wireless
843        if let Some(quality) = self.signal_quality() {
844            score += (quality / 10) as i32;
845        }
846
847        score
848    }
849}
850
851/// Extended transport trait with range mode configuration
852///
853/// Transports that support dynamic range/bandwidth tradeoffs should
854/// implement this trait.
855#[async_trait]
856pub trait ConfigurableTransport: Transport {
857    /// Get available range modes
858    fn range_modes(&self) -> Option<&RangeModeConfig> {
859        None
860    }
861
862    /// Set range mode (returns new capabilities)
863    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    /// Get recommended mode for target distance
870    fn recommend_mode_for_distance(&self, distance_meters: u32) -> Option<RangeMode> {
871        self.range_modes()?.recommend_for_distance(distance_meters)
872    }
873}
874
875// =============================================================================
876// Tests
877// =============================================================================
878
879#[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); // Unlimited
898    }
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        // SF7 has more bandwidth but less range
915        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); // ~1.1 kbps
935
936        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); // 255 byte limit
953
954        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        // 1MB message at 100 Mbps = ~80ms transfer + 10ms latency
972        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        // Should recommend Standard for short range
1009        assert_eq!(config.recommend_for_distance(50), Some(RangeMode::Standard));
1010
1011        // Should recommend Extended for medium range
1012        assert_eq!(
1013            config.recommend_for_distance(150),
1014            Some(RangeMode::Extended)
1015        );
1016
1017        // Should recommend Maximum for long range
1018        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        // Just ensure these compile and can be debugged
1032        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); // auto-set to false when bypass=true
1065        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    // =========================================================================
1076    // PACE Policy Tests (ADR-032)
1077    // =========================================================================
1078
1079    #[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        // Primary available
1149        let mut available = std::collections::HashSet::new();
1150        available.insert("p1".to_string());
1151        assert_eq!(policy.current_level(&available), PaceLevel::Primary);
1152
1153        // Only alternate available
1154        available.clear();
1155        available.insert("a1".to_string());
1156        assert_eq!(policy.current_level(&available), PaceLevel::Alternate);
1157
1158        // Only contingency available
1159        available.clear();
1160        available.insert("c1".to_string());
1161        assert_eq!(policy.current_level(&available), PaceLevel::Contingency);
1162
1163        // Only emergency available
1164        available.clear();
1165        available.insert("e1".to_string());
1166        assert_eq!(policy.current_level(&available), PaceLevel::Emergency);
1167
1168        // Nothing available
1169        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        // At Primary level - only primary
1182        assert_eq!(policy.at_level(PaceLevel::Primary).len(), 1);
1183
1184        // At Alternate level - primary + alternate
1185        assert_eq!(policy.at_level(PaceLevel::Alternate).len(), 2);
1186
1187        // At Contingency level - primary + alternate + contingency
1188        assert_eq!(policy.at_level(PaceLevel::Contingency).len(), 3);
1189
1190        // At Emergency level - all
1191        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        // Test all known spreading factors
1235        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        // Test default/unknown SF
1244        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        // 1000m exceeds the only mode's 50m range
1363        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        // reliable shouldn't be forced to false when bypass is false
1372        assert!(!req.reliable); // default is false
1373    }
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        // PaceLevel::None should return all
1392        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        // Should just return latency when bandwidth is 0/unlimited
1403        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        // QUIC caps: max_bandwidth_bps=100M (>1000), reliable=true, max_message_size=0 (unlimited)
1416        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}