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::thread;
8use std::time::Duration;
9
10use rns_core::transport::types::InterfaceId;
11
12use crate::event::{Event, EventSender};
13use crate::hdlc;
14use crate::interface::Writer;
15use crate::serial::{Parity, SerialConfig, SerialPort};
16
17/// Configuration for a Serial interface.
18#[derive(Debug, Clone)]
19pub struct SerialIfaceConfig {
20    pub name: String,
21    pub port: String,
22    pub speed: u32,
23    pub data_bits: u8,
24    pub parity: Parity,
25    pub stop_bits: u8,
26    pub interface_id: InterfaceId,
27}
28
29impl Default for SerialIfaceConfig {
30    fn default() -> Self {
31        SerialIfaceConfig {
32            name: String::new(),
33            port: String::new(),
34            speed: 9600,
35            data_bits: 8,
36            parity: Parity::None,
37            stop_bits: 1,
38            interface_id: InterfaceId(0),
39        }
40    }
41}
42
43/// Writer that sends HDLC-framed data over a serial port.
44struct SerialWriter {
45    file: std::fs::File,
46}
47
48impl Writer for SerialWriter {
49    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
50        self.file.write_all(&hdlc::frame(data))
51    }
52}
53
54/// Start the serial interface. Opens the port, spawns reader thread.
55/// Returns the writer for the driver.
56pub fn start(config: SerialIfaceConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
57    let serial_config = SerialConfig {
58        path: config.port.clone(),
59        baud: config.speed,
60        data_bits: config.data_bits,
61        parity: config.parity,
62        stop_bits: config.stop_bits,
63    };
64
65    let port = SerialPort::open(&serial_config)?;
66    let reader_file = port.reader()?;
67    let writer_file = port.writer()?;
68
69    let id = config.interface_id;
70
71    // Signal interface up
72    let _ = tx.send(Event::InterfaceUp(id, None, None));
73
74    // Short delay matching Python's configure_device sleep
75    thread::sleep(Duration::from_millis(500));
76
77    // Spawn reader thread
78    thread::Builder::new()
79        .name(format!("serial-reader-{}", id.0))
80        .spawn(move || {
81            reader_loop(reader_file, id, config, tx);
82        })?;
83
84    Ok(Box::new(SerialWriter { file: writer_file }))
85}
86
87/// Reader thread: reads from serial, HDLC-decodes, sends frames to driver.
88fn reader_loop(
89    mut reader: std::fs::File,
90    id: InterfaceId,
91    config: SerialIfaceConfig,
92    tx: EventSender,
93) {
94    let mut decoder = hdlc::Decoder::new();
95    let mut buf = [0u8; 4096];
96
97    loop {
98        match reader.read(&mut buf) {
99            Ok(0) => {
100                // EOF — port closed
101                log::warn!("[{}] serial port closed", config.name);
102                let _ = tx.send(Event::InterfaceDown(id));
103                match reconnect(&config, &tx) {
104                    Some(new_reader) => {
105                        reader = new_reader;
106                        decoder = hdlc::Decoder::new();
107                        continue;
108                    }
109                    None => return,
110                }
111            }
112            Ok(n) => {
113                for frame in decoder.feed(&buf[..n]) {
114                    if tx
115                        .send(Event::Frame {
116                            interface_id: id,
117                            data: frame,
118                            rssi: None,
119                            snr: None,
120                        })
121                        .is_err()
122                    {
123                        return; // driver shut down
124                    }
125                }
126            }
127            Err(e) => {
128                log::warn!("[{}] serial read error: {}", config.name, e);
129                let _ = tx.send(Event::InterfaceDown(id));
130                match reconnect(&config, &tx) {
131                    Some(new_reader) => {
132                        reader = new_reader;
133                        decoder = hdlc::Decoder::new();
134                        continue;
135                    }
136                    None => return,
137                }
138            }
139        }
140    }
141}
142
143/// Attempt to reconnect the serial port. Returns new reader file on success.
144fn reconnect(config: &SerialIfaceConfig, tx: &EventSender) -> Option<std::fs::File> {
145    loop {
146        thread::sleep(Duration::from_secs(5));
147        log::info!(
148            "[{}] attempting to reconnect serial port {}...",
149            config.name,
150            config.port
151        );
152
153        let serial_config = SerialConfig {
154            path: config.port.clone(),
155            baud: config.speed,
156            data_bits: config.data_bits,
157            parity: config.parity,
158            stop_bits: config.stop_bits,
159        };
160
161        match SerialPort::open(&serial_config) {
162            Ok(port) => match (port.reader(), port.writer()) {
163                (Ok(reader), Ok(writer_file)) => {
164                    log::info!("[{}] serial port reconnected", config.name);
165                    let new_writer: Box<dyn Writer> = Box::new(SerialWriter { file: writer_file });
166                    let _ = tx.send(Event::InterfaceUp(
167                        config.interface_id,
168                        Some(new_writer),
169                        None,
170                    ));
171                    thread::sleep(Duration::from_millis(500));
172                    return Some(reader);
173                }
174                _ => {
175                    log::warn!(
176                        "[{}] failed to get reader/writer from serial port",
177                        config.name
178                    );
179                }
180            },
181            Err(e) => {
182                log::warn!("[{}] serial reconnect failed: {}", config.name, e);
183            }
184        }
185    }
186}
187
188// --- Factory implementation ---
189
190use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
191use rns_core::transport::types::InterfaceInfo;
192use std::collections::HashMap;
193
194/// Factory for `SerialInterface`.
195pub struct SerialFactory;
196
197impl InterfaceFactory for SerialFactory {
198    fn type_name(&self) -> &str {
199        "SerialInterface"
200    }
201
202    fn default_ifac_size(&self) -> usize {
203        8
204    }
205
206    fn parse_config(
207        &self,
208        name: &str,
209        id: InterfaceId,
210        params: &HashMap<String, String>,
211    ) -> Result<Box<dyn InterfaceConfigData>, String> {
212        let port = params
213            .get("port")
214            .cloned()
215            .ok_or_else(|| "SerialInterface requires 'port'".to_string())?;
216
217        let speed = params
218            .get("speed")
219            .and_then(|v| v.parse().ok())
220            .unwrap_or(9600u32);
221
222        let data_bits = params
223            .get("databits")
224            .and_then(|v| v.parse().ok())
225            .unwrap_or(8u8);
226
227        let parity = params
228            .get("parity")
229            .map(|v| match v.to_lowercase().as_str() {
230                "e" | "even" => crate::serial::Parity::Even,
231                "o" | "odd" => crate::serial::Parity::Odd,
232                _ => crate::serial::Parity::None,
233            })
234            .unwrap_or(crate::serial::Parity::None);
235
236        let stop_bits = params
237            .get("stopbits")
238            .and_then(|v| v.parse().ok())
239            .unwrap_or(1u8);
240
241        Ok(Box::new(SerialIfaceConfig {
242            name: name.to_string(),
243            port,
244            speed,
245            data_bits,
246            parity,
247            stop_bits,
248            interface_id: id,
249        }))
250    }
251
252    fn start(
253        &self,
254        config: Box<dyn InterfaceConfigData>,
255        ctx: StartContext,
256    ) -> std::io::Result<StartResult> {
257        let serial_config = *config
258            .into_any()
259            .downcast::<SerialIfaceConfig>()
260            .map_err(|_| {
261                std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
262            })?;
263
264        let id = serial_config.interface_id;
265        let name = serial_config.name.clone();
266        let bitrate = Some(serial_config.speed as u64);
267
268        let info = InterfaceInfo {
269            id,
270            name,
271            mode: ctx.mode,
272            gravity: ctx.gravity,
273            recursive_prs: ctx.recursive_prs,
274            announces_from_internal: ctx.announces_from_internal,
275            announces_to_internal: ctx.announces_to_internal,
276            out_capable: true,
277            in_capable: true,
278            bitrate,
279            airtime_profile: None,
280            announce_rate_target: None,
281            announce_rate_grace: 0,
282            announce_rate_penalty: 0.0,
283            announce_cap: rns_core::constants::ANNOUNCE_CAP,
284            is_local_client: false,
285            wants_tunnel: false,
286            tunnel_id: None,
287            mtu: rns_core::constants::MTU as u32,
288            ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
289            ia_freq: 0.0,
290            ip_freq: 0.0,
291            op_freq: 0.0,
292            op_samples: 0,
293            started: crate::time::now(),
294        };
295
296        let writer = start(serial_config, ctx.tx)?;
297
298        Ok(StartResult::Simple {
299            id,
300            info,
301            writer,
302            interface_type_name: "SerialInterface".to_string(),
303        })
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::serial::open_pty_pair;
311    use std::os::unix::io::{AsRawFd, FromRawFd};
312    use std::sync::mpsc;
313    use std::time::Duration;
314
315    /// Helper: poll an fd for reading with timeout (ms). Returns true if data available.
316    fn poll_read(fd: i32, timeout_ms: i32) -> bool {
317        let mut pfd = libc::pollfd {
318            fd,
319            events: libc::POLLIN,
320            revents: 0,
321        };
322        let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
323        ret > 0
324    }
325
326    #[test]
327    fn serial_hdlc_roundtrip() {
328        let (master_fd, slave_fd) = open_pty_pair().unwrap();
329        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
330        let mut slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
331
332        // Write an HDLC frame to the master side
333        let payload: Vec<u8> = (0..32).collect();
334        let framed = hdlc::frame(&payload);
335        master_file.write_all(&framed).unwrap();
336        master_file.flush().unwrap();
337
338        // Read from slave using HDLC decoder
339        assert!(
340            poll_read(slave_file.as_raw_fd(), 2000),
341            "should have data available"
342        );
343
344        let mut decoder = hdlc::Decoder::new();
345        let mut buf = [0u8; 4096];
346        let n = slave_file.read(&mut buf).unwrap();
347        let frames = decoder.feed(&buf[..n]);
348        assert_eq!(frames.len(), 1);
349        assert_eq!(frames[0], payload);
350    }
351
352    #[test]
353    fn serial_writer_frames() {
354        let (master_fd, slave_fd) = open_pty_pair().unwrap();
355
356        let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
357        let mut writer = SerialWriter { file: writer_file };
358
359        let payload = vec![0x01, 0x02, 0x7E, 0x7D, 0x03]; // includes bytes that need HDLC escaping
360        writer.send_frame(&payload).unwrap();
361
362        // Read from master
363        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
364        assert!(poll_read(master_file.as_raw_fd(), 2000), "should have data");
365
366        let expected = hdlc::frame(&payload);
367        let mut buf = [0u8; 256];
368        let n = master_file.read(&mut buf).unwrap();
369        assert_eq!(&buf[..n], &expected[..]);
370    }
371
372    #[test]
373    fn serial_fragmented_read() {
374        let (master_fd, slave_fd) = open_pty_pair().unwrap();
375        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
376        let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
377
378        let (tx, rx) = mpsc::channel::<Event>();
379
380        // Write a frame in two parts to the master
381        let payload: Vec<u8> = (0..32).collect();
382        let framed = hdlc::frame(&payload);
383        let mid = framed.len() / 2;
384
385        // Spawn reader thread first (it will block waiting for data)
386        let reader_thread = thread::spawn(move || {
387            let mut reader = slave_file;
388            let mut decoder = hdlc::Decoder::new();
389            let mut buf = [0u8; 4096];
390
391            loop {
392                match reader.read(&mut buf) {
393                    Ok(n) if n > 0 => {
394                        if let Some(frame) = decoder.feed(&buf[..n]).into_iter().next() {
395                            let _ = tx.send(Event::Frame {
396                                interface_id: InterfaceId(0),
397                                data: frame,
398                                rssi: None,
399                                snr: None,
400                            });
401                            return;
402                        }
403                    }
404                    _ => return,
405                }
406            }
407        });
408
409        // Write first half
410        master_file.write_all(&framed[..mid]).unwrap();
411        master_file.flush().unwrap();
412
413        // Small delay then write second half
414        thread::sleep(Duration::from_millis(50));
415        master_file.write_all(&framed[mid..]).unwrap();
416        master_file.flush().unwrap();
417
418        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
419        match event {
420            Event::Frame { data, .. } => assert_eq!(data, payload),
421            other => panic!("expected Frame, got {:?}", other),
422        }
423
424        let _ = reader_thread.join();
425    }
426
427    #[test]
428    fn serial_reconnect_on_close() {
429        let (master_fd, slave_fd) = open_pty_pair().unwrap();
430
431        let (tx, rx) = mpsc::channel::<Event>();
432        let id = InterfaceId(42);
433
434        // Spawn a reader thread on the slave side
435        let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
436        let reader_thread = thread::spawn(move || {
437            let mut reader = slave_file;
438            let mut buf = [0u8; 4096];
439            let mut decoder = hdlc::Decoder::new();
440            loop {
441                match reader.read(&mut buf) {
442                    Ok(0) => {
443                        let _ = tx.send(Event::InterfaceDown(id));
444                        return;
445                    }
446                    Ok(n) => {
447                        for frame in decoder.feed(&buf[..n]) {
448                            let _ = tx.send(Event::Frame {
449                                interface_id: id,
450                                data: frame,
451                                rssi: None,
452                                snr: None,
453                            });
454                        }
455                    }
456                    Err(_) => {
457                        let _ = tx.send(Event::InterfaceDown(id));
458                        return;
459                    }
460                }
461            }
462        });
463
464        // Close the master fd — this should cause the reader to get EOF
465        unsafe { libc::close(master_fd) };
466
467        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
468        assert!(matches!(event, Event::InterfaceDown(InterfaceId(42))));
469
470        let _ = reader_thread.join();
471    }
472}