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::transmit_buffer::TransmitBuffer;
24use crate::interface::{ListenerControl, Writer};
25
26pub(crate) const HW_MTU: u32 = 262_144;
28pub(crate) const BITRATE: u64 = 1_000_000_000;
29
30pub(crate) fn frame_decoder(ifac_size: usize) -> hdlc::Decoder {
36 hdlc::Decoder::reticulum(HW_MTU as usize, ifac_size)
37}
38
39fn 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#[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#[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
91struct 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 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 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 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
269pub 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 #[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 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
338fn 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#[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 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
533fn 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#[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
808pub 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
828use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
831use std::collections::HashMap;
832
833pub 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
897pub 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 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 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 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 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 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 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 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 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1376
1377 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}