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