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