Skip to main content

rns_net/interface/
serial_iface.rs

1//! Serial interface with HDLC framing.
2//!
3//! Matches Python `SerialInterface.py` — opens a serial port,
4//! reads with HDLC framing, reconnects on failure.
5
6use 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
22/// Upstream `SerialInterface.HW_MTU`.
23const HW_MTU: usize = 564;
24
25/// HDLC decoder bounded by the serial hardware MTU plus on-wire IFAC bytes.
26fn frame_decoder(ifac_size: usize) -> hdlc::Decoder {
27    hdlc::Decoder::reticulum(HW_MTU, ifac_size)
28}
29
30/// Configuration for a Serial interface.
31#[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
56/// Writer that sends HDLC-framed data over a serial port.
57struct 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        // With interface I/O folded the driver writes the port itself.
68        crate::engine::FOLD_IO.then(|| self.file.as_raw_fd())
69    }
70}
71
72/// Start the serial interface. Opens the port, spawns reader thread.
73/// Returns the writer for the driver.
74pub 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    // Signal interface up
129    let _ = tx.send(Event::InterfaceUp(id, None, None));
130
131    // Short delay matching Python's configure_device sleep
132    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    // Spawn reader thread
146    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
156/// Reader thread: reads from serial, HDLC-decodes, sends frames to driver.
157fn 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                // EOF — port closed
197                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; // driver shut down
220                    }
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
242/// What a serial port read on the driver loop needs across reconnects (see
243/// [`crate::engine::FOLD_IO`]).
244struct SerialLink {
245    config: SerialIfaceConfig,
246    tx: EventSender,
247    control: ListenerControl,
248    ifac_size: usize,
249}
250
251impl SerialLink {
252    /// Read `reader` on the driver thread from now on.
253    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    /// [`SerialLink::serve`] a reopened port, reopening it again if that
266    /// fails. Runs on a reconnect thread.
267    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    /// Report the port down and reopen it on a thread of its own, which
288    /// exits once the reopened port is served on the driver.
289    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
310/// The serial port, read on the driver thread.
311struct 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
358/// Attempt to reconnect the serial port. Returns new reader file on success.
359fn 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
414// --- Factory implementation ---
415
416use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
417use rns_core::transport::types::InterfaceInfo;
418use std::collections::HashMap;
419
420/// Factory for `SerialInterface`.
421pub 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    /// Helper: poll an fd for reading with timeout (ms). Returns true if data available.
545    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        // Write an HDLC frame to the master side
562        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        // Read from slave using HDLC decoder
568        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]; // includes bytes that need HDLC escaping
589        writer.send_frame(&payload).unwrap();
590
591        // Read from master
592        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        // Write a frame in two parts to the master
610        let payload: Vec<u8> = (0..32).collect();
611        let framed = hdlc::frame(&payload);
612        let mid = framed.len() / 2;
613
614        // Spawn reader thread first (it will block waiting for data)
615        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        // Write first half
639        master_file.write_all(&framed[..mid]).unwrap();
640        master_file.flush().unwrap();
641
642        // Small delay then write second half
643        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        // Spawn a reader thread on the slave side
664        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        // Close the master fd — this should cause the reader to get EOF
694        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        // Inbound, in two pieces, read by the receiver's wait.
796        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        // Outbound, written by the driver.
811        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        // The port going away is reported, and reopening starts.
820        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}