1use std::io::{self, Read, Write};
7use std::os::fd::AsRawFd;
8use std::sync::Arc;
9use std::thread;
10use std::time::Duration;
11
12use rns_core::transport::types::InterfaceId;
13
14use crate::event::{Event, EventSender, LoopContext, LoopSource, SourceStatus};
15use crate::hdlc;
16use crate::interface::stream_read::{read_device_frames, StreamRead};
17use crate::interface::{
18 poll_readable, wait_while_active, ListenerControl, UnavailableWriter, Writer,
19};
20use crate::serial::{Parity, SerialConfig, SerialPort};
21
22const HW_MTU: usize = 564;
24
25fn frame_decoder(ifac_size: usize) -> hdlc::Decoder {
27 hdlc::Decoder::reticulum(HW_MTU, ifac_size)
28}
29
30#[derive(Debug, Clone)]
32pub struct SerialIfaceConfig {
33 pub name: String,
34 pub port: String,
35 pub speed: u32,
36 pub data_bits: u8,
37 pub parity: Parity,
38 pub stop_bits: u8,
39 pub interface_id: InterfaceId,
40}
41
42impl Default for SerialIfaceConfig {
43 fn default() -> Self {
44 SerialIfaceConfig {
45 name: String::new(),
46 port: String::new(),
47 speed: 9600,
48 data_bits: 8,
49 parity: Parity::None,
50 stop_bits: 1,
51 interface_id: InterfaceId(0),
52 }
53 }
54}
55
56struct SerialWriter {
58 file: std::fs::File,
59}
60
61impl Writer for SerialWriter {
62 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
63 self.file.write_all(&hdlc::frame(data))
64 }
65
66 fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
67 crate::engine::FOLD_IO.then(|| self.file.as_raw_fd())
69 }
70}
71
72pub fn start(config: SerialIfaceConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
75 start_with_control(config, tx, 0).map(|(writer, _)| writer)
76}
77
78fn start_with_control(
79 config: SerialIfaceConfig,
80 tx: EventSender,
81 ifac_size: usize,
82) -> io::Result<(Box<dyn Writer>, ListenerControl)> {
83 let serial_config = SerialConfig {
84 path: config.port.clone(),
85 baud: config.speed,
86 data_bits: config.data_bits,
87 parity: config.parity,
88 stop_bits: config.stop_bits,
89 };
90
91 let control = ListenerControl::new();
92 let port = match SerialPort::open(&serial_config) {
93 Ok(port) => port,
94 Err(error) => {
95 log::warn!(
96 "[{}] could not open serial port {}: {}; retrying",
97 config.name,
98 config.port,
99 error
100 );
101 let retry_control = control.clone();
102 thread::Builder::new()
103 .name(format!("serial-reconnect-{}", config.interface_id.0))
104 .spawn(move || {
105 if let Some(reader) = reconnect(&config, &tx, &retry_control) {
106 if crate::engine::FOLD_IO {
107 let link = Arc::new(SerialLink {
108 config,
109 tx,
110 control: retry_control,
111 ifac_size,
112 });
113 link.serve_or_retry(reader);
114 return;
115 }
116 let id = config.interface_id;
117 reader_loop(reader, id, config, tx, retry_control, ifac_size);
118 }
119 })?;
120 return Ok((Box::new(UnavailableWriter), control));
121 }
122 };
123 let reader_file = port.reader()?;
124 let writer_file = port.writer()?;
125
126 let id = config.interface_id;
127
128 let _ = tx.send(Event::InterfaceUp(id, None, None));
130
131 thread::sleep(Duration::from_millis(500));
133
134 if crate::engine::FOLD_IO {
135 let link = Arc::new(SerialLink {
136 config,
137 tx,
138 control: control.clone(),
139 ifac_size,
140 });
141 link.serve(reader_file)?;
142 return Ok((Box::new(SerialWriter { file: writer_file }), control));
143 }
144
145 let reader_control = control.clone();
147 thread::Builder::new()
148 .name(format!("serial-reader-{}", id.0))
149 .spawn(move || {
150 reader_loop(reader_file, id, config, tx, reader_control, ifac_size);
151 })?;
152
153 Ok((Box::new(SerialWriter { file: writer_file }), control))
154}
155
156fn reader_loop(
158 mut reader: std::fs::File,
159 id: InterfaceId,
160 config: SerialIfaceConfig,
161 tx: EventSender,
162 control: ListenerControl,
163 ifac_size: usize,
164) {
165 let mut decoder = frame_decoder(ifac_size);
166 let mut buf = [0u8; 4096];
167
168 loop {
169 if control.should_stop() {
170 return;
171 }
172 match poll_readable(reader.as_raw_fd(), Duration::from_millis(200)) {
173 Ok(false) => continue,
174 Ok(true) => {}
175 Err(e) => {
176 if control.should_stop() {
177 return;
178 }
179 log::warn!("[{}] serial poll error: {}", config.name, e);
180 let _ = tx.send(Event::InterfaceDown(id));
181 match reconnect(&config, &tx, &control) {
182 Some(new_reader) => {
183 reader = new_reader;
184 decoder = frame_decoder(ifac_size);
185 continue;
186 }
187 None => return,
188 }
189 }
190 }
191 match reader.read(&mut buf) {
192 Ok(0) => {
193 if control.should_stop() {
194 return;
195 }
196 log::warn!("[{}] serial port closed", config.name);
198 let _ = tx.send(Event::InterfaceDown(id));
199 match reconnect(&config, &tx, &control) {
200 Some(new_reader) => {
201 reader = new_reader;
202 decoder = frame_decoder(ifac_size);
203 continue;
204 }
205 None => return,
206 }
207 }
208 Ok(n) => {
209 for frame in decoder.feed(&buf[..n]) {
210 if tx
211 .send(Event::Frame {
212 interface_id: id,
213 data: frame.into(),
214 rssi: None,
215 snr: None,
216 })
217 .is_err()
218 {
219 return; }
221 }
222 }
223 Err(e) => {
224 if control.should_stop() {
225 return;
226 }
227 log::warn!("[{}] serial read error: {}", config.name, e);
228 let _ = tx.send(Event::InterfaceDown(id));
229 match reconnect(&config, &tx, &control) {
230 Some(new_reader) => {
231 reader = new_reader;
232 decoder = frame_decoder(ifac_size);
233 continue;
234 }
235 None => return,
236 }
237 }
238 }
239 }
240}
241
242struct SerialLink {
245 config: SerialIfaceConfig,
246 tx: EventSender,
247 control: ListenerControl,
248 ifac_size: usize,
249}
250
251impl SerialLink {
252 fn serve(self: &Arc<Self>, reader: std::fs::File) -> io::Result<()> {
254 crate::interface::stream_read::set_nonblocking(reader.as_raw_fd())?;
255 self.tx.register_source(
256 crate::interface::loop_writer::read_key(self.config.interface_id),
257 Box::new(SerialSource {
258 reader,
259 decoder: frame_decoder(self.ifac_size),
260 link: Arc::clone(self),
261 }),
262 )
263 }
264
265 fn serve_or_retry(self: &Arc<Self>, mut reader: std::fs::File) {
268 loop {
269 match self.serve(reader) {
270 Ok(()) => return,
271 Err(e) => {
272 log::warn!(
273 "[{}] cannot read the reopened port: {}",
274 self.config.name,
275 e
276 );
277 let _ = self.tx.send(Event::InterfaceDown(self.config.interface_id));
278 }
279 }
280 match reconnect(&self.config, &self.tx, &self.control) {
281 Some(new_reader) => reader = new_reader,
282 None => return,
283 }
284 }
285 }
286
287 fn connection_lost(self: &Arc<Self>) {
290 let id = self.config.interface_id;
291 let _ = self.tx.push_from_loop([Event::InterfaceDown(id)]);
292 let link = Arc::clone(self);
293 let spawned = thread::Builder::new()
294 .name(format!("serial-reconnect-{}", id.0))
295 .spawn(move || {
296 if let Some(reader) = reconnect(&link.config, &link.tx, &link.control) {
297 link.serve_or_retry(reader);
298 }
299 });
300 if let Err(e) = spawned {
301 log::error!(
302 "[{}] cannot start reconnecting, giving up: {}",
303 self.config.name,
304 e
305 );
306 }
307 }
308}
309
310struct SerialSource {
312 reader: std::fs::File,
313 decoder: hdlc::Decoder,
314 link: Arc<SerialLink>,
315}
316
317impl LoopSource for SerialSource {
318 fn fd(&self) -> std::os::fd::RawFd {
319 self.reader.as_raw_fd()
320 }
321
322 fn stopped(&self) -> bool {
323 self.link.control.should_stop()
324 }
325
326 fn on_ready(&mut self, cx: &mut LoopContext<'_>) -> SourceStatus {
327 let link = &self.link;
328 if link.control.should_stop() {
329 return SourceStatus::Remove;
330 }
331 let name = &link.config.name;
332 match read_device_frames(
333 self.reader.as_raw_fd(),
334 &mut self.decoder,
335 cx,
336 &link.tx,
337 link.config.interface_id,
338 name,
339 |_, _| {},
340 ) {
341 StreamRead::Rearm => SourceStatus::Rearm,
342 StreamRead::Suspend => SourceStatus::Suspend,
343 StreamRead::Closed => {
344 log::warn!("[{}] serial port closed", name);
345 link.connection_lost();
346 SourceStatus::Remove
347 }
348 StreamRead::Failed(e) => {
349 log::warn!("[{}] serial read error: {}", name, e);
350 link.connection_lost();
351 SourceStatus::Remove
352 }
353 StreamRead::DriverGone => SourceStatus::Remove,
354 }
355 }
356}
357
358fn reconnect(
360 config: &SerialIfaceConfig,
361 tx: &EventSender,
362 control: &ListenerControl,
363) -> Option<std::fs::File> {
364 loop {
365 if !wait_while_active(control, Duration::from_secs(5)) {
366 return None;
367 }
368 log::info!(
369 "[{}] attempting to reconnect serial port {}...",
370 config.name,
371 config.port
372 );
373
374 let serial_config = SerialConfig {
375 path: config.port.clone(),
376 baud: config.speed,
377 data_bits: config.data_bits,
378 parity: config.parity,
379 stop_bits: config.stop_bits,
380 };
381
382 match SerialPort::open(&serial_config) {
383 Ok(port) => match (port.reader(), port.writer()) {
384 (Ok(reader), Ok(writer_file)) => {
385 if control.should_stop() {
386 return None;
387 }
388 log::info!("[{}] serial port reconnected", config.name);
389 let new_writer: Box<dyn Writer> = Box::new(SerialWriter { file: writer_file });
390 let _ = tx.send(Event::InterfaceUp(
391 config.interface_id,
392 Some(new_writer),
393 None,
394 ));
395 if !wait_while_active(control, Duration::from_millis(500)) {
396 return None;
397 }
398 return Some(reader);
399 }
400 _ => {
401 log::warn!(
402 "[{}] failed to get reader/writer from serial port",
403 config.name
404 );
405 }
406 },
407 Err(e) => {
408 log::warn!("[{}] serial reconnect failed: {}", config.name, e);
409 }
410 }
411 }
412}
413
414use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
417use rns_core::transport::types::InterfaceInfo;
418use std::collections::HashMap;
419
420pub struct SerialFactory;
422
423impl InterfaceFactory for SerialFactory {
424 fn type_name(&self) -> &str {
425 "SerialInterface"
426 }
427
428 fn default_ifac_size(&self) -> usize {
429 8
430 }
431
432 fn parse_config(
433 &self,
434 name: &str,
435 id: InterfaceId,
436 params: &HashMap<String, String>,
437 ) -> Result<Box<dyn InterfaceConfigData>, String> {
438 let port = params
439 .get("port")
440 .cloned()
441 .ok_or_else(|| "SerialInterface requires 'port'".to_string())?;
442
443 let speed = params
444 .get("speed")
445 .and_then(|v| v.parse().ok())
446 .unwrap_or(9600u32);
447
448 let data_bits = params
449 .get("databits")
450 .and_then(|v| v.parse().ok())
451 .unwrap_or(8u8);
452
453 let parity = params
454 .get("parity")
455 .map(|v| match v.to_lowercase().as_str() {
456 "e" | "even" => crate::serial::Parity::Even,
457 "o" | "odd" => crate::serial::Parity::Odd,
458 _ => crate::serial::Parity::None,
459 })
460 .unwrap_or(crate::serial::Parity::None);
461
462 let stop_bits = params
463 .get("stopbits")
464 .and_then(|v| v.parse().ok())
465 .unwrap_or(1u8);
466
467 Ok(Box::new(SerialIfaceConfig {
468 name: name.to_string(),
469 port,
470 speed,
471 data_bits,
472 parity,
473 stop_bits,
474 interface_id: id,
475 }))
476 }
477
478 fn start(
479 &self,
480 config: Box<dyn InterfaceConfigData>,
481 ctx: StartContext,
482 ) -> std::io::Result<StartResult> {
483 let serial_config = *config
484 .into_any()
485 .downcast::<SerialIfaceConfig>()
486 .map_err(|_| {
487 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
488 })?;
489
490 let id = serial_config.interface_id;
491 let name = serial_config.name.clone();
492 let bitrate = Some(serial_config.speed as u64);
493
494 let info = InterfaceInfo {
495 stamp_costs: Default::default(),
496 id,
497 name,
498 mode: ctx.mode,
499 gravity: ctx.gravity,
500 recursive_prs: ctx.recursive_prs,
501 announces_from_internal: ctx.announces_from_internal,
502 announces_to_internal: ctx.announces_to_internal,
503 out_capable: true,
504 in_capable: true,
505 bitrate,
506 airtime_profile: None,
507 announce_rate_target: None,
508 announce_rate_grace: 0,
509 announce_rate_penalty: 0.0,
510 announce_cap: rns_core::constants::ANNOUNCE_CAP,
511 is_local_client: false,
512 wants_tunnel: false,
513 tunnel_id: None,
514 mtu: rns_core::constants::MTU as u32,
515 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
516 ia_freq: 0.0,
517 ip_freq: 0.0,
518 op_freq: 0.0,
519 op_samples: 0,
520 started: crate::time::now(),
521 };
522
523 let ifac_size = ctx.ifac.as_ref().map_or(0, |ifac| ifac.size);
524 let (writer, control) = start_with_control(serial_config, ctx.tx, ifac_size)?;
525
526 Ok(StartResult::Simple {
527 control: Some(control),
528 id,
529 info,
530 writer,
531 interface_type_name: "SerialInterface".to_string(),
532 })
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::serial::open_pty_pair;
540 use std::os::unix::io::{AsRawFd, FromRawFd};
541 use std::sync::mpsc;
542 use std::time::Duration;
543
544 fn poll_read(fd: i32, timeout_ms: i32) -> bool {
546 let mut pfd = libc::pollfd {
547 fd,
548 events: libc::POLLIN,
549 revents: 0,
550 };
551 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
552 ret > 0
553 }
554
555 #[test]
556 fn serial_hdlc_roundtrip() {
557 let (master_fd, slave_fd) = open_pty_pair().unwrap();
558 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
559 let mut slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
560
561 let payload: Vec<u8> = (0..32).collect();
563 let framed = hdlc::frame(&payload);
564 master_file.write_all(&framed).unwrap();
565 master_file.flush().unwrap();
566
567 assert!(
569 poll_read(slave_file.as_raw_fd(), 2000),
570 "should have data available"
571 );
572
573 let mut decoder = hdlc::Decoder::new();
574 let mut buf = [0u8; 4096];
575 let n = slave_file.read(&mut buf).unwrap();
576 let frames = decoder.feed(&buf[..n]);
577 assert_eq!(frames.len(), 1);
578 assert_eq!(frames[0], payload);
579 }
580
581 #[test]
582 fn serial_writer_frames() {
583 let (master_fd, slave_fd) = open_pty_pair().unwrap();
584
585 let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
586 let mut writer = SerialWriter { file: writer_file };
587
588 let payload = vec![0x01, 0x02, 0x7E, 0x7D, 0x03]; writer.send_frame(&payload).unwrap();
590
591 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
593 assert!(poll_read(master_file.as_raw_fd(), 2000), "should have data");
594
595 let expected = hdlc::frame(&payload);
596 let mut buf = [0u8; 256];
597 let n = master_file.read(&mut buf).unwrap();
598 assert_eq!(&buf[..n], &expected[..]);
599 }
600
601 #[test]
602 fn serial_fragmented_read() {
603 let (master_fd, slave_fd) = open_pty_pair().unwrap();
604 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
605 let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
606
607 let (tx, rx) = mpsc::channel::<Event>();
608
609 let payload: Vec<u8> = (0..32).collect();
611 let framed = hdlc::frame(&payload);
612 let mid = framed.len() / 2;
613
614 let reader_thread = thread::spawn(move || {
616 let mut reader = slave_file;
617 let mut decoder = hdlc::Decoder::new();
618 let mut buf = [0u8; 4096];
619
620 loop {
621 match reader.read(&mut buf) {
622 Ok(n) if n > 0 => {
623 if let Some(frame) = decoder.feed(&buf[..n]).into_iter().next() {
624 let _ = tx.send(Event::Frame {
625 interface_id: InterfaceId(0),
626 data: frame.into(),
627 rssi: None,
628 snr: None,
629 });
630 return;
631 }
632 }
633 _ => return,
634 }
635 }
636 });
637
638 master_file.write_all(&framed[..mid]).unwrap();
640 master_file.flush().unwrap();
641
642 thread::sleep(Duration::from_millis(50));
644 master_file.write_all(&framed[mid..]).unwrap();
645 master_file.flush().unwrap();
646
647 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
648 match event {
649 Event::Frame { data, .. } => assert_eq!(data, payload.into()),
650 other => panic!("expected Frame, got {:?}", other),
651 }
652
653 let _ = reader_thread.join();
654 }
655
656 #[test]
657 fn serial_reconnect_on_close() {
658 let (master_fd, slave_fd) = open_pty_pair().unwrap();
659
660 let (tx, rx) = mpsc::channel::<Event>();
661 let id = InterfaceId(42);
662
663 let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
665 let reader_thread = thread::spawn(move || {
666 let mut reader = slave_file;
667 let mut buf = [0u8; 4096];
668 let mut decoder = hdlc::Decoder::new();
669 loop {
670 match reader.read(&mut buf) {
671 Ok(0) => {
672 let _ = tx.send(Event::InterfaceDown(id));
673 return;
674 }
675 Ok(n) => {
676 for frame in decoder.feed(&buf[..n]) {
677 let _ = tx.send(Event::Frame {
678 interface_id: id,
679 data: frame.into(),
680 rssi: None,
681 snr: None,
682 });
683 }
684 }
685 Err(_) => {
686 let _ = tx.send(Event::InterfaceDown(id));
687 return;
688 }
689 }
690 }
691 });
692
693 unsafe { libc::close(master_fd) };
695
696 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
697 assert!(matches!(event, Event::InterfaceDown(InterfaceId(42))));
698
699 let _ = reader_thread.join();
700 }
701
702 #[test]
703 fn detach_stops_idle_serial_reader_without_reconnect() {
704 let (master_fd, slave_fd) = open_pty_pair().unwrap();
705 let master = unsafe { std::fs::File::from_raw_fd(master_fd) };
706 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
707 let (tx, rx) = crate::event::channel();
708 let control = ListenerControl::new();
709 let reader_control = control.clone();
710 let config = SerialIfaceConfig {
711 name: "detach-test".into(),
712 interface_id: InterfaceId(71),
713 ..Default::default()
714 };
715
716 let reader = thread::spawn(move || {
717 reader_loop(slave, InterfaceId(71), config, tx, reader_control, 0)
718 });
719 thread::sleep(Duration::from_millis(50));
720 control.request_stop();
721 reader.join().unwrap();
722 drop(master);
723 assert!(rx.try_recv().is_err());
724 }
725
726 #[test]
727 fn missing_serial_port_recovers_after_it_appears() {
728 let tempdir = tempfile::tempdir().unwrap();
729 let port_path = tempdir.path().join("serial-port");
730 let (tx, rx) = crate::event::channel();
731 let config = SerialIfaceConfig {
732 name: "late-port".into(),
733 port: port_path.display().to_string(),
734 interface_id: InterfaceId(73),
735 ..Default::default()
736 };
737
738 let (_writer, control) = start_with_control(config, tx, 0).unwrap();
739 assert!(rx.try_recv().is_err());
740
741 let (master_fd, slave_fd) = open_pty_pair().unwrap();
742 let master = unsafe { std::fs::File::from_raw_fd(master_fd) };
743 let slave_path = std::fs::read_link(format!("/proc/self/fd/{slave_fd}")).unwrap();
744 std::os::unix::fs::symlink(slave_path, &port_path).unwrap();
745 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
746
747 let event = rx.recv_timeout(Duration::from_secs(8)).unwrap();
748 assert!(matches!(
749 event,
750 Event::InterfaceUp(InterfaceId(73), Some(_), None)
751 ));
752 control.request_stop();
753 drop(slave);
754 drop(master);
755 }
756
757 fn thread_named(prefix: &str) -> bool {
758 std::fs::read_dir("/proc/self/task").unwrap().any(|task| {
759 let comm = task.unwrap().path().join("comm");
760 std::fs::read_to_string(comm).is_ok_and(|comm| comm.trim().starts_with(prefix))
761 })
762 }
763
764 #[test]
765 fn folded_serial_port_runs_on_the_driver_loop() {
766 if !crate::engine::FOLD_IO {
767 return;
768 }
769 let tempdir = tempfile::tempdir().unwrap();
770 let port_path = tempdir.path().join("serial-port");
771 let (master_fd, slave_fd) = open_pty_pair().unwrap();
772 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
773 let slave_path = std::fs::read_link(format!("/proc/self/fd/{slave_fd}")).unwrap();
774 std::os::unix::fs::symlink(slave_path, &port_path).unwrap();
775 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
776
777 let (tx, rx) = crate::event::channel();
778 let config = SerialIfaceConfig {
779 name: "folded-serial".into(),
780 port: port_path.display().to_string(),
781 interface_id: InterfaceId(74),
782 ..Default::default()
783 };
784 let (writer, control) = start_with_control(config, tx.clone(), 0).unwrap();
785 assert!(matches!(
786 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
787 Event::InterfaceUp(InterfaceId(74), None, None)
788 ));
789 assert!(writer.loop_socket().is_some());
790 let (mut writer, _) =
791 crate::interface::wrap_async_writer(writer, InterfaceId(74), "serial", tx, 16);
792 assert!(!thread_named("serial-reader-7"));
793 assert!(!thread_named("iface-writer-74"));
794
795 let payload: Vec<u8> = (0..32).collect();
797 let framed = hdlc::frame(&payload);
798 master.write_all(&framed[..10]).unwrap();
799 master.write_all(&framed[10..]).unwrap();
800 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
801 Event::Frame {
802 interface_id, data, ..
803 } => {
804 assert_eq!(interface_id, InterfaceId(74));
805 assert_eq!(&*data, &payload[..]);
806 }
807 other => panic!("expected Frame, got {other:?}"),
808 }
809
810 let outbound: Vec<u8> = (100..140).collect();
812 writer.send_frame(&outbound).unwrap();
813 writer.flush_pending();
814 assert!(poll_read(master.as_raw_fd(), 2000));
815 let mut buf = [0u8; 256];
816 let n = master.read(&mut buf).unwrap();
817 assert_eq!(&buf[..n], &hdlc::frame(&outbound)[..]);
818
819 drop(master);
821 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
822 Event::InterfaceDown(id) => assert_eq!(id, InterfaceId(74)),
823 other => panic!("expected InterfaceDown, got {other:?}"),
824 }
825 control.request_stop();
826 drop(slave);
827 }
828
829 #[test]
830 fn frame_decoder_accepts_frames_up_to_hw_mtu_plus_ifac() {
831 let ifac_size = 8;
832 let mut decoder = frame_decoder(ifac_size);
833 let payload = vec![0x3c; HW_MTU + ifac_size];
834 let decoded = decoder.feed_with_diagnostics(&hdlc::frame(&payload));
835 assert_eq!(decoded.frames, vec![payload]);
836 assert!(decoded.invalid_frame_lengths.is_empty());
837 }
838
839 #[test]
840 fn frame_decoder_drops_frame_above_hw_mtu_as_invalid() {
841 let mut decoder = frame_decoder(0);
842 let decoded = decoder.feed_with_diagnostics(&hdlc::frame(&vec![0x3c; HW_MTU + 1]));
843 assert!(decoded.frames.is_empty());
844 assert_eq!(decoded.invalid_frame_lengths, vec![HW_MTU + 1]);
845 }
846}