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