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