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.send(Event::Frame { interface_id: id, data: frame }).is_err() {
115                        return; // driver shut down
116                    }
117                }
118            }
119            Err(e) => {
120                log::warn!("[{}] serial read error: {}", config.name, e);
121                let _ = tx.send(Event::InterfaceDown(id));
122                match reconnect(&config, &tx) {
123                    Some(new_reader) => {
124                        reader = new_reader;
125                        decoder = hdlc::Decoder::new();
126                        continue;
127                    }
128                    None => return,
129                }
130            }
131        }
132    }
133}
134
135/// Attempt to reconnect the serial port. Returns new reader file on success.
136fn reconnect(
137    config: &SerialIfaceConfig,
138    tx: &EventSender,
139) -> Option<std::fs::File> {
140    loop {
141        thread::sleep(Duration::from_secs(5));
142        log::info!("[{}] attempting to reconnect serial port {}...", config.name, config.port);
143
144        let serial_config = SerialConfig {
145            path: config.port.clone(),
146            baud: config.speed,
147            data_bits: config.data_bits,
148            parity: config.parity,
149            stop_bits: config.stop_bits,
150        };
151
152        match SerialPort::open(&serial_config) {
153            Ok(port) => {
154                match (port.reader(), port.writer()) {
155                    (Ok(reader), Ok(writer_file)) => {
156                        log::info!("[{}] serial port reconnected", config.name);
157                        let new_writer: Box<dyn Writer> = Box::new(SerialWriter { file: writer_file });
158                        let _ = tx.send(Event::InterfaceUp(config.interface_id, Some(new_writer), None));
159                        thread::sleep(Duration::from_millis(500));
160                        return Some(reader);
161                    }
162                    _ => {
163                        log::warn!("[{}] failed to get reader/writer from serial port", config.name);
164                    }
165                }
166            }
167            Err(e) => {
168                log::warn!("[{}] serial reconnect failed: {}", config.name, e);
169            }
170        }
171    }
172}
173
174// --- Factory implementation ---
175
176use std::collections::HashMap;
177use rns_core::transport::types::InterfaceInfo;
178use super::{InterfaceFactory, InterfaceConfigData, StartContext, StartResult};
179
180/// Factory for `SerialInterface`.
181pub struct SerialFactory;
182
183impl InterfaceFactory for SerialFactory {
184    fn type_name(&self) -> &str { "SerialInterface" }
185
186    fn default_ifac_size(&self) -> usize { 8 }
187
188    fn parse_config(
189        &self,
190        name: &str,
191        id: InterfaceId,
192        params: &HashMap<String, String>,
193    ) -> Result<Box<dyn InterfaceConfigData>, String> {
194        let port = params.get("port")
195            .cloned()
196            .ok_or_else(|| "SerialInterface requires 'port'".to_string())?;
197
198        let speed = params.get("speed")
199            .and_then(|v| v.parse().ok())
200            .unwrap_or(9600u32);
201
202        let data_bits = params.get("databits")
203            .and_then(|v| v.parse().ok())
204            .unwrap_or(8u8);
205
206        let parity = params.get("parity")
207            .map(|v| match v.to_lowercase().as_str() {
208                "e" | "even" => crate::serial::Parity::Even,
209                "o" | "odd" => crate::serial::Parity::Odd,
210                _ => crate::serial::Parity::None,
211            })
212            .unwrap_or(crate::serial::Parity::None);
213
214        let stop_bits = params.get("stopbits")
215            .and_then(|v| v.parse().ok())
216            .unwrap_or(1u8);
217
218        Ok(Box::new(SerialIfaceConfig {
219            name: name.to_string(),
220            port,
221            speed,
222            data_bits,
223            parity,
224            stop_bits,
225            interface_id: id,
226        }))
227    }
228
229    fn start(
230        &self,
231        config: Box<dyn InterfaceConfigData>,
232        ctx: StartContext,
233    ) -> std::io::Result<StartResult> {
234        let serial_config = *config.into_any().downcast::<SerialIfaceConfig>()
235            .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type"))?;
236
237        let id = serial_config.interface_id;
238        let name = serial_config.name.clone();
239        let bitrate = Some(serial_config.speed as u64);
240
241        let info = InterfaceInfo {
242            id,
243            name,
244            mode: ctx.mode,
245            out_capable: true,
246            in_capable: true,
247            bitrate,
248            announce_rate_target: None,
249            announce_rate_grace: 0,
250            announce_rate_penalty: 0.0,
251            announce_cap: rns_core::constants::ANNOUNCE_CAP,
252            is_local_client: false,
253            wants_tunnel: false,
254            tunnel_id: None,
255            mtu: rns_core::constants::MTU as u32,
256            ingress_control: false,
257            ia_freq: 0.0,
258            started: crate::time::now(),
259        };
260
261        let writer = start(serial_config, ctx.tx)?;
262
263        Ok(StartResult::Simple {
264            id,
265            info,
266            writer,
267            interface_type_name: "SerialInterface".to_string(),
268        })
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use crate::serial::open_pty_pair;
276    use std::os::unix::io::{AsRawFd, FromRawFd};
277    use std::sync::mpsc;
278    use std::time::Duration;
279
280    /// Helper: poll an fd for reading with timeout (ms). Returns true if data available.
281    fn poll_read(fd: i32, timeout_ms: i32) -> bool {
282        let mut pfd = libc::pollfd {
283            fd,
284            events: libc::POLLIN,
285            revents: 0,
286        };
287        let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
288        ret > 0
289    }
290
291    #[test]
292    fn serial_hdlc_roundtrip() {
293        let (master_fd, slave_fd) = open_pty_pair().unwrap();
294        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
295        let mut slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
296
297        // Write an HDLC frame to the master side
298        let payload: Vec<u8> = (0..32).collect();
299        let framed = hdlc::frame(&payload);
300        master_file.write_all(&framed).unwrap();
301        master_file.flush().unwrap();
302
303        // Read from slave using HDLC decoder
304        assert!(poll_read(slave_file.as_raw_fd(), 2000), "should have data available");
305
306        let mut decoder = hdlc::Decoder::new();
307        let mut buf = [0u8; 4096];
308        let n = slave_file.read(&mut buf).unwrap();
309        let frames = decoder.feed(&buf[..n]);
310        assert_eq!(frames.len(), 1);
311        assert_eq!(frames[0], payload);
312    }
313
314    #[test]
315    fn serial_writer_frames() {
316        let (master_fd, slave_fd) = open_pty_pair().unwrap();
317
318        let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
319        let mut writer = SerialWriter { file: writer_file };
320
321        let payload = vec![0x01, 0x02, 0x7E, 0x7D, 0x03]; // includes bytes that need HDLC escaping
322        writer.send_frame(&payload).unwrap();
323
324        // Read from master
325        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
326        assert!(poll_read(master_file.as_raw_fd(), 2000), "should have data");
327
328        let expected = hdlc::frame(&payload);
329        let mut buf = [0u8; 256];
330        let n = master_file.read(&mut buf).unwrap();
331        assert_eq!(&buf[..n], &expected[..]);
332    }
333
334    #[test]
335    fn serial_fragmented_read() {
336        let (master_fd, slave_fd) = open_pty_pair().unwrap();
337        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
338        let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
339
340        let (tx, rx) = mpsc::channel::<Event>();
341
342        // Write a frame in two parts to the master
343        let payload: Vec<u8> = (0..32).collect();
344        let framed = hdlc::frame(&payload);
345        let mid = framed.len() / 2;
346
347        // Spawn reader thread first (it will block waiting for data)
348        let reader_thread = thread::spawn(move || {
349            let mut reader = slave_file;
350            let mut decoder = hdlc::Decoder::new();
351            let mut buf = [0u8; 4096];
352
353            loop {
354                match reader.read(&mut buf) {
355                    Ok(n) if n > 0 => {
356                        for frame in decoder.feed(&buf[..n]) {
357                            let _ = tx.send(Event::Frame { interface_id: InterfaceId(0), data: frame });
358                            return;
359                        }
360                    }
361                    _ => return,
362                }
363            }
364        });
365
366        // Write first half
367        master_file.write_all(&framed[..mid]).unwrap();
368        master_file.flush().unwrap();
369
370        // Small delay then write second half
371        thread::sleep(Duration::from_millis(50));
372        master_file.write_all(&framed[mid..]).unwrap();
373        master_file.flush().unwrap();
374
375        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
376        match event {
377            Event::Frame { data, .. } => assert_eq!(data, payload),
378            other => panic!("expected Frame, got {:?}", other),
379        }
380
381        let _ = reader_thread.join();
382    }
383
384    #[test]
385    fn serial_reconnect_on_close() {
386        let (master_fd, slave_fd) = open_pty_pair().unwrap();
387
388        let (tx, rx) = mpsc::channel::<Event>();
389        let id = InterfaceId(42);
390
391        // Spawn a reader thread on the slave side
392        let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
393        let reader_thread = thread::spawn(move || {
394            let mut reader = slave_file;
395            let mut buf = [0u8; 4096];
396            let mut decoder = hdlc::Decoder::new();
397            loop {
398                match reader.read(&mut buf) {
399                    Ok(0) => {
400                        let _ = tx.send(Event::InterfaceDown(id));
401                        return;
402                    }
403                    Ok(n) => {
404                        for frame in decoder.feed(&buf[..n]) {
405                            let _ = tx.send(Event::Frame { interface_id: id, data: frame });
406                        }
407                    }
408                    Err(_) => {
409                        let _ = tx.send(Event::InterfaceDown(id));
410                        return;
411                    }
412                }
413            }
414        });
415
416        // Close the master fd — this should cause the reader to get EOF
417        unsafe { libc::close(master_fd) };
418
419        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
420        assert!(matches!(event, Event::InterfaceDown(InterfaceId(42))));
421
422        let _ = reader_thread.join();
423    }
424}