1use 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#[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#[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
70struct 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 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 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
203pub fn start_server(
209 config: LocalServerConfig,
210 tx: EventSender,
211 next_id: Arc<AtomicU64>,
212) -> io::Result<ListenerControl> {
213 let control = ListenerControl::new();
214 #[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 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
256fn 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#[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 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
419fn 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), 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#[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
675pub 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
695use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
698use std::collections::HashMap;
699
700pub 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
749pub 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 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 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 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 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 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 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 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1188
1189 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}