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