Skip to main content

fips_core/transport/
mod.rs

1//! Transport Layer Abstractions
2//!
3//! Traits and types for FIPS transport drivers. Transports provide the
4//! underlying communication mechanisms (UDP, Ethernet, Tor, etc.) over
5//! which FIPS links are established.
6
7pub mod tcp;
8pub mod tor;
9pub mod udp;
10#[cfg(feature = "webrtc-transport")]
11pub mod webrtc;
12
13#[cfg(feature = "sim-transport")]
14pub mod sim;
15
16#[cfg(any(target_os = "linux", target_os = "macos"))]
17pub mod ethernet;
18
19#[cfg(target_os = "linux")]
20pub mod ble;
21
22#[cfg(feature = "webrtc-transport")]
23use self::webrtc::WebRtcTransport;
24#[cfg(target_os = "linux")]
25use ble::DefaultBleTransport;
26#[cfg(any(target_os = "linux", target_os = "macos"))]
27use ethernet::EthernetTransport;
28use secp256k1::XOnlyPublicKey;
29#[cfg(feature = "sim-transport")]
30use sim::SimTransport;
31use std::fmt;
32use std::net::SocketAddr;
33use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
34use tcp::TcpTransport;
35use thiserror::Error;
36use tor::TorTransport;
37use tor::control::TorMonitoringInfo;
38use udp::UdpTransport;
39
40// ============================================================================
41// Packet Channel Types
42// ============================================================================
43
44/// A packet received from a transport.
45#[derive(Clone, Debug)]
46pub struct ReceivedPacket {
47    /// Which transport received this packet.
48    pub transport_id: TransportId,
49    /// Remote peer address.
50    pub remote_addr: TransportAddr,
51    /// Packet data.
52    pub data: Vec<u8>,
53    /// Receipt timestamp (Unix milliseconds).
54    pub timestamp_ms: u64,
55    /// Monotonic timestamp for optional pipeline queue-wait profiling.
56    #[doc(hidden)]
57    pub trace_enqueued_at: Option<Instant>,
58}
59
60impl ReceivedPacket {
61    /// Create a new received packet with current timestamp.
62    pub fn new(transport_id: TransportId, remote_addr: TransportAddr, data: Vec<u8>) -> Self {
63        let timestamp_ms = SystemTime::now()
64            .duration_since(UNIX_EPOCH)
65            .map(|d| d.as_millis() as u64)
66            .unwrap_or(0);
67        Self {
68            transport_id,
69            remote_addr,
70            data,
71            timestamp_ms,
72            trace_enqueued_at: crate::perf_profile::stamp(),
73        }
74    }
75
76    /// Create a received packet with explicit timestamp.
77    pub fn with_timestamp(
78        transport_id: TransportId,
79        remote_addr: TransportAddr,
80        data: Vec<u8>,
81        timestamp_ms: u64,
82    ) -> Self {
83        Self {
84            transport_id,
85            remote_addr,
86            data,
87            timestamp_ms,
88            trace_enqueued_at: crate::perf_profile::stamp(),
89        }
90    }
91}
92
93/// Channel sender for received packets.
94///
95/// Uses tokio's unbounded mpsc so that per-packet send is a wait-free
96/// linked-list push instead of a semaphore acquisition + `.await`. At
97/// multi-Gbps the bounded variant's per-send cost (semaphore CAS +
98/// waker dance, even on the fast path) is one of the dominant items
99/// on the receive hot path; recvmmsg drains the kernel queue in
100/// 32-packet bursts and we want to dump those into the channel as
101/// fast as possible without each push incurring scheduler bookkeeping.
102///
103/// Backpressure is provided by the kernel UDP receive buffer (the
104/// transport's `recvmmsg` is the only producer for inbound packets);
105/// if the rx_loop falls behind, packets queue up here and the kernel
106/// drops new arrivals once its buffer fills. Memory growth is
107/// effectively bounded because the same rx_loop that consumes this
108/// channel is what runs `process_packet` — if it stalls, recvmmsg
109/// can't run either since they share the runtime.
110pub type PacketTx = tokio::sync::mpsc::UnboundedSender<ReceivedPacket>;
111
112/// Channel receiver for received packets.
113pub type PacketRx = tokio::sync::mpsc::UnboundedReceiver<ReceivedPacket>;
114
115/// Create a packet channel.
116///
117/// The `buffer` argument is kept for API stability with previous
118/// versions of this module (and so call sites don't have to be
119/// touched) but is ignored — the channel is unbounded. See [`PacketTx`]
120/// for the rationale.
121pub fn packet_channel(_buffer: usize) -> (PacketTx, PacketRx) {
122    tokio::sync::mpsc::unbounded_channel()
123}
124
125// ============================================================================
126// Transport Identifiers
127// ============================================================================
128
129/// Unique identifier for a transport instance.
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
131pub struct TransportId(u32);
132
133impl TransportId {
134    /// Create a new transport ID.
135    pub fn new(id: u32) -> Self {
136        Self(id)
137    }
138
139    /// Get the raw ID value.
140    pub fn as_u32(&self) -> u32 {
141        self.0
142    }
143}
144
145impl fmt::Display for TransportId {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(f, "transport:{}", self.0)
148    }
149}
150
151/// Unique identifier for a link instance.
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
153pub struct LinkId(u64);
154
155impl LinkId {
156    /// Create a new link ID.
157    pub fn new(id: u64) -> Self {
158        Self(id)
159    }
160
161    /// Get the raw ID value.
162    pub fn as_u64(&self) -> u64 {
163        self.0
164    }
165}
166
167impl fmt::Display for LinkId {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        write!(f, "link:{}", self.0)
170    }
171}
172
173// ============================================================================
174// Errors
175// ============================================================================
176
177/// Errors related to transport operations.
178#[derive(Debug, Error)]
179pub enum TransportError {
180    #[error("transport not started")]
181    NotStarted,
182
183    #[error("transport already started")]
184    AlreadyStarted,
185
186    #[error("transport failed to start: {0}")]
187    StartFailed(String),
188
189    #[error("transport shutdown failed: {0}")]
190    ShutdownFailed(String),
191
192    #[error("link failed: {0}")]
193    LinkFailed(String),
194
195    #[error("send failed: {0}")]
196    SendFailed(String),
197
198    #[error("receive failed: {0}")]
199    RecvFailed(String),
200
201    #[error("invalid transport address: {0}")]
202    InvalidAddress(String),
203
204    #[error("mtu exceeded: packet {packet_size} > mtu {mtu}")]
205    MtuExceeded { packet_size: usize, mtu: u16 },
206
207    #[error("transport timeout")]
208    Timeout,
209
210    #[error("connection refused")]
211    ConnectionRefused,
212
213    #[error("transport not supported: {0}")]
214    NotSupported(String),
215
216    #[error("io error: {0}")]
217    Io(#[from] std::io::Error),
218}
219
220// ============================================================================
221// Transport Type Metadata
222// ============================================================================
223
224/// Static metadata about a transport type.
225#[derive(Clone, Debug, PartialEq, Eq)]
226pub struct TransportType {
227    /// Human-readable name (e.g., "udp", "ethernet", "tor").
228    pub name: &'static str,
229    /// Whether this transport requires connection establishment.
230    pub connection_oriented: bool,
231    /// Whether the transport guarantees delivery.
232    pub reliable: bool,
233}
234
235impl TransportType {
236    /// UDP/IP transport.
237    pub const UDP: TransportType = TransportType {
238        name: "udp",
239        connection_oriented: false,
240        reliable: false,
241    };
242
243    /// TCP/IP transport.
244    pub const TCP: TransportType = TransportType {
245        name: "tcp",
246        connection_oriented: true,
247        reliable: true,
248    };
249
250    /// Raw Ethernet transport.
251    pub const ETHERNET: TransportType = TransportType {
252        name: "ethernet",
253        connection_oriented: false,
254        reliable: false,
255    };
256
257    /// WiFi (same characteristics as Ethernet).
258    pub const WIFI: TransportType = TransportType {
259        name: "wifi",
260        connection_oriented: false,
261        reliable: false,
262    };
263
264    /// Tor onion transport.
265    pub const TOR: TransportType = TransportType {
266        name: "tor",
267        connection_oriented: true,
268        reliable: true,
269    };
270
271    /// Serial/UART transport.
272    pub const SERIAL: TransportType = TransportType {
273        name: "serial",
274        connection_oriented: false,
275        reliable: true, // typically uses framing with checksums
276    };
277
278    /// BLE L2CAP CoC transport.
279    pub const BLE: TransportType = TransportType {
280        name: "ble",
281        connection_oriented: true,
282        reliable: true, // L2CAP SeqPacket guarantees delivery
283    };
284
285    /// WebRTC DataChannel transport.
286    pub const WEBRTC: TransportType = TransportType {
287        name: "webrtc",
288        connection_oriented: true,
289        reliable: false,
290    };
291
292    /// In-memory simulated packet transport.
293    #[cfg(feature = "sim-transport")]
294    pub const SIM: TransportType = TransportType {
295        name: "sim",
296        connection_oriented: false,
297        reliable: false,
298    };
299
300    /// Check if the transport is connectionless.
301    pub fn is_connectionless(&self) -> bool {
302        !self.connection_oriented
303    }
304}
305
306impl fmt::Display for TransportType {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        write!(f, "{}", self.name)
309    }
310}
311
312// ============================================================================
313// Transport State
314// ============================================================================
315
316/// Transport lifecycle state.
317#[derive(Clone, Copy, Debug, PartialEq, Eq)]
318pub enum TransportState {
319    /// Configured but not started.
320    Configured,
321    /// Initialization in progress.
322    Starting,
323    /// Ready for links.
324    Up,
325    /// Was up, now unavailable.
326    Down,
327    /// Failed to start.
328    Failed,
329}
330
331impl TransportState {
332    /// Check if the transport is operational.
333    pub fn is_operational(&self) -> bool {
334        matches!(self, TransportState::Up)
335    }
336
337    /// Check if the transport can be started.
338    pub fn can_start(&self) -> bool {
339        matches!(
340            self,
341            TransportState::Configured | TransportState::Down | TransportState::Failed
342        )
343    }
344
345    /// Check if the transport is in a terminal state.
346    pub fn is_terminal(&self) -> bool {
347        matches!(self, TransportState::Failed)
348    }
349}
350
351impl fmt::Display for TransportState {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        let s = match self {
354            TransportState::Configured => "configured",
355            TransportState::Starting => "starting",
356            TransportState::Up => "up",
357            TransportState::Down => "down",
358            TransportState::Failed => "failed",
359        };
360        write!(f, "{}", s)
361    }
362}
363
364// ============================================================================
365// Link State
366// ============================================================================
367
368/// Link lifecycle state.
369#[derive(Clone, Copy, Debug, PartialEq, Eq)]
370pub enum LinkState {
371    /// Connection in progress (connection-oriented only).
372    Connecting,
373    /// Ready for traffic.
374    Connected,
375    /// Was connected, now gone.
376    Disconnected,
377    /// Connection attempt failed.
378    Failed,
379}
380
381impl LinkState {
382    /// Check if the link is operational.
383    pub fn is_operational(&self) -> bool {
384        matches!(self, LinkState::Connected)
385    }
386
387    /// Check if the link is in a terminal state.
388    pub fn is_terminal(&self) -> bool {
389        matches!(self, LinkState::Disconnected | LinkState::Failed)
390    }
391}
392
393impl fmt::Display for LinkState {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        let s = match self {
396            LinkState::Connecting => "connecting",
397            LinkState::Connected => "connected",
398            LinkState::Disconnected => "disconnected",
399            LinkState::Failed => "failed",
400        };
401        write!(f, "{}", s)
402    }
403}
404
405/// Direction of link establishment.
406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub enum LinkDirection {
408    /// We initiated the connection.
409    Outbound,
410    /// They initiated the connection.
411    Inbound,
412}
413
414impl fmt::Display for LinkDirection {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        let s = match self {
417            LinkDirection::Outbound => "outbound",
418            LinkDirection::Inbound => "inbound",
419        };
420        write!(f, "{}", s)
421    }
422}
423
424// ============================================================================
425// Transport Address
426// ============================================================================
427
428/// Opaque transport-specific address.
429///
430/// Each transport type interprets this differently:
431/// - UDP/TCP: "host:port" (IP address or DNS hostname)
432/// - Ethernet: MAC address (6 bytes)
433#[derive(Clone, PartialEq, Eq, Hash)]
434pub struct TransportAddr(Vec<u8>);
435
436impl TransportAddr {
437    /// Create a transport address from raw bytes.
438    pub fn new(bytes: Vec<u8>) -> Self {
439        Self(bytes)
440    }
441
442    /// Create a transport address from a byte slice.
443    pub fn from_bytes(bytes: &[u8]) -> Self {
444        Self(bytes.to_vec())
445    }
446
447    /// Create a transport address from a string.
448    pub fn from_string(s: &str) -> Self {
449        Self(s.as_bytes().to_vec())
450    }
451
452    /// Create a transport address from a `SocketAddr` without going
453    /// through `to_string()`.
454    ///
455    /// The standard path is `from_string(&addr.to_string())`, which
456    /// allocates a `String` for the formatted address and then copies
457    /// its bytes into a fresh `Vec<u8>` — two heap allocations per
458    /// inbound packet on the UDP receive hot path. At line rate that's
459    /// a few percent of one core in malloc/free. This variant writes
460    /// the `SocketAddr::Display` representation directly into a
461    /// `Vec<u8>` via `std::io::Write`, halving the alloc count and
462    /// skipping the intermediate `String` materialisation entirely.
463    pub fn from_socket_addr(addr: std::net::SocketAddr) -> Self {
464        use std::io::Write;
465        // Pre-size to fit `[ipv6_lit]:65535` (47 + brackets + colon +
466        // port digits ≈ 56 bytes worst case) so we don't re-grow the
467        // buffer mid-format on common addresses.
468        let mut buf = Vec::with_capacity(56);
469        // The `write!` macro on `&mut Vec<u8>` cannot fail (Vec's
470        // `Write` impl is infallible for in-memory buffers), so the
471        // expect is for shape only.
472        write!(&mut buf, "{addr}").expect("Vec<u8>::write_fmt is infallible");
473        Self(buf)
474    }
475
476    /// Get the raw bytes.
477    pub fn as_bytes(&self) -> &[u8] {
478        &self.0
479    }
480
481    /// Try to interpret as a UTF-8 string.
482    pub fn as_str(&self) -> Option<&str> {
483        std::str::from_utf8(&self.0).ok()
484    }
485
486    /// Get the length in bytes.
487    pub fn len(&self) -> usize {
488        self.0.len()
489    }
490
491    /// Check if empty.
492    pub fn is_empty(&self) -> bool {
493        self.0.is_empty()
494    }
495}
496
497impl fmt::Debug for TransportAddr {
498    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499        match self.as_str() {
500            Some(s) => write!(f, "TransportAddr(\"{}\")", s),
501            None => write!(f, "TransportAddr({:?})", self.0),
502        }
503    }
504}
505
506impl fmt::Display for TransportAddr {
507    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508        // Best-effort display as string if valid UTF-8, else hex
509        match self.as_str() {
510            Some(s) => write!(f, "{}", s),
511            None => {
512                for byte in &self.0 {
513                    write!(f, "{:02x}", byte)?;
514                }
515                Ok(())
516            }
517        }
518    }
519}
520
521impl From<&str> for TransportAddr {
522    fn from(s: &str) -> Self {
523        Self::from_string(s)
524    }
525}
526
527impl From<String> for TransportAddr {
528    fn from(s: String) -> Self {
529        Self(s.into_bytes())
530    }
531}
532
533// ============================================================================
534// Link Statistics
535// ============================================================================
536
537/// Statistics for a link.
538#[derive(Clone, Debug, Default)]
539pub struct LinkStats {
540    /// Total packets sent.
541    pub packets_sent: u64,
542    /// Total packets received.
543    pub packets_recv: u64,
544    /// Total bytes sent.
545    pub bytes_sent: u64,
546    /// Total bytes received.
547    pub bytes_recv: u64,
548    /// Timestamp of last received packet (Unix milliseconds).
549    pub last_recv_ms: u64,
550    /// Estimated round-trip time.
551    rtt_estimate: Option<Duration>,
552    /// Observed packet loss rate (0.0-1.0).
553    pub loss_rate: f32,
554    /// Estimated throughput in bytes/second.
555    pub throughput_estimate: u64,
556}
557
558impl LinkStats {
559    /// Create new link statistics.
560    pub fn new() -> Self {
561        Self::default()
562    }
563
564    /// Record a sent packet.
565    pub fn record_sent(&mut self, bytes: usize) {
566        self.packets_sent += 1;
567        self.bytes_sent += bytes as u64;
568    }
569
570    /// Record a received packet.
571    pub fn record_recv(&mut self, bytes: usize, timestamp_ms: u64) {
572        self.packets_recv += 1;
573        self.bytes_recv += bytes as u64;
574        self.last_recv_ms = timestamp_ms;
575    }
576
577    /// Get the RTT estimate, if available.
578    pub fn rtt_estimate(&self) -> Option<Duration> {
579        self.rtt_estimate
580    }
581
582    /// Update RTT estimate from a probe response.
583    ///
584    /// Uses exponential moving average with alpha=0.2.
585    pub fn update_rtt(&mut self, rtt: Duration) {
586        match self.rtt_estimate {
587            Some(old_rtt) => {
588                let alpha = 0.2;
589                let new_rtt_nanos = (alpha * rtt.as_nanos() as f64
590                    + (1.0 - alpha) * old_rtt.as_nanos() as f64)
591                    as u64;
592                self.rtt_estimate = Some(Duration::from_nanos(new_rtt_nanos));
593            }
594            None => {
595                self.rtt_estimate = Some(rtt);
596            }
597        }
598    }
599
600    /// Time since last receive (for keepalive/timeout).
601    pub fn time_since_recv(&self, current_time_ms: u64) -> u64 {
602        if self.last_recv_ms == 0 {
603            return u64::MAX;
604        }
605        current_time_ms.saturating_sub(self.last_recv_ms)
606    }
607
608    /// Reset all statistics.
609    pub fn reset(&mut self) {
610        *self = Self::default();
611    }
612}
613
614// ============================================================================
615// Link
616// ============================================================================
617
618/// A link to a remote endpoint over a transport.
619#[derive(Clone, Debug)]
620pub struct Link {
621    /// Unique link identifier.
622    link_id: LinkId,
623    /// Which transport this link uses.
624    transport_id: TransportId,
625    /// Transport-specific remote address.
626    remote_addr: TransportAddr,
627    /// Whether we initiated or they initiated.
628    direction: LinkDirection,
629    /// Current link state.
630    state: LinkState,
631    /// Base RTT hint from transport type.
632    base_rtt: Duration,
633    /// Measured statistics.
634    stats: LinkStats,
635    /// When this link was created (Unix milliseconds).
636    created_at: u64,
637}
638
639impl Link {
640    /// Create a new link in Connecting state.
641    pub fn new(
642        link_id: LinkId,
643        transport_id: TransportId,
644        remote_addr: TransportAddr,
645        direction: LinkDirection,
646        base_rtt: Duration,
647    ) -> Self {
648        Self {
649            link_id,
650            transport_id,
651            remote_addr,
652            direction,
653            state: LinkState::Connecting,
654            base_rtt,
655            stats: LinkStats::new(),
656            created_at: 0,
657        }
658    }
659
660    /// Create a link with a creation timestamp.
661    pub fn new_with_timestamp(
662        link_id: LinkId,
663        transport_id: TransportId,
664        remote_addr: TransportAddr,
665        direction: LinkDirection,
666        base_rtt: Duration,
667        created_at: u64,
668    ) -> Self {
669        let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
670        link.created_at = created_at;
671        link
672    }
673
674    /// Create a connectionless link (immediately connected).
675    ///
676    /// For connectionless transports (UDP, Ethernet), links are immediately
677    /// in the Connected state.
678    pub fn connectionless(
679        link_id: LinkId,
680        transport_id: TransportId,
681        remote_addr: TransportAddr,
682        direction: LinkDirection,
683        base_rtt: Duration,
684    ) -> Self {
685        let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
686        link.state = LinkState::Connected;
687        link
688    }
689
690    /// Get the link ID.
691    pub fn link_id(&self) -> LinkId {
692        self.link_id
693    }
694
695    /// Get the transport ID.
696    pub fn transport_id(&self) -> TransportId {
697        self.transport_id
698    }
699
700    /// Get the remote address.
701    pub fn remote_addr(&self) -> &TransportAddr {
702        &self.remote_addr
703    }
704
705    /// Get the link direction.
706    pub fn direction(&self) -> LinkDirection {
707        self.direction
708    }
709
710    /// Get the current state.
711    pub fn state(&self) -> LinkState {
712        self.state
713    }
714
715    /// Get the base RTT hint.
716    pub fn base_rtt(&self) -> Duration {
717        self.base_rtt
718    }
719
720    /// Get the link statistics.
721    pub fn stats(&self) -> &LinkStats {
722        &self.stats
723    }
724
725    /// Get mutable access to link statistics.
726    pub fn stats_mut(&mut self) -> &mut LinkStats {
727        &mut self.stats
728    }
729
730    /// Get the creation timestamp.
731    pub fn created_at(&self) -> u64 {
732        self.created_at
733    }
734
735    /// Set the creation timestamp.
736    pub fn set_created_at(&mut self, timestamp: u64) {
737        self.created_at = timestamp;
738    }
739
740    /// Mark the link as connected.
741    pub fn set_connected(&mut self) {
742        self.state = LinkState::Connected;
743    }
744
745    /// Mark the link as disconnected.
746    pub fn set_disconnected(&mut self) {
747        self.state = LinkState::Disconnected;
748    }
749
750    /// Mark the link as failed.
751    pub fn set_failed(&mut self) {
752        self.state = LinkState::Failed;
753    }
754
755    /// Check if this link is operational.
756    pub fn is_operational(&self) -> bool {
757        self.state.is_operational()
758    }
759
760    /// Check if this link is in a terminal state.
761    pub fn is_terminal(&self) -> bool {
762        self.state.is_terminal()
763    }
764
765    /// Get effective RTT (measured if available, else base hint).
766    pub fn effective_rtt(&self) -> Duration {
767        self.stats.rtt_estimate().unwrap_or(self.base_rtt)
768    }
769
770    /// Age of the link in milliseconds.
771    pub fn age(&self, current_time_ms: u64) -> u64 {
772        if self.created_at == 0 {
773            return 0;
774        }
775        current_time_ms.saturating_sub(self.created_at)
776    }
777}
778
779// ============================================================================
780// Discovered Peer
781// ============================================================================
782
783/// A peer discovered via transport-layer discovery.
784#[derive(Clone, Debug)]
785pub struct DiscoveredPeer {
786    /// Transport that discovered this peer.
787    pub transport_id: TransportId,
788    /// Transport address where the peer was found.
789    pub addr: TransportAddr,
790    /// Optional hint about the peer's identity (if known from discovery).
791    pub pubkey_hint: Option<XOnlyPublicKey>,
792}
793
794impl DiscoveredPeer {
795    /// Create a discovered peer without identity hint.
796    pub fn new(transport_id: TransportId, addr: TransportAddr) -> Self {
797        Self {
798            transport_id,
799            addr,
800            pubkey_hint: None,
801        }
802    }
803
804    /// Create a discovered peer with identity hint.
805    pub fn with_hint(
806        transport_id: TransportId,
807        addr: TransportAddr,
808        pubkey: XOnlyPublicKey,
809    ) -> Self {
810        Self {
811            transport_id,
812            addr,
813            pubkey_hint: Some(pubkey),
814        }
815    }
816}
817
818// ============================================================================
819// Transport Trait
820// ============================================================================
821
822/// Transport trait defining the interface for transport drivers.
823///
824/// This is a simplified synchronous trait. Actual implementations would
825/// be async and use channels for event delivery.
826pub trait Transport {
827    /// Get the transport identifier.
828    fn transport_id(&self) -> TransportId;
829
830    /// Get the transport type metadata.
831    fn transport_type(&self) -> &TransportType;
832
833    /// Get the current state.
834    fn state(&self) -> TransportState;
835
836    /// Get the MTU for this transport.
837    fn mtu(&self) -> u16;
838
839    /// Get the MTU for a specific link.
840    ///
841    /// Returns the MTU negotiated for the given transport address, or
842    /// falls back to the transport-wide default if the address is unknown
843    /// or the transport doesn't support per-link MTU negotiation.
844    fn link_mtu(&self, addr: &TransportAddr) -> u16 {
845        let _ = addr;
846        self.mtu()
847    }
848
849    /// Start the transport.
850    fn start(&mut self) -> Result<(), TransportError>;
851
852    /// Stop the transport.
853    fn stop(&mut self) -> Result<(), TransportError>;
854
855    /// Send data to a transport address.
856    fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<(), TransportError>;
857
858    /// Discover potential peers (if supported).
859    fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError>;
860
861    /// Whether to auto-connect to peers returned by discover().
862    /// Default: false. Concrete transports read from their own config.
863    fn auto_connect(&self) -> bool {
864        false
865    }
866
867    /// Whether to accept inbound handshake initiations on this transport.
868    /// Default: true (preserves UDP's current implicit behavior).
869    fn accept_connections(&self) -> bool {
870        true
871    }
872
873    /// Close a specific connection (connection-oriented transports only).
874    ///
875    /// For connectionless transports (UDP, Ethernet), this is a no-op.
876    /// Connection-oriented transports (TCP, Tor) remove the connection
877    /// from their pool and drop the underlying stream.
878    fn close_connection(&self, _addr: &TransportAddr) {
879        // Default no-op for connectionless transports
880    }
881}
882
883// ============================================================================
884// Connection State (for non-blocking connect)
885// ============================================================================
886
887/// State of a transport-level connection attempt.
888///
889/// Used by connection-oriented transports (TCP, Tor) to report the progress
890/// of a background connection attempt initiated by `connect()`.
891#[derive(Clone, Debug, PartialEq, Eq)]
892pub enum ConnectionState {
893    /// No connection attempt in progress for this address.
894    None,
895    /// Connection attempt is in progress (background task running).
896    Connecting,
897    /// Connection is established and ready for send().
898    Connected,
899    /// Connection attempt failed with the given error message.
900    Failed(String),
901}
902
903// ============================================================================
904// Transport Congestion
905// ============================================================================
906
907/// Transport-local congestion indicators.
908///
909/// All fields are optional — transports report what they can.
910/// Consumers compute deltas from cumulative counters.
911#[derive(Clone, Debug, Default)]
912pub struct TransportCongestion {
913    /// Cumulative packets dropped by kernel/OS before reaching the application.
914    /// Monotonically increasing since transport start.
915    pub recv_drops: Option<u64>,
916}
917
918// ============================================================================
919// Transport Handle
920// ============================================================================
921
922/// Wrapper enum for concrete transport implementations.
923///
924/// This enables polymorphic transport handling without trait objects,
925/// supporting async methods that the sync Transport trait cannot express.
926pub enum TransportHandle {
927    /// UDP/IP transport.
928    Udp(UdpTransport),
929    /// In-memory simulated packet transport.
930    #[cfg(feature = "sim-transport")]
931    Sim(SimTransport),
932    /// Raw Ethernet transport.
933    #[cfg(any(target_os = "linux", target_os = "macos"))]
934    Ethernet(EthernetTransport),
935    /// TCP/IP transport.
936    Tcp(TcpTransport),
937    /// Tor transport (via SOCKS5).
938    Tor(TorTransport),
939    /// WebRTC DataChannel transport.
940    #[cfg(feature = "webrtc-transport")]
941    WebRtc(Box<WebRtcTransport>),
942    /// BLE L2CAP transport.
943    #[cfg(target_os = "linux")]
944    Ble(DefaultBleTransport),
945}
946
947impl TransportHandle {
948    /// Start the transport asynchronously.
949    pub async fn start(&mut self) -> Result<(), TransportError> {
950        match self {
951            TransportHandle::Udp(t) => t.start_async().await,
952            #[cfg(feature = "sim-transport")]
953            TransportHandle::Sim(t) => t.start_async().await,
954            #[cfg(any(target_os = "linux", target_os = "macos"))]
955            TransportHandle::Ethernet(t) => t.start_async().await,
956            TransportHandle::Tcp(t) => t.start_async().await,
957            TransportHandle::Tor(t) => t.start_async().await,
958            #[cfg(feature = "webrtc-transport")]
959            TransportHandle::WebRtc(t) => t.start_async().await,
960            #[cfg(target_os = "linux")]
961            TransportHandle::Ble(t) => t.start_async().await,
962        }
963    }
964
965    /// Stop the transport asynchronously.
966    pub async fn stop(&mut self) -> Result<(), TransportError> {
967        match self {
968            TransportHandle::Udp(t) => t.stop_async().await,
969            #[cfg(feature = "sim-transport")]
970            TransportHandle::Sim(t) => t.stop_async().await,
971            #[cfg(any(target_os = "linux", target_os = "macos"))]
972            TransportHandle::Ethernet(t) => t.stop_async().await,
973            TransportHandle::Tcp(t) => t.stop_async().await,
974            TransportHandle::Tor(t) => t.stop_async().await,
975            #[cfg(feature = "webrtc-transport")]
976            TransportHandle::WebRtc(t) => t.stop_async().await,
977            #[cfg(target_os = "linux")]
978            TransportHandle::Ble(t) => t.stop_async().await,
979        }
980    }
981
982    /// Send data to a remote address asynchronously.
983    pub async fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<usize, TransportError> {
984        match self {
985            TransportHandle::Udp(t) => t.send_async(addr, data).await,
986            #[cfg(feature = "sim-transport")]
987            TransportHandle::Sim(t) => t.send_async(addr, data).await,
988            #[cfg(any(target_os = "linux", target_os = "macos"))]
989            TransportHandle::Ethernet(t) => t.send_async(addr, data).await,
990            TransportHandle::Tcp(t) => t.send_async(addr, data).await,
991            TransportHandle::Tor(t) => t.send_async(addr, data).await,
992            #[cfg(feature = "webrtc-transport")]
993            TransportHandle::WebRtc(t) => t.send_async(addr, data).await,
994            #[cfg(target_os = "linux")]
995            TransportHandle::Ble(t) => t.send_async(addr, data).await,
996        }
997    }
998
999    /// Flush any pending outbound batch buffered by the transport.
1000    /// Called by the rx_loop at end-of-drain so that trailing packets
1001    /// of a burst don't sit in the buffer waiting for the threshold.
1002    /// Only the UDP transport batches today (via `sendmmsg(2)`); other
1003    /// transports treat this as a no-op.
1004    pub async fn flush_pending_send(&self) {
1005        if let TransportHandle::Udp(t) = self {
1006            t.flush_pending_send().await;
1007        }
1008    }
1009
1010    /// Get the transport ID.
1011    pub fn transport_id(&self) -> TransportId {
1012        match self {
1013            TransportHandle::Udp(t) => t.transport_id(),
1014            #[cfg(feature = "sim-transport")]
1015            TransportHandle::Sim(t) => t.transport_id(),
1016            #[cfg(any(target_os = "linux", target_os = "macos"))]
1017            TransportHandle::Ethernet(t) => t.transport_id(),
1018            TransportHandle::Tcp(t) => t.transport_id(),
1019            TransportHandle::Tor(t) => t.transport_id(),
1020            #[cfg(feature = "webrtc-transport")]
1021            TransportHandle::WebRtc(t) => t.transport_id(),
1022            #[cfg(target_os = "linux")]
1023            TransportHandle::Ble(t) => t.transport_id(),
1024        }
1025    }
1026
1027    /// Get the instance name (if configured as a named instance).
1028    pub fn name(&self) -> Option<&str> {
1029        match self {
1030            TransportHandle::Udp(t) => t.name(),
1031            #[cfg(feature = "sim-transport")]
1032            TransportHandle::Sim(t) => t.name(),
1033            #[cfg(any(target_os = "linux", target_os = "macos"))]
1034            TransportHandle::Ethernet(t) => t.name(),
1035            TransportHandle::Tcp(t) => t.name(),
1036            TransportHandle::Tor(t) => t.name(),
1037            #[cfg(feature = "webrtc-transport")]
1038            TransportHandle::WebRtc(t) => t.name(),
1039            #[cfg(target_os = "linux")]
1040            TransportHandle::Ble(t) => t.name(),
1041        }
1042    }
1043
1044    /// Get the transport type metadata.
1045    pub fn transport_type(&self) -> &TransportType {
1046        match self {
1047            TransportHandle::Udp(t) => t.transport_type(),
1048            #[cfg(feature = "sim-transport")]
1049            TransportHandle::Sim(t) => t.transport_type(),
1050            #[cfg(any(target_os = "linux", target_os = "macos"))]
1051            TransportHandle::Ethernet(t) => t.transport_type(),
1052            TransportHandle::Tcp(t) => t.transport_type(),
1053            TransportHandle::Tor(t) => t.transport_type(),
1054            #[cfg(feature = "webrtc-transport")]
1055            TransportHandle::WebRtc(t) => t.transport_type(),
1056            #[cfg(target_os = "linux")]
1057            TransportHandle::Ble(t) => t.transport_type(),
1058        }
1059    }
1060
1061    /// Get current transport state.
1062    pub fn state(&self) -> TransportState {
1063        match self {
1064            TransportHandle::Udp(t) => t.state(),
1065            #[cfg(feature = "sim-transport")]
1066            TransportHandle::Sim(t) => t.state(),
1067            #[cfg(any(target_os = "linux", target_os = "macos"))]
1068            TransportHandle::Ethernet(t) => t.state(),
1069            TransportHandle::Tcp(t) => t.state(),
1070            TransportHandle::Tor(t) => t.state(),
1071            #[cfg(feature = "webrtc-transport")]
1072            TransportHandle::WebRtc(t) => t.state(),
1073            #[cfg(target_os = "linux")]
1074            TransportHandle::Ble(t) => t.state(),
1075        }
1076    }
1077
1078    /// Get the transport MTU.
1079    pub fn mtu(&self) -> u16 {
1080        match self {
1081            TransportHandle::Udp(t) => t.mtu(),
1082            #[cfg(feature = "sim-transport")]
1083            TransportHandle::Sim(t) => t.mtu(),
1084            #[cfg(any(target_os = "linux", target_os = "macos"))]
1085            TransportHandle::Ethernet(t) => t.mtu(),
1086            TransportHandle::Tcp(t) => t.mtu(),
1087            TransportHandle::Tor(t) => t.mtu(),
1088            #[cfg(feature = "webrtc-transport")]
1089            TransportHandle::WebRtc(t) => t.mtu(),
1090            #[cfg(target_os = "linux")]
1091            TransportHandle::Ble(t) => t.mtu(),
1092        }
1093    }
1094
1095    /// Get the MTU for a specific link address.
1096    ///
1097    /// Falls back to transport-wide MTU if the transport doesn't
1098    /// support per-link MTU or the address is unknown.
1099    pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
1100        match self {
1101            TransportHandle::Udp(t) => t.link_mtu(addr),
1102            #[cfg(feature = "sim-transport")]
1103            TransportHandle::Sim(t) => t.link_mtu(addr),
1104            #[cfg(any(target_os = "linux", target_os = "macos"))]
1105            TransportHandle::Ethernet(t) => t.link_mtu(addr),
1106            TransportHandle::Tcp(t) => t.link_mtu(addr),
1107            TransportHandle::Tor(t) => t.link_mtu(addr),
1108            #[cfg(feature = "webrtc-transport")]
1109            TransportHandle::WebRtc(t) => t.link_mtu(addr),
1110            #[cfg(target_os = "linux")]
1111            TransportHandle::Ble(t) => t.link_mtu(addr),
1112        }
1113    }
1114
1115    /// Get the local bound address (UDP/TCP only, returns None for other transports).
1116    pub fn local_addr(&self) -> Option<std::net::SocketAddr> {
1117        match self {
1118            TransportHandle::Udp(t) => t.local_addr(),
1119            #[cfg(feature = "sim-transport")]
1120            TransportHandle::Sim(_) => None,
1121            #[cfg(any(target_os = "linux", target_os = "macos"))]
1122            TransportHandle::Ethernet(_) => None,
1123            TransportHandle::Tcp(t) => t.local_addr(),
1124            TransportHandle::Tor(_) => None,
1125            #[cfg(feature = "webrtc-transport")]
1126            TransportHandle::WebRtc(_) => None,
1127            #[cfg(target_os = "linux")]
1128            TransportHandle::Ble(_) => None,
1129        }
1130    }
1131
1132    /// Get the interface name (Ethernet only, returns None for other transports).
1133    pub fn interface_name(&self) -> Option<&str> {
1134        match self {
1135            TransportHandle::Udp(_) => None,
1136            #[cfg(feature = "sim-transport")]
1137            TransportHandle::Sim(_) => None,
1138            #[cfg(any(target_os = "linux", target_os = "macos"))]
1139            TransportHandle::Ethernet(t) => Some(t.interface_name()),
1140            TransportHandle::Tcp(_) => None,
1141            TransportHandle::Tor(_) => None,
1142            #[cfg(feature = "webrtc-transport")]
1143            TransportHandle::WebRtc(_) => None,
1144            #[cfg(target_os = "linux")]
1145            TransportHandle::Ble(_) => None,
1146        }
1147    }
1148
1149    /// Get the onion service address (Tor only, returns None for other transports).
1150    pub fn onion_address(&self) -> Option<&str> {
1151        match self {
1152            TransportHandle::Tor(t) => t.onion_address(),
1153            _ => None,
1154        }
1155    }
1156
1157    /// Get cached Tor daemon monitoring info (Tor only).
1158    pub fn tor_monitoring(&self) -> Option<TorMonitoringInfo> {
1159        match self {
1160            TransportHandle::Tor(t) => t.cached_monitoring(),
1161            _ => None,
1162        }
1163    }
1164
1165    /// Get the Tor transport mode (Tor only).
1166    pub fn tor_mode(&self) -> Option<&str> {
1167        match self {
1168            TransportHandle::Tor(t) => Some(t.mode()),
1169            _ => None,
1170        }
1171    }
1172
1173    /// Drain discovered peers from this transport.
1174    pub fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1175        match self {
1176            TransportHandle::Udp(t) => t.discover(),
1177            #[cfg(feature = "sim-transport")]
1178            TransportHandle::Sim(t) => t.discover(),
1179            #[cfg(any(target_os = "linux", target_os = "macos"))]
1180            TransportHandle::Ethernet(t) => t.discover(),
1181            TransportHandle::Tcp(t) => t.discover(),
1182            TransportHandle::Tor(t) => t.discover(),
1183            #[cfg(feature = "webrtc-transport")]
1184            TransportHandle::WebRtc(t) => t.discover(),
1185            #[cfg(target_os = "linux")]
1186            TransportHandle::Ble(t) => t.discover(),
1187        }
1188    }
1189
1190    /// Whether this transport auto-connects to discovered peers.
1191    pub fn auto_connect(&self) -> bool {
1192        match self {
1193            TransportHandle::Udp(t) => t.auto_connect(),
1194            #[cfg(feature = "sim-transport")]
1195            TransportHandle::Sim(t) => t.auto_connect(),
1196            #[cfg(any(target_os = "linux", target_os = "macos"))]
1197            TransportHandle::Ethernet(t) => t.auto_connect(),
1198            TransportHandle::Tcp(t) => t.auto_connect(),
1199            TransportHandle::Tor(t) => t.auto_connect(),
1200            #[cfg(feature = "webrtc-transport")]
1201            TransportHandle::WebRtc(t) => t.auto_connect(),
1202            #[cfg(target_os = "linux")]
1203            TransportHandle::Ble(t) => t.auto_connect(),
1204        }
1205    }
1206
1207    /// Whether this transport accepts inbound connections.
1208    pub fn accept_connections(&self) -> bool {
1209        match self {
1210            TransportHandle::Udp(t) => t.accept_connections(),
1211            #[cfg(feature = "sim-transport")]
1212            TransportHandle::Sim(t) => t.accept_connections(),
1213            #[cfg(any(target_os = "linux", target_os = "macos"))]
1214            TransportHandle::Ethernet(t) => t.accept_connections(),
1215            TransportHandle::Tcp(t) => t.accept_connections(),
1216            TransportHandle::Tor(t) => t.accept_connections(),
1217            #[cfg(feature = "webrtc-transport")]
1218            TransportHandle::WebRtc(t) => t.accept_connections(),
1219            #[cfg(target_os = "linux")]
1220            TransportHandle::Ble(t) => t.accept_connections(),
1221        }
1222    }
1223
1224    /// Initiate a non-blocking connection to a remote address.
1225    ///
1226    /// For connection-oriented transports (TCP, Tor), spawns a background
1227    /// task to establish the connection. For connectionless transports
1228    /// (UDP, Ethernet), this is a no-op that returns Ok immediately.
1229    ///
1230    /// Poll `connection_state()` to check when the connection is ready.
1231    pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> {
1232        match self {
1233            TransportHandle::Udp(_) => Ok(()), // connectionless
1234            #[cfg(feature = "sim-transport")]
1235            TransportHandle::Sim(_) => Ok(()), // connectionless
1236            #[cfg(any(target_os = "linux", target_os = "macos"))]
1237            TransportHandle::Ethernet(_) => Ok(()), // connectionless
1238            TransportHandle::Tcp(t) => t.connect_async(addr).await,
1239            TransportHandle::Tor(t) => t.connect_async(addr).await,
1240            #[cfg(feature = "webrtc-transport")]
1241            TransportHandle::WebRtc(t) => t.connect_async(addr).await,
1242            #[cfg(target_os = "linux")]
1243            TransportHandle::Ble(t) => t.connect_async(addr).await,
1244        }
1245    }
1246
1247    /// Query the state of a connection attempt to a remote address.
1248    ///
1249    /// For connectionless transports, always returns `ConnectionState::Connected`
1250    /// (they are always "connected"). For connection-oriented transports, returns
1251    /// the current state of the background connection attempt.
1252    pub fn connection_state(&self, addr: &TransportAddr) -> ConnectionState {
1253        match self {
1254            TransportHandle::Udp(_) => ConnectionState::Connected,
1255            #[cfg(feature = "sim-transport")]
1256            TransportHandle::Sim(_) => ConnectionState::Connected,
1257            #[cfg(any(target_os = "linux", target_os = "macos"))]
1258            TransportHandle::Ethernet(_) => ConnectionState::Connected,
1259            TransportHandle::Tcp(t) => t.connection_state_sync(addr),
1260            TransportHandle::Tor(t) => t.connection_state_sync(addr),
1261            #[cfg(feature = "webrtc-transport")]
1262            TransportHandle::WebRtc(t) => t.connection_state_sync(addr),
1263            #[cfg(target_os = "linux")]
1264            TransportHandle::Ble(t) => t.connection_state_sync(addr),
1265        }
1266    }
1267
1268    /// Close a specific connection on this transport.
1269    ///
1270    /// No-op for connectionless transports. For TCP/Tor, removes the
1271    /// connection from the pool and drops the stream.
1272    pub async fn close_connection(&self, addr: &TransportAddr) {
1273        match self {
1274            TransportHandle::Udp(t) => t.close_connection(addr),
1275            #[cfg(feature = "sim-transport")]
1276            TransportHandle::Sim(t) => t.close_connection(addr),
1277            #[cfg(any(target_os = "linux", target_os = "macos"))]
1278            TransportHandle::Ethernet(t) => t.close_connection(addr),
1279            TransportHandle::Tcp(t) => t.close_connection_async(addr).await,
1280            TransportHandle::Tor(t) => t.close_connection_async(addr).await,
1281            #[cfg(feature = "webrtc-transport")]
1282            TransportHandle::WebRtc(t) => t.close_connection_async(addr).await,
1283            #[cfg(target_os = "linux")]
1284            TransportHandle::Ble(t) => t.close_connection_async(addr).await,
1285        }
1286    }
1287
1288    /// Check if transport is operational.
1289    pub fn is_operational(&self) -> bool {
1290        self.state().is_operational()
1291    }
1292
1293    /// Query transport-local congestion indicators.
1294    ///
1295    /// Returns a snapshot of congestion signals that the transport can
1296    /// observe locally (e.g., kernel receive buffer drops). Fields are
1297    /// `None` when the transport doesn't support that signal.
1298    pub fn congestion(&self) -> TransportCongestion {
1299        match self {
1300            TransportHandle::Udp(t) => t.congestion(),
1301            #[cfg(feature = "sim-transport")]
1302            TransportHandle::Sim(_) => TransportCongestion::default(),
1303            #[cfg(any(target_os = "linux", target_os = "macos"))]
1304            TransportHandle::Ethernet(_) => TransportCongestion::default(),
1305            TransportHandle::Tcp(_) => TransportCongestion::default(),
1306            TransportHandle::Tor(_) => TransportCongestion::default(),
1307            #[cfg(feature = "webrtc-transport")]
1308            TransportHandle::WebRtc(_) => TransportCongestion::default(),
1309            #[cfg(target_os = "linux")]
1310            TransportHandle::Ble(_) => TransportCongestion::default(),
1311        }
1312    }
1313
1314    /// Get transport-specific stats as a JSON value.
1315    ///
1316    /// Returns a snapshot of counters for the specific transport type.
1317    pub fn transport_stats(&self) -> serde_json::Value {
1318        match self {
1319            TransportHandle::Udp(t) => {
1320                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1321            }
1322            #[cfg(feature = "sim-transport")]
1323            TransportHandle::Sim(t) => serde_json::to_value(t.stats()).unwrap_or_default(),
1324            #[cfg(any(target_os = "linux", target_os = "macos"))]
1325            TransportHandle::Ethernet(t) => {
1326                let snap = t.stats().snapshot();
1327                serde_json::json!({
1328                    "frames_sent": snap.frames_sent,
1329                    "frames_recv": snap.frames_recv,
1330                    "bytes_sent": snap.bytes_sent,
1331                    "bytes_recv": snap.bytes_recv,
1332                    "send_errors": snap.send_errors,
1333                    "recv_errors": snap.recv_errors,
1334                    "beacons_sent": snap.beacons_sent,
1335                    "beacons_recv": snap.beacons_recv,
1336                    "frames_too_short": snap.frames_too_short,
1337                    "frames_too_long": snap.frames_too_long,
1338                })
1339            }
1340            TransportHandle::Tcp(t) => {
1341                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1342            }
1343            TransportHandle::Tor(t) => {
1344                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1345            }
1346            #[cfg(feature = "webrtc-transport")]
1347            TransportHandle::WebRtc(t) => serde_json::json!({
1348                "mtu": t.mtu(),
1349                "state": t.state().to_string(),
1350            }),
1351            #[cfg(target_os = "linux")]
1352            TransportHandle::Ble(t) => {
1353                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1354            }
1355        }
1356    }
1357}
1358
1359// ============================================================================
1360// DNS Resolution
1361// ============================================================================
1362
1363/// Resolve a TransportAddr to a SocketAddr.
1364///
1365/// Fast path: if the address parses as a numeric IP:port, returns
1366/// immediately with no DNS lookup. Otherwise, treats the address as
1367/// `hostname:port` and performs async DNS resolution via the system
1368/// resolver.
1369pub(crate) async fn resolve_socket_addr(
1370    addr: &TransportAddr,
1371) -> Result<SocketAddr, TransportError> {
1372    let s = addr
1373        .as_str()
1374        .ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?;
1375
1376    // Fast path: numeric IP address — no DNS lookup
1377    if let Ok(sock_addr) = s.parse::<SocketAddr>() {
1378        return Ok(sock_addr);
1379    }
1380
1381    // Slow path: DNS resolution
1382    tokio::net::lookup_host(s)
1383        .await
1384        .map_err(|e| {
1385            TransportError::InvalidAddress(format!("DNS resolution failed for {}: {}", s, e))
1386        })?
1387        .next()
1388        .ok_or_else(|| {
1389            TransportError::InvalidAddress(format!(
1390                "DNS resolution returned no addresses for {}",
1391                s
1392            ))
1393        })
1394}
1395
1396// ============================================================================
1397// Tests
1398// ============================================================================
1399
1400#[cfg(test)]
1401mod tests {
1402    use super::*;
1403
1404    #[test]
1405    fn test_transport_id() {
1406        let id = TransportId::new(42);
1407        assert_eq!(id.as_u32(), 42);
1408        assert_eq!(format!("{}", id), "transport:42");
1409    }
1410
1411    #[test]
1412    fn test_link_id() {
1413        let id = LinkId::new(12345);
1414        assert_eq!(id.as_u64(), 12345);
1415        assert_eq!(format!("{}", id), "link:12345");
1416    }
1417
1418    #[test]
1419    fn test_transport_state_transitions() {
1420        assert!(TransportState::Configured.can_start());
1421        assert!(TransportState::Down.can_start());
1422        assert!(TransportState::Failed.can_start());
1423        assert!(!TransportState::Starting.can_start());
1424        assert!(!TransportState::Up.can_start());
1425
1426        assert!(TransportState::Up.is_operational());
1427        assert!(!TransportState::Starting.is_operational());
1428        assert!(!TransportState::Failed.is_operational());
1429    }
1430
1431    #[test]
1432    fn test_link_state() {
1433        assert!(LinkState::Connected.is_operational());
1434        assert!(!LinkState::Connecting.is_operational());
1435        assert!(!LinkState::Disconnected.is_operational());
1436        assert!(!LinkState::Failed.is_operational());
1437
1438        assert!(LinkState::Disconnected.is_terminal());
1439        assert!(LinkState::Failed.is_terminal());
1440        assert!(!LinkState::Connected.is_terminal());
1441    }
1442
1443    #[test]
1444    #[allow(clippy::assertions_on_constants)]
1445    fn test_transport_type_constants() {
1446        // These assertions verify the constant definitions are correct
1447        assert!(!TransportType::UDP.connection_oriented);
1448        assert!(!TransportType::UDP.reliable);
1449        assert!(TransportType::UDP.is_connectionless());
1450
1451        assert!(TransportType::TOR.connection_oriented);
1452        assert!(TransportType::TOR.reliable);
1453        assert!(!TransportType::TOR.is_connectionless());
1454
1455        assert_eq!(TransportType::UDP.name, "udp");
1456        assert_eq!(TransportType::ETHERNET.name, "ethernet");
1457    }
1458
1459    #[test]
1460    fn test_transport_addr_string() {
1461        let addr = TransportAddr::from_string("192.168.1.1:2121");
1462        assert_eq!(format!("{}", addr), "192.168.1.1:2121");
1463        assert_eq!(addr.as_str(), Some("192.168.1.1:2121"));
1464    }
1465
1466    #[test]
1467    fn test_transport_addr_binary() {
1468        // Binary address with invalid UTF-8 bytes (0xff, 0x80 are invalid UTF-8)
1469        let binary = TransportAddr::new(vec![0xff, 0x80, 0x2b, 0x3c, 0x4d, 0x5e]);
1470        assert_eq!(format!("{}", binary), "ff802b3c4d5e");
1471        assert!(binary.as_str().is_none());
1472        assert_eq!(binary.len(), 6);
1473    }
1474
1475    #[test]
1476    fn test_transport_addr_from_string() {
1477        let addr: TransportAddr = "test:1234".into();
1478        assert_eq!(addr.as_str(), Some("test:1234"));
1479
1480        let addr2: TransportAddr = String::from("hello").into();
1481        assert_eq!(addr2.as_str(), Some("hello"));
1482    }
1483
1484    #[test]
1485    fn test_link_stats_basic() {
1486        let mut stats = LinkStats::new();
1487
1488        stats.record_sent(100);
1489        stats.record_recv(200, 1000);
1490
1491        assert_eq!(stats.packets_sent, 1);
1492        assert_eq!(stats.bytes_sent, 100);
1493        assert_eq!(stats.packets_recv, 1);
1494        assert_eq!(stats.bytes_recv, 200);
1495        assert_eq!(stats.last_recv_ms, 1000);
1496    }
1497
1498    #[test]
1499    fn test_link_stats_rtt() {
1500        let mut stats = LinkStats::new();
1501
1502        assert!(stats.rtt_estimate().is_none());
1503
1504        stats.update_rtt(Duration::from_millis(100));
1505        assert_eq!(stats.rtt_estimate(), Some(Duration::from_millis(100)));
1506
1507        // Second update uses EMA
1508        stats.update_rtt(Duration::from_millis(200));
1509        // EMA: 0.2 * 200 + 0.8 * 100 = 120ms
1510        let rtt = stats.rtt_estimate().unwrap();
1511        assert!(rtt.as_millis() >= 110 && rtt.as_millis() <= 130);
1512    }
1513
1514    #[test]
1515    fn test_link_stats_time_since_recv() {
1516        let mut stats = LinkStats::new();
1517
1518        // No receive yet
1519        assert_eq!(stats.time_since_recv(1000), u64::MAX);
1520
1521        stats.record_recv(100, 500);
1522        assert_eq!(stats.time_since_recv(1000), 500);
1523        assert_eq!(stats.time_since_recv(500), 0);
1524    }
1525
1526    #[test]
1527    fn test_link_creation() {
1528        let link = Link::new(
1529            LinkId::new(1),
1530            TransportId::new(1),
1531            TransportAddr::from_string("test"),
1532            LinkDirection::Outbound,
1533            Duration::from_millis(50),
1534        );
1535
1536        assert_eq!(link.state(), LinkState::Connecting);
1537        assert!(!link.is_operational());
1538        assert_eq!(link.direction(), LinkDirection::Outbound);
1539    }
1540
1541    #[test]
1542    fn test_link_connectionless() {
1543        let link = Link::connectionless(
1544            LinkId::new(1),
1545            TransportId::new(1),
1546            TransportAddr::from_string("test"),
1547            LinkDirection::Inbound,
1548            Duration::from_millis(5),
1549        );
1550
1551        assert_eq!(link.state(), LinkState::Connected);
1552        assert!(link.is_operational());
1553    }
1554
1555    #[test]
1556    fn test_link_state_changes() {
1557        let mut link = Link::new(
1558            LinkId::new(1),
1559            TransportId::new(1),
1560            TransportAddr::from_string("test"),
1561            LinkDirection::Outbound,
1562            Duration::from_millis(50),
1563        );
1564
1565        assert!(!link.is_operational());
1566
1567        link.set_connected();
1568        assert!(link.is_operational());
1569        assert!(!link.is_terminal());
1570
1571        link.set_disconnected();
1572        assert!(!link.is_operational());
1573        assert!(link.is_terminal());
1574    }
1575
1576    #[test]
1577    fn test_link_effective_rtt() {
1578        let mut link = Link::connectionless(
1579            LinkId::new(1),
1580            TransportId::new(1),
1581            TransportAddr::from_string("test"),
1582            LinkDirection::Inbound,
1583            Duration::from_millis(50),
1584        );
1585
1586        // Before measurement, uses base RTT
1587        assert_eq!(link.effective_rtt(), Duration::from_millis(50));
1588
1589        // After measurement, uses measured RTT
1590        link.stats_mut().update_rtt(Duration::from_millis(100));
1591        assert_eq!(link.effective_rtt(), Duration::from_millis(100));
1592    }
1593
1594    #[test]
1595    fn test_link_age() {
1596        let mut link = Link::new(
1597            LinkId::new(1),
1598            TransportId::new(1),
1599            TransportAddr::from_string("test"),
1600            LinkDirection::Outbound,
1601            Duration::from_millis(50),
1602        );
1603
1604        // No timestamp set
1605        assert_eq!(link.age(1000), 0);
1606
1607        link.set_created_at(500);
1608        assert_eq!(link.age(1000), 500);
1609        assert_eq!(link.age(500), 0);
1610    }
1611
1612    #[test]
1613    fn test_discovered_peer() {
1614        let peer = DiscoveredPeer::new(
1615            TransportId::new(1),
1616            TransportAddr::from_string("192.168.1.1:2121"),
1617        );
1618
1619        assert_eq!(peer.transport_id, TransportId::new(1));
1620        assert!(peer.pubkey_hint.is_none());
1621    }
1622
1623    #[test]
1624    fn test_link_direction_display() {
1625        assert_eq!(format!("{}", LinkDirection::Outbound), "outbound");
1626        assert_eq!(format!("{}", LinkDirection::Inbound), "inbound");
1627    }
1628
1629    #[test]
1630    fn test_transport_state_display() {
1631        assert_eq!(format!("{}", TransportState::Up), "up");
1632        assert_eq!(format!("{}", TransportState::Failed), "failed");
1633    }
1634
1635    #[test]
1636    fn test_received_packet() {
1637        let packet = ReceivedPacket::new(
1638            TransportId::new(1),
1639            TransportAddr::from_string("192.168.1.1:2121"),
1640            vec![1, 2, 3, 4],
1641        );
1642
1643        assert_eq!(packet.transport_id, TransportId::new(1));
1644        assert_eq!(packet.data, vec![1, 2, 3, 4]);
1645        assert!(packet.timestamp_ms > 0);
1646    }
1647
1648    #[test]
1649    fn test_received_packet_with_timestamp() {
1650        let packet = ReceivedPacket::with_timestamp(
1651            TransportId::new(1),
1652            TransportAddr::from_string("test"),
1653            vec![5, 6],
1654            12345,
1655        );
1656
1657        assert_eq!(packet.timestamp_ms, 12345);
1658    }
1659
1660    #[tokio::test]
1661    async fn test_packet_channel() {
1662        let (tx, mut rx) = packet_channel(10);
1663
1664        let packet = ReceivedPacket::new(
1665            TransportId::new(1),
1666            TransportAddr::from_string("test"),
1667            vec![1, 2, 3],
1668        );
1669
1670        tx.send(packet.clone()).unwrap();
1671
1672        let received = rx.recv().await.unwrap();
1673        assert_eq!(received.data, vec![1, 2, 3]);
1674    }
1675
1676    // ========================================================================
1677    // link_mtu tests
1678    // ========================================================================
1679
1680    /// Minimal mock transport for testing the default link_mtu() behavior.
1681    struct MockTransport {
1682        id: TransportId,
1683        mtu_value: u16,
1684    }
1685
1686    impl MockTransport {
1687        fn new(mtu: u16) -> Self {
1688            Self {
1689                id: TransportId::new(99),
1690                mtu_value: mtu,
1691            }
1692        }
1693    }
1694
1695    impl Transport for MockTransport {
1696        fn transport_id(&self) -> TransportId {
1697            self.id
1698        }
1699        fn transport_type(&self) -> &TransportType {
1700            &TransportType::UDP
1701        }
1702        fn state(&self) -> TransportState {
1703            TransportState::Up
1704        }
1705        fn mtu(&self) -> u16 {
1706            self.mtu_value
1707        }
1708        fn start(&mut self) -> Result<(), TransportError> {
1709            Ok(())
1710        }
1711        fn stop(&mut self) -> Result<(), TransportError> {
1712            Ok(())
1713        }
1714        fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
1715            Ok(())
1716        }
1717        fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1718            Ok(vec![])
1719        }
1720    }
1721
1722    /// Mock transport that overrides link_mtu() to return per-link values.
1723    struct PerLinkMtuTransport {
1724        id: TransportId,
1725        default_mtu: u16,
1726        /// Address-specific MTU overrides.
1727        overrides: Vec<(TransportAddr, u16)>,
1728    }
1729
1730    impl PerLinkMtuTransport {
1731        fn new(default_mtu: u16, overrides: Vec<(TransportAddr, u16)>) -> Self {
1732            Self {
1733                id: TransportId::new(100),
1734                default_mtu,
1735                overrides,
1736            }
1737        }
1738    }
1739
1740    impl Transport for PerLinkMtuTransport {
1741        fn transport_id(&self) -> TransportId {
1742            self.id
1743        }
1744        fn transport_type(&self) -> &TransportType {
1745            &TransportType::UDP
1746        }
1747        fn state(&self) -> TransportState {
1748            TransportState::Up
1749        }
1750        fn mtu(&self) -> u16 {
1751            self.default_mtu
1752        }
1753        fn link_mtu(&self, addr: &TransportAddr) -> u16 {
1754            for (a, mtu) in &self.overrides {
1755                if a == addr {
1756                    return *mtu;
1757                }
1758            }
1759            self.mtu()
1760        }
1761        fn start(&mut self) -> Result<(), TransportError> {
1762            Ok(())
1763        }
1764        fn stop(&mut self) -> Result<(), TransportError> {
1765            Ok(())
1766        }
1767        fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
1768            Ok(())
1769        }
1770        fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1771            Ok(vec![])
1772        }
1773    }
1774
1775    #[test]
1776    fn test_link_mtu_default_falls_back_to_mtu() {
1777        let transport = MockTransport::new(1280);
1778        let addr = TransportAddr::from_string("192.168.1.1:2121");
1779
1780        // Default link_mtu() should return the transport-wide mtu()
1781        assert_eq!(transport.link_mtu(&addr), 1280);
1782        assert_eq!(transport.link_mtu(&addr), transport.mtu());
1783
1784        // Any address should return the same value
1785        let other_addr = TransportAddr::from_string("10.0.0.1:5000");
1786        assert_eq!(transport.link_mtu(&other_addr), 1280);
1787    }
1788
1789    #[test]
1790    fn test_link_mtu_per_link_override() {
1791        let addr_a = TransportAddr::from_string("192.168.1.1:2121");
1792        let addr_b = TransportAddr::from_string("10.0.0.1:5000");
1793        let addr_unknown = TransportAddr::from_string("172.16.0.1:6000");
1794
1795        let transport =
1796            PerLinkMtuTransport::new(1280, vec![(addr_a.clone(), 512), (addr_b.clone(), 247)]);
1797
1798        // Known addresses return their per-link MTU
1799        assert_eq!(transport.link_mtu(&addr_a), 512);
1800        assert_eq!(transport.link_mtu(&addr_b), 247);
1801
1802        // Unknown address falls back to transport-wide default
1803        assert_eq!(transport.link_mtu(&addr_unknown), 1280);
1804        assert_eq!(transport.mtu(), 1280);
1805    }
1806
1807    #[test]
1808    fn test_transport_handle_link_mtu_delegation() {
1809        use crate::config::UdpConfig;
1810        use crate::transport::udp::UdpTransport;
1811
1812        let config = UdpConfig::default();
1813        let expected_mtu = config.mtu();
1814        let (tx, _rx) = packet_channel(1);
1815        let transport = UdpTransport::new(TransportId::new(1), None, config, tx);
1816        let handle = TransportHandle::Udp(transport);
1817
1818        let addr = TransportAddr::from_string("192.168.1.1:2121");
1819
1820        // TransportHandle::link_mtu() should delegate and return the same
1821        // as TransportHandle::mtu() for UDP (no per-link overrides)
1822        assert_eq!(handle.link_mtu(&addr), expected_mtu);
1823        assert_eq!(handle.link_mtu(&addr), handle.mtu());
1824    }
1825}