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