1use std::io::{self, Read, Write};
7use std::os::fd::AsRawFd;
8use std::thread;
9use std::time::Duration;
10
11use rns_core::transport::types::InterfaceId;
12
13use crate::event::{Event, EventSender};
14use crate::hdlc;
15use crate::interface::{
16 poll_readable, wait_while_active, ListenerControl, UnavailableWriter, Writer,
17};
18use crate::serial::{Parity, SerialConfig, SerialPort};
19
20#[derive(Debug, Clone)]
22pub struct SerialIfaceConfig {
23 pub name: String,
24 pub port: String,
25 pub speed: u32,
26 pub data_bits: u8,
27 pub parity: Parity,
28 pub stop_bits: u8,
29 pub interface_id: InterfaceId,
30}
31
32impl Default for SerialIfaceConfig {
33 fn default() -> Self {
34 SerialIfaceConfig {
35 name: String::new(),
36 port: String::new(),
37 speed: 9600,
38 data_bits: 8,
39 parity: Parity::None,
40 stop_bits: 1,
41 interface_id: InterfaceId(0),
42 }
43 }
44}
45
46struct SerialWriter {
48 file: std::fs::File,
49}
50
51impl Writer for SerialWriter {
52 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
53 self.file.write_all(&hdlc::frame(data))
54 }
55}
56
57pub fn start(config: SerialIfaceConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
60 start_with_control(config, tx).map(|(writer, _)| writer)
61}
62
63fn start_with_control(
64 config: SerialIfaceConfig,
65 tx: EventSender,
66) -> io::Result<(Box<dyn Writer>, ListenerControl)> {
67 let serial_config = SerialConfig {
68 path: config.port.clone(),
69 baud: config.speed,
70 data_bits: config.data_bits,
71 parity: config.parity,
72 stop_bits: config.stop_bits,
73 };
74
75 let control = ListenerControl::new();
76 let port = match SerialPort::open(&serial_config) {
77 Ok(port) => port,
78 Err(error) => {
79 log::warn!(
80 "[{}] could not open serial port {}: {}; retrying",
81 config.name,
82 config.port,
83 error
84 );
85 let retry_control = control.clone();
86 thread::Builder::new()
87 .name(format!("serial-reconnect-{}", config.interface_id.0))
88 .spawn(move || {
89 if let Some(reader) = reconnect(&config, &tx, &retry_control) {
90 let id = config.interface_id;
91 reader_loop(reader, id, config, tx, retry_control);
92 }
93 })?;
94 return Ok((Box::new(UnavailableWriter), control));
95 }
96 };
97 let reader_file = port.reader()?;
98 let writer_file = port.writer()?;
99
100 let id = config.interface_id;
101
102 let _ = tx.send(Event::InterfaceUp(id, None, None));
104
105 thread::sleep(Duration::from_millis(500));
107
108 let reader_control = control.clone();
110 thread::Builder::new()
111 .name(format!("serial-reader-{}", id.0))
112 .spawn(move || {
113 reader_loop(reader_file, id, config, tx, reader_control);
114 })?;
115
116 Ok((Box::new(SerialWriter { file: writer_file }), control))
117}
118
119fn reader_loop(
121 mut reader: std::fs::File,
122 id: InterfaceId,
123 config: SerialIfaceConfig,
124 tx: EventSender,
125 control: ListenerControl,
126) {
127 let mut decoder = hdlc::Decoder::new();
128 let mut buf = [0u8; 4096];
129
130 loop {
131 if control.should_stop() {
132 return;
133 }
134 match poll_readable(reader.as_raw_fd(), Duration::from_millis(200)) {
135 Ok(false) => continue,
136 Ok(true) => {}
137 Err(e) => {
138 if control.should_stop() {
139 return;
140 }
141 log::warn!("[{}] serial poll error: {}", config.name, e);
142 let _ = tx.send(Event::InterfaceDown(id));
143 match reconnect(&config, &tx, &control) {
144 Some(new_reader) => {
145 reader = new_reader;
146 decoder = hdlc::Decoder::new();
147 continue;
148 }
149 None => return,
150 }
151 }
152 }
153 match reader.read(&mut buf) {
154 Ok(0) => {
155 if control.should_stop() {
156 return;
157 }
158 log::warn!("[{}] serial port closed", config.name);
160 let _ = tx.send(Event::InterfaceDown(id));
161 match reconnect(&config, &tx, &control) {
162 Some(new_reader) => {
163 reader = new_reader;
164 decoder = hdlc::Decoder::new();
165 continue;
166 }
167 None => return,
168 }
169 }
170 Ok(n) => {
171 for frame in decoder.feed(&buf[..n]) {
172 if tx
173 .send(Event::Frame {
174 interface_id: id,
175 data: frame,
176 rssi: None,
177 snr: None,
178 })
179 .is_err()
180 {
181 return; }
183 }
184 }
185 Err(e) => {
186 if control.should_stop() {
187 return;
188 }
189 log::warn!("[{}] serial read error: {}", config.name, e);
190 let _ = tx.send(Event::InterfaceDown(id));
191 match reconnect(&config, &tx, &control) {
192 Some(new_reader) => {
193 reader = new_reader;
194 decoder = hdlc::Decoder::new();
195 continue;
196 }
197 None => return,
198 }
199 }
200 }
201 }
202}
203
204fn reconnect(
206 config: &SerialIfaceConfig,
207 tx: &EventSender,
208 control: &ListenerControl,
209) -> Option<std::fs::File> {
210 loop {
211 if !wait_while_active(control, Duration::from_secs(5)) {
212 return None;
213 }
214 log::info!(
215 "[{}] attempting to reconnect serial port {}...",
216 config.name,
217 config.port
218 );
219
220 let serial_config = SerialConfig {
221 path: config.port.clone(),
222 baud: config.speed,
223 data_bits: config.data_bits,
224 parity: config.parity,
225 stop_bits: config.stop_bits,
226 };
227
228 match SerialPort::open(&serial_config) {
229 Ok(port) => match (port.reader(), port.writer()) {
230 (Ok(reader), Ok(writer_file)) => {
231 if control.should_stop() {
232 return None;
233 }
234 log::info!("[{}] serial port reconnected", config.name);
235 let new_writer: Box<dyn Writer> = Box::new(SerialWriter { file: writer_file });
236 let _ = tx.send(Event::InterfaceUp(
237 config.interface_id,
238 Some(new_writer),
239 None,
240 ));
241 if !wait_while_active(control, Duration::from_millis(500)) {
242 return None;
243 }
244 return Some(reader);
245 }
246 _ => {
247 log::warn!(
248 "[{}] failed to get reader/writer from serial port",
249 config.name
250 );
251 }
252 },
253 Err(e) => {
254 log::warn!("[{}] serial reconnect failed: {}", config.name, e);
255 }
256 }
257 }
258}
259
260use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
263use rns_core::transport::types::InterfaceInfo;
264use std::collections::HashMap;
265
266pub struct SerialFactory;
268
269impl InterfaceFactory for SerialFactory {
270 fn type_name(&self) -> &str {
271 "SerialInterface"
272 }
273
274 fn default_ifac_size(&self) -> usize {
275 8
276 }
277
278 fn parse_config(
279 &self,
280 name: &str,
281 id: InterfaceId,
282 params: &HashMap<String, String>,
283 ) -> Result<Box<dyn InterfaceConfigData>, String> {
284 let port = params
285 .get("port")
286 .cloned()
287 .ok_or_else(|| "SerialInterface requires 'port'".to_string())?;
288
289 let speed = params
290 .get("speed")
291 .and_then(|v| v.parse().ok())
292 .unwrap_or(9600u32);
293
294 let data_bits = params
295 .get("databits")
296 .and_then(|v| v.parse().ok())
297 .unwrap_or(8u8);
298
299 let parity = params
300 .get("parity")
301 .map(|v| match v.to_lowercase().as_str() {
302 "e" | "even" => crate::serial::Parity::Even,
303 "o" | "odd" => crate::serial::Parity::Odd,
304 _ => crate::serial::Parity::None,
305 })
306 .unwrap_or(crate::serial::Parity::None);
307
308 let stop_bits = params
309 .get("stopbits")
310 .and_then(|v| v.parse().ok())
311 .unwrap_or(1u8);
312
313 Ok(Box::new(SerialIfaceConfig {
314 name: name.to_string(),
315 port,
316 speed,
317 data_bits,
318 parity,
319 stop_bits,
320 interface_id: id,
321 }))
322 }
323
324 fn start(
325 &self,
326 config: Box<dyn InterfaceConfigData>,
327 ctx: StartContext,
328 ) -> std::io::Result<StartResult> {
329 let serial_config = *config
330 .into_any()
331 .downcast::<SerialIfaceConfig>()
332 .map_err(|_| {
333 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
334 })?;
335
336 let id = serial_config.interface_id;
337 let name = serial_config.name.clone();
338 let bitrate = Some(serial_config.speed as u64);
339
340 let info = InterfaceInfo {
341 id,
342 name,
343 mode: ctx.mode,
344 gravity: ctx.gravity,
345 recursive_prs: ctx.recursive_prs,
346 announces_from_internal: ctx.announces_from_internal,
347 announces_to_internal: ctx.announces_to_internal,
348 out_capable: true,
349 in_capable: true,
350 bitrate,
351 airtime_profile: None,
352 announce_rate_target: None,
353 announce_rate_grace: 0,
354 announce_rate_penalty: 0.0,
355 announce_cap: rns_core::constants::ANNOUNCE_CAP,
356 is_local_client: false,
357 wants_tunnel: false,
358 tunnel_id: None,
359 mtu: rns_core::constants::MTU as u32,
360 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
361 ia_freq: 0.0,
362 ip_freq: 0.0,
363 op_freq: 0.0,
364 op_samples: 0,
365 started: crate::time::now(),
366 };
367
368 let (writer, control) = start_with_control(serial_config, ctx.tx)?;
369
370 Ok(StartResult::Simple {
371 control: Some(control),
372 id,
373 info,
374 writer,
375 interface_type_name: "SerialInterface".to_string(),
376 })
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use crate::serial::open_pty_pair;
384 use std::os::unix::io::{AsRawFd, FromRawFd};
385 use std::sync::mpsc;
386 use std::time::Duration;
387
388 fn poll_read(fd: i32, timeout_ms: i32) -> bool {
390 let mut pfd = libc::pollfd {
391 fd,
392 events: libc::POLLIN,
393 revents: 0,
394 };
395 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
396 ret > 0
397 }
398
399 #[test]
400 fn serial_hdlc_roundtrip() {
401 let (master_fd, slave_fd) = open_pty_pair().unwrap();
402 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
403 let mut slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
404
405 let payload: Vec<u8> = (0..32).collect();
407 let framed = hdlc::frame(&payload);
408 master_file.write_all(&framed).unwrap();
409 master_file.flush().unwrap();
410
411 assert!(
413 poll_read(slave_file.as_raw_fd(), 2000),
414 "should have data available"
415 );
416
417 let mut decoder = hdlc::Decoder::new();
418 let mut buf = [0u8; 4096];
419 let n = slave_file.read(&mut buf).unwrap();
420 let frames = decoder.feed(&buf[..n]);
421 assert_eq!(frames.len(), 1);
422 assert_eq!(frames[0], payload);
423 }
424
425 #[test]
426 fn serial_writer_frames() {
427 let (master_fd, slave_fd) = open_pty_pair().unwrap();
428
429 let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
430 let mut writer = SerialWriter { file: writer_file };
431
432 let payload = vec![0x01, 0x02, 0x7E, 0x7D, 0x03]; writer.send_frame(&payload).unwrap();
434
435 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
437 assert!(poll_read(master_file.as_raw_fd(), 2000), "should have data");
438
439 let expected = hdlc::frame(&payload);
440 let mut buf = [0u8; 256];
441 let n = master_file.read(&mut buf).unwrap();
442 assert_eq!(&buf[..n], &expected[..]);
443 }
444
445 #[test]
446 fn serial_fragmented_read() {
447 let (master_fd, slave_fd) = open_pty_pair().unwrap();
448 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
449 let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
450
451 let (tx, rx) = mpsc::channel::<Event>();
452
453 let payload: Vec<u8> = (0..32).collect();
455 let framed = hdlc::frame(&payload);
456 let mid = framed.len() / 2;
457
458 let reader_thread = thread::spawn(move || {
460 let mut reader = slave_file;
461 let mut decoder = hdlc::Decoder::new();
462 let mut buf = [0u8; 4096];
463
464 loop {
465 match reader.read(&mut buf) {
466 Ok(n) if n > 0 => {
467 if let Some(frame) = decoder.feed(&buf[..n]).into_iter().next() {
468 let _ = tx.send(Event::Frame {
469 interface_id: InterfaceId(0),
470 data: frame,
471 rssi: None,
472 snr: None,
473 });
474 return;
475 }
476 }
477 _ => return,
478 }
479 }
480 });
481
482 master_file.write_all(&framed[..mid]).unwrap();
484 master_file.flush().unwrap();
485
486 thread::sleep(Duration::from_millis(50));
488 master_file.write_all(&framed[mid..]).unwrap();
489 master_file.flush().unwrap();
490
491 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
492 match event {
493 Event::Frame { data, .. } => assert_eq!(data, payload),
494 other => panic!("expected Frame, got {:?}", other),
495 }
496
497 let _ = reader_thread.join();
498 }
499
500 #[test]
501 fn serial_reconnect_on_close() {
502 let (master_fd, slave_fd) = open_pty_pair().unwrap();
503
504 let (tx, rx) = mpsc::channel::<Event>();
505 let id = InterfaceId(42);
506
507 let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
509 let reader_thread = thread::spawn(move || {
510 let mut reader = slave_file;
511 let mut buf = [0u8; 4096];
512 let mut decoder = hdlc::Decoder::new();
513 loop {
514 match reader.read(&mut buf) {
515 Ok(0) => {
516 let _ = tx.send(Event::InterfaceDown(id));
517 return;
518 }
519 Ok(n) => {
520 for frame in decoder.feed(&buf[..n]) {
521 let _ = tx.send(Event::Frame {
522 interface_id: id,
523 data: frame,
524 rssi: None,
525 snr: None,
526 });
527 }
528 }
529 Err(_) => {
530 let _ = tx.send(Event::InterfaceDown(id));
531 return;
532 }
533 }
534 }
535 });
536
537 unsafe { libc::close(master_fd) };
539
540 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
541 assert!(matches!(event, Event::InterfaceDown(InterfaceId(42))));
542
543 let _ = reader_thread.join();
544 }
545
546 #[test]
547 fn detach_stops_idle_serial_reader_without_reconnect() {
548 let (master_fd, slave_fd) = open_pty_pair().unwrap();
549 let master = unsafe { std::fs::File::from_raw_fd(master_fd) };
550 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
551 let (tx, rx) = crate::event::channel();
552 let control = ListenerControl::new();
553 let reader_control = control.clone();
554 let config = SerialIfaceConfig {
555 name: "detach-test".into(),
556 interface_id: InterfaceId(71),
557 ..Default::default()
558 };
559
560 let reader =
561 thread::spawn(move || reader_loop(slave, InterfaceId(71), config, tx, reader_control));
562 thread::sleep(Duration::from_millis(50));
563 control.request_stop();
564 reader.join().unwrap();
565 drop(master);
566 assert!(rx.try_recv().is_err());
567 }
568
569 #[test]
570 fn missing_serial_port_recovers_after_it_appears() {
571 let tempdir = tempfile::tempdir().unwrap();
572 let port_path = tempdir.path().join("serial-port");
573 let (tx, rx) = crate::event::channel();
574 let config = SerialIfaceConfig {
575 name: "late-port".into(),
576 port: port_path.display().to_string(),
577 interface_id: InterfaceId(73),
578 ..Default::default()
579 };
580
581 let (_writer, control) = start_with_control(config, tx).unwrap();
582 assert!(rx.try_recv().is_err());
583
584 let (master_fd, slave_fd) = open_pty_pair().unwrap();
585 let master = unsafe { std::fs::File::from_raw_fd(master_fd) };
586 let slave_path = std::fs::read_link(format!("/proc/self/fd/{slave_fd}")).unwrap();
587 std::os::unix::fs::symlink(slave_path, &port_path).unwrap();
588 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
589
590 let event = rx.recv_timeout(Duration::from_secs(8)).unwrap();
591 assert!(matches!(
592 event,
593 Event::InterfaceUp(InterfaceId(73), Some(_), None)
594 ));
595 control.request_stop();
596 drop(slave);
597 drop(master);
598 }
599}