Skip to main content

fips_core/transport/
handle.rs

1//! Enum wrapper for concrete transport implementations.
2
3#[cfg(target_os = "linux")]
4use super::ble::DefaultBleTransport;
5#[cfg(any(target_os = "linux", target_os = "macos"))]
6use super::ethernet::EthernetTransport;
7#[cfg(feature = "sim-transport")]
8use super::sim::SimTransport;
9use super::tcp::TcpTransport;
10use super::tor::TorTransport;
11use super::tor::control::TorMonitoringInfo;
12use super::udp::UdpTransport;
13#[cfg(feature = "webrtc-transport")]
14use super::webrtc::WebRtcTransport;
15use super::{
16    ConnectionState, DiscoveredPeer, Transport, TransportAddr, TransportCongestion, TransportError,
17    TransportId, TransportState, TransportType,
18};
19
20const TCP_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(500);
21
22/// Wrapper enum for concrete transport implementations.
23///
24/// This enables polymorphic transport handling without trait objects,
25/// supporting async methods that the sync Transport trait cannot express.
26pub enum TransportHandle {
27    /// UDP/IP transport.
28    Udp(UdpTransport),
29    /// In-memory simulated packet transport.
30    #[cfg(feature = "sim-transport")]
31    Sim(SimTransport),
32    /// Raw Ethernet transport.
33    #[cfg(any(target_os = "linux", target_os = "macos"))]
34    Ethernet(EthernetTransport),
35    /// TCP/IP transport.
36    Tcp(TcpTransport),
37    /// Tor transport (via SOCKS5).
38    Tor(TorTransport),
39    /// WebRTC DataChannel transport.
40    #[cfg(feature = "webrtc-transport")]
41    WebRtc(Box<WebRtcTransport>),
42    /// BLE L2CAP transport.
43    #[cfg(target_os = "linux")]
44    Ble(DefaultBleTransport),
45}
46
47impl TransportHandle {
48    /// Start the transport asynchronously.
49    pub async fn start(&mut self) -> Result<(), TransportError> {
50        match self {
51            TransportHandle::Udp(t) => t.start_async().await,
52            #[cfg(feature = "sim-transport")]
53            TransportHandle::Sim(t) => t.start_async().await,
54            #[cfg(any(target_os = "linux", target_os = "macos"))]
55            TransportHandle::Ethernet(t) => t.start_async().await,
56            TransportHandle::Tcp(t) => t.start_async().await,
57            TransportHandle::Tor(t) => t.start_async().await,
58            #[cfg(feature = "webrtc-transport")]
59            TransportHandle::WebRtc(t) => t.start_async().await,
60            #[cfg(target_os = "linux")]
61            TransportHandle::Ble(t) => t.start_async().await,
62        }
63    }
64
65    /// Stop the transport asynchronously.
66    pub async fn stop(&mut self) -> Result<(), TransportError> {
67        match self {
68            TransportHandle::Udp(t) => t.stop_async().await,
69            #[cfg(feature = "sim-transport")]
70            TransportHandle::Sim(t) => t.stop_async().await,
71            #[cfg(any(target_os = "linux", target_os = "macos"))]
72            TransportHandle::Ethernet(t) => t.stop_async().await,
73            TransportHandle::Tcp(t) => t.stop_async().await,
74            TransportHandle::Tor(t) => t.stop_async().await,
75            #[cfg(feature = "webrtc-transport")]
76            TransportHandle::WebRtc(t) => t.stop_async().await,
77            #[cfg(target_os = "linux")]
78            TransportHandle::Ble(t) => t.stop_async().await,
79        }
80    }
81
82    /// Send data to a remote address asynchronously.
83    pub async fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<usize, TransportError> {
84        match self {
85            TransportHandle::Udp(t) => t.send_async(addr, data).await,
86            #[cfg(feature = "sim-transport")]
87            TransportHandle::Sim(t) => t.send_async(addr, data).await,
88            #[cfg(any(target_os = "linux", target_os = "macos"))]
89            TransportHandle::Ethernet(t) => t.send_async(addr, data).await,
90            TransportHandle::Tcp(t) => {
91                tokio::time::timeout(TCP_SEND_TIMEOUT, t.send_async(addr, data))
92                    .await
93                    .unwrap_or(Err(TransportError::Timeout))
94            }
95            TransportHandle::Tor(t) => t.send_async(addr, data).await,
96            #[cfg(feature = "webrtc-transport")]
97            TransportHandle::WebRtc(t) => t.send_async(addr, data).await,
98            #[cfg(target_os = "linux")]
99            TransportHandle::Ble(t) => t.send_async(addr, data).await,
100        }
101    }
102
103    /// Get the transport ID.
104    pub fn transport_id(&self) -> TransportId {
105        match self {
106            TransportHandle::Udp(t) => t.transport_id(),
107            #[cfg(feature = "sim-transport")]
108            TransportHandle::Sim(t) => t.transport_id(),
109            #[cfg(any(target_os = "linux", target_os = "macos"))]
110            TransportHandle::Ethernet(t) => t.transport_id(),
111            TransportHandle::Tcp(t) => t.transport_id(),
112            TransportHandle::Tor(t) => t.transport_id(),
113            #[cfg(feature = "webrtc-transport")]
114            TransportHandle::WebRtc(t) => t.transport_id(),
115            #[cfg(target_os = "linux")]
116            TransportHandle::Ble(t) => t.transport_id(),
117        }
118    }
119
120    /// Get the instance name (if configured as a named instance).
121    pub fn name(&self) -> Option<&str> {
122        match self {
123            TransportHandle::Udp(t) => t.name(),
124            #[cfg(feature = "sim-transport")]
125            TransportHandle::Sim(t) => t.name(),
126            #[cfg(any(target_os = "linux", target_os = "macos"))]
127            TransportHandle::Ethernet(t) => t.name(),
128            TransportHandle::Tcp(t) => t.name(),
129            TransportHandle::Tor(t) => t.name(),
130            #[cfg(feature = "webrtc-transport")]
131            TransportHandle::WebRtc(t) => t.name(),
132            #[cfg(target_os = "linux")]
133            TransportHandle::Ble(t) => t.name(),
134        }
135    }
136
137    /// Get the transport type metadata.
138    pub fn transport_type(&self) -> &TransportType {
139        match self {
140            TransportHandle::Udp(t) => t.transport_type(),
141            #[cfg(feature = "sim-transport")]
142            TransportHandle::Sim(t) => t.transport_type(),
143            #[cfg(any(target_os = "linux", target_os = "macos"))]
144            TransportHandle::Ethernet(t) => t.transport_type(),
145            TransportHandle::Tcp(t) => t.transport_type(),
146            TransportHandle::Tor(t) => t.transport_type(),
147            #[cfg(feature = "webrtc-transport")]
148            TransportHandle::WebRtc(t) => t.transport_type(),
149            #[cfg(target_os = "linux")]
150            TransportHandle::Ble(t) => t.transport_type(),
151        }
152    }
153
154    /// Get current transport state.
155    pub fn state(&self) -> TransportState {
156        match self {
157            TransportHandle::Udp(t) => t.state(),
158            #[cfg(feature = "sim-transport")]
159            TransportHandle::Sim(t) => t.state(),
160            #[cfg(any(target_os = "linux", target_os = "macos"))]
161            TransportHandle::Ethernet(t) => t.state(),
162            TransportHandle::Tcp(t) => t.state(),
163            TransportHandle::Tor(t) => t.state(),
164            #[cfg(feature = "webrtc-transport")]
165            TransportHandle::WebRtc(t) => t.state(),
166            #[cfg(target_os = "linux")]
167            TransportHandle::Ble(t) => t.state(),
168        }
169    }
170
171    /// Get the transport MTU.
172    pub fn mtu(&self) -> u16 {
173        match self {
174            TransportHandle::Udp(t) => t.mtu(),
175            #[cfg(feature = "sim-transport")]
176            TransportHandle::Sim(t) => t.mtu(),
177            #[cfg(any(target_os = "linux", target_os = "macos"))]
178            TransportHandle::Ethernet(t) => t.mtu(),
179            TransportHandle::Tcp(t) => t.mtu(),
180            TransportHandle::Tor(t) => t.mtu(),
181            #[cfg(feature = "webrtc-transport")]
182            TransportHandle::WebRtc(t) => t.mtu(),
183            #[cfg(target_os = "linux")]
184            TransportHandle::Ble(t) => t.mtu(),
185        }
186    }
187
188    /// Get the MTU for a specific link address.
189    ///
190    /// Falls back to transport-wide MTU if the transport doesn't
191    /// support per-link MTU or the address is unknown.
192    pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
193        match self {
194            TransportHandle::Udp(t) => t.link_mtu(addr),
195            #[cfg(feature = "sim-transport")]
196            TransportHandle::Sim(t) => t.link_mtu(addr),
197            #[cfg(any(target_os = "linux", target_os = "macos"))]
198            TransportHandle::Ethernet(t) => t.link_mtu(addr),
199            TransportHandle::Tcp(t) => t.link_mtu(addr),
200            TransportHandle::Tor(t) => t.link_mtu(addr),
201            #[cfg(feature = "webrtc-transport")]
202            TransportHandle::WebRtc(t) => t.link_mtu(addr),
203            #[cfg(target_os = "linux")]
204            TransportHandle::Ble(t) => t.link_mtu(addr),
205        }
206    }
207
208    /// Get the local bound address (UDP/TCP only, returns None for other transports).
209    pub fn local_addr(&self) -> Option<std::net::SocketAddr> {
210        match self {
211            TransportHandle::Udp(t) => t.local_addr(),
212            #[cfg(feature = "sim-transport")]
213            TransportHandle::Sim(_) => None,
214            #[cfg(any(target_os = "linux", target_os = "macos"))]
215            TransportHandle::Ethernet(_) => None,
216            TransportHandle::Tcp(t) => t.local_addr(),
217            TransportHandle::Tor(_) => None,
218            #[cfg(feature = "webrtc-transport")]
219            TransportHandle::WebRtc(_) => None,
220            #[cfg(target_os = "linux")]
221            TransportHandle::Ble(_) => None,
222        }
223    }
224
225    /// Resolve a UDP target only if doing so cannot block on DNS.
226    ///
227    /// Numeric UDP addresses are returned directly; hostnames are returned
228    /// only when the UDP transport already has a fresh cached resolution.
229    pub(crate) fn resolved_udp_socket_addr_if_cached(
230        &self,
231        addr: &TransportAddr,
232    ) -> Option<std::net::SocketAddr> {
233        match self {
234            TransportHandle::Udp(t) => t.resolved_socket_addr_if_cached(addr),
235            _ => None,
236        }
237    }
238
239    /// Get the interface name (Ethernet only, returns None for other transports).
240    pub fn interface_name(&self) -> Option<&str> {
241        match self {
242            TransportHandle::Udp(_) => None,
243            #[cfg(feature = "sim-transport")]
244            TransportHandle::Sim(_) => None,
245            #[cfg(any(target_os = "linux", target_os = "macos"))]
246            TransportHandle::Ethernet(t) => Some(t.interface_name()),
247            TransportHandle::Tcp(_) => None,
248            TransportHandle::Tor(_) => None,
249            #[cfg(feature = "webrtc-transport")]
250            TransportHandle::WebRtc(_) => None,
251            #[cfg(target_os = "linux")]
252            TransportHandle::Ble(_) => None,
253        }
254    }
255
256    /// Get the onion service address (Tor only, returns None for other transports).
257    pub fn onion_address(&self) -> Option<&str> {
258        match self {
259            TransportHandle::Tor(t) => t.onion_address(),
260            _ => None,
261        }
262    }
263
264    /// Get cached Tor daemon monitoring info (Tor only).
265    pub fn tor_monitoring(&self) -> Option<TorMonitoringInfo> {
266        match self {
267            TransportHandle::Tor(t) => t.cached_monitoring(),
268            _ => None,
269        }
270    }
271
272    /// Get the Tor transport mode (Tor only).
273    pub fn tor_mode(&self) -> Option<&str> {
274        match self {
275            TransportHandle::Tor(t) => Some(t.mode()),
276            _ => None,
277        }
278    }
279
280    /// Drain discovered peers from this transport.
281    pub fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
282        match self {
283            TransportHandle::Udp(t) => t.discover(),
284            #[cfg(feature = "sim-transport")]
285            TransportHandle::Sim(t) => t.discover(),
286            #[cfg(any(target_os = "linux", target_os = "macos"))]
287            TransportHandle::Ethernet(t) => t.discover(),
288            TransportHandle::Tcp(t) => t.discover(),
289            TransportHandle::Tor(t) => t.discover(),
290            #[cfg(feature = "webrtc-transport")]
291            TransportHandle::WebRtc(t) => t.discover(),
292            #[cfg(target_os = "linux")]
293            TransportHandle::Ble(t) => t.discover(),
294        }
295    }
296
297    /// Whether this transport auto-connects to discovered peers.
298    pub fn auto_connect(&self) -> bool {
299        match self {
300            TransportHandle::Udp(t) => t.auto_connect(),
301            #[cfg(feature = "sim-transport")]
302            TransportHandle::Sim(t) => t.auto_connect(),
303            #[cfg(any(target_os = "linux", target_os = "macos"))]
304            TransportHandle::Ethernet(t) => t.auto_connect(),
305            TransportHandle::Tcp(t) => t.auto_connect(),
306            TransportHandle::Tor(t) => t.auto_connect(),
307            #[cfg(feature = "webrtc-transport")]
308            TransportHandle::WebRtc(t) => t.auto_connect(),
309            #[cfg(target_os = "linux")]
310            TransportHandle::Ble(t) => t.auto_connect(),
311        }
312    }
313
314    /// Whether this transport accepts inbound connections.
315    pub fn accept_connections(&self) -> bool {
316        match self {
317            TransportHandle::Udp(t) => t.accept_connections(),
318            #[cfg(feature = "sim-transport")]
319            TransportHandle::Sim(t) => t.accept_connections(),
320            #[cfg(any(target_os = "linux", target_os = "macos"))]
321            TransportHandle::Ethernet(t) => t.accept_connections(),
322            TransportHandle::Tcp(t) => t.accept_connections(),
323            TransportHandle::Tor(t) => t.accept_connections(),
324            #[cfg(feature = "webrtc-transport")]
325            TransportHandle::WebRtc(t) => t.accept_connections(),
326            #[cfg(target_os = "linux")]
327            TransportHandle::Ble(t) => t.accept_connections(),
328        }
329    }
330
331    /// Initiate a non-blocking connection to a remote address.
332    ///
333    /// For connection-oriented transports (TCP, Tor), spawns a background
334    /// task to establish the connection. For connectionless transports
335    /// (UDP, Ethernet), this is a no-op that returns Ok immediately.
336    ///
337    /// Poll `connection_state()` to check when the connection is ready.
338    pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> {
339        match self {
340            TransportHandle::Udp(_) => Ok(()), // connectionless
341            #[cfg(feature = "sim-transport")]
342            TransportHandle::Sim(_) => Ok(()), // connectionless
343            #[cfg(any(target_os = "linux", target_os = "macos"))]
344            TransportHandle::Ethernet(_) => Ok(()), // connectionless
345            TransportHandle::Tcp(t) => t.connect_async(addr).await,
346            TransportHandle::Tor(t) => t.connect_async(addr).await,
347            #[cfg(feature = "webrtc-transport")]
348            TransportHandle::WebRtc(t) => t.connect_async(addr).await,
349            #[cfg(target_os = "linux")]
350            TransportHandle::Ble(t) => t.connect_async(addr).await,
351        }
352    }
353
354    /// Query the state of a connection attempt to a remote address.
355    ///
356    /// For connectionless transports, always returns `ConnectionState::Connected`
357    /// (they are always "connected"). For connection-oriented transports, returns
358    /// the current state of the background connection attempt.
359    pub fn connection_state(&self, addr: &TransportAddr) -> ConnectionState {
360        match self {
361            TransportHandle::Udp(_) => ConnectionState::Connected,
362            #[cfg(feature = "sim-transport")]
363            TransportHandle::Sim(_) => ConnectionState::Connected,
364            #[cfg(any(target_os = "linux", target_os = "macos"))]
365            TransportHandle::Ethernet(_) => ConnectionState::Connected,
366            TransportHandle::Tcp(t) => t.connection_state_sync(addr),
367            TransportHandle::Tor(t) => t.connection_state_sync(addr),
368            #[cfg(feature = "webrtc-transport")]
369            TransportHandle::WebRtc(t) => t.connection_state_sync(addr),
370            #[cfg(target_os = "linux")]
371            TransportHandle::Ble(t) => t.connection_state_sync(addr),
372        }
373    }
374
375    /// Close a specific connection on this transport.
376    ///
377    /// No-op for connectionless transports. For TCP/Tor, removes the
378    /// connection from the pool and drops the stream.
379    pub async fn close_connection(&self, addr: &TransportAddr) {
380        match self {
381            TransportHandle::Udp(t) => t.close_connection(addr),
382            #[cfg(feature = "sim-transport")]
383            TransportHandle::Sim(t) => t.close_connection(addr),
384            #[cfg(any(target_os = "linux", target_os = "macos"))]
385            TransportHandle::Ethernet(t) => t.close_connection(addr),
386            TransportHandle::Tcp(t) => t.close_connection_async(addr).await,
387            TransportHandle::Tor(t) => t.close_connection_async(addr).await,
388            #[cfg(feature = "webrtc-transport")]
389            TransportHandle::WebRtc(t) => t.close_connection_async(addr).await,
390            #[cfg(target_os = "linux")]
391            TransportHandle::Ble(t) => t.close_connection_async(addr).await,
392        }
393    }
394
395    /// Check if transport is operational.
396    pub fn is_operational(&self) -> bool {
397        self.state().is_operational()
398    }
399
400    /// Query transport-local congestion indicators.
401    ///
402    /// Returns a snapshot of congestion signals that the transport can
403    /// observe locally (e.g., kernel receive buffer drops). Fields are
404    /// `None` when the transport doesn't support that signal.
405    pub fn congestion(&self) -> TransportCongestion {
406        match self {
407            TransportHandle::Udp(t) => t.congestion(),
408            #[cfg(feature = "sim-transport")]
409            TransportHandle::Sim(_) => TransportCongestion::default(),
410            #[cfg(any(target_os = "linux", target_os = "macos"))]
411            TransportHandle::Ethernet(_) => TransportCongestion::default(),
412            TransportHandle::Tcp(_) => TransportCongestion::default(),
413            TransportHandle::Tor(_) => TransportCongestion::default(),
414            #[cfg(feature = "webrtc-transport")]
415            TransportHandle::WebRtc(_) => TransportCongestion::default(),
416            #[cfg(target_os = "linux")]
417            TransportHandle::Ble(_) => TransportCongestion::default(),
418        }
419    }
420
421    /// Get transport-specific stats as a JSON value.
422    ///
423    /// Returns a snapshot of counters for the specific transport type.
424    pub fn transport_stats(&self) -> serde_json::Value {
425        match self {
426            TransportHandle::Udp(t) => {
427                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
428            }
429            #[cfg(feature = "sim-transport")]
430            TransportHandle::Sim(t) => serde_json::to_value(t.stats()).unwrap_or_default(),
431            #[cfg(any(target_os = "linux", target_os = "macos"))]
432            TransportHandle::Ethernet(t) => {
433                let snap = t.stats().snapshot();
434                serde_json::json!({
435                    "frames_sent": snap.frames_sent,
436                    "frames_recv": snap.frames_recv,
437                    "bytes_sent": snap.bytes_sent,
438                    "bytes_recv": snap.bytes_recv,
439                    "send_errors": snap.send_errors,
440                    "recv_errors": snap.recv_errors,
441                    "beacons_sent": snap.beacons_sent,
442                    "beacons_recv": snap.beacons_recv,
443                    "frames_too_short": snap.frames_too_short,
444                    "frames_too_long": snap.frames_too_long,
445                })
446            }
447            TransportHandle::Tcp(t) => {
448                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
449            }
450            TransportHandle::Tor(t) => {
451                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
452            }
453            #[cfg(feature = "webrtc-transport")]
454            TransportHandle::WebRtc(t) => serde_json::json!({
455                "mtu": t.mtu(),
456                "state": t.state().to_string(),
457            }),
458            #[cfg(target_os = "linux")]
459            TransportHandle::Ble(t) => {
460                serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
461            }
462        }
463    }
464}