Skip to main content

rns_net/interface/
mod.rs

1//! Network interface abstractions.
2
3#[cfg(feature = "iface-auto")]
4pub mod auto;
5#[cfg(feature = "iface-kiss")]
6pub mod ax25_kiss;
7#[cfg(feature = "iface-backbone")]
8pub mod backbone;
9#[cfg(feature = "iface-i2p")]
10pub mod i2p;
11#[cfg(feature = "iface-kiss")]
12pub mod kiss_iface;
13#[cfg(feature = "iface-local")]
14pub mod local;
15pub(crate) mod loop_writer;
16#[cfg(feature = "iface-pipe")]
17pub mod pipe;
18pub mod registry;
19#[cfg(feature = "iface-rnode")]
20pub mod rnode;
21#[cfg(feature = "iface-serial")]
22pub mod serial_iface;
23#[cfg(feature = "iface-tcp")]
24pub mod tcp;
25#[cfg(feature = "iface-tcp")]
26pub mod tcp_server;
27pub(crate) mod transmit_buffer;
28#[cfg(feature = "iface-udp")]
29pub mod udp;
30#[cfg(all(feature = "iface-weave", target_os = "linux"))]
31pub mod weave;
32
33use std::any::Any;
34use std::collections::HashMap;
35use std::io;
36use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
37use std::sync::mpsc::TrySendError;
38use std::sync::{Arc, Condvar, Mutex, MutexGuard};
39use std::thread;
40use std::time::{Duration, Instant};
41
42use crate::event::EventSender;
43use crate::ifac::IfacState;
44use rns_core::transport::types::{InterfaceId, InterfaceInfo};
45
46/// Host callback used where every Reticulum underlay socket must be explicitly
47/// excluded from an application VPN, notably Android `VpnService.protect()`.
48pub trait SocketProtector: Send + Sync {
49    fn protect(&self, fd: std::os::unix::io::RawFd) -> io::Result<()>;
50}
51
52fn socket_protector_slot() -> &'static std::sync::RwLock<Option<Arc<dyn SocketProtector>>> {
53    static SLOT: std::sync::OnceLock<std::sync::RwLock<Option<Arc<dyn SocketProtector>>>> =
54        std::sync::OnceLock::new();
55    SLOT.get_or_init(|| std::sync::RwLock::new(None))
56}
57
58/// Exclusive process-scoped socket protection registration.
59///
60/// Android VPN services should run their private node in a dedicated process.
61/// A second registration fails instead of replacing a callback used by live
62/// sockets.
63pub struct SocketProtectorGuard {
64    protector: Arc<dyn SocketProtector>,
65}
66
67pub fn install_socket_protector(
68    protector: Arc<dyn SocketProtector>,
69) -> io::Result<SocketProtectorGuard> {
70    let mut slot = socket_protector_slot()
71        .write()
72        .unwrap_or_else(|poisoned| poisoned.into_inner());
73    if slot.is_some() {
74        return Err(io::Error::new(
75            io::ErrorKind::AlreadyExists,
76            "an underlay socket protector is already installed",
77        ));
78    }
79    *slot = Some(Arc::clone(&protector));
80    Ok(SocketProtectorGuard { protector })
81}
82
83#[cfg(test)]
84mod socket_protector_tests {
85    use super::*;
86    use std::sync::atomic::{AtomicUsize, Ordering};
87
88    static TEST_LOCK: Mutex<()> = Mutex::new(());
89    struct CountingProtector(Arc<AtomicUsize>);
90    impl SocketProtector for CountingProtector {
91        fn protect(&self, _fd: std::os::unix::io::RawFd) -> io::Result<()> {
92            self.0.fetch_add(1, Ordering::Relaxed);
93            Ok(())
94        }
95    }
96
97    #[test]
98    fn underlay_helper_invokes_installed_protector_without_mark() {
99        use std::os::fd::AsRawFd;
100        let _lock = TEST_LOCK.lock().unwrap();
101        let calls = Arc::new(AtomicUsize::new(0));
102        let _guard =
103            install_socket_protector(Arc::new(CountingProtector(Arc::clone(&calls)))).unwrap();
104        let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
105        apply_underlay_mark(socket.as_raw_fd(), None).unwrap();
106        assert!(calls.load(Ordering::Relaxed) >= 1);
107    }
108}
109
110impl Drop for SocketProtectorGuard {
111    fn drop(&mut self) {
112        let mut slot = socket_protector_slot()
113            .write()
114            .unwrap_or_else(|poisoned| poisoned.into_inner());
115        if slot
116            .as_ref()
117            .is_some_and(|installed| Arc::ptr_eq(installed, &self.protector))
118        {
119            *slot = None;
120        }
121    }
122}
123
124fn protect_underlay_socket(fd: std::os::unix::io::RawFd) -> io::Result<()> {
125    let protector = socket_protector_slot()
126        .read()
127        .unwrap_or_else(|poisoned| poisoned.into_inner())
128        .clone();
129    if let Some(protector) = protector {
130        protector.protect(fd)?;
131    }
132    Ok(())
133}
134
135/// Bind a socket to a specific network interface using `SO_BINDTODEVICE`.
136///
137/// Requires `CAP_NET_RAW` or root on Linux.
138#[cfg(target_os = "linux")]
139pub fn bind_to_device(fd: std::os::unix::io::RawFd, device: &str) -> io::Result<()> {
140    let dev_bytes = device.as_bytes();
141    if dev_bytes.len() >= libc::IFNAMSIZ {
142        return Err(io::Error::new(
143            io::ErrorKind::InvalidInput,
144            format!("device name too long: {}", device),
145        ));
146    }
147    let ret = unsafe {
148        libc::setsockopt(
149            fd,
150            libc::SOL_SOCKET,
151            libc::SO_BINDTODEVICE,
152            dev_bytes.as_ptr() as *const libc::c_void,
153            dev_bytes.len() as libc::socklen_t,
154        )
155    };
156    if ret != 0 {
157        return Err(io::Error::last_os_error());
158    }
159    Ok(())
160}
161
162/// Apply a Linux firewall mark to an IP underlay socket.
163///
164/// The mark must be set before `connect()` so policy routing can keep Reticulum
165/// underlay traffic outside application-level full tunnels. Configuring a mark
166/// is fail-closed: callers must not silently create an unmarked socket when the
167/// operation is not permitted.
168#[cfg(target_os = "linux")]
169pub fn apply_underlay_mark(fd: std::os::unix::io::RawFd, mark: Option<u32>) -> io::Result<()> {
170    protect_underlay_socket(fd)?;
171    let Some(mark) = mark else {
172        return Ok(());
173    };
174    let ret = unsafe {
175        libc::setsockopt(
176            fd,
177            libc::SOL_SOCKET,
178            libc::SO_MARK,
179            &mark as *const _ as *const libc::c_void,
180            std::mem::size_of::<u32>() as libc::socklen_t,
181        )
182    };
183    if ret != 0 {
184        return Err(io::Error::last_os_error());
185    }
186    Ok(())
187}
188
189#[cfg(not(target_os = "linux"))]
190pub fn apply_underlay_mark(fd: std::os::unix::io::RawFd, mark: Option<u32>) -> io::Result<()> {
191    protect_underlay_socket(fd)?;
192    if mark.is_some() {
193        return Err(io::Error::new(
194            io::ErrorKind::Unsupported,
195            "underlay socket marks are only supported on Linux",
196        ));
197    }
198    Ok(())
199}
200
201/// Connect a TCP socket after applying underlay routing options.
202#[cfg(target_os = "linux")]
203pub fn connect_tcp_with_options(
204    addr: &std::net::SocketAddr,
205    device: Option<&str>,
206    underlay_mark: Option<u32>,
207    timeout: Duration,
208) -> io::Result<std::net::TcpStream> {
209    use std::os::unix::io::AsRawFd;
210
211    let socket = socket2::Socket::new(
212        socket2::Domain::for_address(*addr),
213        socket2::Type::STREAM,
214        Some(socket2::Protocol::TCP),
215    )?;
216    apply_underlay_mark(socket.as_raw_fd(), underlay_mark)?;
217    if let Some(device) = device {
218        bind_to_device(socket.as_raw_fd(), device)?;
219    }
220    socket.connect_timeout(&(*addr).into(), timeout)?;
221    Ok(socket.into())
222}
223
224/// Validate at node startup that the configured mark can actually be applied.
225pub fn validate_underlay_mark(mark: Option<u32>) -> io::Result<()> {
226    let Some(_) = mark else {
227        return Ok(());
228    };
229    let socket = socket2::Socket::new(
230        socket2::Domain::IPV4,
231        socket2::Type::DGRAM,
232        Some(socket2::Protocol::UDP),
233    )?;
234    use std::os::unix::io::AsRawFd;
235    apply_underlay_mark(socket.as_raw_fd(), mark)
236}
237
238/// Writable end of an interface. Held by the driver.
239///
240/// Each implementation wraps a socket + framing.
241pub trait Writer: Send {
242    fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
243
244    /// Write one frame without silently dropping it. WouldBlock means the
245    /// frame was accepted and `flush` must finish it before success is reported.
246    /// Writers with intentional drop policies must override this method.
247    fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
248        self.send_frame(data)
249    }
250
251    /// Internal async-writer admission. Leaves completion untouched on failure.
252    #[doc(hidden)]
253    fn enqueue_confirmed(
254        &mut self,
255        _data: &[u8],
256        _completion: &mut Option<crate::link_send::Completion>,
257    ) -> io::Result<()> {
258        Err(io::Error::new(
259            io::ErrorKind::Unsupported,
260            "confirmed sends require an async interface writer",
261        ))
262    }
263
264    /// Like [`Writer::send_frame`], but a writer with its own worker may
265    /// postpone waking it until [`Writer::wake`]. The driver uses this for
266    /// forwarded frames and wakes each touched writer once per event batch.
267    fn send_frame_deferred(&mut self, data: &[u8]) -> io::Result<()> {
268        self.send_frame(data)
269    }
270
271    /// Deferred send of shared frame bytes; writers with a worker queue may
272    /// retain the shared buffer instead of copying it.
273    fn send_shared_deferred(
274        &mut self,
275        data: &rns_core::transport::types::PacketBytes,
276    ) -> io::Result<()> {
277        self.send_frame_deferred(data)
278    }
279
280    /// Wake a worker postponed by [`Writer::send_frame_deferred`].
281    fn wake(&mut self) {}
282
283    /// Write out what a driver-run writer has buffered (see `loop_writer`).
284    /// The driver calls this on every writer before it waits for more
285    /// input, so frames from one pass leave in as few writes as possible.
286    fn flush_pending(&mut self) {}
287
288    fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
289        for frame in frames {
290            self.send_frame(frame)?;
291        }
292        Ok(())
293    }
294
295    /// Resume bytes accepted by a previous call that returned `WouldBlock`.
296    fn flush(&mut self) -> io::Result<()> {
297        Ok(())
298    }
299
300    /// Share an exact byte admission budget with the async producer when this
301    /// writer uses dataplane egress control.
302    fn egress_control(&self) -> Option<EgressControl> {
303        None
304    }
305
306    /// A stream socket the driver may write directly, without a worker
307    /// thread (see `loop_writer`). Writers returning one must only frame
308    /// with HDLC and write the result to this descriptor.
309    fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
310        None
311    }
312
313    /// Byte-budget egress rules for a [`Writer::loop_socket`] writer, taken
314    /// once when the driver starts writing it. Without them the driver
315    /// applies its default backpressure.
316    #[doc(hidden)]
317    fn loop_egress(&mut self) -> Option<loop_writer::LoopEgress> {
318        None
319    }
320
321    /// Called by the driver once per tick, so driver-run writers can detect
322    /// a peer that stopped reading even when nothing new is sent.
323    #[doc(hidden)]
324    fn tick(&mut self) {}
325
326    fn shutdown(&mut self) {}
327}
328
329/// Hasher for maps keyed by [`InterfaceId`], looked up several times per
330/// forwarded packet. IDs are small trusted integers, so a multiplicative mix
331/// replaces SipHash, whose per-lookup cost showed up in forwarding profiles.
332#[derive(Default, Clone, Copy)]
333pub struct InterfaceIdHasher(u64);
334
335impl std::hash::Hasher for InterfaceIdHasher {
336    fn write(&mut self, bytes: &[u8]) {
337        for byte in bytes {
338            self.write_u64(u64::from(*byte));
339        }
340    }
341
342    fn write_u64(&mut self, value: u64) {
343        self.0 = (self.0 ^ value).wrapping_mul(0x9E37_79B9_7F4A_7C15);
344    }
345
346    fn finish(&self) -> u64 {
347        self.0 ^ (self.0 >> 29)
348    }
349}
350
351/// Map keyed by interface ID using [`InterfaceIdHasher`].
352pub type InterfaceMap<V> =
353    HashMap<InterfaceId, V, std::hash::BuildHasherDefault<InterfaceIdHasher>>;
354
355pub const DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY: usize = 256;
356
357/// Longest a sleeping writer worker goes without rechecking its queue, a
358/// backstop for wakeups postponed by `send_frame_deferred`.
359const WRITER_RECHECK: Duration = Duration::from_millis(5);
360
361/// Longest time a producer waits for a busy async writer to free queue space
362/// or byte budget before treating the frame as undeliverable.
363///
364/// A healthy writer drains its queue in well under a millisecond, so this
365/// bound converts momentary bursts into backpressure (the producer pauses and
366/// the inbound queue fills) instead of silent loss. A writer that stays full
367/// for the whole bound is treated as stalled: the frame is refused and the
368/// driver's per-interface send deferral protects other interfaces.
369pub const ASYNC_WRITER_BACKPRESSURE_WAIT: Duration = Duration::from_millis(20);
370
371#[derive(Clone)]
372#[doc(hidden)]
373pub struct EgressControl {
374    byte_limit: usize,
375    reserved_bytes: Arc<AtomicUsize>,
376    stalled: Arc<AtomicBool>,
377    dropped_frames: Arc<AtomicU64>,
378    dropped_bytes: Arc<AtomicU64>,
379}
380
381impl EgressControl {
382    pub(crate) fn new(byte_limit: usize) -> Self {
383        Self {
384            byte_limit,
385            reserved_bytes: Arc::new(AtomicUsize::new(0)),
386            stalled: Arc::new(AtomicBool::new(false)),
387            dropped_frames: Arc::new(AtomicU64::new(0)),
388            dropped_bytes: Arc::new(AtomicU64::new(0)),
389        }
390    }
391
392    fn try_reserve(&self, bytes: usize) -> bool {
393        if self.stalled.load(Ordering::Relaxed) {
394            return false;
395        }
396        let mut current = self.reserved_bytes.load(Ordering::Relaxed);
397        loop {
398            let Some(next) = current.checked_add(bytes) else {
399                return false;
400            };
401            if next > self.byte_limit {
402                return false;
403            }
404            match self.reserved_bytes.compare_exchange_weak(
405                current,
406                next,
407                Ordering::Relaxed,
408                Ordering::Relaxed,
409            ) {
410                Ok(_) => return true,
411                Err(observed) => current = observed,
412            }
413        }
414    }
415
416    fn release(&self, bytes: usize) {
417        self.reserved_bytes.fetch_sub(bytes, Ordering::Relaxed);
418    }
419
420    pub(crate) fn stalled(&self) -> bool {
421        self.stalled.load(Ordering::Relaxed)
422    }
423
424    pub(crate) fn set_stalled(&self, stalled: bool) {
425        self.stalled.store(stalled, Ordering::Relaxed);
426    }
427
428    pub(crate) fn record_drop(&self, bytes: usize) {
429        self.dropped_frames.fetch_add(1, Ordering::Relaxed);
430        self.dropped_bytes
431            .fetch_add(bytes as u64, Ordering::Relaxed);
432    }
433}
434
435/// Return whether an interface type normally operates on a shared medium.
436///
437/// This is a type-level hint: runtime configuration can still describe links
438/// with different physical properties. It mirrors Reticulum's interface-class
439/// defaults and is suitable for discovery/capability metadata.
440pub fn shared_medium_hint(interface_type: &str) -> bool {
441    matches!(
442        interface_type,
443        "AX25KISSInterface"
444            | "KISSInterface"
445            | "PipeInterface"
446            | "RNodeInterface"
447            | "RNodeMultiInterface"
448            | "SerialInterface"
449            | "UDPInterface"
450    )
451}
452
453/// Hardware MTU tier shared by TCP and Backbone interfaces.
454pub(crate) const fn auto_mtu_for_bitrate(bitrate: u64) -> Option<u32> {
455    if bitrate >= 1_000_000_000 {
456        Some(524_288)
457    } else if bitrate >= 750_000_000 {
458        Some(262_144)
459    } else if bitrate >= 400_000_000 {
460        Some(131_072)
461    } else if bitrate >= 200_000_000 {
462        Some(65_536)
463    } else if bitrate >= 100_000_000 {
464        Some(32_768)
465    } else if bitrate >= 10_000_000 {
466        Some(16_384)
467    } else if bitrate >= 5_000_000 {
468        Some(8_192)
469    } else if bitrate >= 2_000_000 {
470        Some(4_096)
471    } else if bitrate >= 1_000_000 {
472        Some(2_048)
473    } else if bitrate >= 62_500 {
474        Some(1_024)
475    } else {
476        None
477    }
478}
479
480/// Hardware MTU of a TCP interface unless `bitrate` or `fixed_mtu` is configured.
481pub(crate) const TCP_DEFAULT_MTU: u32 = 65535;
482
483/// The optional `bitrate` and `fixed_mtu` keys of a TCP interface, with
484/// upstream's meaning: `fixed_mtu` sets the hardware MTU and must be at least
485/// the base MTU; `bitrate` is the interface bitrate.
486pub(crate) fn parse_tcp_rate_params(
487    params: &HashMap<String, String>,
488) -> Result<(Option<u64>, Option<u32>), String> {
489    let bitrate = match params.get("bitrate") {
490        Some(value) => Some(
491            value
492                .parse::<u64>()
493                .map_err(|_| format!("invalid bitrate {value:?}"))?,
494        ),
495        None => None,
496    };
497    let fixed_mtu = match params.get("fixed_mtu") {
498        Some(value) => {
499            let mtu = value
500                .parse::<u32>()
501                .map_err(|_| format!("invalid fixed_mtu {value:?}"))?;
502            if (mtu as usize) < rns_core::constants::MTU {
503                return Err(format!("Configured MTU of {mtu} bytes is too small"));
504            }
505            Some(mtu)
506        }
507        None => None,
508    };
509    Ok((bitrate, fixed_mtu))
510}
511
512/// Hardware MTU of a TCP interface: `fixed_mtu` when configured, else
513/// upstream's autoconfiguration tier for a configured `bitrate`, else
514/// [`TCP_DEFAULT_MTU`]. Below the lowest tier upstream sets no hardware MTU,
515/// which leaves links at the base MTU.
516pub(crate) fn tcp_hardware_mtu(bitrate: Option<u64>, fixed_mtu: Option<u32>) -> u32 {
517    match (fixed_mtu, bitrate) {
518        (Some(mtu), _) => mtu,
519        (None, Some(bitrate)) => {
520            auto_mtu_for_bitrate(bitrate).unwrap_or(rns_core::constants::MTU as u32)
521        }
522        (None, None) => TCP_DEFAULT_MTU,
523    }
524}
525
526pub(crate) fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>, label: &str) -> MutexGuard<'a, T> {
527    match mutex.lock() {
528        Ok(guard) => guard,
529        Err(poisoned) => {
530            log::warn!("recovering poisoned mutex: {}", label);
531            poisoned.into_inner()
532        }
533    }
534}
535
536#[derive(Clone, Default)]
537pub struct ListenerControl {
538    stop: Arc<AtomicBool>,
539    running: Option<Arc<AtomicBool>>,
540}
541
542impl ListenerControl {
543    pub fn new() -> Self {
544        Self {
545            stop: Arc::new(AtomicBool::new(false)),
546            running: None,
547        }
548    }
549
550    pub(crate) fn with_running_flag(running: Arc<AtomicBool>) -> Self {
551        Self {
552            stop: Arc::new(AtomicBool::new(false)),
553            running: Some(running),
554        }
555    }
556
557    pub fn request_stop(&self) {
558        self.stop.store(true, Ordering::Relaxed);
559        if let Some(running) = &self.running {
560            running.store(false, Ordering::Relaxed);
561        }
562    }
563
564    pub fn should_stop(&self) -> bool {
565        self.stop.load(Ordering::Relaxed)
566    }
567
568    pub(crate) fn same_instance(&self, other: &Self) -> bool {
569        Arc::ptr_eq(&self.stop, &other.stop)
570    }
571}
572
573/// Wait for serial input without keeping a detached reader blocked forever.
574pub(crate) fn poll_readable(fd: std::os::fd::RawFd, timeout: Duration) -> io::Result<bool> {
575    let mut pfd = libc::pollfd {
576        fd,
577        events: libc::POLLIN,
578        revents: 0,
579    };
580    let timeout_ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
581    let result = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
582    if result < 0 {
583        let error = io::Error::last_os_error();
584        return if error.kind() == io::ErrorKind::Interrupted {
585            Ok(false)
586        } else {
587            Err(error)
588        };
589    }
590    if pfd.revents & libc::POLLNVAL != 0 {
591        return Err(io::Error::new(
592            io::ErrorKind::BrokenPipe,
593            "serial port closed",
594        ));
595    }
596    Ok(result > 0)
597}
598
599pub(crate) fn wait_while_active(control: &ListenerControl, duration: Duration) -> bool {
600    let deadline = Instant::now() + duration;
601    while !control.should_stop() {
602        let remaining = deadline.saturating_duration_since(Instant::now());
603        if remaining.is_zero() {
604            return true;
605        }
606        thread::sleep(remaining.min(Duration::from_millis(100)));
607    }
608    false
609}
610
611pub(crate) struct UnavailableWriter;
612
613impl Writer for UnavailableWriter {
614    fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
615        Err(io::Error::new(
616            io::ErrorKind::NotConnected,
617            "interface is waiting for its port",
618        ))
619    }
620}
621
622#[derive(Clone, Default)]
623pub struct AsyncWriterMetrics {
624    queued_frames: Arc<AtomicUsize>,
625    worker_alive: Arc<AtomicBool>,
626    egress_control: Option<EgressControl>,
627    /// Signalled by the worker whenever it frees queue slots or byte budget.
628    space: Arc<(Mutex<()>, Condvar)>,
629    /// Producers inside a backpressure wait. Incremented under the `space`
630    /// lock before the producer's queue attempt, so a worker that frees space
631    /// after a failed attempt always observes it and notifies.
632    space_waiters: Arc<AtomicUsize>,
633}
634
635impl AsyncWriterMetrics {
636    fn notify_space(&self) {
637        if self.space_waiters.load(Ordering::SeqCst) == 0 {
638            return;
639        }
640        let (lock, condvar) = &*self.space;
641        let _guard = lock_or_recover(lock, "async writer space");
642        condvar.notify_all();
643    }
644
645    pub fn queued_frames(&self) -> usize {
646        self.queued_frames.load(Ordering::Relaxed)
647    }
648
649    pub fn worker_alive(&self) -> bool {
650        self.worker_alive.load(Ordering::Relaxed)
651    }
652
653    /// Exact encoded bytes waiting in the async queue or concrete writer.
654    pub fn tx_buffered(&self) -> usize {
655        self.egress_control
656            .as_ref()
657            .map(|control| control.reserved_bytes.load(Ordering::Relaxed))
658            .unwrap_or(0)
659    }
660
661    pub fn tx_drops(&self) -> u64 {
662        self.egress_control
663            .as_ref()
664            .map(|control| control.dropped_frames.load(Ordering::Relaxed))
665            .unwrap_or(0)
666    }
667
668    pub fn tx_dropped_bytes(&self) -> u64 {
669        self.egress_control
670            .as_ref()
671            .map(|control| control.dropped_bytes.load(Ordering::Relaxed))
672            .unwrap_or(0)
673    }
674
675    pub fn tx_stalled(&self) -> bool {
676        self.egress_control
677            .as_ref()
678            .is_some_and(EgressControl::stalled)
679    }
680}
681
682/// Bounded frame queue between the driver and one interface writer thread.
683///
684/// The worker drains every queued frame in one lock acquisition. This
685/// replaces a std sync channel whose receiver spins while it observes a send
686/// in progress, which on a single CPU burned the rest of the time slice.
687struct WriterQueue {
688    state: Mutex<WriterQueueState>,
689    ready: Condvar,
690    capacity: usize,
691}
692
693struct WriterQueueState {
694    frames: std::collections::VecDeque<QueuedFrame>,
695    sender_closed: bool,
696    receiver_closed: bool,
697    receiver_waiting: bool,
698}
699
700impl WriterQueue {
701    fn new(capacity: usize) -> Arc<Self> {
702        Arc::new(Self {
703            state: Mutex::new(WriterQueueState {
704                frames: std::collections::VecDeque::new(),
705                sender_closed: false,
706                receiver_closed: false,
707                receiver_waiting: false,
708            }),
709            ready: Condvar::new(),
710            capacity: capacity.max(1),
711        })
712    }
713
714    /// Wait until frames are queued, then move all of them into `out`.
715    /// Returns false once the sender is gone and nothing remains.
716    fn recv_all(&self, out: &mut Vec<QueuedFrame>) -> bool {
717        let mut state = lock_or_recover(&self.state, "async writer queue");
718        while state.frames.is_empty() {
719            if state.sender_closed {
720                return false;
721            }
722            state.receiver_waiting = true;
723            // Producers may postpone the wakeup (see send_frame_deferred);
724            // the periodic recheck bounds any delay if one is never sent.
725            state = match self.ready.wait_timeout(state, WRITER_RECHECK) {
726                Ok((state, _)) => state,
727                Err(poisoned) => poisoned.into_inner().0,
728            };
729            state.receiver_waiting = false;
730        }
731        out.extend(state.frames.drain(..));
732        true
733    }
734
735    /// Wake the worker if it sleeps while frames are queued.
736    fn kick(&self) {
737        let state = lock_or_recover(&self.state, "async writer queue");
738        let wake = state.receiver_waiting && !state.frames.is_empty();
739        drop(state);
740        if wake {
741            self.ready.notify_one();
742        }
743    }
744
745    fn close_receiver(&self) {
746        lock_or_recover(&self.state, "async writer queue").receiver_closed = true;
747    }
748}
749
750/// Producer handle; dropping it lets the worker exit after draining.
751struct WriterTx(Arc<WriterQueue>);
752
753impl WriterTx {
754    /// Queue a frame. Returns whether the worker still needs waking: false
755    /// when it was woken here (`notify`) or was not waiting.
756    fn try_send(
757        &self,
758        frame: QueuedFrame,
759        notify: bool,
760    ) -> Result<bool, TrySendError<QueuedFrame>> {
761        let mut state = lock_or_recover(&self.0.state, "async writer queue");
762        if state.receiver_closed {
763            return Err(TrySendError::Disconnected(frame));
764        }
765        if state.frames.len() >= self.0.capacity {
766            return Err(TrySendError::Full(frame));
767        }
768        state.frames.push_back(frame);
769        let waiting = state.receiver_waiting;
770        drop(state);
771        if waiting && notify {
772            self.0.ready.notify_one();
773        }
774        Ok(waiting && !notify)
775    }
776}
777
778impl Drop for WriterTx {
779    fn drop(&mut self) {
780        lock_or_recover(&self.0.state, "async writer queue").sender_closed = true;
781        self.0.ready.notify_one();
782    }
783}
784
785struct AsyncWriter {
786    tx: WriterTx,
787    metrics: AsyncWriterMetrics,
788    /// Set after a backpressure wait times out. Until a frame is admitted
789    /// again, unconfirmed sends make a single attempt instead of waiting,
790    /// so a stalled peer costs the producer at most one wait.
791    skip_wait: bool,
792    /// Set while a deferred send is in progress.
793    defer_wake: bool,
794    /// A deferred send left the worker asleep with frames queued.
795    wake_pending: bool,
796    /// Longest wait for queue space before an unconfirmed frame is refused.
797    /// [`ASYNC_WRITER_BACKPRESSURE_WAIT`] outside tests.
798    backpressure_wait: Duration,
799}
800
801enum Admission {
802    Done,
803    Refused(QueuedFrame),
804}
805
806/// Frame bytes queued for a writer: owned, or shared with the driver's
807/// forwarding buffer so queueing a forwarded frame does not copy it.
808pub enum FrameBytes {
809    Owned(Vec<u8>),
810    Shared(rns_core::transport::types::PacketBytes),
811}
812
813impl std::ops::Deref for FrameBytes {
814    type Target = [u8];
815
816    fn deref(&self) -> &[u8] {
817        match self {
818            FrameBytes::Owned(bytes) => bytes,
819            FrameBytes::Shared(bytes) => bytes,
820        }
821    }
822}
823
824struct QueuedFrame {
825    data: FrameBytes,
826    reserved_bytes: usize,
827    completion: Option<crate::link_send::Completion>,
828}
829
830impl Writer for AsyncWriter {
831    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
832        self.enqueue(data, &mut None)
833    }
834
835    fn send_frame_deferred(&mut self, data: &[u8]) -> io::Result<()> {
836        self.defer_wake = true;
837        let result = self.enqueue(data, &mut None);
838        self.defer_wake = false;
839        result
840    }
841
842    fn send_shared_deferred(
843        &mut self,
844        data: &rns_core::transport::types::PacketBytes,
845    ) -> io::Result<()> {
846        self.defer_wake = true;
847        let result = self.enqueue_unconfirmed(FrameBytes::Shared(data.clone()));
848        self.defer_wake = false;
849        result
850    }
851
852    fn wake(&mut self) {
853        if std::mem::take(&mut self.wake_pending) {
854            self.tx.0.kick();
855        }
856    }
857
858    fn enqueue_confirmed(
859        &mut self,
860        data: &[u8],
861        completion: &mut Option<crate::link_send::Completion>,
862    ) -> io::Result<()> {
863        self.enqueue(data, completion)
864    }
865}
866
867impl AsyncWriter {
868    /// Admit an unconfirmed frame, waiting up to the backpressure wait
869    /// ([`ASYNC_WRITER_BACKPRESSURE_WAIT`]) while the worker is busy.
870    ///
871    /// A stalled peer (as reported by the byte-budget controller) is refused
872    /// immediately so that one dead connection cannot repeatedly pause the
873    /// driver. Over-budget frames that can never fit are dropped as before.
874    fn enqueue_unconfirmed(&mut self, data: FrameBytes) -> io::Result<()> {
875        let mut frame = QueuedFrame {
876            data,
877            reserved_bytes: 0,
878            completion: None,
879        };
880        // Fast path without the space lock: almost every frame fits.
881        match self.try_admit(frame)? {
882            Admission::Done => return Ok(()),
883            Admission::Refused(returned) => frame = returned,
884        }
885        if self.skip_wait {
886            return self.refuse(frame);
887        }
888        // The worker must run to free space; wake it if a deferred send left
889        // it asleep.
890        self.wake();
891        let space = Arc::clone(&self.metrics.space);
892        let (lock, condvar) = &*space;
893        let mut guard = lock_or_recover(lock, "async writer space");
894        // Register before retrying: a worker that frees space after this
895        // retry fails observes the waiter and must take the lock to notify,
896        // which it can only do once this thread is waiting.
897        self.metrics.space_waiters.fetch_add(1, Ordering::SeqCst);
898        let deadline = Instant::now() + self.backpressure_wait;
899        let result = loop {
900            match self.try_admit(frame) {
901                Err(error) => break Err(error),
902                Ok(Admission::Done) => break Ok(()),
903                Ok(Admission::Refused(returned)) => frame = returned,
904            }
905            let now = Instant::now();
906            if now >= deadline {
907                self.skip_wait = true;
908                break self.refuse(frame);
909            }
910            guard = match condvar.wait_timeout(guard, deadline - now) {
911                Ok((guard, _)) => guard,
912                Err(poisoned) => poisoned.into_inner().0,
913            };
914        };
915        self.metrics.space_waiters.fetch_sub(1, Ordering::SeqCst);
916        drop(guard);
917        result
918    }
919
920    /// One admission attempt. Frames that can never be admitted (over the
921    /// byte limit, or a stalled peer) are dropped and counted, as before.
922    fn try_admit(&mut self, mut frame: QueuedFrame) -> io::Result<Admission> {
923        if !self.metrics.worker_alive() {
924            return Err(io::Error::new(
925                io::ErrorKind::BrokenPipe,
926                "interface writer worker is offline",
927            ));
928        }
929        if let Some(control) = &self.metrics.egress_control {
930            let bytes = crate::hdlc::framed_len(&frame.data);
931            if bytes > control.byte_limit || control.stalled() {
932                control.record_drop(bytes);
933                return Ok(Admission::Done);
934            }
935            if !control.try_reserve(bytes) {
936                return Ok(Admission::Refused(frame));
937            }
938            frame.reserved_bytes = bytes;
939        }
940        self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
941        match self.tx.try_send(frame, !self.defer_wake) {
942            Ok(needs_wake) => {
943                self.skip_wait = false;
944                self.wake_pending |= needs_wake;
945                Ok(Admission::Done)
946            }
947            Err(TrySendError::Full(mut returned)) => {
948                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
949                if let Some(control) = &self.metrics.egress_control {
950                    control.release(returned.reserved_bytes);
951                }
952                returned.reserved_bytes = 0;
953                Ok(Admission::Refused(returned))
954            }
955            Err(TrySendError::Disconnected(returned)) => {
956                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
957                if let Some(control) = &self.metrics.egress_control {
958                    control.release(returned.reserved_bytes);
959                }
960                self.metrics.worker_alive.store(false, Ordering::Relaxed);
961                Err(io::Error::new(
962                    io::ErrorKind::BrokenPipe,
963                    "interface writer worker disconnected",
964                ))
965            }
966        }
967    }
968
969    /// Give up on a frame that did not fit. A frame refused for byte budget
970    /// is dropped and counted (the pre-existing egress-control policy); a
971    /// frame refused for queue slots is reported as would-block.
972    fn refuse(&self, frame: QueuedFrame) -> io::Result<()> {
973        if let Some(control) = &self.metrics.egress_control {
974            let bytes = crate::hdlc::framed_len(&frame.data);
975            if self.metrics.queued_frames() < self.tx.0.capacity {
976                control.record_drop(bytes);
977                return Ok(());
978            }
979        }
980        Err(io::Error::new(
981            io::ErrorKind::WouldBlock,
982            "interface writer queue is full",
983        ))
984    }
985
986    fn enqueue(
987        &mut self,
988        data: &[u8],
989        completion: &mut Option<crate::link_send::Completion>,
990    ) -> io::Result<()> {
991        if !self.metrics.worker_alive() {
992            return Err(io::Error::new(
993                io::ErrorKind::BrokenPipe,
994                "interface writer worker is offline",
995            ));
996        }
997
998        // Unconfirmed frames (transit forwarding, announces) wait briefly for a
999        // busy worker instead of being refused at the first full queue.
1000        // Confirmed sends keep their immediate WouldBlock contract: their
1001        // callers already retry through the completion machinery.
1002        if completion.is_none() {
1003            return self.enqueue_unconfirmed(FrameBytes::Owned(data.to_vec()));
1004        }
1005
1006        let reserved_bytes = if let Some(control) = &self.metrics.egress_control {
1007            let framed_bytes = crate::hdlc::framed_len(data);
1008            if !control.try_reserve(framed_bytes) {
1009                if completion.is_some() {
1010                    if framed_bytes > control.byte_limit {
1011                        return Err(io::Error::new(
1012                            io::ErrorKind::InvalidInput,
1013                            "frame exceeds interface byte budget",
1014                        ));
1015                    }
1016                    return Err(io::Error::new(
1017                        io::ErrorKind::WouldBlock,
1018                        "interface egress budget is full",
1019                    ));
1020                }
1021                control.record_drop(framed_bytes);
1022                return Ok(());
1023            }
1024            framed_bytes
1025        } else {
1026            0
1027        };
1028
1029        // Publish accounting before the frame becomes visible to the worker.
1030        self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
1031        match self.tx.try_send(
1032            QueuedFrame {
1033                data: FrameBytes::Owned(data.to_vec()),
1034                reserved_bytes,
1035                completion: completion.take(),
1036            },
1037            true,
1038        ) {
1039            Ok(_) => Ok(()),
1040            Err(TrySendError::Full(frame)) => {
1041                *completion = frame.completion;
1042                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
1043                if let Some(control) = &self.metrics.egress_control {
1044                    control.release(frame.reserved_bytes);
1045                }
1046                Err(io::Error::new(
1047                    io::ErrorKind::WouldBlock,
1048                    "interface writer queue is full",
1049                ))
1050            }
1051            Err(TrySendError::Disconnected(frame)) => {
1052                *completion = frame.completion;
1053                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
1054                if let Some(control) = &self.metrics.egress_control {
1055                    control.release(frame.reserved_bytes);
1056                }
1057                self.metrics.worker_alive.store(false, Ordering::Relaxed);
1058                Err(io::Error::new(
1059                    io::ErrorKind::BrokenPipe,
1060                    "interface writer worker disconnected",
1061                ))
1062            }
1063        }
1064    }
1065}
1066
1067pub fn wrap_async_writer(
1068    writer: Box<dyn Writer>,
1069    interface_id: InterfaceId,
1070    interface_name: &str,
1071    event_tx: EventSender,
1072    queue_capacity: usize,
1073) -> (Box<dyn Writer>, AsyncWriterMetrics) {
1074    wrap_async_writer_with_wait(
1075        writer,
1076        interface_id,
1077        interface_name,
1078        event_tx,
1079        queue_capacity,
1080        ASYNC_WRITER_BACKPRESSURE_WAIT,
1081    )
1082}
1083
1084/// [`wrap_async_writer`] with an explicit backpressure wait, so tests can use
1085/// a wait long enough to stay deterministic on a loaded machine.
1086fn wrap_async_writer_with_wait(
1087    writer: Box<dyn Writer>,
1088    interface_id: InterfaceId,
1089    interface_name: &str,
1090    event_tx: EventSender,
1091    queue_capacity: usize,
1092    backpressure_wait: Duration,
1093) -> (Box<dyn Writer>, AsyncWriterMetrics) {
1094    if let Some(fd) = writer.loop_socket() {
1095        return loop_writer::wrap(writer, fd, interface_id, interface_name, event_tx);
1096    }
1097    let egress_control = writer.egress_control();
1098    let queue = WriterQueue::new(queue_capacity);
1099    let (tx, rx) = (WriterTx(Arc::clone(&queue)), queue);
1100    let metrics = AsyncWriterMetrics {
1101        queued_frames: Arc::new(AtomicUsize::new(0)),
1102        worker_alive: Arc::new(AtomicBool::new(true)),
1103        egress_control,
1104        space: Arc::default(),
1105        space_waiters: Arc::default(),
1106    };
1107    let metrics_thread = metrics.clone();
1108    let name = interface_name.to_string();
1109
1110    let spawn_result = thread::Builder::new()
1111        .name(format!("iface-writer-{}", interface_id.0))
1112        .spawn(move || async_writer_loop(writer, rx, interface_id, name, event_tx, metrics_thread));
1113
1114    if let Err(err) = spawn_result {
1115        metrics.worker_alive.store(false, Ordering::Relaxed);
1116        log::error!(
1117            "[{}:{}] failed to spawn async writer thread: {}",
1118            interface_name,
1119            interface_id.0,
1120            err
1121        );
1122        return (Box::new(DirectWriterFallback), metrics);
1123    }
1124
1125    (
1126        Box::new(AsyncWriter {
1127            tx,
1128            metrics: metrics.clone(),
1129            skip_wait: false,
1130            defer_wake: false,
1131            wake_pending: false,
1132            backpressure_wait,
1133        }),
1134        metrics,
1135    )
1136}
1137
1138struct DirectWriterFallback;
1139
1140impl Writer for DirectWriterFallback {
1141    fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1142        Err(io::Error::other("interface writer worker unavailable"))
1143    }
1144}
1145
1146fn async_writer_loop(
1147    mut writer: Box<dyn Writer>,
1148    rx: Arc<WriterQueue>,
1149    interface_id: InterfaceId,
1150    interface_name: String,
1151    event_tx: EventSender,
1152    metrics: AsyncWriterMetrics,
1153) {
1154    let mut batch = Vec::new();
1155    while rx.recv_all(&mut batch) {
1156        let queued = std::mem::take(&mut batch);
1157        let reserved_bytes = queued.iter().map(|frame| frame.reserved_bytes).sum();
1158        metrics
1159            .queued_frames
1160            .fetch_sub(queued.len(), Ordering::Relaxed);
1161        metrics.notify_space();
1162        if event_tx.link_send_pool().in_flight() > 0 {
1163            let _ = event_tx.try_send(crate::event::Event::LinkWriterReady);
1164        }
1165
1166        let mut result = Ok(());
1167        if queued.iter().any(|frame| frame.completion.is_some()) {
1168            for mut frame in queued {
1169                let confirmed = frame.completion.is_some();
1170                if frame.completion.as_ref().is_some_and(|c| c.is_finished()) {
1171                    result = Err(io::Error::new(
1172                        io::ErrorKind::Interrupted,
1173                        "transmission cancelled by shutdown",
1174                    ));
1175                }
1176                if result.is_ok() {
1177                    result = if frame.completion.is_some() {
1178                        writer.send_frame_confirmed(&frame.data)
1179                    } else {
1180                        writer.send_frame(&frame.data)
1181                    };
1182                    while result
1183                        .as_ref()
1184                        .is_err_and(|e| e.kind() == io::ErrorKind::WouldBlock)
1185                    {
1186                        if frame.completion.as_ref().is_some_and(|c| c.is_finished()) {
1187                            result = Err(io::Error::new(
1188                                io::ErrorKind::Interrupted,
1189                                "transmission cancelled by shutdown",
1190                            ));
1191                            break;
1192                        }
1193                        thread::sleep(Duration::from_millis(1));
1194                        result = writer.flush();
1195                    }
1196                }
1197                if let Some(completion) = frame.completion.take() {
1198                    completion.finish(
1199                        result.as_ref().map(|_| ()).map_err(|e| {
1200                            crate::link_send::LinkSendError::WriteFailed(e.to_string())
1201                        }),
1202                    );
1203                }
1204                // Packet-level policy/validation errors do not imply a broken
1205                // connection (e.g. a Local client that temporarily holds TX).
1206                if confirmed
1207                    && result.as_ref().is_err_and(|error| {
1208                        matches!(
1209                            error.kind(),
1210                            io::ErrorKind::PermissionDenied
1211                                | io::ErrorKind::InvalidInput
1212                                | io::ErrorKind::Unsupported
1213                        )
1214                    })
1215                {
1216                    result = Ok(());
1217                }
1218            }
1219        } else {
1220            let frames: Vec<_> = queued.into_iter().map(|frame| frame.data).collect();
1221            result = writer.send_frames(&frames);
1222        }
1223        while result
1224            .as_ref()
1225            .is_err_and(|error| error.kind() == io::ErrorKind::WouldBlock)
1226        {
1227            thread::sleep(Duration::from_millis(1));
1228            result = writer.flush();
1229        }
1230
1231        if let Some(control) = &metrics.egress_control {
1232            control.release(reserved_bytes);
1233        }
1234        metrics.notify_space();
1235        if event_tx.link_send_pool().in_flight() > 0 {
1236            let _ = event_tx.try_send(crate::event::Event::LinkWriterReady);
1237        }
1238
1239        if let Err(err) = result {
1240            metrics.worker_alive.store(false, Ordering::Relaxed);
1241            log::warn!(
1242                "[{}:{}] async writer exiting after send failure: {}",
1243                interface_name,
1244                interface_id.0,
1245                err
1246            );
1247            writer.shutdown();
1248            rx.close_receiver();
1249            metrics.notify_space();
1250            let _ = event_tx.send(crate::event::Event::InterfaceDown(interface_id));
1251            return;
1252        }
1253    }
1254
1255    rx.close_receiver();
1256    metrics.worker_alive.store(false, Ordering::Relaxed);
1257    metrics.notify_space();
1258}
1259
1260pub use crate::common::interface_stats::{InterfaceStats, TrafficRates, ANNOUNCE_SAMPLE_MAX};
1261
1262use crate::common::management::InterfaceStatusView;
1263
1264/// Everything the driver tracks per interface.
1265pub struct InterfaceEntry {
1266    pub id: InterfaceId,
1267    pub info: InterfaceInfo,
1268    pub writer: Box<dyn Writer>,
1269    pub async_writer_metrics: Option<AsyncWriterMetrics>,
1270    /// Administrative enable/disable state.
1271    pub enabled: bool,
1272    pub online: bool,
1273    /// True for dynamically spawned interfaces (e.g. TCP server clients).
1274    /// These are fully removed on InterfaceDown rather than just marked offline.
1275    pub dynamic: bool,
1276    /// IFAC state for this interface, if access codes are enabled.
1277    pub ifac: Option<IfacState>,
1278    /// Traffic statistics.
1279    pub stats: InterfaceStats,
1280    /// Human-readable interface type string (e.g. "TCPClientInterface").
1281    pub interface_type: String,
1282    /// Next time a send should be retried after a transient WouldBlock.
1283    pub send_retry_at: Option<Instant>,
1284    /// Current retry backoff for transient send failures.
1285    pub send_retry_backoff: Duration,
1286}
1287
1288/// Result of starting an interface via a factory.
1289// Boxing `Simple` would change the published factory result representation.
1290#[allow(clippy::large_enum_variant)]
1291pub enum StartResult {
1292    /// One writer, registered immediately (TcpClient, Udp, Serial, etc.)
1293    Simple {
1294        id: InterfaceId,
1295        info: InterfaceInfo,
1296        writer: Box<dyn Writer>,
1297        interface_type_name: String,
1298        control: Option<ListenerControl>,
1299    },
1300    /// Spawns a listener; dynamic interfaces arrive via Event::InterfaceUp (TcpServer, Auto, I2P, etc.)
1301    Listener { control: Option<ListenerControl> },
1302    /// Multiple subinterfaces from one config (RNode).
1303    Multi {
1304        subinterfaces: Vec<SubInterface>,
1305        control: Option<ListenerControl>,
1306    },
1307}
1308
1309/// A single subinterface returned from a multi-interface factory.
1310pub struct SubInterface {
1311    pub id: InterfaceId,
1312    pub info: InterfaceInfo,
1313    pub writer: Box<dyn Writer>,
1314    pub interface_type_name: String,
1315}
1316
1317/// Context passed to [`InterfaceFactory::start()`].
1318pub struct StartContext {
1319    pub tx: EventSender,
1320    pub next_dynamic_id: Arc<AtomicU64>,
1321    pub mode: u8,
1322    pub gravity: i64,
1323    pub recursive_prs: bool,
1324    pub announces_from_internal: bool,
1325    pub announces_to_internal: Option<bool>,
1326    pub ingress_control: rns_core::transport::types::IngressControlConfig,
1327    pub ifac: Option<IfacState>,
1328    /// Linux `SO_MARK` value applied to IP underlay sockets before connect.
1329    pub underlay_mark: Option<u32>,
1330}
1331
1332/// Opaque interface config data. Each factory downcasts to its concrete type.
1333pub trait InterfaceConfigData: Send + Any {
1334    fn as_any(&self) -> &dyn Any;
1335    fn into_any(self: Box<Self>) -> Box<dyn Any>;
1336}
1337
1338/// ConfigObj section passed to factories that need nested `[[[sections]]]`.
1339pub struct ConfigSection<'a> {
1340    pub params: &'a HashMap<String, String>,
1341    pub children: &'a [crate::config::ParsedSubinterface],
1342}
1343
1344#[derive(Debug, Clone)]
1345pub struct DynamicInterfaceTemplate {
1346    pub parent_id: InterfaceId,
1347    pub interface_type: String,
1348    pub ifac: Option<IfacState>,
1349    pub mode: u8,
1350    pub gravity: i64,
1351    pub recursive_prs: bool,
1352    pub announces_from_internal: bool,
1353    pub announces_to_internal: Option<bool>,
1354}
1355
1356impl DynamicInterfaceTemplate {
1357    pub fn registration(
1358        &self,
1359        mut info: InterfaceInfo,
1360    ) -> crate::event::DynamicInterfaceRegistration {
1361        info.mode = self.mode;
1362        info.gravity = self.gravity;
1363        info.recursive_prs = self.recursive_prs;
1364        info.announces_from_internal = self.announces_from_internal;
1365        info.announces_to_internal = self.announces_to_internal;
1366        crate::event::DynamicInterfaceRegistration {
1367            info,
1368            interface_type: self.interface_type.clone(),
1369            parent_id: self.parent_id,
1370            telemetry: crate::event::InterfaceTelemetry::default(),
1371            ifac: self.ifac.clone(),
1372        }
1373    }
1374}
1375
1376impl<T: Send + 'static> InterfaceConfigData for T {
1377    fn as_any(&self) -> &dyn Any {
1378        self
1379    }
1380
1381    fn into_any(self: Box<Self>) -> Box<dyn Any> {
1382        self
1383    }
1384}
1385
1386/// Factory that can parse config and start an interface type.
1387pub trait InterfaceFactory: Send + Sync {
1388    /// Config-file type name, e.g. "TCPClientInterface".
1389    fn type_name(&self) -> &str;
1390
1391    /// Default IFAC size (bytes). 8 for serial/kiss/rnode, 16 for others.
1392    fn default_ifac_size(&self) -> usize {
1393        16
1394    }
1395
1396    /// Parse from key-value params (config file or external).
1397    fn parse_config(
1398        &self,
1399        name: &str,
1400        id: InterfaceId,
1401        params: &HashMap<String, String>,
1402    ) -> Result<Box<dyn InterfaceConfigData>, String>;
1403
1404    /// Parse a complete ConfigObj interface section. Flat factories inherit
1405    /// the compatibility default; multi-interface factories can inspect children.
1406    fn parse_config_section(
1407        &self,
1408        name: &str,
1409        id: InterfaceId,
1410        section: ConfigSection<'_>,
1411    ) -> Result<Box<dyn InterfaceConfigData>, String> {
1412        self.parse_config(name, id, section.params)
1413    }
1414
1415    /// Start the interface from parsed config.
1416    fn start(
1417        &self,
1418        config: Box<dyn InterfaceConfigData>,
1419        ctx: StartContext,
1420    ) -> io::Result<StartResult>;
1421}
1422
1423impl InterfaceStatusView for InterfaceEntry {
1424    fn id(&self) -> InterfaceId {
1425        self.id
1426    }
1427    fn info(&self) -> &InterfaceInfo {
1428        &self.info
1429    }
1430    fn online(&self) -> bool {
1431        self.online
1432    }
1433    fn stats(&self) -> &InterfaceStats {
1434        &self.stats
1435    }
1436    fn tx_drops(&self) -> u64 {
1437        self.async_writer_metrics
1438            .as_ref()
1439            .map(AsyncWriterMetrics::tx_drops)
1440            .unwrap_or(0)
1441    }
1442    fn tx_dropped_bytes(&self) -> u64 {
1443        self.async_writer_metrics
1444            .as_ref()
1445            .map(AsyncWriterMetrics::tx_dropped_bytes)
1446            .unwrap_or(0)
1447    }
1448    fn tx_stalled(&self) -> bool {
1449        self.async_writer_metrics
1450            .as_ref()
1451            .is_some_and(AsyncWriterMetrics::tx_stalled)
1452    }
1453    fn tx_buffered(&self) -> usize {
1454        self.async_writer_metrics
1455            .as_ref()
1456            .map(AsyncWriterMetrics::tx_buffered)
1457            .unwrap_or(0)
1458    }
1459}
1460
1461#[cfg(test)]
1462mod tests {
1463    use super::*;
1464
1465    #[test]
1466    fn tcp_interfaces_take_upstream_bitrate_and_fixed_mtu_keys() {
1467        let params = |pairs: &[(&str, &str)]| -> HashMap<String, String> {
1468            pairs
1469                .iter()
1470                .map(|(k, v)| (k.to_string(), v.to_string()))
1471                .collect()
1472        };
1473        for (factory, type_name) in [
1474            (
1475                &tcp::TcpClientFactory as &dyn InterfaceFactory,
1476                "TCPClientInterface",
1477            ),
1478            (&tcp_server::TcpServerFactory, "TCPServerInterface"),
1479        ] {
1480            assert_eq!(factory.type_name(), type_name);
1481            let rates = |pairs: &[(&str, &str)]| {
1482                let config = factory
1483                    .parse_config("bench", InterfaceId(1), &params(pairs))
1484                    .map_err(|error| error.to_string())?;
1485                let any = config.into_any();
1486                Ok::<_, String>(
1487                    if let Some(c) = any.downcast_ref::<tcp::TcpClientConfig>() {
1488                        (c.bitrate, c.fixed_mtu)
1489                    } else {
1490                        let c = any.downcast_ref::<tcp_server::TcpServerConfig>().unwrap();
1491                        (c.bitrate, c.fixed_mtu)
1492                    },
1493                )
1494            };
1495            assert_eq!(rates(&[]), Ok((None, None)));
1496            assert_eq!(
1497                rates(&[("bitrate", "1000000000"), ("fixed_mtu", "500")]),
1498                Ok((Some(1_000_000_000), Some(500)))
1499            );
1500            assert_eq!(
1501                rates(&[("fixed_mtu", "499")]),
1502                Err("Configured MTU of 499 bytes is too small".to_string())
1503            );
1504            assert!(rates(&[("bitrate", "fast")]).is_err());
1505        }
1506    }
1507
1508    #[test]
1509    fn tcp_hardware_mtu_follows_fixed_mtu_then_the_bitrate_tier() {
1510        assert_eq!(tcp_hardware_mtu(None, None), TCP_DEFAULT_MTU);
1511        assert_eq!(tcp_hardware_mtu(Some(10_000_000), None), 16_384);
1512        assert_eq!(tcp_hardware_mtu(Some(1_000_000_000), None), 524_288);
1513        assert_eq!(tcp_hardware_mtu(Some(1_000_000_000), Some(500)), 500);
1514        assert_eq!(tcp_hardware_mtu(None, Some(1_200)), 1_200);
1515        // Below the lowest tier upstream sets no hardware MTU.
1516        assert_eq!(
1517            tcp_hardware_mtu(Some(9_600), None),
1518            rns_core::constants::MTU as u32
1519        );
1520    }
1521
1522    #[test]
1523    fn shared_medium_hint_matches_interface_class_defaults() {
1524        for interface_type in [
1525            "AX25KISSInterface",
1526            "KISSInterface",
1527            "PipeInterface",
1528            "RNodeInterface",
1529            "RNodeMultiInterface",
1530            "SerialInterface",
1531            "UDPInterface",
1532        ] {
1533            assert!(shared_medium_hint(interface_type), "{interface_type}");
1534        }
1535        for interface_type in [
1536            "AutoInterface",
1537            "BackboneInterface",
1538            "I2PInterface",
1539            "LocalInterface",
1540            "TCPClientInterface",
1541            "TCPServerInterface",
1542        ] {
1543            assert!(!shared_medium_hint(interface_type), "{interface_type}");
1544        }
1545    }
1546    use crate::event::Event;
1547    use rns_core::constants;
1548    use std::sync::mpsc;
1549
1550    struct MockWriter {
1551        sent: Vec<Vec<u8>>,
1552    }
1553
1554    impl MockWriter {
1555        fn new() -> Self {
1556            MockWriter { sent: Vec::new() }
1557        }
1558    }
1559
1560    impl Writer for MockWriter {
1561        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1562            self.sent.push(data.to_vec());
1563            Ok(())
1564        }
1565    }
1566
1567    #[test]
1568    fn interface_entry_construction() {
1569        let entry = InterfaceEntry {
1570            id: InterfaceId(1),
1571            info: InterfaceInfo {
1572                id: InterfaceId(1),
1573                name: String::new(),
1574                mode: constants::MODE_FULL,
1575                gravity: 0,
1576                recursive_prs: false,
1577                announces_from_internal: true,
1578                announces_to_internal: None,
1579                out_capable: true,
1580                in_capable: true,
1581                bitrate: None,
1582                airtime_profile: None,
1583                announce_rate_target: None,
1584                announce_rate_grace: 0,
1585                announce_rate_penalty: 0.0,
1586                announce_cap: constants::ANNOUNCE_CAP,
1587                is_local_client: false,
1588                wants_tunnel: false,
1589                tunnel_id: None,
1590                mtu: constants::MTU as u32,
1591                ia_freq: 0.0,
1592                ip_freq: 0.0,
1593                op_freq: 0.0,
1594                op_samples: 0,
1595                started: 0.0,
1596                ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1597            },
1598            writer: Box::new(MockWriter::new()),
1599            async_writer_metrics: None,
1600            enabled: true,
1601            online: false,
1602            dynamic: false,
1603            ifac: None,
1604            stats: InterfaceStats::default(),
1605            interface_type: String::new(),
1606            send_retry_at: None,
1607            send_retry_backoff: Duration::ZERO,
1608        };
1609        assert_eq!(entry.id, InterfaceId(1));
1610        assert!(!entry.online);
1611        assert!(!entry.dynamic);
1612    }
1613
1614    #[test]
1615    fn dynamic_registration_inherits_announces_to_internal() {
1616        let template = DynamicInterfaceTemplate {
1617            parent_id: InterfaceId(7),
1618            interface_type: "dynamic-test".into(),
1619            ifac: None,
1620            mode: constants::MODE_GATEWAY,
1621            gravity: -3,
1622            recursive_prs: true,
1623            announces_from_internal: false,
1624            announces_to_internal: Some(true),
1625        };
1626        let info = InterfaceInfo {
1627            id: InterfaceId(8),
1628            name: "child".into(),
1629            mode: constants::MODE_FULL,
1630            gravity: 0,
1631            recursive_prs: false,
1632            announces_from_internal: true,
1633            announces_to_internal: None,
1634            out_capable: true,
1635            in_capable: true,
1636            bitrate: None,
1637            airtime_profile: None,
1638            announce_rate_target: None,
1639            announce_rate_grace: 0,
1640            announce_rate_penalty: 0.0,
1641            announce_cap: constants::ANNOUNCE_CAP,
1642            is_local_client: false,
1643            wants_tunnel: false,
1644            tunnel_id: None,
1645            mtu: constants::MTU as u32,
1646            ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1647            ia_freq: 0.0,
1648            ip_freq: 0.0,
1649            op_freq: 0.0,
1650            op_samples: 0,
1651            started: 0.0,
1652        };
1653
1654        let registration = template.registration(info);
1655
1656        assert_eq!(registration.info.mode, constants::MODE_GATEWAY);
1657        assert_eq!(registration.info.gravity, -3);
1658        assert!(registration.info.recursive_prs);
1659        assert!(!registration.info.announces_from_internal);
1660        assert_eq!(registration.info.announces_to_internal, Some(true));
1661    }
1662
1663    #[test]
1664    fn mock_writer_captures_bytes() {
1665        let mut writer = MockWriter::new();
1666        writer.send_frame(b"hello").unwrap();
1667        writer.send_frame(b"world").unwrap();
1668        assert_eq!(writer.sent.len(), 2);
1669        assert_eq!(writer.sent[0], b"hello");
1670        assert_eq!(writer.sent[1], b"world");
1671    }
1672
1673    #[test]
1674    fn writer_send_frame_produces_output() {
1675        let mut writer = MockWriter::new();
1676        let data = vec![0x01, 0x02, 0x03];
1677        writer.send_frame(&data).unwrap();
1678        assert_eq!(writer.sent[0], data);
1679    }
1680
1681    struct BlockingWriter {
1682        entered_tx: mpsc::Sender<()>,
1683        release_rx: mpsc::Receiver<()>,
1684    }
1685
1686    #[test]
1687    fn confirmed_writer_waits_for_flush_and_reports_write_failures() {
1688        use crate::link_send::{Completion, LinkSendError, SendPool};
1689        use futures::FutureExt;
1690        struct FlushWriter {
1691            entered: mpsc::Sender<()>,
1692            release: mpsc::Receiver<()>,
1693            writes: Arc<AtomicUsize>,
1694        }
1695        impl Writer for FlushWriter {
1696            fn send_frame(&mut self, _: &[u8]) -> io::Result<()> {
1697                self.writes.fetch_add(1, Ordering::SeqCst);
1698                Err(io::ErrorKind::WouldBlock.into())
1699            }
1700            fn flush(&mut self) -> io::Result<()> {
1701                self.entered.send(()).unwrap();
1702                self.release.recv().unwrap();
1703                Ok(())
1704            }
1705        }
1706        let (event_tx, _rx) = crate::event::channel();
1707        let (entered, entered_rx) = mpsc::channel();
1708        let (release_tx, release) = mpsc::channel();
1709        let writes = Arc::new(AtomicUsize::new(0));
1710        let pool = SendPool::new(1);
1711        let (completion, mut receipt) = Completion::new(pool.try_acquire().unwrap());
1712        let (mut writer, _) = wrap_async_writer(
1713            Box::new(FlushWriter {
1714                entered,
1715                release,
1716                writes: writes.clone(),
1717            }),
1718            InterfaceId(999),
1719            "flush test",
1720            event_tx.clone(),
1721            1,
1722        );
1723        writer
1724            .enqueue_confirmed(b"once", &mut Some(completion))
1725            .unwrap();
1726        entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1727        assert!((&mut receipt).now_or_never().is_none());
1728        release_tx.send(()).unwrap();
1729        receipt.wait().unwrap();
1730        assert_eq!(
1731            writes.load(Ordering::SeqCst),
1732            1,
1733            "flush must not resubmit the frame"
1734        );
1735
1736        let (completion, receipt) = Completion::new(pool.try_acquire().unwrap());
1737        let (mut writer, _) = wrap_async_writer(
1738            Box::new(FailingWriter {
1739                shutdown_called: Arc::new(AtomicBool::new(false)),
1740            }),
1741            InterfaceId(998),
1742            "failure test",
1743            event_tx,
1744            1,
1745        );
1746        writer
1747            .enqueue_confirmed(b"fail", &mut Some(completion))
1748            .unwrap();
1749        assert_eq!(
1750            receipt.wait(),
1751            Err(LinkSendError::WriteFailed("boom".into()))
1752        );
1753    }
1754
1755    impl Writer for BlockingWriter {
1756        fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1757            let _ = self.entered_tx.send(());
1758            let _ = self.release_rx.recv();
1759            Ok(())
1760        }
1761    }
1762
1763    struct EgressControlledBlockingWriter {
1764        inner: BlockingWriter,
1765        control: EgressControl,
1766    }
1767
1768    impl Writer for EgressControlledBlockingWriter {
1769        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1770            self.inner.send_frame(data)
1771        }
1772
1773        fn egress_control(&self) -> Option<EgressControl> {
1774            Some(self.control.clone())
1775        }
1776    }
1777
1778    struct FailingWriter {
1779        shutdown_called: Arc<AtomicBool>,
1780    }
1781
1782    struct RecordingWriter {
1783        sent_tx: mpsc::Sender<Vec<u8>>,
1784    }
1785
1786    impl Writer for RecordingWriter {
1787        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1788            self.sent_tx
1789                .send(data.to_vec())
1790                .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "test receiver closed"))
1791        }
1792    }
1793
1794    struct BatchRecordingWriter {
1795        batch_tx: mpsc::Sender<usize>,
1796        first_release: Option<mpsc::Receiver<()>>,
1797    }
1798
1799    impl Writer for BatchRecordingWriter {
1800        fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1801            unreachable!("the async worker must use batch delivery")
1802        }
1803
1804        fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
1805            self.batch_tx.send(frames.len()).unwrap();
1806            if let Some(release) = self.first_release.take() {
1807                release.recv().unwrap();
1808            }
1809            Ok(())
1810        }
1811    }
1812
1813    impl Writer for FailingWriter {
1814        fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1815            Err(io::Error::new(io::ErrorKind::BrokenPipe, "boom"))
1816        }
1817
1818        fn shutdown(&mut self) {
1819            self.shutdown_called.store(true, Ordering::Relaxed);
1820        }
1821    }
1822
1823    #[test]
1824    fn async_writer_returns_wouldblock_when_queue_is_full() {
1825        let (event_tx, _event_rx) = crate::event::channel();
1826        let (entered_tx, entered_rx) = mpsc::channel();
1827        let (release_tx, release_rx) = mpsc::channel();
1828        let (mut writer, metrics) = wrap_async_writer(
1829            Box::new(BlockingWriter {
1830                entered_tx,
1831                release_rx,
1832            }),
1833            InterfaceId(7),
1834            "test",
1835            event_tx,
1836            1,
1837        );
1838
1839        writer.send_frame(&[1]).unwrap();
1840        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1841        writer.send_frame(&[2]).unwrap();
1842        assert_eq!(metrics.queued_frames(), 1);
1843        let err = writer.send_frame(&[3]).unwrap_err();
1844        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1845
1846        release_tx.send(()).unwrap();
1847        entered_rx
1848            .recv_timeout(Duration::from_secs(1))
1849            .expect("the queued frame must wake the writer without another send trigger");
1850        assert_eq!(metrics.queued_frames(), 0);
1851        release_tx.send(()).unwrap();
1852    }
1853
1854    #[test]
1855    fn deferred_sends_are_delivered_on_wake_or_by_the_recheck_backstop() {
1856        struct Recorder(mpsc::Sender<Vec<u8>>);
1857        impl Writer for Recorder {
1858            fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1859                self.0.send(data.to_vec()).unwrap();
1860                Ok(())
1861            }
1862        }
1863        let (event_tx, _event_rx) = crate::event::channel();
1864        let (sent_tx, sent_rx) = mpsc::channel();
1865        let (mut writer, _metrics) = wrap_async_writer(
1866            Box::new(Recorder(sent_tx)),
1867            InterfaceId(9),
1868            "test",
1869            event_tx,
1870            8,
1871        );
1872        // Let the worker go to sleep on the empty queue.
1873        std::thread::sleep(Duration::from_millis(20));
1874        writer.send_frame_deferred(&[1]).unwrap();
1875        writer.send_frame_deferred(&[2]).unwrap();
1876        writer.wake();
1877        assert_eq!(
1878            sent_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
1879            vec![1]
1880        );
1881        assert_eq!(
1882            sent_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
1883            vec![2]
1884        );
1885
1886        std::thread::sleep(Duration::from_millis(20));
1887        writer.send_frame_deferred(&[3]).unwrap();
1888        // No wake: the periodic recheck still delivers the frame.
1889        assert_eq!(
1890            sent_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
1891            vec![3]
1892        );
1893    }
1894
1895    #[test]
1896    fn async_writer_waits_briefly_for_space_then_fails_fast_while_stalled() {
1897        let (event_tx, _event_rx) = crate::event::channel();
1898        let (entered_tx, entered_rx) = mpsc::channel();
1899        let (release_tx, release_rx) = mpsc::channel();
1900        // Far longer than the production wait: the release below must reach
1901        // the worker, and a non-waiting send must return, within this bound
1902        // even when the machine is busy.
1903        let wait = Duration::from_millis(500);
1904        let (mut writer, metrics) = wrap_async_writer_with_wait(
1905            Box::new(BlockingWriter {
1906                entered_tx,
1907                release_rx,
1908            }),
1909            InterfaceId(8),
1910            "test",
1911            event_tx,
1912            1,
1913            wait,
1914        );
1915        writer.send_frame(&[1]).unwrap();
1916        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1917        writer.send_frame(&[2]).unwrap();
1918
1919        // The worker frees the slot while the producer is waiting: the frame
1920        // is admitted instead of being refused.
1921        let release = release_tx.clone();
1922        let releaser = std::thread::spawn(move || {
1923            std::thread::sleep(Duration::from_millis(5));
1924            release.send(()).unwrap();
1925        });
1926        writer.send_frame(&[3]).unwrap();
1927        releaser.join().unwrap();
1928        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1929        assert_eq!(metrics.queued_frames(), 1);
1930
1931        // A writer that stays full costs one bounded wait; later sends do not
1932        // wait again until a frame is admitted.
1933        let started = Instant::now();
1934        let err = writer.send_frame(&[4]).unwrap_err();
1935        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1936        assert!(started.elapsed() >= wait);
1937        let started = Instant::now();
1938        let err = writer.send_frame(&[5]).unwrap_err();
1939        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1940        assert!(started.elapsed() < wait);
1941
1942        for _ in 0..3 {
1943            let _ = release_tx.send(());
1944        }
1945    }
1946
1947    #[test]
1948    fn async_writer_shares_exact_egress_byte_valve_and_stall_gate() {
1949        let (event_tx, _event_rx) = crate::event::channel();
1950        let (entered_tx, entered_rx) = mpsc::channel();
1951        let (release_tx, release_rx) = mpsc::channel();
1952        let control = EgressControl::new(4);
1953        let (mut writer, metrics) = wrap_async_writer(
1954            Box::new(EgressControlledBlockingWriter {
1955                inner: BlockingWriter {
1956                    entered_tx,
1957                    release_rx,
1958                },
1959                control: control.clone(),
1960            }),
1961            InterfaceId(8),
1962            "egress-test",
1963            event_tx,
1964            8,
1965        );
1966
1967        // Two ordinary bytes occupy exactly four HDLC bytes on wire.
1968        writer.send_frame(&[1, 2]).unwrap();
1969        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1970        assert_eq!(control.reserved_bytes.load(Ordering::Relaxed), 4);
1971        assert_eq!(metrics.tx_buffered(), 4);
1972
1973        // The hard valve drops instead of retaining another four-byte frame.
1974        writer.send_frame(&[3, 4]).unwrap();
1975        assert_eq!(control.reserved_bytes.load(Ordering::Relaxed), 4);
1976        assert_eq!(control.dropped_frames.load(Ordering::Relaxed), 1);
1977        assert_eq!(control.dropped_bytes.load(Ordering::Relaxed), 4);
1978        assert_eq!(metrics.tx_drops(), 1);
1979        assert_eq!(metrics.tx_dropped_bytes(), 4);
1980
1981        release_tx.send(()).unwrap();
1982        let deadline = Instant::now() + Duration::from_secs(1);
1983        while control.reserved_bytes.load(Ordering::Relaxed) != 0 {
1984            assert!(
1985                Instant::now() < deadline,
1986                "egress reservation did not drain"
1987            );
1988            thread::yield_now();
1989        }
1990
1991        control.set_stalled(true);
1992        assert!(metrics.tx_stalled());
1993        writer.send_frame(&[5]).unwrap();
1994        assert_eq!(control.dropped_frames.load(Ordering::Relaxed), 2);
1995        assert_eq!(control.dropped_bytes.load(Ordering::Relaxed), 7);
1996        assert_eq!(metrics.queued_frames(), 0);
1997    }
1998
1999    #[test]
2000    fn async_writer_reports_interface_down_after_worker_failure() {
2001        let (event_tx, event_rx) = crate::event::channel();
2002        let shutdown_called = Arc::new(AtomicBool::new(false));
2003        let (mut writer, metrics) = wrap_async_writer(
2004            Box::new(FailingWriter {
2005                shutdown_called: Arc::clone(&shutdown_called),
2006            }),
2007            InterfaceId(9),
2008            "fail",
2009            event_tx,
2010            2,
2011        );
2012
2013        writer.send_frame(&[1]).unwrap();
2014        let event = event_rx.recv_timeout(Duration::from_secs(1)).unwrap();
2015        assert!(matches!(event, Event::InterfaceDown(InterfaceId(9))));
2016        assert!(!metrics.worker_alive());
2017        assert!(shutdown_called.load(Ordering::Relaxed));
2018    }
2019
2020    #[test]
2021    fn async_writer_preserves_order_through_fifty_thousand_frame_burst() {
2022        const FRAME_COUNT: usize = 50_000;
2023        let (event_tx, _event_rx) = crate::event::channel();
2024        let (sent_tx, sent_rx) = mpsc::channel();
2025        let (mut writer, metrics) = wrap_async_writer(
2026            Box::new(RecordingWriter { sent_tx }),
2027            InterfaceId(11),
2028            "burst",
2029            event_tx,
2030            FRAME_COUNT,
2031        );
2032
2033        for index in 0..FRAME_COUNT {
2034            let payload = (index as u64).to_be_bytes();
2035            writer.send_frame(&payload).unwrap();
2036        }
2037
2038        for index in 0..FRAME_COUNT {
2039            let received = sent_rx.recv_timeout(Duration::from_secs(2)).unwrap();
2040            assert_eq!(received, (index as u64).to_be_bytes());
2041        }
2042        assert!(metrics.worker_alive());
2043    }
2044
2045    #[test]
2046    fn async_writer_coalesces_frames_waiting_behind_an_active_batch() {
2047        let (event_tx, _event_rx) = crate::event::channel();
2048        let (batch_tx, batch_rx) = mpsc::channel();
2049        let (release_tx, release_rx) = mpsc::channel();
2050        let (mut writer, _) = wrap_async_writer(
2051            Box::new(BatchRecordingWriter {
2052                batch_tx,
2053                first_release: Some(release_rx),
2054            }),
2055            InterfaceId(12),
2056            "batch",
2057            event_tx,
2058            16,
2059        );
2060
2061        writer.send_frame(&[0]).unwrap();
2062        assert_eq!(batch_rx.recv_timeout(Duration::from_secs(1)).unwrap(), 1);
2063        for index in 1..=10 {
2064            writer.send_frame(&[index]).unwrap();
2065        }
2066        release_tx.send(()).unwrap();
2067
2068        assert_eq!(batch_rx.recv_timeout(Duration::from_secs(1)).unwrap(), 10);
2069    }
2070}