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