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