Skip to main content

rns_net/interface/
local.rs

1//! Local shared instance interface.
2//!
3//! Provides communication between the shared RNS instance and local client
4//! programs. Uses Unix abstract sockets on Linux, TCP on other platforms.
5//! HDLC framing over the connection (same as TCP interfaces).
6//!
7//! Two modes:
8//! - `LocalServer`: The shared instance binds and accepts client connections.
9//! - `LocalClient`: Connects to an existing shared instance.
10
11use std::io::{self, Read, Write};
12use std::net::{TcpListener, TcpStream};
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex};
15use std::thread;
16use std::time::{Duration, Instant};
17
18use rns_core::constants;
19use rns_core::transport::types::{InterfaceId, InterfaceInfo};
20
21use crate::event::{Event, EventSender};
22use crate::hdlc;
23use crate::interface::transmit_buffer::TransmitBuffer;
24use crate::interface::{ListenerControl, Writer};
25
26mod folded;
27
28/// Hardware MTU used by both sides of the local shared-instance interface.
29pub(crate) const HW_MTU: u32 = 262_144;
30pub(crate) const BITRATE: u64 = 1_000_000_000;
31
32/// HDLC decoder for frames received on a local shared-instance connection.
33///
34/// Accepts frames up to [`HW_MTU`] plus `ifac_size` bytes, matching Python
35/// `LocalInterface`'s `ReceiveBuffer(mtu=HW_MTU)`. Local connections normally
36/// carry no IFAC, so callers pass 0 unless a template configures one.
37pub(crate) fn frame_decoder(ifac_size: usize) -> hdlc::Decoder {
38    hdlc::Decoder::reticulum(HW_MTU as usize, ifac_size)
39}
40
41/// IFAC bytes a spawned local client connection may carry on the wire.
42fn template_ifac_size(template: Option<&super::DynamicInterfaceTemplate>) -> usize {
43    template
44        .and_then(|template| template.ifac.as_ref())
45        .map_or(0, |ifac| ifac.size)
46}
47
48#[cfg(target_os = "android")]
49const CLIENT_SLEEP_PAUSE_TIMEOUT: Duration = Duration::from_secs(12);
50#[cfg(target_os = "android")]
51const PHY_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5);
52
53/// Configuration for a Local server (shared instance).
54#[derive(Debug, Clone)]
55pub struct LocalServerConfig {
56    pub instance_name: String,
57    pub port: u16,
58    pub interface_id: InterfaceId,
59}
60
61impl Default for LocalServerConfig {
62    fn default() -> Self {
63        LocalServerConfig {
64            instance_name: "default".into(),
65            port: 37428,
66            interface_id: InterfaceId(0),
67        }
68    }
69}
70
71/// Configuration for a Local client (connecting to shared instance).
72#[derive(Debug, Clone)]
73pub struct LocalClientConfig {
74    pub name: String,
75    pub instance_name: String,
76    pub port: u16,
77    pub interface_id: InterfaceId,
78    pub reconnect_wait: Duration,
79}
80
81impl Default for LocalClientConfig {
82    fn default() -> Self {
83        LocalClientConfig {
84            name: "Local shared instance".into(),
85            instance_name: "default".into(),
86            port: 37428,
87            interface_id: InterfaceId(0),
88            reconnect_wait: Duration::from_secs(8),
89        }
90    }
91}
92
93/// HDLC writer over a TCP or Unix stream.
94struct LocalWriter {
95    stream: TcpStream,
96    sleep_hold: Option<ClientSleepHold>,
97    transmit_buffer: TransmitBuffer,
98}
99
100impl Writer for LocalWriter {
101    fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
102        if self
103            .sleep_hold
104            .as_ref()
105            .is_some_and(ClientSleepHold::should_drop_outbound)
106        {
107            return Err(io::Error::new(
108                io::ErrorKind::PermissionDenied,
109                "local client has paused transmission",
110            ));
111        }
112        append_and_drain(
113            &mut self.transmit_buffer,
114            &mut self.stream,
115            &[crate::interface::FrameBytes::Owned(data.to_vec())],
116        )
117    }
118    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
119        self.send_frames(&[crate::interface::FrameBytes::Owned(data.to_vec())])
120    }
121
122    fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
123        if self
124            .sleep_hold
125            .as_ref()
126            .is_some_and(ClientSleepHold::should_drop_outbound)
127        {
128            log::debug!("TX paused for LocalInterface client, dropping outbound packet");
129            return Ok(());
130        }
131        append_and_drain(&mut self.transmit_buffer, &mut self.stream, frames)
132    }
133}
134
135fn append_and_drain(
136    buffer: &mut TransmitBuffer,
137    writer: &mut impl Write,
138    frames: &[crate::interface::FrameBytes],
139) -> io::Result<()> {
140    for frame in frames {
141        let _ = buffer.append(hdlc::frame(frame));
142    }
143    buffer.flush();
144    while buffer.sendable_bytes() > 0 {
145        if buffer.drain_to(writer)? == 0 {
146            return Err(io::Error::new(
147                io::ErrorKind::WriteZero,
148                "local writer made no progress",
149            ));
150        }
151    }
152    Ok(())
153}
154
155#[derive(Clone)]
156struct ClientSleepHold {
157    timeout: Duration,
158    deadline: Arc<Mutex<Instant>>,
159}
160
161impl ClientSleepHold {
162    #[cfg_attr(not(any(target_os = "android", test)), allow(dead_code))]
163    fn new(timeout: Duration) -> Self {
164        Self {
165            timeout,
166            deadline: Arc::new(Mutex::new(Instant::now() + timeout)),
167        }
168    }
169
170    fn refresh(&self) {
171        *lock_or_recover(&self.deadline) = Instant::now() + self.timeout;
172    }
173
174    fn should_drop_outbound(&self) -> bool {
175        Instant::now() > *lock_or_recover(&self.deadline)
176    }
177}
178
179fn lock_or_recover<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
180    match mutex.lock() {
181        Ok(guard) => guard,
182        Err(poisoned) => poisoned.into_inner(),
183    }
184}
185
186fn android_client_sleep_hold() -> Option<ClientSleepHold> {
187    #[cfg(target_os = "android")]
188    {
189        Some(ClientSleepHold::new(CLIENT_SLEEP_PAUSE_TIMEOUT))
190    }
191    #[cfg(not(target_os = "android"))]
192    {
193        None
194    }
195}
196
197#[cfg_attr(not(any(target_os = "android", test)), allow(dead_code))]
198fn spawn_physical_keepalive_loop(
199    mut writer: Box<dyn Writer>,
200    interface_id: InterfaceId,
201    interface_name: String,
202    interval: Duration,
203) {
204    // A physical keepalive is always an empty logical frame. Keeping that
205    // payload literal at the writer boundary prevents any prior application
206    // data from being captured or retransmitted by the non-polling path.
207    thread::Builder::new()
208        .name(format!("local-phy-keepalive-{}", interface_id.0))
209        .spawn(move || loop {
210            thread::sleep(interval);
211            if let Err(err) = writer.send_frame(&[]) {
212                log::debug!(
213                    "[{}:{}] LocalInterface physical keepalive stopped: {}",
214                    interface_name,
215                    interface_id.0,
216                    err
217                );
218                return;
219            }
220        })
221        .ok();
222}
223
224fn maybe_spawn_local_client_phy_keepalive(
225    stream: &LocalClientStream,
226    interface_id: InterfaceId,
227    interface_name: &str,
228) -> io::Result<()> {
229    #[cfg(target_os = "android")]
230    {
231        let writer = local_client_stream_writer(stream)?;
232        spawn_physical_keepalive_loop(
233            writer,
234            interface_id,
235            interface_name.to_string(),
236            PHY_KEEPALIVE_INTERVAL,
237        );
238    }
239
240    #[cfg(not(target_os = "android"))]
241    {
242        let _ = (stream, interface_id, interface_name);
243    }
244
245    Ok(())
246}
247
248#[cfg(target_os = "linux")]
249mod unix_socket {
250    use std::io;
251    use std::os::linux::net::SocketAddrExt;
252    use std::os::unix::net::{SocketAddr, UnixListener, UnixStream};
253
254    fn abstract_addr(instance_name: &str) -> io::Result<SocketAddr> {
255        SocketAddr::from_abstract_name(format!("rns/{}", instance_name))
256    }
257
258    /// Try to bind a Unix abstract socket with the given instance name.
259    pub fn try_bind_unix(instance_name: &str) -> io::Result<UnixListener> {
260        let addr = abstract_addr(instance_name)?;
261        UnixListener::bind_addr(&addr)
262    }
263
264    /// Try to connect to a Unix abstract socket.
265    pub fn try_connect_unix(instance_name: &str) -> io::Result<UnixStream> {
266        let addr = abstract_addr(instance_name)?;
267        UnixStream::connect_addr(&addr)
268    }
269}
270
271// ==================== LOCAL SERVER ====================
272
273/// Start a local server (shared instance).
274/// Tries Unix abstract socket first on Linux, falls back to TCP.
275/// Spawns an acceptor thread. Each client gets a dynamically allocated InterfaceId.
276pub fn start_server(
277    config: LocalServerConfig,
278    tx: EventSender,
279    next_id: Arc<AtomicU64>,
280) -> io::Result<ListenerControl> {
281    start_server_with_template(config, tx, next_id, None)
282}
283
284fn start_server_with_template(
285    config: LocalServerConfig,
286    tx: EventSender,
287    next_id: Arc<AtomicU64>,
288    dynamic_template: Option<super::DynamicInterfaceTemplate>,
289) -> io::Result<ListenerControl> {
290    let control = ListenerControl::new();
291    // With interface I/O folded, the listener and its clients are served on
292    // the driver loop, keyed by the server's interface id.
293    let fold = crate::engine::FOLD_IO;
294    let key = crate::interface::loop_writer::read_key(config.interface_id);
295    // Try Unix socket first on Linux
296    #[cfg(target_os = "linux")]
297    {
298        match unix_socket::try_bind_unix(&config.instance_name) {
299            Ok(listener) => {
300                listener.set_nonblocking(true)?;
301                log::info!(
302                    "Local server using Unix socket: rns/{}",
303                    config.instance_name
304                );
305                let name = format!("rns/{}", config.instance_name);
306                if fold {
307                    folded::serve(
308                        folded::Listener::Unix(listener),
309                        key,
310                        name,
311                        tx,
312                        next_id,
313                        control.clone(),
314                        dynamic_template,
315                    )?;
316                    return Ok(control);
317                }
318                let listener_control = control.clone();
319                thread::Builder::new()
320                    .name("local-server".into())
321                    .spawn(move || {
322                        unix_server_loop(
323                            listener,
324                            name,
325                            tx,
326                            next_id,
327                            listener_control,
328                            dynamic_template,
329                        );
330                    })?;
331                return Ok(control);
332            }
333            Err(e) => {
334                log::info!("Unix socket bind failed ({}), falling back to TCP", e);
335            }
336        }
337    }
338
339    // Fallback: TCP on localhost
340    let addr = format!("127.0.0.1:{}", config.port);
341    let listener = TcpListener::bind(&addr)?;
342    listener.set_nonblocking(true)?;
343
344    log::info!("Local server listening on TCP {}", addr);
345
346    if fold {
347        folded::serve(
348            folded::Listener::Tcp(listener),
349            key,
350            addr,
351            tx,
352            next_id,
353            control.clone(),
354            dynamic_template,
355        )?;
356        return Ok(control);
357    }
358    let listener_control = control.clone();
359    thread::Builder::new()
360        .name("local-server".into())
361        .spawn(move || {
362            tcp_server_loop(listener, tx, next_id, listener_control, dynamic_template);
363        })?;
364
365    Ok(control)
366}
367
368/// TCP server accept loop for local interface.
369fn tcp_server_loop(
370    listener: TcpListener,
371    tx: EventSender,
372    next_id: Arc<AtomicU64>,
373    control: ListenerControl,
374    dynamic_template: Option<super::DynamicInterfaceTemplate>,
375) {
376    let mut listener = match crate::listen::Listener::new(listener) {
377        Ok((listener, wake)) => {
378            control.set_wake(wake);
379            listener
380        }
381        Err(e) => {
382            log::error!("Local TCP listener could not wait for connections: {}", e);
383            return;
384        }
385    };
386    loop {
387        if control.should_stop() {
388            log::info!("Local TCP listener stopping");
389            return;
390        }
391
392        let stream_result = listener.get().accept().map(|(stream, _)| stream);
393        let stream = match stream_result {
394            Ok(s) => s,
395            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
396                if let Err(e) = listener.wait() {
397                    log::warn!("Local server wait failed: {}", e);
398                    thread::sleep(Duration::from_millis(50));
399                }
400                continue;
401            }
402            Err(e) => {
403                log::warn!("Local server accept failed: {}", e);
404                continue;
405            }
406        };
407
408        if let Err(e) = stream.set_nodelay(true) {
409            log::warn!("Local server set_nodelay failed: {}", e);
410        }
411
412        let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
413        spawn_local_client_handler(stream, client_id, tx.clone(), dynamic_template.as_ref());
414    }
415}
416
417/// Unix socket server accept loop for local interface.
418#[cfg(target_os = "linux")]
419fn unix_server_loop(
420    listener: std::os::unix::net::UnixListener,
421    name: String,
422    tx: EventSender,
423    next_id: Arc<AtomicU64>,
424    control: ListenerControl,
425    dynamic_template: Option<super::DynamicInterfaceTemplate>,
426) {
427    let mut listener = match crate::listen::Listener::new(listener) {
428        Ok((listener, wake)) => {
429            control.set_wake(wake);
430            listener
431        }
432        Err(e) => {
433            log::error!(
434                "[{}] Local Unix listener could not wait for connections: {}",
435                name,
436                e
437            );
438            return;
439        }
440    };
441    loop {
442        if control.should_stop() {
443            log::info!("[{}] Local Unix listener stopping", name);
444            return;
445        }
446
447        let stream_result = listener.get().accept().map(|(stream, _)| stream);
448        let stream = match stream_result {
449            Ok(s) => s,
450            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
451                if let Err(e) = listener.wait() {
452                    log::warn!("[{}] Local server wait failed: {}", name, e);
453                    thread::sleep(Duration::from_millis(50));
454                }
455                continue;
456            }
457            Err(e) => {
458                log::warn!("[{}] Local server accept failed: {}", name, e);
459                continue;
460            }
461        };
462
463        let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
464
465        // Convert UnixStream to a pair of read/write handles
466        let writer_stream = match stream.try_clone() {
467            Ok(s) => s,
468            Err(e) => {
469                log::warn!("Local server clone failed: {}", e);
470                continue;
471            }
472        };
473
474        let sleep_hold = android_client_sleep_hold();
475        let info = make_local_interface_info(client_id);
476        let writer: Box<dyn Writer> = Box::new(UnixLocalWriter {
477            stream: writer_stream,
478            sleep_hold: sleep_hold.clone(),
479            transmit_buffer: TransmitBuffer::new(),
480        });
481
482        let event = if let Some(template) = &dynamic_template {
483            Event::DynamicInterfaceUp {
484                id: client_id,
485                writer,
486                registration: Box::new(template.registration(info)),
487            }
488        } else {
489            Event::InterfaceUp(client_id, Some(writer), Some(Box::new(info)))
490        };
491        if tx.send(event).is_err() {
492            return;
493        }
494
495        let client_tx = tx.clone();
496        let ifac_size = template_ifac_size(dynamic_template.as_ref());
497        thread::Builder::new()
498            .name(format!("local-unix-reader-{}", client_id.0))
499            .spawn(move || {
500                unix_reader_loop(stream, client_id, client_tx, sleep_hold, ifac_size);
501            })
502            .ok();
503    }
504}
505
506#[cfg(target_os = "linux")]
507struct UnixLocalWriter {
508    stream: std::os::unix::net::UnixStream,
509    sleep_hold: Option<ClientSleepHold>,
510    transmit_buffer: TransmitBuffer,
511}
512
513#[cfg(target_os = "linux")]
514impl Writer for UnixLocalWriter {
515    fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
516        if self
517            .sleep_hold
518            .as_ref()
519            .is_some_and(ClientSleepHold::should_drop_outbound)
520        {
521            return Err(io::Error::new(
522                io::ErrorKind::PermissionDenied,
523                "local client has paused transmission",
524            ));
525        }
526        append_and_drain(
527            &mut self.transmit_buffer,
528            &mut self.stream,
529            &[crate::interface::FrameBytes::Owned(data.to_vec())],
530        )
531    }
532    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
533        self.send_frames(&[crate::interface::FrameBytes::Owned(data.to_vec())])
534    }
535
536    fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
537        if self
538            .sleep_hold
539            .as_ref()
540            .is_some_and(ClientSleepHold::should_drop_outbound)
541        {
542            log::debug!("TX paused for LocalInterface client, dropping outbound packet");
543            return Ok(());
544        }
545        append_and_drain(&mut self.transmit_buffer, &mut self.stream, frames)
546    }
547}
548
549#[cfg(target_os = "linux")]
550fn unix_reader_loop(
551    mut stream: std::os::unix::net::UnixStream,
552    id: InterfaceId,
553    tx: EventSender,
554    sleep_hold: Option<ClientSleepHold>,
555    ifac_size: usize,
556) {
557    use std::io::Read;
558    let mut decoder = frame_decoder(ifac_size);
559    let mut buf = [0u8; 4096];
560
561    loop {
562        match stream.read(&mut buf) {
563            Ok(0) => {
564                let _ = tx.send(Event::InterfaceDown(id));
565                return;
566            }
567            Ok(n) => {
568                if let Some(ref sleep_hold) = sleep_hold {
569                    sleep_hold.refresh();
570                }
571                for frame in decoder.feed(&buf[..n]) {
572                    if tx
573                        .send(Event::Frame {
574                            interface_id: id,
575                            data: frame.into(),
576                            rssi: None,
577                            snr: None,
578                        })
579                        .is_err()
580                    {
581                        return;
582                    }
583                }
584            }
585            Err(_) => {
586                let _ = tx.send(Event::InterfaceDown(id));
587                return;
588            }
589        }
590    }
591}
592
593/// Spawn handler threads for a connected TCP local client.
594fn spawn_local_client_handler(
595    stream: TcpStream,
596    client_id: InterfaceId,
597    tx: EventSender,
598    dynamic_template: Option<&super::DynamicInterfaceTemplate>,
599) {
600    let writer_stream = match stream.try_clone() {
601        Ok(s) => s,
602        Err(e) => {
603            log::warn!("Local server clone failed: {}", e);
604            return;
605        }
606    };
607
608    let sleep_hold = android_client_sleep_hold();
609    let info = make_local_interface_info(client_id);
610    let writer: Box<dyn Writer> = Box::new(LocalWriter {
611        stream: writer_stream,
612        sleep_hold: sleep_hold.clone(),
613        transmit_buffer: TransmitBuffer::new(),
614    });
615
616    let event = if let Some(template) = dynamic_template {
617        Event::DynamicInterfaceUp {
618            id: client_id,
619            writer,
620            registration: Box::new(template.registration(info)),
621        }
622    } else {
623        Event::InterfaceUp(client_id, Some(writer), Some(Box::new(info)))
624    };
625    if tx.send(event).is_err() {
626        return;
627    }
628
629    let ifac_size = template_ifac_size(dynamic_template);
630    thread::Builder::new()
631        .name(format!("local-reader-{}", client_id.0))
632        .spawn(move || {
633            tcp_reader_loop(stream, client_id, tx, sleep_hold, ifac_size);
634        })
635        .ok();
636}
637
638fn tcp_reader_loop(
639    mut stream: TcpStream,
640    id: InterfaceId,
641    tx: EventSender,
642    sleep_hold: Option<ClientSleepHold>,
643    ifac_size: usize,
644) {
645    let mut decoder = frame_decoder(ifac_size);
646    let mut buf = [0u8; 4096];
647
648    loop {
649        match stream.read(&mut buf) {
650            Ok(0) => {
651                log::info!("Local client {} disconnected", id.0);
652                let _ = tx.send(Event::InterfaceDown(id));
653                return;
654            }
655            Ok(n) => {
656                if let Some(ref sleep_hold) = sleep_hold {
657                    sleep_hold.refresh();
658                }
659                for frame in decoder.feed(&buf[..n]) {
660                    if tx
661                        .send(Event::Frame {
662                            interface_id: id,
663                            data: frame.into(),
664                            rssi: None,
665                            snr: None,
666                        })
667                        .is_err()
668                    {
669                        return;
670                    }
671                }
672            }
673            Err(e) => {
674                log::warn!("Local client {} read error: {}", id.0, e);
675                let _ = tx.send(Event::InterfaceDown(id));
676                return;
677            }
678        }
679    }
680}
681
682fn make_local_interface_info(id: InterfaceId) -> InterfaceInfo {
683    InterfaceInfo {
684        stamp_costs: Default::default(),
685        id,
686        name: String::from("LocalInterface"),
687        mode: constants::MODE_FULL,
688        gravity: 0,
689        recursive_prs: false,
690        announces_from_internal: true,
691        announces_to_internal: None,
692        out_capable: true,
693        in_capable: true,
694        bitrate: Some(BITRATE),
695        airtime_profile: None,
696        announce_rate_target: None,
697        announce_rate_grace: 0,
698        announce_rate_penalty: 0.0,
699        announce_cap: constants::ANNOUNCE_CAP,
700        is_local_client: true,
701        wants_tunnel: false,
702        tunnel_id: None,
703        mtu: HW_MTU,
704        ia_freq: 0.0,
705        ip_freq: 0.0,
706        op_freq: 0.0,
707        op_samples: 0,
708        started: 0.0,
709        ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
710    }
711}
712
713// ==================== LOCAL CLIENT ====================
714
715#[cfg(target_os = "linux")]
716enum LocalClientStream {
717    Unix(std::os::unix::net::UnixStream),
718    Tcp(TcpStream),
719}
720
721#[cfg(target_os = "linux")]
722impl LocalClientStream {
723    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
724        match self {
725            LocalClientStream::Unix(stream) => stream.read(buf),
726            LocalClientStream::Tcp(stream) => stream.read(buf),
727        }
728    }
729
730    fn writer(&self) -> io::Result<Box<dyn Writer>> {
731        match self {
732            LocalClientStream::Unix(stream) => Ok(Box::new(UnixLocalWriter {
733                stream: stream.try_clone()?,
734                sleep_hold: None,
735                transmit_buffer: TransmitBuffer::new(),
736            })),
737            LocalClientStream::Tcp(stream) => Ok(Box::new(LocalWriter {
738                stream: stream.try_clone()?,
739                sleep_hold: None,
740                transmit_buffer: TransmitBuffer::new(),
741            })),
742        }
743    }
744}
745
746#[cfg(not(target_os = "linux"))]
747type LocalClientStream = TcpStream;
748
749#[cfg(not(target_os = "linux"))]
750fn local_client_stream_writer(stream: &LocalClientStream) -> io::Result<Box<dyn Writer>> {
751    if crate::engine::FOLD_IO {
752        return folded::client_writer(stream);
753    }
754    Ok(Box::new(LocalWriter {
755        stream: stream.try_clone()?,
756        sleep_hold: None,
757        transmit_buffer: TransmitBuffer::new(),
758    }))
759}
760
761#[cfg(target_os = "linux")]
762fn local_client_stream_writer(stream: &LocalClientStream) -> io::Result<Box<dyn Writer>> {
763    if crate::engine::FOLD_IO {
764        return folded::client_writer(stream);
765    }
766    stream.writer()
767}
768
769fn try_connect_tcp(config: &LocalClientConfig) -> io::Result<TcpStream> {
770    let addr = format!("127.0.0.1:{}", config.port);
771    let stream = TcpStream::connect(&addr)?;
772    stream.set_nodelay(true)?;
773    log::info!(
774        "[{}] Connected to shared instance via TCP {}",
775        config.name,
776        addr
777    );
778    Ok(stream)
779}
780
781#[cfg(target_os = "linux")]
782fn try_connect_local_client(config: &LocalClientConfig) -> io::Result<LocalClientStream> {
783    match unix_socket::try_connect_unix(&config.instance_name) {
784        Ok(stream) => {
785            log::info!(
786                "[{}] Connected to shared instance via Unix socket: rns/{}",
787                config.name,
788                config.instance_name
789            );
790            Ok(LocalClientStream::Unix(stream))
791        }
792        Err(e) => {
793            log::info!(
794                "[{}] Unix socket connect failed ({}), trying TCP",
795                config.name,
796                e
797            );
798            try_connect_tcp(config).map(LocalClientStream::Tcp)
799        }
800    }
801}
802
803#[cfg(not(target_os = "linux"))]
804fn try_connect_local_client(config: &LocalClientConfig) -> io::Result<LocalClientStream> {
805    try_connect_tcp(config)
806}
807
808fn reconnect_local_client(config: &LocalClientConfig, tx: &EventSender) -> LocalClientStream {
809    loop {
810        thread::sleep(config.reconnect_wait);
811        match try_connect_local_client(config) {
812            Ok(stream) => match local_client_stream_writer(&stream) {
813                Ok(writer) => {
814                    let _ = maybe_spawn_local_client_phy_keepalive(
815                        &stream,
816                        config.interface_id,
817                        &config.name,
818                    );
819                    let _ = tx.send(Event::InterfaceUp(config.interface_id, Some(writer), None));
820                    return stream;
821                }
822                Err(e) => {
823                    log::warn!("[{}] failed to clone reconnect writer: {}", config.name, e);
824                }
825            },
826            Err(e) => {
827                log::warn!("[{}] reconnect failed: {}", config.name, e);
828            }
829        }
830    }
831}
832
833fn local_client_reader_loop(
834    mut stream: LocalClientStream,
835    config: LocalClientConfig,
836    tx: EventSender,
837) {
838    let id = config.interface_id;
839    let mut decoder = frame_decoder(0);
840    let mut buf = [0u8; 4096];
841
842    loop {
843        match stream.read(&mut buf) {
844            Ok(0) => {
845                log::warn!("[{}] shared connection closed", config.name);
846                let _ = tx.send(Event::InterfaceDown(id));
847                stream = reconnect_local_client(&config, &tx);
848                decoder = frame_decoder(0);
849            }
850            Ok(n) => {
851                for frame in decoder.feed(&buf[..n]) {
852                    if tx
853                        .send(Event::Frame {
854                            interface_id: id,
855                            data: frame.into(),
856                            rssi: None,
857                            snr: None,
858                        })
859                        .is_err()
860                    {
861                        return;
862                    }
863                }
864            }
865            Err(e) => {
866                log::warn!("[{}] shared read error: {}", config.name, e);
867                let _ = tx.send(Event::InterfaceDown(id));
868                stream = reconnect_local_client(&config, &tx);
869                decoder = frame_decoder(0);
870            }
871        }
872    }
873}
874
875/// Start a local client (connect to shared instance).
876/// Tries Unix socket first on Linux, falls back to TCP.
877/// Returns the writer for the driver.
878pub fn start_client(config: LocalClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
879    let id = config.interface_id;
880    let stream = try_connect_local_client(&config)?;
881    let writer = local_client_stream_writer(&stream)?;
882    maybe_spawn_local_client_phy_keepalive(&stream, id, &config.name)?;
883
884    let _ = tx.send(Event::InterfaceUp(id, None, None));
885
886    if crate::engine::FOLD_IO {
887        folded::serve_client(stream, config, tx)?;
888        return Ok(writer);
889    }
890    thread::Builder::new()
891        .name(format!("local-client-reader-{}", id.0))
892        .spawn(move || {
893            local_client_reader_loop(stream, config, tx);
894        })?;
895
896    Ok(writer)
897}
898
899// --- Factory implementations ---
900
901use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
902use std::collections::HashMap;
903
904/// Factory for `LocalServerInterface`.
905pub struct LocalServerFactory;
906
907impl InterfaceFactory for LocalServerFactory {
908    fn type_name(&self) -> &str {
909        "LocalServerInterface"
910    }
911
912    fn parse_config(
913        &self,
914        _name: &str,
915        id: InterfaceId,
916        params: &HashMap<String, String>,
917    ) -> Result<Box<dyn InterfaceConfigData>, String> {
918        let instance_name = params
919            .get("instance_name")
920            .cloned()
921            .unwrap_or_else(|| "default".into());
922        let port = params
923            .get("port")
924            .and_then(|v| v.parse().ok())
925            .unwrap_or(37428);
926
927        Ok(Box::new(LocalServerConfig {
928            instance_name,
929            port,
930            interface_id: id,
931        }))
932    }
933
934    fn start(
935        &self,
936        config: Box<dyn InterfaceConfigData>,
937        ctx: StartContext,
938    ) -> std::io::Result<StartResult> {
939        let server_config = *config
940            .into_any()
941            .downcast::<LocalServerConfig>()
942            .map_err(|_| {
943                std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
944            })?;
945
946        let parent_id = server_config.interface_id;
947        let control = start_server_with_template(
948            server_config,
949            ctx.tx,
950            ctx.next_dynamic_id,
951            Some(super::DynamicInterfaceTemplate {
952                parent_id,
953                interface_type: "LocalClientInterface".into(),
954                ifac: ctx.ifac,
955                mode: ctx.mode,
956                gravity: ctx.gravity,
957                recursive_prs: ctx.recursive_prs,
958                announces_from_internal: ctx.announces_from_internal,
959                announces_to_internal: ctx.announces_to_internal,
960                stamp_costs: ctx.stamp_costs,
961            }),
962        )?;
963        Ok(StartResult::Listener {
964            control: Some(control),
965        })
966    }
967}
968
969/// Factory for `LocalClientInterface`.
970pub struct LocalClientFactory;
971
972impl InterfaceFactory for LocalClientFactory {
973    fn type_name(&self) -> &str {
974        "LocalClientInterface"
975    }
976
977    fn parse_config(
978        &self,
979        _name: &str,
980        id: InterfaceId,
981        params: &HashMap<String, String>,
982    ) -> Result<Box<dyn InterfaceConfigData>, String> {
983        let instance_name = params
984            .get("instance_name")
985            .cloned()
986            .unwrap_or_else(|| "default".into());
987        let port = params
988            .get("port")
989            .and_then(|v| v.parse().ok())
990            .unwrap_or(37428);
991
992        Ok(Box::new(LocalClientConfig {
993            instance_name,
994            port,
995            interface_id: id,
996            ..LocalClientConfig::default()
997        }))
998    }
999
1000    fn start(
1001        &self,
1002        config: Box<dyn InterfaceConfigData>,
1003        ctx: StartContext,
1004    ) -> std::io::Result<StartResult> {
1005        let client_config = *config
1006            .into_any()
1007            .downcast::<LocalClientConfig>()
1008            .map_err(|_| {
1009                std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
1010            })?;
1011
1012        let id = client_config.interface_id;
1013        let name = client_config.name.clone();
1014        let info = InterfaceInfo {
1015            stamp_costs: Default::default(),
1016            id,
1017            name,
1018            mode: ctx.mode,
1019            gravity: ctx.gravity,
1020            recursive_prs: ctx.recursive_prs,
1021            announces_from_internal: ctx.announces_from_internal,
1022            announces_to_internal: ctx.announces_to_internal,
1023            out_capable: true,
1024            in_capable: true,
1025            bitrate: Some(BITRATE),
1026            airtime_profile: None,
1027            announce_rate_target: None,
1028            announce_rate_grace: 0,
1029            announce_rate_penalty: 0.0,
1030            announce_cap: rns_core::constants::ANNOUNCE_CAP,
1031            is_local_client: false,
1032            wants_tunnel: false,
1033            tunnel_id: None,
1034            mtu: HW_MTU,
1035            ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1036            ia_freq: 0.0,
1037            ip_freq: 0.0,
1038            op_freq: 0.0,
1039            op_samples: 0,
1040            started: crate::time::now(),
1041        };
1042
1043        // Build the complete interface metadata before connecting: a local peer
1044        // can deliver an event as soon as the socket opens. The driver installs
1045        // this StartResult before it consumes queued interface events.
1046        let writer = start_client(client_config, ctx.tx)?;
1047
1048        Ok(StartResult::Simple {
1049            control: None,
1050            id,
1051            info,
1052            writer,
1053            interface_type_name: "LocalInterface".to_string(),
1054        })
1055    }
1056}
1057
1058#[cfg(test)]
1059mod tests {
1060    use super::*;
1061    use std::sync::mpsc;
1062    use std::sync::mpsc::RecvTimeoutError;
1063
1064    #[cfg(target_os = "linux")]
1065    type TestClient = std::os::unix::net::UnixStream;
1066
1067    #[cfg(not(target_os = "linux"))]
1068    type TestClient = TcpStream;
1069
1070    fn connect_test_client(instance_name: &str, _port: u16) -> TestClient {
1071        #[cfg(target_os = "linux")]
1072        {
1073            unix_socket::try_connect_unix(instance_name).unwrap()
1074        }
1075
1076        #[cfg(not(target_os = "linux"))]
1077        {
1078            TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap()
1079        }
1080    }
1081
1082    fn find_free_port() -> u16 {
1083        crate::test_support::port()
1084    }
1085
1086    #[test]
1087    fn local_interface_metadata_uses_explicit_hardware_mtu() {
1088        let info = make_local_interface_info(InterfaceId(42));
1089        assert_eq!(info.bitrate, Some(1_000_000_000));
1090        assert_eq!(info.mtu, 262_144);
1091    }
1092
1093    #[test]
1094    fn server_bind_tcp() {
1095        let port = find_free_port();
1096        let instance_name = crate::test_support::instance_name("test-bind");
1097        let (tx, _rx) = crate::event::channel();
1098        let next_id = Arc::new(AtomicU64::new(7000));
1099
1100        let config = LocalServerConfig {
1101            instance_name: instance_name.clone(),
1102            port,
1103            interface_id: InterfaceId(70),
1104        };
1105
1106        // We force TCP by using a unique instance name that won't conflict
1107        // with any existing Unix socket
1108        start_server(config, tx, next_id).unwrap();
1109        thread::sleep(Duration::from_millis(50));
1110
1111        connect_test_client(&instance_name, port);
1112    }
1113
1114    fn thread_named(prefix: &str) -> bool {
1115        std::fs::read_dir("/proc/self/task").unwrap().any(|task| {
1116            let comm = task.unwrap().path().join("comm");
1117            std::fs::read_to_string(comm).is_ok_and(|comm| comm.trim().starts_with(prefix))
1118        })
1119    }
1120
1121    #[test]
1122    fn folded_server_serves_clients_on_the_driver_loop() {
1123        if !crate::engine::FOLD_IO {
1124            return;
1125        }
1126        let port = find_free_port();
1127        let instance_name = crate::test_support::instance_name("test-folded");
1128        let (tx, rx) = crate::event::channel();
1129        let next_id = Arc::new(AtomicU64::new(7900));
1130        let config = LocalServerConfig {
1131            instance_name: instance_name.clone(),
1132            port,
1133            interface_id: InterfaceId(79),
1134        };
1135        let control = start_server(config, tx.clone(), next_id).unwrap();
1136        let mut client = connect_test_client(&instance_name, port);
1137
1138        // Registered, then written by the driver with no worker thread.
1139        let writer = match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
1140            Event::InterfaceUp(id, Some(writer), Some(_)) => {
1141                assert_eq!(id, InterfaceId(7900));
1142                writer
1143            }
1144            other => panic!("expected InterfaceUp, got {:?}", other),
1145        };
1146        assert!(writer.loop_socket().is_some());
1147        let (mut writer, _) =
1148            crate::interface::wrap_async_writer(writer, InterfaceId(7900), "local", tx, 16);
1149        assert!(!thread_named("local-unix-read"));
1150        assert!(!thread_named("local-reader-79"));
1151        assert!(!thread_named("iface-writer-79"));
1152
1153        // A stopped listener leaves connected clients served.
1154        control.request_stop();
1155        let payload: Vec<u8> = (0..32).collect();
1156        client.write_all(&hdlc::frame(&payload)).unwrap();
1157        match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
1158            Event::Frame {
1159                interface_id, data, ..
1160            } => {
1161                assert_eq!(interface_id, InterfaceId(7900));
1162                assert_eq!(&*data, &payload[..]);
1163            }
1164            other => panic!("expected Frame, got {:?}", other),
1165        }
1166
1167        let outbound: Vec<u8> = (100..140).collect();
1168        writer.send_frame(&outbound).unwrap();
1169        writer.flush_pending();
1170        let mut decoder = frame_decoder(0);
1171        let mut buf = [0u8; 256];
1172        let n = client.read(&mut buf).unwrap();
1173        assert_eq!(decoder.feed(&buf[..n]), vec![outbound]);
1174
1175        drop(client);
1176        match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
1177            Event::InterfaceDown(id) => assert_eq!(id, InterfaceId(7900)),
1178            other => panic!("expected InterfaceDown, got {:?}", other),
1179        }
1180    }
1181
1182    #[test]
1183    fn server_accept_client() {
1184        let port = find_free_port();
1185        let instance_name = crate::test_support::instance_name("test-accept");
1186        let (tx, rx) = crate::event::channel();
1187        let next_id = Arc::new(AtomicU64::new(7100));
1188
1189        let config = LocalServerConfig {
1190            instance_name: instance_name.clone(),
1191            port,
1192            interface_id: InterfaceId(71),
1193        };
1194
1195        start_server(config, tx, next_id).unwrap();
1196        thread::sleep(Duration::from_millis(50));
1197
1198        connect_test_client(&instance_name, port);
1199
1200        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1201        match event {
1202            Event::InterfaceUp(id, writer, info) => {
1203                assert_eq!(id, InterfaceId(7100));
1204                assert!(writer.is_some());
1205                assert!(info.is_some());
1206            }
1207            other => panic!("expected InterfaceUp, got {:?}", other),
1208        }
1209    }
1210
1211    #[test]
1212    fn server_stop_prevents_new_accepts() {
1213        let port = find_free_port();
1214        let instance_name = crate::test_support::instance_name("test-stop");
1215        let (tx, rx) = crate::event::channel();
1216        let next_id = Arc::new(AtomicU64::new(7150));
1217
1218        let config = LocalServerConfig {
1219            instance_name: instance_name.clone(),
1220            port,
1221            interface_id: InterfaceId(71),
1222        };
1223
1224        let control = start_server(config, tx, next_id).unwrap();
1225        thread::sleep(Duration::from_millis(50));
1226        control.request_stop();
1227        thread::sleep(Duration::from_millis(120));
1228
1229        #[cfg(target_os = "linux")]
1230        let connect_result = unix_socket::try_connect_unix(&instance_name);
1231
1232        #[cfg(not(target_os = "linux"))]
1233        let connect_result = TcpStream::connect(format!("127.0.0.1:{}", port));
1234
1235        if let Ok(stream) = connect_result {
1236            drop(stream);
1237        }
1238
1239        match rx.recv_timeout(Duration::from_millis(200)) {
1240            Err(RecvTimeoutError::Timeout) | Err(RecvTimeoutError::Disconnected) => {}
1241            other => panic!("expected no InterfaceUp after server stop, got {:?}", other),
1242        }
1243    }
1244
1245    #[test]
1246    fn client_send_receive() {
1247        let port = find_free_port();
1248        let (server_tx, server_rx) = crate::event::channel();
1249        let next_id = Arc::new(AtomicU64::new(7200));
1250
1251        let server_config = LocalServerConfig {
1252            instance_name: crate::test_support::instance_name("test-sr"),
1253            port,
1254            interface_id: InterfaceId(72),
1255        };
1256
1257        start_server(server_config, server_tx, next_id).unwrap();
1258        thread::sleep(Duration::from_millis(50));
1259
1260        // Connect client
1261        let (client_tx, client_rx) = crate::event::channel();
1262        let client_config = LocalClientConfig {
1263            name: "test-client".into(),
1264            instance_name: crate::test_support::instance_name("test-sr"),
1265            port,
1266            interface_id: InterfaceId(73),
1267            reconnect_wait: Duration::from_secs(1),
1268        };
1269
1270        let mut client_writer = start_client(client_config, client_tx).unwrap();
1271
1272        // Get server-side InterfaceUp
1273        let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1274        let mut server_writer = match event {
1275            Event::InterfaceUp(_, Some(w), Some(info)) => {
1276                assert!(info.is_local_client);
1277                w
1278            }
1279            other => panic!("expected InterfaceUp with writer, got {:?}", other),
1280        };
1281
1282        // Get client-side InterfaceUp
1283        let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1284        match event {
1285            Event::InterfaceUp(id, _, _) => assert_eq!(id, InterfaceId(73)),
1286            other => panic!("expected InterfaceUp, got {:?}", other),
1287        }
1288
1289        // Client sends to server
1290        let payload: Vec<u8> = (0..32).collect();
1291        client_writer.send_frame(&payload).unwrap();
1292
1293        let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1294        match event {
1295            Event::Frame { data, .. } => assert_eq!(data, payload.into()),
1296            other => panic!("expected Frame, got {:?}", other),
1297        }
1298
1299        // Server sends to client
1300        let payload2: Vec<u8> = (100..132).collect();
1301        server_writer.send_frame(&payload2).unwrap();
1302
1303        let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1304        match event {
1305            Event::Frame { data, .. } => assert_eq!(data, payload2.into()),
1306            other => panic!("expected Frame, got {:?}", other),
1307        }
1308    }
1309
1310    #[test]
1311    fn local_shared_instance_writer_is_exempt_from_egress_control() {
1312        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1313        let client = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
1314        let (server_stream, _) = listener.accept().unwrap();
1315        let writer = LocalWriter {
1316            stream: server_stream,
1317            sleep_hold: None,
1318            transmit_buffer: TransmitBuffer::new(),
1319        };
1320
1321        assert!(writer.egress_control().is_none());
1322        drop(client);
1323    }
1324
1325    #[test]
1326    fn sleep_hold_drops_outbound_after_timeout_and_refresh_restores() {
1327        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1328        let addr = listener.local_addr().unwrap();
1329        let mut client = TcpStream::connect(addr).unwrap();
1330        let (server_stream, _) = listener.accept().unwrap();
1331        client
1332            .set_read_timeout(Some(Duration::from_millis(200)))
1333            .unwrap();
1334
1335        let sleep_hold = ClientSleepHold::new(Duration::from_millis(30));
1336        let mut writer = LocalWriter {
1337            stream: server_stream,
1338            sleep_hold: Some(sleep_hold.clone()),
1339            transmit_buffer: TransmitBuffer::new(),
1340        };
1341
1342        writer.send_frame(b"live").unwrap();
1343        let mut buf = [0u8; 16];
1344        let n = client.read(&mut buf).unwrap();
1345        assert!(n > 0);
1346
1347        thread::sleep(Duration::from_millis(50));
1348        assert_eq!(
1349            writer
1350                .send_frame_confirmed(b"confirmed")
1351                .unwrap_err()
1352                .kind(),
1353            io::ErrorKind::PermissionDenied
1354        );
1355        writer.send_frame(b"drop").unwrap();
1356        let err = client.read(&mut buf).unwrap_err();
1357        assert!(
1358            matches!(
1359                err.kind(),
1360                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
1361            ),
1362            "paused writer should not emit bytes, got {err:?}"
1363        );
1364
1365        sleep_hold.refresh();
1366        writer.send_frame(b"again").unwrap();
1367        let n = client.read(&mut buf).unwrap();
1368        assert!(n > 0);
1369    }
1370
1371    #[test]
1372    fn tcp_reader_refreshes_sleep_hold_on_inbound_data() {
1373        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1374        let addr = listener.local_addr().unwrap();
1375        let mut client = TcpStream::connect(addr).unwrap();
1376        let (server_stream, _) = listener.accept().unwrap();
1377        let writer_stream = server_stream.try_clone().unwrap();
1378        client
1379            .set_read_timeout(Some(Duration::from_millis(200)))
1380            .unwrap();
1381
1382        let sleep_hold = ClientSleepHold::new(Duration::from_millis(30));
1383        let reader_sleep_hold = sleep_hold.clone();
1384        let (tx, rx) = crate::event::channel();
1385        thread::spawn(move || {
1386            tcp_reader_loop(
1387                server_stream,
1388                InterfaceId(98),
1389                tx,
1390                Some(reader_sleep_hold),
1391                0,
1392            );
1393        });
1394
1395        let mut writer = LocalWriter {
1396            stream: writer_stream,
1397            sleep_hold: Some(sleep_hold),
1398            transmit_buffer: TransmitBuffer::new(),
1399        };
1400
1401        thread::sleep(Duration::from_millis(50));
1402        writer.send_frame(b"drop").unwrap();
1403        let mut buf = [0u8; 32];
1404        let err = client.read(&mut buf).unwrap_err();
1405        assert!(
1406            matches!(
1407                err.kind(),
1408                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
1409            ),
1410            "paused writer should not emit bytes, got {err:?}"
1411        );
1412
1413        let inbound = vec![0x42; constants::HEADER_MINSIZE + 1];
1414        client.write_all(&hdlc::frame(&inbound)).unwrap();
1415        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1416        match event {
1417            Event::Frame {
1418                interface_id,
1419                data,
1420                rssi: _,
1421                snr: _,
1422            } => {
1423                assert_eq!(interface_id, InterfaceId(98));
1424                assert_eq!(data, inbound.into());
1425            }
1426            other => panic!("expected Frame, got {:?}", other),
1427        }
1428
1429        writer.send_frame(b"again").unwrap();
1430        let n = client.read(&mut buf).unwrap();
1431        assert!(n > 0);
1432    }
1433
1434    #[test]
1435    fn physical_keepalive_loop_sends_empty_hdlc_frame() {
1436        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1437        let addr = listener.local_addr().unwrap();
1438        let mut client = TcpStream::connect(addr).unwrap();
1439        let (server_stream, _) = listener.accept().unwrap();
1440        client
1441            .set_read_timeout(Some(Duration::from_millis(500)))
1442            .unwrap();
1443
1444        let writer: Box<dyn Writer> = Box::new(LocalWriter {
1445            stream: server_stream,
1446            sleep_hold: None,
1447            transmit_buffer: TransmitBuffer::new(),
1448        });
1449        spawn_physical_keepalive_loop(
1450            writer,
1451            InterfaceId(99),
1452            "test-local".into(),
1453            Duration::from_millis(10),
1454        );
1455
1456        let mut buf = [0u8; 2];
1457        client.read_exact(&mut buf).unwrap();
1458        assert_eq!(buf, [0x7E, 0x7E]);
1459    }
1460
1461    #[test]
1462    fn multiple_local_clients() {
1463        let port = find_free_port();
1464        let instance_name = crate::test_support::instance_name("test-multi");
1465        let (tx, rx) = crate::event::channel();
1466        let next_id = Arc::new(AtomicU64::new(7300));
1467
1468        let config = LocalServerConfig {
1469            instance_name: instance_name.clone(),
1470            port,
1471            interface_id: InterfaceId(74),
1472        };
1473
1474        start_server(config, tx, next_id).unwrap();
1475        thread::sleep(Duration::from_millis(50));
1476
1477        let _client1 = connect_test_client(&instance_name, port);
1478        let _client2 = connect_test_client(&instance_name, port);
1479
1480        let mut ids = Vec::new();
1481        for _ in 0..2 {
1482            let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1483            match event {
1484                Event::InterfaceUp(id, _, _) => ids.push(id),
1485                other => panic!("expected InterfaceUp, got {:?}", other),
1486            }
1487        }
1488
1489        assert_eq!(ids.len(), 2);
1490        assert_ne!(ids[0], ids[1]);
1491    }
1492
1493    #[test]
1494    fn client_disconnect_detected() {
1495        let port = find_free_port();
1496        let instance_name = crate::test_support::instance_name("test-dc");
1497        let (tx, rx) = crate::event::channel();
1498        let next_id = Arc::new(AtomicU64::new(7400));
1499
1500        let config = LocalServerConfig {
1501            instance_name: instance_name.clone(),
1502            port,
1503            interface_id: InterfaceId(75),
1504        };
1505
1506        start_server(config, tx, next_id).unwrap();
1507        thread::sleep(Duration::from_millis(50));
1508
1509        #[cfg(target_os = "linux")]
1510        let client = unix_socket::try_connect_unix(&instance_name).unwrap();
1511
1512        #[cfg(not(target_os = "linux"))]
1513        let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1514
1515        // Drain InterfaceUp
1516        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1517
1518        // Disconnect
1519        drop(client);
1520
1521        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1522        assert!(
1523            matches!(event, Event::InterfaceDown(_)),
1524            "expected InterfaceDown, got {:?}",
1525            event
1526        );
1527    }
1528
1529    #[test]
1530    fn client_reconnects_after_tcp_restart() {
1531        let port = find_free_port();
1532        let addr = format!("127.0.0.1:{}", port);
1533        let instance_name = format!("test-reconnect-{}", port);
1534
1535        let listener1 = TcpListener::bind(&addr).unwrap();
1536        let (accepted1_tx, accepted1_rx) = mpsc::channel();
1537        thread::spawn(move || {
1538            let (stream, _) = listener1.accept().unwrap();
1539            accepted1_tx.send(stream).unwrap();
1540        });
1541
1542        let (client_tx, client_rx) = crate::event::channel();
1543        let client_config = LocalClientConfig {
1544            name: "test-client".into(),
1545            instance_name,
1546            port,
1547            interface_id: InterfaceId(76),
1548            reconnect_wait: Duration::from_millis(50),
1549        };
1550
1551        let _writer = start_client(client_config, client_tx).unwrap();
1552        let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1553        assert!(matches!(
1554            event,
1555            Event::InterfaceUp(InterfaceId(76), None, None)
1556        ));
1557
1558        let stream1 = accepted1_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1559        drop(stream1);
1560
1561        let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1562        assert!(matches!(event, Event::InterfaceDown(InterfaceId(76))));
1563
1564        let listener2 = TcpListener::bind(&addr).unwrap();
1565        let (accepted2_tx, accepted2_rx) = mpsc::channel();
1566        thread::spawn(move || {
1567            let (stream, _) = listener2.accept().unwrap();
1568            accepted2_tx.send(stream).unwrap();
1569        });
1570
1571        let mut reconnected_writer = None;
1572        for _ in 0..10 {
1573            let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1574            match event {
1575                Event::InterfaceUp(InterfaceId(76), writer, None) if writer.is_some() => {
1576                    reconnected_writer = writer;
1577                    break;
1578                }
1579                _ => {}
1580            }
1581        }
1582
1583        let mut reconnected_writer = reconnected_writer.expect("missing reconnect writer");
1584        let mut stream2 = accepted2_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1585        reconnected_writer.send_frame(b"client->server").unwrap();
1586        stream2
1587            .set_read_timeout(Some(Duration::from_secs(2)))
1588            .unwrap();
1589        let mut buf = [0u8; 64];
1590        let n = stream2.read(&mut buf).unwrap();
1591        assert!(n > 0, "expected bytes from refreshed writer");
1592    }
1593
1594    #[cfg(target_os = "linux")]
1595    #[test]
1596    fn unix_abstract_socket_helpers_work() {
1597        let instance_name = format!(
1598            "test-abstract-{}",
1599            std::time::SystemTime::now()
1600                .duration_since(std::time::UNIX_EPOCH)
1601                .unwrap()
1602                .as_nanos()
1603        );
1604
1605        let listener = unix_socket::try_bind_unix(&instance_name).unwrap();
1606        let accept_thread = thread::spawn(move || listener.accept().unwrap().0);
1607
1608        let mut client = unix_socket::try_connect_unix(&instance_name).unwrap();
1609        let mut server = accept_thread.join().unwrap();
1610
1611        client.write_all(b"ping").unwrap();
1612        let mut buf = [0u8; 4];
1613        server.read_exact(&mut buf).unwrap();
1614        assert_eq!(&buf, b"ping");
1615    }
1616
1617    #[test]
1618    fn tcp_reader_delivers_frame_above_64k_sent_in_small_chunks() {
1619        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1620        let addr = listener.local_addr().unwrap();
1621        let mut client = TcpStream::connect(addr).unwrap();
1622        let (server_stream, _) = listener.accept().unwrap();
1623
1624        let (tx, rx) = crate::event::channel();
1625        thread::spawn(move || {
1626            tcp_reader_loop(server_stream, InterfaceId(97), tx, None, 0);
1627        });
1628
1629        let payload: Vec<u8> = (0..100 * 1024).map(|i| (i % 251) as u8).collect();
1630        let framed = hdlc::frame(&payload);
1631        for chunk in framed.chunks(4096) {
1632            client.write_all(chunk).unwrap();
1633        }
1634        client.flush().unwrap();
1635
1636        match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
1637            Event::Frame {
1638                interface_id, data, ..
1639            } => {
1640                assert_eq!(interface_id, InterfaceId(97));
1641                assert_eq!(data, payload.into());
1642            }
1643            other => panic!("expected Frame, got {:?}", other),
1644        }
1645    }
1646
1647    #[test]
1648    fn frame_decoder_accepts_hw_mtu_frame_in_chunks() {
1649        let mut decoder = frame_decoder(0);
1650        let payload: Vec<u8> = (0..HW_MTU as usize).map(|i| (i % 253) as u8).collect();
1651        let framed = hdlc::frame(&payload);
1652        let mut frames = Vec::new();
1653        let mut invalid = Vec::new();
1654        for chunk in framed.chunks(4096) {
1655            let decoded = decoder.feed_with_diagnostics(chunk);
1656            frames.extend(decoded.frames);
1657            invalid.extend(decoded.invalid_frame_lengths);
1658        }
1659        assert_eq!(frames, vec![payload]);
1660        assert!(invalid.is_empty());
1661    }
1662
1663    #[test]
1664    fn frame_decoder_drops_frame_above_hw_mtu_as_invalid() {
1665        let mut decoder = frame_decoder(0);
1666        let oversized = vec![0x42; HW_MTU as usize + 1];
1667        let valid = vec![0x24; constants::HEADER_MINSIZE + 1];
1668        let mut stream = hdlc::frame(&oversized);
1669        stream.extend_from_slice(&hdlc::frame(&valid));
1670        let mut frames = Vec::new();
1671        let mut invalid = Vec::new();
1672        for chunk in stream.chunks(4096) {
1673            let decoded = decoder.feed_with_diagnostics(chunk);
1674            frames.extend(decoded.frames);
1675            invalid.extend(decoded.invalid_frame_lengths);
1676        }
1677        assert_eq!(frames, vec![valid]);
1678        assert_eq!(invalid, vec![HW_MTU as usize + 1]);
1679    }
1680
1681    #[test]
1682    fn frame_decoder_allows_template_ifac_bytes() {
1683        let ifac_size = 16;
1684        let mut decoder = frame_decoder(ifac_size);
1685        let payload = vec![0x11; HW_MTU as usize + ifac_size];
1686        let decoded = decoder.feed_with_diagnostics(&hdlc::frame(&payload));
1687        assert_eq!(decoded.frames, vec![payload]);
1688        assert!(decoded.invalid_frame_lengths.is_empty());
1689    }
1690}