Skip to main content

ant_quic/
connection_router.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Connection Router for Protocol Engine Selection
9//!
10//! This module provides automatic routing of connections through either the
11//! QUIC engine (for broadband transports) or the Constrained engine (for
12//! BLE/LoRa/Serial transports) based on transport capabilities.
13//!
14//! # Architecture
15//!
16//! ```text
17//! ┌─────────────────────────────────────────────────────────┐
18//! │                    Application                          │
19//! ├─────────────────────────────────────────────────────────┤
20//! │                  ConnectionRouter                       │
21//! │  - Capability-based engine selection                    │
22//! │  - Unified API for both engines                         │
23//! ├──────────────────────┬──────────────────────────────────┤
24//! │    QUIC Engine       │     Constrained Engine           │
25//! │  (NatTraversalEnd.)  │   (ConstrainedTransport)         │
26//! ├──────────────────────┼──────────────────────────────────┤
27//! │    UDP Transport     │   BLE/LoRa/Serial Transport      │
28//! └──────────────────────┴──────────────────────────────────┘
29//! ```
30//!
31//! # Engine Selection
32//!
33//! The router selects the protocol engine based on [`TransportCapabilities`]:
34//!
35//! | Transport | MTU | Bandwidth | Engine |
36//! |-----------|-----|-----------|--------|
37//! | UDP | 1500 | High | QUIC |
38//! | BLE | 244 | Low | Constrained |
39//! | LoRa | 250 | Very Low | Constrained |
40//! | Serial | 1024 | Medium | Constrained |
41//!
42//! # Example
43//!
44//! ```rust,ignore
45//! use ant_quic::connection_router::{ConnectionRouter, RouterConfig};
46//! use ant_quic::transport::TransportAddr;
47//!
48//! // Create router with default config
49//! let router = ConnectionRouter::new(RouterConfig::default());
50//!
51//! // Connect to a peer - engine selected automatically
52//! let ble_addr = TransportAddr::Ble {
53//!     device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
54//!     service_uuid: None,
55//! };
56//!
57//! // This will use the Constrained engine
58//! let conn = router.connect(&ble_addr).await?;
59//!
60//! // Send data through the routed connection
61//! conn.send(b"Hello!").await?;
62//! ```
63
64use std::fmt;
65use std::net::SocketAddr;
66use std::sync::Arc;
67
68use crate::constrained::{
69    AdapterEvent, ConnectionId as ConstrainedConnId, ConstrainedError, ConstrainedHandle,
70    ConstrainedTransport, ConstrainedTransportConfig,
71};
72use crate::high_level::Connection as QuicConnection;
73use crate::nat_traversal_api::{NatTraversalEndpoint, NatTraversalError, PeerId};
74use crate::transport::{ProtocolEngine, TransportAddr, TransportCapabilities, TransportRegistry};
75
76/// Error type for connection routing operations
77#[derive(Debug, Clone)]
78pub enum RouterError {
79    /// No suitable transport available for the address
80    NoTransportAvailable {
81        /// The address that couldn't be routed
82        addr: TransportAddr,
83    },
84
85    /// Connection failed on the selected engine
86    ConnectionFailed {
87        /// Which engine was used
88        engine: ProtocolEngine,
89        /// Underlying error message
90        reason: String,
91    },
92
93    /// Send operation failed
94    SendFailed {
95        /// Error message
96        reason: String,
97    },
98
99    /// Receive operation failed
100    ReceiveFailed {
101        /// Error message
102        reason: String,
103    },
104
105    /// Connection is closed
106    ConnectionClosed,
107
108    /// Router is shutting down
109    ShuttingDown,
110
111    /// Constrained engine error
112    Constrained(ConstrainedError),
113
114    /// QUIC engine error
115    Quic {
116        /// Error message
117        reason: String,
118    },
119
120    /// NAT traversal error from the QUIC engine
121    NatTraversal(NatTraversalError),
122
123    /// Endpoint not initialized
124    EndpointNotInitialized,
125}
126
127impl fmt::Display for RouterError {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match self {
130            Self::NoTransportAvailable { addr } => {
131                write!(f, "no transport available for address: {addr}")
132            }
133            Self::ConnectionFailed { engine, reason } => {
134                write!(f, "connection failed on {engine} engine: {reason}")
135            }
136            Self::SendFailed { reason } => write!(f, "send failed: {reason}"),
137            Self::ReceiveFailed { reason } => write!(f, "receive failed: {reason}"),
138            Self::ConnectionClosed => write!(f, "connection is closed"),
139            Self::ShuttingDown => write!(f, "router is shutting down"),
140            Self::Constrained(e) => write!(f, "constrained error: {e}"),
141            Self::Quic { reason } => write!(f, "QUIC error: {reason}"),
142            Self::NatTraversal(e) => write!(f, "NAT traversal error: {e}"),
143            Self::EndpointNotInitialized => write!(f, "QUIC endpoint not initialized"),
144        }
145    }
146}
147
148impl std::error::Error for RouterError {}
149
150impl From<ConstrainedError> for RouterError {
151    fn from(err: ConstrainedError) -> Self {
152        Self::Constrained(err)
153    }
154}
155
156impl From<NatTraversalError> for RouterError {
157    fn from(err: NatTraversalError) -> Self {
158        Self::NatTraversal(err)
159    }
160}
161
162/// Configuration for the connection router
163#[derive(Debug, Clone)]
164pub struct RouterConfig {
165    /// Configuration for the constrained engine
166    pub constrained_config: ConstrainedTransportConfig,
167
168    /// Whether to prefer QUIC when both engines are available
169    pub prefer_quic: bool,
170
171    /// Enable metrics collection
172    pub enable_metrics: bool,
173
174    /// Maximum concurrent routed connections
175    pub max_connections: usize,
176}
177
178impl Default for RouterConfig {
179    fn default() -> Self {
180        Self {
181            constrained_config: ConstrainedTransportConfig::default(),
182            prefer_quic: true,
183            enable_metrics: true,
184            max_connections: 256,
185        }
186    }
187}
188
189impl RouterConfig {
190    /// Create config optimized for BLE-heavy workloads
191    pub fn for_ble_focus() -> Self {
192        Self {
193            constrained_config: ConstrainedTransportConfig::for_ble(),
194            prefer_quic: false,
195            enable_metrics: true,
196            max_connections: 32,
197        }
198    }
199
200    /// Create config optimized for LoRa-heavy workloads
201    pub fn for_lora_focus() -> Self {
202        Self {
203            constrained_config: ConstrainedTransportConfig::for_lora(),
204            prefer_quic: false,
205            enable_metrics: true,
206            max_connections: 16,
207        }
208    }
209
210    /// Create config for mixed transport environments
211    pub fn for_mixed() -> Self {
212        Self {
213            constrained_config: ConstrainedTransportConfig::default(),
214            prefer_quic: true,
215            enable_metrics: true,
216            max_connections: 128,
217        }
218    }
219}
220
221/// A routed connection that abstracts over QUIC and Constrained engines
222pub enum RoutedConnection {
223    /// Connection through the QUIC engine
224    Quic {
225        /// Remote address
226        remote: TransportAddr,
227        /// Connection identifier
228        connection_id: u64,
229        /// Peer ID of the remote peer
230        peer_id: PeerId,
231        /// The actual QUIC connection handle
232        connection: QuicConnection,
233    },
234
235    /// Connection through the Constrained engine
236    Constrained {
237        /// Remote address
238        remote: TransportAddr,
239        /// Constrained connection ID
240        connection_id: ConstrainedConnId,
241        /// Handle to the constrained transport
242        handle: ConstrainedHandle,
243    },
244}
245
246impl fmt::Debug for RoutedConnection {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        match self {
249            Self::Quic {
250                remote,
251                connection_id,
252                peer_id,
253                ..
254            } => f
255                .debug_struct("RoutedConnection::Quic")
256                .field("remote", remote)
257                .field("connection_id", connection_id)
258                .field("peer_id", peer_id)
259                .finish_non_exhaustive(),
260            Self::Constrained {
261                remote,
262                connection_id,
263                handle,
264            } => f
265                .debug_struct("RoutedConnection::Constrained")
266                .field("remote", remote)
267                .field("connection_id", connection_id)
268                .field("handle", handle)
269                .finish(),
270        }
271    }
272}
273
274impl RoutedConnection {
275    /// Get the remote address of this connection
276    pub fn remote_addr(&self) -> &TransportAddr {
277        match self {
278            Self::Quic { remote, .. } => remote,
279            Self::Constrained { remote, .. } => remote,
280        }
281    }
282
283    /// Get which protocol engine this connection uses
284    pub fn engine(&self) -> ProtocolEngine {
285        match self {
286            Self::Quic { .. } => ProtocolEngine::Quic,
287            Self::Constrained { .. } => ProtocolEngine::Constrained,
288        }
289    }
290
291    /// Check if this is a constrained connection
292    pub fn is_constrained(&self) -> bool {
293        matches!(self, Self::Constrained { .. })
294    }
295
296    /// Check if this is a QUIC connection
297    pub fn is_quic(&self) -> bool {
298        matches!(self, Self::Quic { .. })
299    }
300
301    /// Get the QUIC connection if this is a QUIC routed connection
302    pub fn quic_connection(&self) -> Option<&QuicConnection> {
303        match self {
304            Self::Quic { connection, .. } => Some(connection),
305            Self::Constrained { .. } => None,
306        }
307    }
308
309    /// Get the peer ID for this connection
310    ///
311    /// Returns Some(peer_id) for QUIC connections, None for constrained connections
312    /// (constrained connections don't have peer IDs in the same sense)
313    pub fn peer_id(&self) -> Option<&PeerId> {
314        match self {
315            Self::Quic { peer_id, .. } => Some(peer_id),
316            Self::Constrained { .. } => None,
317        }
318    }
319
320    /// Get the connection ID
321    pub fn connection_id(&self) -> u64 {
322        match self {
323            Self::Quic { connection_id, .. } => *connection_id,
324            Self::Constrained { connection_id, .. } => connection_id.0 as u64,
325        }
326    }
327
328    /// Send data through this connection (constrained path)
329    ///
330    /// For QUIC connections, use the async send methods on the connection directly
331    /// via `quic_connection()`. This method is primarily for constrained connections.
332    pub fn send(&self, data: &[u8]) -> Result<(), RouterError> {
333        match self {
334            Self::Quic { .. } => {
335                // QUIC send requires async - users should use open_uni() or open_bi()
336                // on the connection directly. This sync API is for constrained only.
337                Err(RouterError::SendFailed {
338                    reason: "QUIC send requires async streams - use quic_connection().open_uni() or open_bi()".into(),
339                })
340            }
341            Self::Constrained {
342                connection_id,
343                handle,
344                ..
345            } => {
346                handle.send(*connection_id, data)?;
347                Ok(())
348            }
349        }
350    }
351
352    /// Receive data from this connection (non-blocking, constrained path)
353    ///
354    /// For QUIC connections, use the async receive methods on the connection directly
355    /// via `quic_connection()`. This method is primarily for constrained connections.
356    pub fn recv(&self) -> Result<Option<Vec<u8>>, RouterError> {
357        match self {
358            Self::Quic { .. } => {
359                // QUIC recv requires async - users should use accept_uni() or accept_bi()
360                // on the connection directly.
361                Err(RouterError::ReceiveFailed {
362                    reason: "QUIC recv requires async streams - use quic_connection().accept_uni() or accept_bi()".into(),
363                })
364            }
365            Self::Constrained {
366                connection_id,
367                handle,
368                ..
369            } => {
370                let data = handle.recv(*connection_id)?;
371                Ok(data)
372            }
373        }
374    }
375
376    /// Close this connection
377    pub fn close(&self) -> Result<(), RouterError> {
378        match self {
379            Self::Quic { connection, .. } => {
380                // QUIC close - use VarInt(0) for graceful close
381                connection.close(crate::VarInt::from_u32(0), b"connection closed");
382                Ok(())
383            }
384            Self::Constrained {
385                connection_id,
386                handle,
387                ..
388            } => {
389                handle.close(*connection_id)?;
390                Ok(())
391            }
392        }
393    }
394
395    /// Check if this connection is still open
396    pub fn is_open(&self) -> bool {
397        match self {
398            Self::Quic { connection, .. } => connection.close_reason().is_none(),
399            Self::Constrained {
400                connection_id,
401                handle,
402                ..
403            } => handle
404                .connection_state(*connection_id)
405                .map(|s| matches!(s, crate::constrained::ConnectionState::Established))
406                .unwrap_or(false),
407        }
408    }
409
410    /// Close this connection with a reason code
411    ///
412    /// For QUIC connections, the reason code is passed to the QUIC close frame.
413    /// For constrained connections, the reason code is logged but not transmitted
414    /// (constrained protocol has simpler close handling).
415    pub fn close_with_reason(
416        &self,
417        reason_code: u32,
418        reason_text: &[u8],
419    ) -> Result<(), RouterError> {
420        match self {
421            Self::Quic { connection, .. } => {
422                connection.close(crate::VarInt::from_u32(reason_code), reason_text);
423                Ok(())
424            }
425            Self::Constrained {
426                connection_id,
427                handle,
428                ..
429            } => {
430                tracing::debug!(
431                    connection_id = connection_id.0,
432                    reason_code,
433                    "closing constrained connection with reason"
434                );
435                handle.close(*connection_id)?;
436                Ok(())
437            }
438        }
439    }
440
441    /// Send data asynchronously (unified API)
442    ///
443    /// This method provides a unified async send API that works for both QUIC and
444    /// constrained connections. For QUIC, it opens a unidirectional stream and sends
445    /// the data. For constrained, it uses the sync send path.
446    pub async fn send_async(&self, data: &[u8]) -> Result<(), RouterError> {
447        match self {
448            Self::Quic { connection, .. } => {
449                // Open a unidirectional stream and send data
450                let mut send_stream =
451                    connection
452                        .open_uni()
453                        .await
454                        .map_err(|e| RouterError::SendFailed {
455                            reason: format!("failed to open QUIC stream: {e}"),
456                        })?;
457
458                send_stream
459                    .write_all(data)
460                    .await
461                    .map_err(|e| RouterError::SendFailed {
462                        reason: format!("failed to write to QUIC stream: {e}"),
463                    })?;
464
465                send_stream.finish().map_err(|e| RouterError::SendFailed {
466                    reason: format!("failed to finish QUIC stream: {e}"),
467                })?;
468
469                Ok(())
470            }
471            Self::Constrained {
472                connection_id,
473                handle,
474                ..
475            } => {
476                // Constrained send is sync, but we expose it as async for uniformity
477                handle.send(*connection_id, data)?;
478                Ok(())
479            }
480        }
481    }
482
483    /// Receive data asynchronously (unified API)
484    ///
485    /// This method provides a unified async receive API that works for both QUIC and
486    /// constrained connections. For QUIC, it accepts a unidirectional stream and reads
487    /// data. For constrained, it polls the sync recv path.
488    ///
489    /// Note: For QUIC, this opens a new incoming stream each time. For more control
490    /// over stream management, use `quic_connection()` directly.
491    pub async fn recv_async(&self) -> Result<Vec<u8>, RouterError> {
492        match self {
493            Self::Quic { connection, .. } => {
494                // Accept an incoming unidirectional stream
495                let mut recv_stream =
496                    connection
497                        .accept_uni()
498                        .await
499                        .map_err(|e| RouterError::ReceiveFailed {
500                            reason: format!("failed to accept QUIC stream: {e}"),
501                        })?;
502
503                // Read all data from the stream
504                let data = recv_stream.read_to_end(64 * 1024).await.map_err(|e| {
505                    RouterError::ReceiveFailed {
506                        reason: format!("failed to read from QUIC stream: {e}"),
507                    }
508                })?;
509
510                Ok(data)
511            }
512            Self::Constrained {
513                connection_id,
514                handle,
515                ..
516            } => {
517                // Constrained recv is sync - poll until data is available
518                // This is a simple implementation; a production version might use
519                // tokio::time::interval for periodic polling
520                let data =
521                    handle
522                        .recv(*connection_id)?
523                        .ok_or_else(|| RouterError::ReceiveFailed {
524                            reason: "no data available from constrained connection".into(),
525                        })?;
526                Ok(data)
527            }
528        }
529    }
530
531    /// Get the maximum transmission unit (MTU) for this connection
532    ///
533    /// Returns the maximum payload size that can be sent in a single message.
534    pub fn mtu(&self) -> usize {
535        match self {
536            Self::Quic { .. } => {
537                // QUIC typically supports large datagrams, but we return a conservative
538                // estimate for stream data. Actual QUIC datagram MTU depends on path.
539                1200 // QUIC minimum MTU
540            }
541            Self::Constrained { .. } => {
542                // Constrained engine uses smaller MTU for BLE/LoRa compatibility
543                244 // BLE typical ATT MTU - 3 bytes header
544            }
545        }
546    }
547
548    /// Get statistics for this connection
549    pub fn stats(&self) -> ConnectionStats {
550        match self {
551            Self::Quic { connection, .. } => {
552                let quic_stats = connection.stats();
553                ConnectionStats {
554                    bytes_sent: quic_stats.udp_tx.bytes,
555                    bytes_received: quic_stats.udp_rx.bytes,
556                    packets_sent: quic_stats.udp_tx.datagrams,
557                    packets_received: quic_stats.udp_rx.datagrams,
558                    engine: ProtocolEngine::Quic,
559                }
560            }
561            Self::Constrained { .. } => {
562                // Constrained engine doesn't expose detailed stats yet
563                ConnectionStats {
564                    bytes_sent: 0,
565                    bytes_received: 0,
566                    packets_sent: 0,
567                    packets_received: 0,
568                    engine: ProtocolEngine::Constrained,
569                }
570            }
571        }
572    }
573}
574
575/// Statistics for a routed connection
576#[derive(Debug, Clone)]
577pub struct ConnectionStats {
578    /// Total bytes sent
579    pub bytes_sent: u64,
580    /// Total bytes received
581    pub bytes_received: u64,
582    /// Total packets sent
583    pub packets_sent: u64,
584    /// Total packets received
585    pub packets_received: u64,
586    /// Which engine this connection uses
587    pub engine: ProtocolEngine,
588}
589
590impl ConnectionStats {
591    /// Create stats for a QUIC connection
592    pub fn new_quic() -> Self {
593        Self {
594            bytes_sent: 0,
595            bytes_received: 0,
596            packets_sent: 0,
597            packets_received: 0,
598            engine: ProtocolEngine::Quic,
599        }
600    }
601
602    /// Create stats for a constrained connection
603    pub fn new_constrained() -> Self {
604        Self {
605            bytes_sent: 0,
606            bytes_received: 0,
607            packets_sent: 0,
608            packets_received: 0,
609            engine: ProtocolEngine::Constrained,
610        }
611    }
612}
613
614/// Reason for engine selection decision
615#[derive(Debug, Clone, PartialEq, Eq)]
616pub enum SelectionReason {
617    /// Transport supports full QUIC (bandwidth >= 10kbps, MTU >= 1200, RTT < 2s)
618    SupportsQuic,
619    /// Transport too constrained for QUIC
620    TooConstrained,
621    /// QUIC preferred but unavailable, falling back to constrained
622    QuicUnavailableFallback,
623    /// Constrained preferred but unavailable, falling back to QUIC
624    ConstrainedUnavailableFallback,
625    /// User preference override (prefer_quic config)
626    UserPreference,
627    /// Explicit address type mapping
628    AddressTypeMapping,
629}
630
631impl fmt::Display for SelectionReason {
632    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
633        match self {
634            Self::SupportsQuic => write!(f, "transport supports full QUIC"),
635            Self::TooConstrained => write!(f, "transport too constrained for QUIC"),
636            Self::QuicUnavailableFallback => write!(f, "QUIC unavailable, using constrained"),
637            Self::ConstrainedUnavailableFallback => {
638                write!(f, "constrained unavailable, using QUIC")
639            }
640            Self::UserPreference => write!(f, "user preference"),
641            Self::AddressTypeMapping => write!(f, "address type mapping"),
642        }
643    }
644}
645
646/// Result of an engine selection decision
647#[derive(Debug, Clone)]
648pub struct SelectionResult {
649    /// The selected protocol engine
650    pub engine: ProtocolEngine,
651    /// Reason for the selection
652    pub reason: SelectionReason,
653    /// Whether this was a fallback from the preferred choice
654    pub is_fallback: bool,
655    /// Capability assessment
656    pub capabilities_met: bool,
657}
658
659impl SelectionResult {
660    /// Create a new selection result
661    pub fn new(engine: ProtocolEngine, reason: SelectionReason) -> Self {
662        Self {
663            engine,
664            reason,
665            is_fallback: false,
666            capabilities_met: true,
667        }
668    }
669
670    /// Mark this as a fallback selection
671    pub fn with_fallback(mut self) -> Self {
672        self.is_fallback = true;
673        self
674    }
675}
676
677/// Unified router events that map from both engine types
678#[derive(Debug, Clone)]
679pub enum RouterEvent {
680    /// New incoming connection established
681    Connected {
682        /// Connection ID (opaque, engine-specific)
683        connection_id: u64,
684        /// Remote address
685        remote: TransportAddr,
686        /// Which engine handles this connection
687        engine: ProtocolEngine,
688    },
689
690    /// Data received on a connection
691    DataReceived {
692        /// Connection ID
693        connection_id: u64,
694        /// Received data
695        data: Vec<u8>,
696        /// Which engine
697        engine: ProtocolEngine,
698    },
699
700    /// Connection closed
701    Disconnected {
702        /// Connection ID
703        connection_id: u64,
704        /// Reason for disconnection
705        reason: String,
706        /// Which engine
707        engine: ProtocolEngine,
708    },
709
710    /// Connection error
711    Error {
712        /// Connection ID (if applicable)
713        connection_id: Option<u64>,
714        /// Error description
715        error: String,
716        /// Which engine
717        engine: ProtocolEngine,
718    },
719}
720
721impl RouterEvent {
722    /// Get the engine type for this event
723    pub fn engine(&self) -> ProtocolEngine {
724        match self {
725            Self::Connected { engine, .. }
726            | Self::DataReceived { engine, .. }
727            | Self::Disconnected { engine, .. }
728            | Self::Error { engine, .. } => *engine,
729        }
730    }
731
732    /// Get connection ID if available
733    pub fn connection_id(&self) -> Option<u64> {
734        match self {
735            Self::Connected { connection_id, .. }
736            | Self::DataReceived { connection_id, .. }
737            | Self::Disconnected { connection_id, .. } => Some(*connection_id),
738            Self::Error { connection_id, .. } => *connection_id,
739        }
740    }
741
742    /// Create from constrained adapter event
743    pub fn from_adapter_event(event: AdapterEvent, addr_lookup: Option<&TransportAddr>) -> Self {
744        match event {
745            AdapterEvent::ConnectionAccepted {
746                connection_id,
747                remote_addr,
748            } => Self::Connected {
749                connection_id: connection_id.0 as u64,
750                remote: remote_addr.into(),
751                engine: ProtocolEngine::Constrained,
752            },
753            AdapterEvent::ConnectionEstablished { connection_id } => Self::Connected {
754                connection_id: connection_id.0 as u64,
755                remote: addr_lookup.cloned().unwrap_or_else(|| {
756                    TransportAddr::Udp(std::net::SocketAddr::from(([0, 0, 0, 0], 0)))
757                }),
758                engine: ProtocolEngine::Constrained,
759            },
760            AdapterEvent::DataReceived {
761                connection_id,
762                data,
763            } => Self::DataReceived {
764                connection_id: connection_id.0 as u64,
765                data,
766                engine: ProtocolEngine::Constrained,
767            },
768            AdapterEvent::ConnectionClosed { connection_id } => Self::Disconnected {
769                connection_id: connection_id.0 as u64,
770                reason: "connection closed".into(),
771                engine: ProtocolEngine::Constrained,
772            },
773            AdapterEvent::ConnectionError {
774                connection_id,
775                error,
776            } => Self::Error {
777                connection_id: Some(connection_id.0 as u64),
778                error,
779                engine: ProtocolEngine::Constrained,
780            },
781            AdapterEvent::Transmit { .. } => {
782                // Transmit events are internal, not exposed to router users
783                // We convert them to a no-op error event
784                Self::Error {
785                    connection_id: None,
786                    error: "internal transmit event".into(),
787                    engine: ProtocolEngine::Constrained,
788                }
789            }
790        }
791    }
792}
793
794/// Router statistics
795#[derive(Debug, Clone, Default)]
796pub struct RouterStats {
797    /// Total connections routed through QUIC
798    pub quic_connections: u64,
799
800    /// Total connections routed through Constrained
801    pub constrained_connections: u64,
802
803    /// Total bytes sent via QUIC
804    pub quic_bytes_sent: u64,
805
806    /// Total bytes sent via Constrained
807    pub constrained_bytes_sent: u64,
808
809    /// Total bytes received via QUIC
810    pub quic_bytes_received: u64,
811
812    /// Total bytes received via Constrained
813    pub constrained_bytes_received: u64,
814
815    /// Connection failures
816    pub connection_failures: u64,
817
818    /// Engine selection decisions (QUIC chosen)
819    pub quic_selections: u64,
820
821    /// Engine selection decisions (Constrained chosen)
822    pub constrained_selections: u64,
823
824    /// Fallback selections (when preferred engine unavailable)
825    pub fallback_selections: u64,
826
827    /// Total events processed
828    pub events_processed: u64,
829}
830
831/// Connection router for automatic protocol engine selection
832///
833/// The router examines transport capabilities and routes connections
834/// through either QUIC or the Constrained engine as appropriate.
835pub struct ConnectionRouter {
836    /// Router configuration
837    config: RouterConfig,
838
839    /// Constrained transport (created lazily when needed)
840    constrained_transport: Option<ConstrainedTransport>,
841
842    /// Transport registry for capability lookups
843    registry: Option<Arc<TransportRegistry>>,
844
845    /// NAT traversal endpoint for QUIC connections
846    quic_endpoint: Option<Arc<NatTraversalEndpoint>>,
847
848    /// Router statistics
849    stats: RouterStats,
850
851    /// Next QUIC connection ID (for tracking)
852    next_quic_id: u64,
853}
854
855impl ConnectionRouter {
856    /// Create a new connection router
857    pub fn new(config: RouterConfig) -> Self {
858        Self {
859            config,
860            constrained_transport: None,
861            registry: None,
862            quic_endpoint: None,
863            stats: RouterStats::default(),
864            next_quic_id: 1,
865        }
866    }
867
868    /// Create router with a transport registry
869    pub fn with_registry(config: RouterConfig, registry: Arc<TransportRegistry>) -> Self {
870        Self {
871            config,
872            constrained_transport: None,
873            registry: Some(registry),
874            quic_endpoint: None,
875            stats: RouterStats::default(),
876            next_quic_id: 1,
877        }
878    }
879
880    /// Create router with a QUIC endpoint
881    pub fn with_quic_endpoint(
882        config: RouterConfig,
883        quic_endpoint: Arc<NatTraversalEndpoint>,
884    ) -> Self {
885        Self {
886            config,
887            constrained_transport: None,
888            registry: None,
889            quic_endpoint: Some(quic_endpoint),
890            stats: RouterStats::default(),
891            next_quic_id: 1,
892        }
893    }
894
895    /// Create router with both transport registry and QUIC endpoint
896    pub fn with_full_config(
897        config: RouterConfig,
898        registry: Arc<TransportRegistry>,
899        quic_endpoint: Arc<NatTraversalEndpoint>,
900    ) -> Self {
901        Self {
902            config,
903            constrained_transport: None,
904            registry: Some(registry),
905            quic_endpoint: Some(quic_endpoint),
906            stats: RouterStats::default(),
907            next_quic_id: 1,
908        }
909    }
910
911    /// Set the QUIC endpoint after construction
912    pub fn set_quic_endpoint(&mut self, endpoint: Arc<NatTraversalEndpoint>) {
913        self.quic_endpoint = Some(endpoint);
914    }
915
916    /// Check if QUIC endpoint is available
917    pub fn is_quic_available(&self) -> bool {
918        self.quic_endpoint.is_some()
919    }
920
921    /// Select the appropriate protocol engine for a transport
922    pub fn select_engine(&mut self, capabilities: &TransportCapabilities) -> ProtocolEngine {
923        let result = self.select_engine_detailed(capabilities);
924        result.engine
925    }
926
927    /// Select engine with detailed selection result
928    pub fn select_engine_detailed(
929        &mut self,
930        capabilities: &TransportCapabilities,
931    ) -> SelectionResult {
932        let supports_quic = capabilities.supports_full_quic();
933
934        let (engine, reason) = if supports_quic {
935            // Transport can handle QUIC
936            if self.config.prefer_quic {
937                (ProtocolEngine::Quic, SelectionReason::SupportsQuic)
938            } else {
939                // User prefers constrained even when QUIC is available
940                (ProtocolEngine::Constrained, SelectionReason::UserPreference)
941            }
942        } else {
943            // Transport cannot handle QUIC - must use constrained
944            (ProtocolEngine::Constrained, SelectionReason::TooConstrained)
945        };
946
947        // Update selection stats
948        match engine {
949            ProtocolEngine::Quic => self.stats.quic_selections += 1,
950            ProtocolEngine::Constrained => self.stats.constrained_selections += 1,
951        }
952
953        tracing::debug!(
954            engine = ?engine,
955            reason = %reason,
956            supports_quic = supports_quic,
957            bandwidth_bps = capabilities.bandwidth_bps,
958            mtu = capabilities.mtu,
959            "engine selection decision"
960        );
961
962        SelectionResult {
963            engine,
964            reason,
965            is_fallback: false,
966            capabilities_met: supports_quic || engine == ProtocolEngine::Constrained,
967        }
968    }
969
970    /// Select engine with fallback support
971    ///
972    /// If the preferred engine is unavailable (e.g., QUIC endpoint not initialized),
973    /// this method will attempt to use the fallback engine.
974    pub fn select_engine_with_fallback(
975        &mut self,
976        capabilities: &TransportCapabilities,
977        quic_available: bool,
978        constrained_available: bool,
979    ) -> Result<SelectionResult, RouterError> {
980        let preferred = self.select_engine_detailed(capabilities);
981
982        // Check if preferred engine is available
983        let (engine, result) = match preferred.engine {
984            ProtocolEngine::Quic if quic_available => (ProtocolEngine::Quic, preferred),
985            ProtocolEngine::Quic if constrained_available => {
986                // Fall back to constrained
987                self.stats.fallback_selections += 1;
988                tracing::warn!(
989                    preferred = "QUIC",
990                    fallback = "Constrained",
991                    "preferred engine unavailable, using fallback"
992                );
993                (
994                    ProtocolEngine::Constrained,
995                    SelectionResult {
996                        engine: ProtocolEngine::Constrained,
997                        reason: SelectionReason::QuicUnavailableFallback,
998                        is_fallback: true,
999                        capabilities_met: true,
1000                    },
1001                )
1002            }
1003            ProtocolEngine::Constrained if constrained_available => {
1004                (ProtocolEngine::Constrained, preferred)
1005            }
1006            ProtocolEngine::Constrained if quic_available && capabilities.supports_full_quic() => {
1007                // Fall back to QUIC (only if transport supports it)
1008                self.stats.fallback_selections += 1;
1009                tracing::warn!(
1010                    preferred = "Constrained",
1011                    fallback = "QUIC",
1012                    "preferred engine unavailable, using fallback"
1013                );
1014                (
1015                    ProtocolEngine::Quic,
1016                    SelectionResult {
1017                        engine: ProtocolEngine::Quic,
1018                        reason: SelectionReason::ConstrainedUnavailableFallback,
1019                        is_fallback: true,
1020                        capabilities_met: true,
1021                    },
1022                )
1023            }
1024            _ => {
1025                // No suitable engine available
1026                tracing::error!(
1027                    quic_available,
1028                    constrained_available,
1029                    "no suitable engine available"
1030                );
1031                return Err(RouterError::NoTransportAvailable {
1032                    addr: TransportAddr::Udp(
1033                        "0.0.0.0:0"
1034                            .parse()
1035                            .unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], 0))),
1036                    ),
1037                });
1038            }
1039        };
1040
1041        // Adjust stats for fallback
1042        if result.is_fallback {
1043            match engine {
1044                ProtocolEngine::Quic => {
1045                    self.stats.quic_selections += 1;
1046                    self.stats.constrained_selections =
1047                        self.stats.constrained_selections.saturating_sub(1);
1048                }
1049                ProtocolEngine::Constrained => {
1050                    self.stats.constrained_selections += 1;
1051                    self.stats.quic_selections = self.stats.quic_selections.saturating_sub(1);
1052                }
1053            }
1054        }
1055
1056        Ok(result)
1057    }
1058
1059    /// Select engine based on destination address
1060    pub fn select_engine_for_addr(&mut self, addr: &TransportAddr) -> ProtocolEngine {
1061        self.select_engine_for_addr_detailed(addr).engine
1062    }
1063
1064    /// Select engine based on destination address with detailed result
1065    pub fn select_engine_for_addr_detailed(&mut self, addr: &TransportAddr) -> SelectionResult {
1066        // Determine capabilities based on address type
1067        let capabilities = Self::capabilities_for_addr(addr);
1068        self.select_engine_detailed(&capabilities)
1069    }
1070
1071    /// Get transport capabilities for an address type
1072    pub fn capabilities_for_addr(addr: &TransportAddr) -> TransportCapabilities {
1073        match addr {
1074            TransportAddr::Udp(_) => TransportCapabilities::broadband(),
1075            TransportAddr::Ble { .. } => TransportCapabilities::ble(),
1076            TransportAddr::LoRa { .. } => TransportCapabilities::lora_long_range(),
1077            TransportAddr::Serial { .. } => TransportCapabilities::serial_115200(),
1078            TransportAddr::Ax25 { .. } => TransportCapabilities::packet_radio_1200(),
1079            // Overlay networks use broadband-equivalent capabilities
1080            TransportAddr::I2p { .. } => TransportCapabilities::broadband(),
1081            TransportAddr::Yggdrasil { .. } => TransportCapabilities::broadband(),
1082            TransportAddr::Broadcast { .. } => TransportCapabilities::broadband(),
1083        }
1084    }
1085
1086    /// Connect to a remote address, automatically selecting the engine (sync version)
1087    ///
1088    /// This method only works for constrained connections. For QUIC connections,
1089    /// use `connect_async()` instead.
1090    pub fn connect(&mut self, remote: &TransportAddr) -> Result<RoutedConnection, RouterError> {
1091        let engine = self.select_engine_for_addr(remote);
1092
1093        match engine {
1094            ProtocolEngine::Quic => self.connect_quic(remote),
1095            ProtocolEngine::Constrained => self.connect_constrained(remote),
1096        }
1097    }
1098
1099    /// Connect to a remote address, automatically selecting the engine (async version)
1100    ///
1101    /// This method handles both QUIC and constrained connections. For QUIC connections,
1102    /// it requires a peer ID and server name.
1103    pub async fn connect_async(
1104        &mut self,
1105        remote: &TransportAddr,
1106        peer_id: Option<PeerId>,
1107        server_name: Option<&str>,
1108    ) -> Result<RoutedConnection, RouterError> {
1109        let engine = self.select_engine_for_addr(remote);
1110
1111        match engine {
1112            ProtocolEngine::Quic => {
1113                // QUIC requires peer_id and server_name
1114                let peer_id = peer_id.ok_or_else(|| RouterError::Quic {
1115                    reason: "peer_id required for QUIC connections".into(),
1116                })?;
1117                let server_name = server_name.ok_or_else(|| RouterError::Quic {
1118                    reason: "server_name required for QUIC connections".into(),
1119                })?;
1120                self.connect_quic_async(remote, peer_id, server_name).await
1121            }
1122            ProtocolEngine::Constrained => {
1123                // Constrained connections are sync, so we can just call the sync version
1124                self.connect_constrained(remote)
1125            }
1126        }
1127    }
1128
1129    /// Connect to a QUIC peer by peer ID and address
1130    ///
1131    /// Convenience method for QUIC connections that doesn't require engine selection
1132    /// (assumes QUIC is appropriate for the given address).
1133    pub async fn connect_peer(
1134        &mut self,
1135        peer_id: PeerId,
1136        remote_addr: SocketAddr,
1137        server_name: &str,
1138    ) -> Result<RoutedConnection, RouterError> {
1139        let transport_addr = TransportAddr::Udp(remote_addr);
1140        self.connect_quic_async(&transport_addr, peer_id, server_name)
1141            .await
1142    }
1143
1144    /// Connect using the QUIC engine (sync version)
1145    ///
1146    /// This method returns an error indicating async is required for QUIC connections.
1147    /// Use `connect_quic_async` instead for actual QUIC connections.
1148    fn connect_quic(&mut self, remote: &TransportAddr) -> Result<RoutedConnection, RouterError> {
1149        // QUIC connections require async - this sync version returns an error
1150        // directing users to use the async method
1151        Err(RouterError::Quic {
1152            reason: format!(
1153                "QUIC connections require async - use connect_async() for address {}",
1154                remote
1155            ),
1156        })
1157    }
1158
1159    /// Connect using the QUIC engine (async version)
1160    ///
1161    /// This method initiates a QUIC connection through the NatTraversalEndpoint.
1162    pub async fn connect_quic_async(
1163        &mut self,
1164        remote: &TransportAddr,
1165        peer_id: PeerId,
1166        server_name: &str,
1167    ) -> Result<RoutedConnection, RouterError> {
1168        let endpoint = self
1169            .quic_endpoint
1170            .as_ref()
1171            .ok_or(RouterError::EndpointNotInitialized)?;
1172
1173        // Extract socket address from transport address
1174        let socket_addr = remote.as_socket_addr().ok_or_else(|| RouterError::Quic {
1175            reason: format!("Cannot extract socket address from {remote} for QUIC connection"),
1176        })?;
1177
1178        // Connect through the NAT traversal endpoint
1179        let connection = endpoint
1180            .connect_to_peer(peer_id, server_name, socket_addr)
1181            .await?;
1182
1183        // Assign connection ID and update stats
1184        let connection_id = self.next_quic_id;
1185        self.next_quic_id += 1;
1186        self.stats.quic_connections += 1;
1187
1188        tracing::info!(
1189            connection_id,
1190            peer = ?peer_id,
1191            remote = %socket_addr,
1192            "QUIC connection established via router"
1193        );
1194
1195        Ok(RoutedConnection::Quic {
1196            remote: remote.clone(),
1197            connection_id,
1198            peer_id,
1199            connection,
1200        })
1201    }
1202
1203    /// Connect using the Constrained engine
1204    fn connect_constrained(
1205        &mut self,
1206        remote: &TransportAddr,
1207    ) -> Result<RoutedConnection, RouterError> {
1208        // Initialize constrained transport if needed
1209        if self.constrained_transport.is_none() {
1210            let transport = ConstrainedTransport::new(self.config.constrained_config.clone());
1211            self.constrained_transport = Some(transport);
1212        }
1213
1214        let transport =
1215            self.constrained_transport
1216                .as_ref()
1217                .ok_or(RouterError::NoTransportAvailable {
1218                    addr: remote.clone(),
1219                })?;
1220
1221        let handle = transport.handle();
1222        let connection_id = handle.connect(remote)?;
1223
1224        self.stats.constrained_connections += 1;
1225
1226        Ok(RoutedConnection::Constrained {
1227            remote: remote.clone(),
1228            connection_id,
1229            handle,
1230        })
1231    }
1232
1233    /// Get the constrained transport handle (for direct access if needed)
1234    pub fn constrained_handle(&self) -> Option<ConstrainedHandle> {
1235        self.constrained_transport.as_ref().map(|t| t.handle())
1236    }
1237
1238    /// Check if a transport supports QUIC
1239    pub fn supports_quic(&self, addr: &TransportAddr) -> bool {
1240        let capabilities = Self::capabilities_for_addr(addr);
1241        capabilities.supports_full_quic()
1242    }
1243
1244    /// Check if constrained engine is initialized
1245    pub fn is_constrained_initialized(&self) -> bool {
1246        self.constrained_transport.is_some()
1247    }
1248
1249    /// Get router statistics
1250    pub fn stats(&self) -> &RouterStats {
1251        &self.stats
1252    }
1253
1254    /// Get router configuration
1255    pub fn config(&self) -> &RouterConfig {
1256        &self.config
1257    }
1258
1259    /// Get the transport registry if one was configured
1260    pub fn registry(&self) -> Option<&Arc<TransportRegistry>> {
1261        self.registry.as_ref()
1262    }
1263
1264    /// Process incoming constrained events (raw adapter events)
1265    pub fn poll_constrained_events(&self) -> Vec<AdapterEvent> {
1266        let mut events = Vec::new();
1267        if let Some(handle) = self.constrained_handle() {
1268            while let Some(event) = handle.next_event() {
1269                events.push(event);
1270            }
1271        }
1272        events
1273    }
1274
1275    /// Poll for unified router events from all engines
1276    ///
1277    /// Note: This is a sync method that only polls constrained events.
1278    /// For QUIC events, use `poll_events_async()` or the event callback
1279    /// mechanism on the NatTraversalEndpoint.
1280    pub fn poll_events(&mut self) -> Vec<RouterEvent> {
1281        let mut events = Vec::new();
1282
1283        // Collect constrained events and convert to unified format
1284        if let Some(handle) = self.constrained_handle() {
1285            while let Some(adapter_event) = handle.next_event() {
1286                let router_event = RouterEvent::from_adapter_event(adapter_event, None);
1287
1288                // Update stats based on event type
1289                if let RouterEvent::DataReceived { data, .. } = &router_event {
1290                    self.stats.constrained_bytes_received += data.len() as u64;
1291                }
1292
1293                self.stats.events_processed += 1;
1294                events.push(router_event);
1295            }
1296        }
1297
1298        events
1299    }
1300
1301    /// Accept an incoming QUIC connection
1302    ///
1303    /// This method waits for an incoming connection on the QUIC endpoint
1304    /// and returns it wrapped as a RoutedConnection.
1305    pub async fn accept_quic(&mut self) -> Result<RoutedConnection, RouterError> {
1306        let endpoint = self
1307            .quic_endpoint
1308            .as_ref()
1309            .ok_or(RouterError::EndpointNotInitialized)?;
1310
1311        let (peer_id, connection) = endpoint.accept_connection().await?;
1312
1313        // Get remote address from the connection
1314        let remote_addr = connection.remote_address();
1315        let transport_addr = TransportAddr::Udp(remote_addr);
1316
1317        // Assign connection ID and update stats
1318        let connection_id = self.next_quic_id;
1319        self.next_quic_id += 1;
1320        self.stats.quic_connections += 1;
1321
1322        tracing::info!(
1323            connection_id,
1324            peer = ?peer_id,
1325            remote = %remote_addr,
1326            "Accepted incoming QUIC connection via router"
1327        );
1328
1329        Ok(RoutedConnection::Quic {
1330            remote: transport_addr,
1331            connection_id,
1332            peer_id,
1333            connection,
1334        })
1335    }
1336
1337    /// Get the QUIC endpoint (for advanced use)
1338    pub fn quic_endpoint(&self) -> Option<&Arc<NatTraversalEndpoint>> {
1339        self.quic_endpoint.as_ref()
1340    }
1341
1342    /// Process incoming data from a constrained transport
1343    ///
1344    /// This should be called when data is received from the underlying
1345    /// transport (e.g., BLE characteristic notification, LoRa packet).
1346    pub fn process_constrained_incoming(
1347        &mut self,
1348        remote: &TransportAddr,
1349        data: &[u8],
1350    ) -> Result<Vec<RouterEvent>, RouterError> {
1351        let handle = self
1352            .constrained_handle()
1353            .ok_or(RouterError::NoTransportAvailable {
1354                addr: remote.clone(),
1355            })?;
1356
1357        // Process the incoming data through the constrained engine
1358        handle.process_incoming(remote, data)?;
1359
1360        // Collect any resulting events
1361        let mut events = Vec::new();
1362        while let Some(adapter_event) = handle.next_event() {
1363            let router_event = RouterEvent::from_adapter_event(adapter_event, Some(remote));
1364
1365            if let RouterEvent::DataReceived { data, .. } = &router_event {
1366                self.stats.constrained_bytes_received += data.len() as u64;
1367            }
1368
1369            self.stats.events_processed += 1;
1370            events.push(router_event);
1371        }
1372
1373        Ok(events)
1374    }
1375
1376    /// Get connection state for a constrained connection
1377    pub fn constrained_connection_state(
1378        &self,
1379        connection_id: ConstrainedConnId,
1380    ) -> Option<crate::constrained::ConnectionState> {
1381        self.constrained_handle()
1382            .and_then(|h| h.connection_state(connection_id))
1383    }
1384
1385    /// Get all active constrained connection IDs
1386    pub fn active_constrained_connections(&self) -> Vec<ConstrainedConnId> {
1387        self.constrained_handle()
1388            .map(|h| h.active_connections())
1389            .unwrap_or_default()
1390    }
1391}
1392
1393impl fmt::Debug for ConnectionRouter {
1394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1395        f.debug_struct("ConnectionRouter")
1396            .field("config", &self.config)
1397            .field(
1398                "constrained_initialized",
1399                &self.constrained_transport.is_some(),
1400            )
1401            .field("stats", &self.stats)
1402            .finish()
1403    }
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408    use super::*;
1409
1410    #[test]
1411    fn test_router_config_default() {
1412        let config = RouterConfig::default();
1413        assert!(config.prefer_quic);
1414        assert!(config.enable_metrics);
1415        assert_eq!(config.max_connections, 256);
1416    }
1417
1418    #[test]
1419    fn test_router_config_presets() {
1420        let ble_config = RouterConfig::for_ble_focus();
1421        assert!(!ble_config.prefer_quic);
1422        assert_eq!(ble_config.max_connections, 32);
1423
1424        let lora_config = RouterConfig::for_lora_focus();
1425        assert!(!lora_config.prefer_quic);
1426        assert_eq!(lora_config.max_connections, 16);
1427
1428        let mixed_config = RouterConfig::for_mixed();
1429        assert!(mixed_config.prefer_quic);
1430        assert_eq!(mixed_config.max_connections, 128);
1431    }
1432
1433    #[test]
1434    fn test_engine_selection_for_udp() {
1435        let mut router = ConnectionRouter::new(RouterConfig::default());
1436        let addr = TransportAddr::Udp("127.0.0.1:9000".parse().unwrap());
1437
1438        let engine = router.select_engine_for_addr(&addr);
1439        assert_eq!(engine, ProtocolEngine::Quic);
1440        assert_eq!(router.stats().quic_selections, 1);
1441    }
1442
1443    #[test]
1444    fn test_engine_selection_for_ble() {
1445        let mut router = ConnectionRouter::new(RouterConfig::default());
1446        let addr = TransportAddr::Ble {
1447            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1448            service_uuid: None,
1449        };
1450
1451        let engine = router.select_engine_for_addr(&addr);
1452        assert_eq!(engine, ProtocolEngine::Constrained);
1453        assert_eq!(router.stats().constrained_selections, 1);
1454    }
1455
1456    #[test]
1457    fn test_engine_selection_for_lora() {
1458        let mut router = ConnectionRouter::new(RouterConfig::default());
1459        let addr = TransportAddr::LoRa {
1460            device_addr: [0x12, 0x34, 0x56, 0x78],
1461            params: crate::transport::LoRaParams::default(),
1462        };
1463
1464        let engine = router.select_engine_for_addr(&addr);
1465        assert_eq!(engine, ProtocolEngine::Constrained);
1466    }
1467
1468    #[test]
1469    fn test_supports_quic() {
1470        let router = ConnectionRouter::new(RouterConfig::default());
1471
1472        let udp_addr = TransportAddr::Udp("127.0.0.1:9000".parse().unwrap());
1473        assert!(router.supports_quic(&udp_addr));
1474
1475        let ble_addr = TransportAddr::Ble {
1476            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1477            service_uuid: None,
1478        };
1479        assert!(!router.supports_quic(&ble_addr));
1480    }
1481
1482    #[test]
1483    fn test_connect_constrained() {
1484        let mut router = ConnectionRouter::new(RouterConfig::default());
1485        let addr = TransportAddr::Ble {
1486            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1487            service_uuid: None,
1488        };
1489
1490        let conn = router.connect(&addr);
1491        assert!(conn.is_ok());
1492
1493        let conn = conn.unwrap();
1494        assert!(conn.is_constrained());
1495        assert_eq!(conn.engine(), ProtocolEngine::Constrained);
1496        assert_eq!(conn.remote_addr(), &addr);
1497        assert_eq!(router.stats().constrained_connections, 1);
1498    }
1499
1500    #[test]
1501    fn test_connect_quic_requires_async() {
1502        // QUIC connections require async - the sync connect() method
1503        // should return an error for QUIC addresses
1504        let mut router = ConnectionRouter::new(RouterConfig::default());
1505        let addr = TransportAddr::Udp("127.0.0.1:9000".parse().unwrap());
1506
1507        let result = router.connect(&addr);
1508        assert!(result.is_err());
1509
1510        // Should be a QUIC error indicating async is required
1511        if let Err(RouterError::Quic { reason }) = result {
1512            assert!(reason.contains("async"));
1513        } else {
1514            panic!("Expected RouterError::Quic");
1515        }
1516    }
1517
1518    #[test]
1519    fn test_quic_endpoint_availability() {
1520        let router = ConnectionRouter::new(RouterConfig::default());
1521        assert!(!router.is_quic_available());
1522
1523        // Can't easily test with_quic_endpoint in a unit test without
1524        // setting up a full NatTraversalEndpoint, but we can verify the method exists
1525    }
1526
1527    #[test]
1528    fn test_router_with_registry() {
1529        let registry = Arc::new(crate::transport::TransportRegistry::new());
1530        let router = ConnectionRouter::with_registry(RouterConfig::default(), registry.clone());
1531        assert!(router.registry().is_some());
1532        assert!(!router.is_quic_available());
1533    }
1534
1535    #[test]
1536    fn test_routed_connection_send_constrained() {
1537        let mut router = ConnectionRouter::new(RouterConfig::default());
1538        let addr = TransportAddr::Ble {
1539            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1540            service_uuid: None,
1541        };
1542
1543        let conn = router.connect(&addr).unwrap();
1544
1545        // Send should work (connection is in SYN_SENT state, so data gets queued)
1546        // Note: actual transmission happens after handshake
1547        let result = conn.send(b"test data");
1548        // May fail because connection not established - that's expected
1549        // The important thing is it doesn't panic
1550        let _ = result;
1551    }
1552
1553    #[test]
1554    fn test_routed_connection_close() {
1555        let mut router = ConnectionRouter::new(RouterConfig::default());
1556        let addr = TransportAddr::Ble {
1557            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1558            service_uuid: None,
1559        };
1560
1561        let conn = router.connect(&addr).unwrap();
1562        let result = conn.close();
1563        assert!(result.is_ok());
1564    }
1565
1566    #[test]
1567    fn test_router_stats() {
1568        let mut router = ConnectionRouter::new(RouterConfig::default());
1569
1570        // Make some selections
1571        let udp_addr = TransportAddr::Udp("127.0.0.1:9000".parse().unwrap());
1572        let ble_addr = TransportAddr::Ble {
1573            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1574            service_uuid: None,
1575        };
1576
1577        let _ = router.select_engine_for_addr(&udp_addr);
1578        let _ = router.select_engine_for_addr(&udp_addr);
1579        let _ = router.select_engine_for_addr(&ble_addr);
1580
1581        let stats = router.stats();
1582        assert_eq!(stats.quic_selections, 2);
1583        assert_eq!(stats.constrained_selections, 1);
1584    }
1585
1586    #[test]
1587    fn test_constrained_handle_access() {
1588        let mut router = ConnectionRouter::new(RouterConfig::default());
1589
1590        // Initially no handle
1591        assert!(router.constrained_handle().is_none());
1592
1593        // After connecting constrained, handle is available
1594        let addr = TransportAddr::Ble {
1595            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1596            service_uuid: None,
1597        };
1598        let _ = router.connect(&addr);
1599
1600        assert!(router.constrained_handle().is_some());
1601    }
1602
1603    #[test]
1604    fn test_router_error_display() {
1605        let err = RouterError::NoTransportAvailable {
1606            addr: TransportAddr::Udp("127.0.0.1:9000".parse().unwrap()),
1607        };
1608        assert!(format!("{err}").contains("no transport available"));
1609
1610        let err = RouterError::ConnectionFailed {
1611            engine: ProtocolEngine::Quic,
1612            reason: "timeout".into(),
1613        };
1614        assert!(format!("{err}").contains("QUIC"));
1615        assert!(format!("{err}").contains("timeout"));
1616
1617        let err = RouterError::ConnectionClosed;
1618        assert!(format!("{err}").contains("closed"));
1619    }
1620
1621    // ========================================================================
1622    // Task 2: Protocol Selection Logic Tests
1623    // ========================================================================
1624
1625    #[test]
1626    fn test_select_engine_detailed_udp() {
1627        let mut router = ConnectionRouter::new(RouterConfig::default());
1628        let capabilities = TransportCapabilities::broadband();
1629
1630        let result = router.select_engine_detailed(&capabilities);
1631        assert_eq!(result.engine, ProtocolEngine::Quic);
1632        assert_eq!(result.reason, SelectionReason::SupportsQuic);
1633        assert!(!result.is_fallback);
1634        assert!(result.capabilities_met);
1635    }
1636
1637    #[test]
1638    fn test_select_engine_detailed_ble() {
1639        let mut router = ConnectionRouter::new(RouterConfig::default());
1640        let capabilities = TransportCapabilities::ble();
1641
1642        let result = router.select_engine_detailed(&capabilities);
1643        assert_eq!(result.engine, ProtocolEngine::Constrained);
1644        assert_eq!(result.reason, SelectionReason::TooConstrained);
1645        assert!(!result.is_fallback);
1646    }
1647
1648    #[test]
1649    fn test_select_engine_detailed_user_preference() {
1650        // Configure router to prefer constrained even for broadband
1651        let mut config = RouterConfig::default();
1652        config.prefer_quic = false;
1653        let mut router = ConnectionRouter::new(config);
1654        let capabilities = TransportCapabilities::broadband();
1655
1656        let result = router.select_engine_detailed(&capabilities);
1657        assert_eq!(result.engine, ProtocolEngine::Constrained);
1658        assert_eq!(result.reason, SelectionReason::UserPreference);
1659    }
1660
1661    #[test]
1662    fn test_select_engine_with_fallback_quic_available() {
1663        let mut router = ConnectionRouter::new(RouterConfig::default());
1664        let capabilities = TransportCapabilities::broadband();
1665
1666        let result = router
1667            .select_engine_with_fallback(&capabilities, true, false)
1668            .unwrap();
1669        assert_eq!(result.engine, ProtocolEngine::Quic);
1670        assert!(!result.is_fallback);
1671    }
1672
1673    #[test]
1674    fn test_select_engine_with_fallback_to_constrained() {
1675        let mut router = ConnectionRouter::new(RouterConfig::default());
1676        let capabilities = TransportCapabilities::broadband();
1677
1678        // QUIC unavailable, constrained available
1679        let result = router
1680            .select_engine_with_fallback(&capabilities, false, true)
1681            .unwrap();
1682        assert_eq!(result.engine, ProtocolEngine::Constrained);
1683        assert!(result.is_fallback);
1684        assert_eq!(result.reason, SelectionReason::QuicUnavailableFallback);
1685        assert_eq!(router.stats().fallback_selections, 1);
1686    }
1687
1688    #[test]
1689    fn test_select_engine_with_fallback_constrained_preferred() {
1690        let config = RouterConfig::for_ble_focus();
1691        let mut router = ConnectionRouter::new(config);
1692        let capabilities = TransportCapabilities::broadband();
1693
1694        // Constrained preferred but unavailable, QUIC available
1695        // Should fallback to QUIC since transport supports it
1696        let result = router
1697            .select_engine_with_fallback(&capabilities, true, false)
1698            .unwrap();
1699        assert_eq!(result.engine, ProtocolEngine::Quic);
1700        assert!(result.is_fallback);
1701        assert_eq!(
1702            result.reason,
1703            SelectionReason::ConstrainedUnavailableFallback
1704        );
1705    }
1706
1707    #[test]
1708    fn test_select_engine_with_fallback_no_engines() {
1709        let mut router = ConnectionRouter::new(RouterConfig::default());
1710        let capabilities = TransportCapabilities::broadband();
1711
1712        // Neither engine available
1713        let result = router.select_engine_with_fallback(&capabilities, false, false);
1714        assert!(result.is_err());
1715    }
1716
1717    #[test]
1718    fn test_capabilities_for_addr_coverage() {
1719        // Test all address types return valid capabilities
1720        let udp = TransportAddr::Udp("127.0.0.1:9000".parse().unwrap());
1721        assert!(ConnectionRouter::capabilities_for_addr(&udp).supports_full_quic());
1722
1723        let ble = TransportAddr::Ble {
1724            device_id: [0; 6],
1725            service_uuid: None,
1726        };
1727        assert!(!ConnectionRouter::capabilities_for_addr(&ble).supports_full_quic());
1728
1729        let lora = TransportAddr::LoRa {
1730            device_addr: [0; 4],
1731            params: crate::transport::LoRaParams::default(),
1732        };
1733        assert!(!ConnectionRouter::capabilities_for_addr(&lora).supports_full_quic());
1734
1735        // Serial should be constrained (MTU < 1200)
1736        let serial = TransportAddr::serial("/dev/ttyUSB0");
1737        let serial_caps = ConnectionRouter::capabilities_for_addr(&serial);
1738        assert!(!serial_caps.supports_full_quic());
1739
1740        // Overlay networks should support QUIC
1741        let i2p = TransportAddr::I2p {
1742            destination: Box::new([0u8; 387]),
1743        };
1744        assert!(ConnectionRouter::capabilities_for_addr(&i2p).supports_full_quic());
1745
1746        let yggdrasil = TransportAddr::yggdrasil([0; 16]);
1747        assert!(ConnectionRouter::capabilities_for_addr(&yggdrasil).supports_full_quic());
1748    }
1749
1750    #[test]
1751    fn test_selection_reason_display() {
1752        assert!(format!("{}", SelectionReason::SupportsQuic).contains("QUIC"));
1753        assert!(format!("{}", SelectionReason::TooConstrained).contains("constrained"));
1754        assert!(format!("{}", SelectionReason::QuicUnavailableFallback).contains("unavailable"));
1755        assert!(format!("{}", SelectionReason::UserPreference).contains("preference"));
1756    }
1757
1758    #[test]
1759    fn test_selection_result_with_fallback() {
1760        let result = SelectionResult::new(ProtocolEngine::Quic, SelectionReason::SupportsQuic);
1761        assert!(!result.is_fallback);
1762
1763        let fallback_result = result.with_fallback();
1764        assert!(fallback_result.is_fallback);
1765        assert_eq!(fallback_result.engine, ProtocolEngine::Quic);
1766    }
1767
1768    #[test]
1769    fn test_is_constrained_initialized() {
1770        let mut router = ConnectionRouter::new(RouterConfig::default());
1771        assert!(!router.is_constrained_initialized());
1772
1773        // Initialize by connecting to BLE
1774        let addr = TransportAddr::Ble {
1775            device_id: [0; 6],
1776            service_uuid: None,
1777        };
1778        let _ = router.connect(&addr);
1779
1780        assert!(router.is_constrained_initialized());
1781    }
1782
1783    #[test]
1784    fn test_fallback_stats_tracking() {
1785        let mut router = ConnectionRouter::new(RouterConfig::default());
1786        let capabilities = TransportCapabilities::broadband();
1787
1788        // Normal selection - no fallback
1789        let _ = router.select_engine_with_fallback(&capabilities, true, true);
1790        assert_eq!(router.stats().fallback_selections, 0);
1791
1792        // Fallback selection
1793        let _ = router.select_engine_with_fallback(&capabilities, false, true);
1794        assert_eq!(router.stats().fallback_selections, 1);
1795    }
1796
1797    // ========================================================================
1798    // Task 4: QUIC Connection Integration Tests
1799    // ========================================================================
1800
1801    #[test]
1802    fn test_router_error_nat_traversal() {
1803        // Test that NatTraversalError converts to RouterError properly
1804        use crate::nat_traversal_api::NatTraversalError;
1805
1806        let nat_err = NatTraversalError::Timeout;
1807        let router_err: RouterError = nat_err.into();
1808        let msg = format!("{router_err}");
1809        assert!(msg.contains("NAT traversal"));
1810    }
1811
1812    #[test]
1813    fn test_router_error_endpoint_not_initialized() {
1814        let err = RouterError::EndpointNotInitialized;
1815        let msg = format!("{err}");
1816        assert!(msg.contains("not initialized"));
1817    }
1818
1819    #[test]
1820    fn test_routed_connection_accessors_constrained() {
1821        let mut router = ConnectionRouter::new(RouterConfig::default());
1822        let addr = TransportAddr::Ble {
1823            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
1824            service_uuid: None,
1825        };
1826
1827        let conn = router.connect(&addr).unwrap();
1828
1829        // Test accessors
1830        assert_eq!(conn.engine(), ProtocolEngine::Constrained);
1831        assert!(conn.is_constrained());
1832        assert!(!conn.is_quic());
1833        assert!(conn.quic_connection().is_none());
1834        assert!(conn.peer_id().is_none());
1835        assert_eq!(conn.remote_addr(), &addr);
1836
1837        // Connection ID should be valid
1838        let _conn_id = conn.connection_id();
1839    }
1840
1841    #[test]
1842    fn test_set_quic_endpoint() {
1843        let router = ConnectionRouter::new(RouterConfig::default());
1844        assert!(!router.is_quic_available());
1845        assert!(router.quic_endpoint().is_none());
1846
1847        // We can't easily construct a NatTraversalEndpoint in a unit test,
1848        // but we verify the setter method exists and the state tracking works
1849    }
1850
1851    #[test]
1852    fn test_router_debug_impl() {
1853        let router = ConnectionRouter::new(RouterConfig::default());
1854        let debug_str = format!("{router:?}");
1855        assert!(debug_str.contains("ConnectionRouter"));
1856        assert!(debug_str.contains("config"));
1857        assert!(debug_str.contains("stats"));
1858    }
1859
1860    #[test]
1861    fn test_routed_connection_debug_constrained() {
1862        let mut router = ConnectionRouter::new(RouterConfig::default());
1863        let addr = TransportAddr::Ble {
1864            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1865            service_uuid: None,
1866        };
1867
1868        let conn = router.connect(&addr).unwrap();
1869        let debug_str = format!("{conn:?}");
1870        assert!(debug_str.contains("Constrained"));
1871    }
1872
1873    #[test]
1874    fn test_router_event_engine_accessor() {
1875        let event = RouterEvent::Connected {
1876            connection_id: 1,
1877            remote: TransportAddr::Udp("127.0.0.1:9000".parse().unwrap()),
1878            engine: ProtocolEngine::Quic,
1879        };
1880        assert_eq!(event.engine(), ProtocolEngine::Quic);
1881
1882        let event = RouterEvent::DataReceived {
1883            connection_id: 2,
1884            data: vec![1, 2, 3],
1885            engine: ProtocolEngine::Constrained,
1886        };
1887        assert_eq!(event.engine(), ProtocolEngine::Constrained);
1888    }
1889
1890    #[test]
1891    fn test_router_event_connection_id() {
1892        let event = RouterEvent::Connected {
1893            connection_id: 42,
1894            remote: TransportAddr::Udp("127.0.0.1:9000".parse().unwrap()),
1895            engine: ProtocolEngine::Quic,
1896        };
1897        assert_eq!(event.connection_id(), Some(42));
1898
1899        let event = RouterEvent::Error {
1900            connection_id: None,
1901            error: "test error".into(),
1902            engine: ProtocolEngine::Constrained,
1903        };
1904        assert_eq!(event.connection_id(), None);
1905    }
1906
1907    #[test]
1908    fn test_router_with_fallback_quic_unavailable_but_transport_supports() {
1909        // When QUIC is unavailable but transport supports QUIC,
1910        // should fall back to constrained
1911        let mut router = ConnectionRouter::new(RouterConfig::default());
1912        let capabilities = TransportCapabilities::broadband();
1913
1914        let result = router
1915            .select_engine_with_fallback(&capabilities, false, true)
1916            .unwrap();
1917        assert_eq!(result.engine, ProtocolEngine::Constrained);
1918        assert!(result.is_fallback);
1919        assert_eq!(result.reason, SelectionReason::QuicUnavailableFallback);
1920    }
1921
1922    #[test]
1923    fn test_poll_events_empty() {
1924        let mut router = ConnectionRouter::new(RouterConfig::default());
1925        let events = router.poll_events();
1926        assert!(events.is_empty());
1927    }
1928
1929    #[test]
1930    fn test_poll_events_after_constrained_connect() {
1931        let mut router = ConnectionRouter::new(RouterConfig::default());
1932        let addr = TransportAddr::Ble {
1933            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
1934            service_uuid: None,
1935        };
1936
1937        // Connect to initialize constrained transport
1938        let _ = router.connect(&addr);
1939
1940        // Poll events - should return empty since no actual network activity
1941        let events = router.poll_events();
1942        // Events may or may not be present depending on timing
1943        let _ = events;
1944    }
1945
1946    // ========================================================================
1947    // Task 5: Unified Send/Receive API Tests
1948    // ========================================================================
1949
1950    #[test]
1951    fn test_connection_mtu() {
1952        let mut router = ConnectionRouter::new(RouterConfig::default());
1953        let addr = TransportAddr::Ble {
1954            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
1955            service_uuid: None,
1956        };
1957
1958        let conn = router.connect(&addr).unwrap();
1959        let mtu = conn.mtu();
1960
1961        // BLE MTU should be small (244 bytes for typical ATT MTU)
1962        assert_eq!(mtu, 244);
1963    }
1964
1965    #[test]
1966    fn test_connection_stats_constrained() {
1967        let mut router = ConnectionRouter::new(RouterConfig::default());
1968        let addr = TransportAddr::Ble {
1969            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
1970            service_uuid: None,
1971        };
1972
1973        let conn = router.connect(&addr).unwrap();
1974        let stats = conn.stats();
1975
1976        assert_eq!(stats.engine, ProtocolEngine::Constrained);
1977        // Initial stats should be zero (no traffic yet)
1978        assert_eq!(stats.bytes_sent, 0);
1979        assert_eq!(stats.bytes_received, 0);
1980    }
1981
1982    #[test]
1983    fn test_connection_stats_constructors() {
1984        let quic_stats = ConnectionStats::new_quic();
1985        assert_eq!(quic_stats.engine, ProtocolEngine::Quic);
1986        assert_eq!(quic_stats.bytes_sent, 0);
1987
1988        let constrained_stats = ConnectionStats::new_constrained();
1989        assert_eq!(constrained_stats.engine, ProtocolEngine::Constrained);
1990        assert_eq!(constrained_stats.bytes_sent, 0);
1991    }
1992
1993    #[test]
1994    fn test_close_with_reason_constrained() {
1995        let mut router = ConnectionRouter::new(RouterConfig::default());
1996        let addr = TransportAddr::Ble {
1997            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
1998            service_uuid: None,
1999        };
2000
2001        let conn = router.connect(&addr).unwrap();
2002        let result = conn.close_with_reason(42, b"test close");
2003        assert!(result.is_ok());
2004    }
2005
2006    #[test]
2007    fn test_is_open_after_close() {
2008        let mut router = ConnectionRouter::new(RouterConfig::default());
2009        let addr = TransportAddr::Ble {
2010            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
2011            service_uuid: None,
2012        };
2013
2014        let conn = router.connect(&addr).unwrap();
2015
2016        // Connection might be in SYN_SENT state initially
2017        // After close, it should not be "established"
2018        let _ = conn.close();
2019        // is_open() checks for Established state, which shouldn't be true after close
2020        // (though depending on timing, it may never have been established)
2021    }
2022
2023    #[tokio::test]
2024    async fn test_send_async_constrained() {
2025        let mut router = ConnectionRouter::new(RouterConfig::default());
2026        let addr = TransportAddr::Ble {
2027            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
2028            service_uuid: None,
2029        };
2030
2031        let conn = router.connect(&addr).unwrap();
2032
2033        // Send async on constrained - may fail because connection not established
2034        // but should not panic
2035        let result = conn.send_async(b"test data").await;
2036        // Result depends on connection state - we just verify no panic
2037        let _ = result;
2038    }
2039
2040    #[tokio::test]
2041    async fn test_recv_async_constrained_no_data() {
2042        let mut router = ConnectionRouter::new(RouterConfig::default());
2043        let addr = TransportAddr::Ble {
2044            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
2045            service_uuid: None,
2046        };
2047
2048        let conn = router.connect(&addr).unwrap();
2049
2050        // Recv async on constrained - should fail because no data available
2051        let result = conn.recv_async().await;
2052        assert!(result.is_err());
2053    }
2054}