1use std::collections::VecDeque;
8use std::io::{self, Read, Write};
9use std::os::fd::AsRawFd;
10use std::sync::{Arc, Mutex};
11use std::thread;
12use std::time::{Duration, Instant};
13
14use rns_core::transport::types::InterfaceId;
15
16use crate::event::{Event, EventSender};
17use crate::interface::{
18 lock_or_recover, poll_readable, wait_while_active, ListenerControl, UnavailableWriter, Writer,
19};
20use crate::kiss;
21use crate::serial::{Parity, SerialConfig, SerialPort};
22
23mod folded;
24
25#[derive(Debug, Clone)]
27pub struct KissIfaceConfig {
28 pub name: String,
29 pub port: String,
30 pub speed: u32,
31 pub data_bits: u8,
32 pub parity: Parity,
33 pub stop_bits: u8,
34 pub preamble: u16, pub txtail: u16, pub persistence: u8, pub slottime: u16, pub flow_control: bool, pub beacon_interval: Option<u32>, pub beacon_data: Option<Vec<u8>>, pub interface_id: InterfaceId,
42 pub ax25_source: Option<Ax25Address>,
43 pub interface_type_name: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Ax25Address {
48 pub callsign: String,
49 pub ssid: u8,
50}
51
52impl Default for KissIfaceConfig {
53 fn default() -> Self {
54 KissIfaceConfig {
55 name: String::new(),
56 port: String::new(),
57 speed: 9600,
58 data_bits: 8,
59 parity: Parity::None,
60 stop_bits: 1,
61 preamble: 350,
62 txtail: 20,
63 persistence: 64,
64 slottime: 20,
65 flow_control: false,
66 beacon_interval: None,
67 beacon_data: None,
68 interface_id: InterfaceId(0),
69 ax25_source: None,
70 interface_type_name: "KISSInterface".into(),
71 }
72 }
73}
74
75struct FlowState {
77 ready: bool,
78 queue: VecDeque<Vec<u8>>,
79 lock_time: Instant,
80}
81
82struct KissWriter {
85 confirmed_pending: Option<Vec<u8>>,
86 file: std::fs::File,
87 flow_control: bool,
88 flow_state: Arc<Mutex<FlowState>>,
89 ax25_source: Option<Ax25Address>,
90}
91
92impl Writer for KissWriter {
93 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
94 if !self.flow_control {
95 return self.send_frame(data);
96 }
97 let data = self
98 .ax25_source
99 .as_ref()
100 .map(|source| super::ax25_kiss::encode_ui_frame(source, data))
101 .unwrap_or_else(|| data.to_vec());
102 self.confirmed_pending = Some(kiss::frame(&data));
103 self.flush()
104 }
105
106 fn flush(&mut self) -> io::Result<()> {
107 if self.confirmed_pending.is_none() {
108 return Ok(());
109 }
110 let mut state = lock_or_recover(&self.flow_state, "kiss flow state");
111 if !state.ready || !state.queue.is_empty() {
112 return Err(io::ErrorKind::WouldBlock.into());
113 }
114 state.ready = false;
115 state.lock_time = Instant::now();
116 drop(state);
117 self.file.write_all(&self.confirmed_pending.take().unwrap())
118 }
119 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
120 let data = self
121 .ax25_source
122 .as_ref()
123 .map(|source| super::ax25_kiss::encode_ui_frame(source, data))
124 .unwrap_or_else(|| data.to_vec());
125 if self.flow_control {
126 let mut state = lock_or_recover(&self.flow_state, "kiss flow state");
127 if state.ready {
128 state.ready = false;
129 state.lock_time = Instant::now();
130 drop(state);
131 self.file.write_all(&kiss::frame(&data))
132 } else {
133 state.queue.push_back(data);
134 Ok(())
135 }
136 } else {
137 self.file.write_all(&kiss::frame(&data))
138 }
139 }
140}
141
142pub fn start(config: KissIfaceConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
144 start_with_control(config, tx).map(|(writer, _)| writer)
145}
146
147fn start_with_control(
148 config: KissIfaceConfig,
149 tx: EventSender,
150) -> io::Result<(Box<dyn Writer>, ListenerControl)> {
151 let serial_config = SerialConfig {
152 path: config.port.clone(),
153 baud: config.speed,
154 data_bits: config.data_bits,
155 parity: config.parity,
156 stop_bits: config.stop_bits,
157 };
158
159 let control = ListenerControl::new();
160 let flow_state = Arc::new(Mutex::new(FlowState {
161 ready: true,
162 queue: VecDeque::new(),
163 lock_time: Instant::now(),
164 }));
165 let port = match SerialPort::open(&serial_config) {
166 Ok(port) => port,
167 Err(error) => {
168 log::warn!(
169 "[{}] could not open KISS port {}: {}; retrying",
170 config.name,
171 config.port,
172 error
173 );
174 spawn_initial_reconnect(config, tx, flow_state, control.clone())?;
175 return Ok((Box::new(UnavailableWriter), control));
176 }
177 };
178 let reader_file = port.reader()?;
179 let mut writer_file = port.writer()?;
180 let flow_writer_file = port.writer()?;
181
182 let id = config.interface_id;
183
184 thread::sleep(Duration::from_secs(2));
186
187 if let Err(error) = configure_tnc(&mut writer_file, &config) {
189 log::warn!(
190 "[{}] initial TNC configuration failed: {}; retrying",
191 config.name,
192 error
193 );
194 drop(reader_file);
195 drop(writer_file);
196 drop(flow_writer_file);
197 spawn_initial_reconnect(config, tx, flow_state, control.clone())?;
198 return Ok((Box::new(UnavailableWriter), control));
199 }
200
201 let _ = tx.send(Event::InterfaceUp(id, None, None));
202
203 if crate::engine::FOLD_IO {
204 drop(flow_writer_file);
205 let link = folded::KissLink::new(config, tx, control.clone());
206 return Ok((link.serve(reader_file, writer_file)?, control));
207 }
208
209 let reader_flow_state = flow_state.clone();
210
211 let reader_config = config.clone();
213 let reader_control = control.clone();
214 thread::Builder::new()
215 .name(format!("kiss-reader-{}", id.0))
216 .spawn(move || {
217 reader_loop(
218 reader_file,
219 flow_writer_file,
220 id,
221 reader_config,
222 tx,
223 reader_flow_state,
224 reader_control,
225 );
226 })?;
227
228 Ok((
229 Box::new(KissWriter {
230 confirmed_pending: None,
231 file: writer_file,
232 flow_control: config.flow_control,
233 flow_state,
234 ax25_source: config.ax25_source.clone(),
235 }),
236 control,
237 ))
238}
239
240fn spawn_initial_reconnect(
241 config: KissIfaceConfig,
242 tx: EventSender,
243 flow_state: Arc<Mutex<FlowState>>,
244 control: ListenerControl,
245) -> io::Result<()> {
246 if crate::engine::FOLD_IO {
247 return folded::KissLink::new(config, tx, control).reopen();
248 }
249 thread::Builder::new()
250 .name(format!("kiss-reconnect-{}", config.interface_id.0))
251 .spawn(move || {
252 if let Some((reader, flow_writer)) = reconnect(&config, &tx, &flow_state, &control) {
253 let id = config.interface_id;
254 reader_loop(reader, flow_writer, id, config, tx, flow_state, control);
255 }
256 })?;
257 Ok(())
258}
259
260fn configure_tnc(writer: &mut std::fs::File, config: &KissIfaceConfig) -> io::Result<()> {
263 log::info!("[{}] configuring KISS interface parameters", config.name);
264
265 let preamble_val = (config.preamble / 10).min(255) as u8;
267 writer.write_all(&kiss::command_frame(kiss::CMD_TXDELAY, &[preamble_val]))?;
268
269 let txtail_val = (config.txtail / 10).min(255) as u8;
271 writer.write_all(&kiss::command_frame(kiss::CMD_TXTAIL, &[txtail_val]))?;
272
273 writer.write_all(&kiss::command_frame(kiss::CMD_P, &[config.persistence]))?;
275
276 let slottime_val = (config.slottime / 10).min(255) as u8;
278 writer.write_all(&kiss::command_frame(kiss::CMD_SLOTTIME, &[slottime_val]))?;
279
280 writer.write_all(&kiss::command_frame(kiss::CMD_READY, &[0x01]))?;
282
283 log::info!("[{}] KISS interface configured", config.name);
284 Ok(())
285}
286
287fn reader_loop(
290 mut reader: std::fs::File,
291 mut flow_writer: std::fs::File,
292 id: InterfaceId,
293 config: KissIfaceConfig,
294 tx: EventSender,
295 flow_state: Arc<Mutex<FlowState>>,
296 control: ListenerControl,
297) {
298 let mut decoder = kiss::Decoder::new();
299 let mut buf = [0u8; 4096];
300 let mut first_tx: Option<Instant> = None;
301
302 loop {
303 if control.should_stop() {
304 return;
305 }
306 match poll_readable(reader.as_raw_fd(), Duration::from_millis(200)) {
307 Ok(false) => continue,
308 Ok(true) => {}
309 Err(e) => {
310 if control.should_stop() {
311 return;
312 }
313 log::warn!("[{}] KISS poll error: {}", config.name, e);
314 let _ = tx.send(Event::InterfaceDown(id));
315 match reconnect(&config, &tx, &flow_state, &control) {
316 Some((new_reader, new_flow_writer)) => {
317 reader = new_reader;
318 flow_writer = new_flow_writer;
319 decoder = kiss::Decoder::new();
320 continue;
321 }
322 None => return,
323 }
324 }
325 }
326 match reader.read(&mut buf) {
327 Ok(0) => {
328 if control.should_stop() {
329 return;
330 }
331 log::warn!("[{}] KISS port closed", config.name);
332 let _ = tx.send(Event::InterfaceDown(id));
333 match reconnect(&config, &tx, &flow_state, &control) {
334 Some((new_reader, new_flow_writer)) => {
335 reader = new_reader;
336 flow_writer = new_flow_writer;
337 decoder = kiss::Decoder::new();
338 continue;
339 }
340 None => return,
341 }
342 }
343 Ok(n) => {
344 for event in decoder.feed(&buf[..n]) {
345 match event {
346 kiss::KissEvent::DataFrame(data) => {
347 let data = if config.ax25_source.is_some() {
348 match super::ax25_kiss::decode_ui_frame(&data) {
349 Some(data) => data.to_vec(),
350 None => continue,
351 }
352 } else {
353 data
354 };
355 if tx
356 .send(Event::Frame {
357 interface_id: id,
358 data: data.into(),
359 rssi: None,
360 snr: None,
361 })
362 .is_err()
363 {
364 return;
365 }
366 }
367 kiss::KissEvent::Ready => {
368 process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
369 }
370 }
371 }
372 }
373 Err(e) => {
374 if control.should_stop() {
375 return;
376 }
377 log::warn!("[{}] KISS read error: {}", config.name, e);
378 let _ = tx.send(Event::InterfaceDown(id));
379 match reconnect(&config, &tx, &flow_state, &control) {
380 Some((new_reader, new_flow_writer)) => {
381 reader = new_reader;
382 flow_writer = new_flow_writer;
383 decoder = kiss::Decoder::new();
384 continue;
385 }
386 None => return,
387 }
388 }
389 }
390
391 if config.flow_control {
393 let state = lock_or_recover(&flow_state, "kiss flow state");
394 if !state.ready && state.lock_time.elapsed() > Duration::from_secs(5) {
395 drop(state);
396 log::warn!("[{}] unlocking flow control due to timeout", config.name);
397 process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
398 }
399 }
400
401 if let (Some(interval), Some(ref beacon_data)) =
403 (config.beacon_interval, &config.beacon_data)
404 {
405 if let Some(first) = first_tx {
406 if first.elapsed() > Duration::from_secs(interval as u64) {
407 log::debug!("[{}] transmitting beacon data", config.name);
408 let mut frame = beacon_data.clone();
410 while frame.len() < 15 {
411 frame.push(0x00);
412 }
413 let _ = flow_writer.write_all(&kiss::frame(&frame));
414 first_tx = None;
415 }
416 }
417 }
418 }
419}
420
421fn process_queue(
423 flow_state: &Arc<Mutex<FlowState>>,
424 writer: &mut std::fs::File,
425 first_tx: &mut Option<Instant>,
426 _config: &KissIfaceConfig,
427) {
428 let mut state = lock_or_recover(flow_state, "kiss flow state");
429 if let Some(data) = state.queue.pop_front() {
430 state.ready = false;
431 state.lock_time = Instant::now();
432 drop(state);
433 let _ = writer.write_all(&kiss::frame(&data));
434 if first_tx.is_none() {
435 *first_tx = Some(Instant::now());
436 }
437 } else {
438 state.ready = true;
439 }
440}
441
442fn reconnect(
445 config: &KissIfaceConfig,
446 tx: &EventSender,
447 flow_state: &Arc<Mutex<FlowState>>,
448 control: &ListenerControl,
449) -> Option<(std::fs::File, std::fs::File)> {
450 loop {
451 let (reader, cfg_writer) = reopen_port(config, control)?;
452 let flow_writer = match cfg_writer.try_clone() {
453 Ok(flow_writer) => flow_writer,
454 Err(e) => {
455 log::warn!(
456 "[{}] failed to get handles from serial port: {}",
457 config.name,
458 e
459 );
460 continue;
461 }
462 };
463 let mut state = lock_or_recover(flow_state, "kiss flow state");
465 state.ready = true;
466 state.queue.clear();
467 drop(state);
468
469 let new_writer: Box<dyn Writer> = Box::new(KissWriter {
470 confirmed_pending: None,
471 file: cfg_writer,
472 flow_control: config.flow_control,
473 flow_state: flow_state.clone(),
474 ax25_source: config.ax25_source.clone(),
475 });
476 let _ = tx.send(Event::InterfaceUp(
477 config.interface_id,
478 Some(new_writer),
479 None,
480 ));
481 log::info!("[{}] KISS port reconnected", config.name);
482 return Some((reader, flow_writer));
483 }
484}
485
486fn reopen_port(
490 config: &KissIfaceConfig,
491 control: &ListenerControl,
492) -> Option<(std::fs::File, std::fs::File)> {
493 loop {
494 if !wait_while_active(control, Duration::from_secs(5)) {
495 return None;
496 }
497 log::info!(
498 "[{}] attempting to reconnect KISS port {}...",
499 config.name,
500 config.port
501 );
502
503 let serial_config = SerialConfig {
504 path: config.port.clone(),
505 baud: config.speed,
506 data_bits: config.data_bits,
507 parity: config.parity,
508 stop_bits: config.stop_bits,
509 };
510
511 match SerialPort::open(&serial_config) {
512 Ok(port) => match (port.reader(), port.writer()) {
513 (Ok(reader), Ok(mut cfg_writer)) => {
514 if !wait_while_active(control, Duration::from_secs(2)) {
516 return None;
517 }
518 if let Err(e) = configure_tnc(&mut cfg_writer, config) {
519 log::warn!("[{}] TNC config failed: {}", config.name, e);
520 continue;
521 }
522 return Some((reader, cfg_writer));
523 }
524 _ => {
525 log::warn!("[{}] failed to get handles from serial port", config.name);
526 }
527 },
528 Err(e) => {
529 log::warn!("[{}] KISS reconnect failed: {}", config.name, e);
530 }
531 }
532 }
533}
534
535use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
538use rns_core::transport::types::InterfaceInfo;
539use std::collections::HashMap;
540
541pub struct KissFactory;
543
544impl InterfaceFactory for KissFactory {
545 fn type_name(&self) -> &str {
546 "KISSInterface"
547 }
548
549 fn default_ifac_size(&self) -> usize {
550 8
551 }
552
553 fn parse_config(
554 &self,
555 name: &str,
556 id: InterfaceId,
557 params: &HashMap<String, String>,
558 ) -> Result<Box<dyn InterfaceConfigData>, String> {
559 let port = params
560 .get("port")
561 .cloned()
562 .ok_or_else(|| "KISSInterface requires 'port'".to_string())?;
563
564 let speed = params
565 .get("speed")
566 .and_then(|v| v.parse().ok())
567 .unwrap_or(9600u32);
568
569 let data_bits = params
570 .get("databits")
571 .and_then(|v| v.parse().ok())
572 .unwrap_or(8u8);
573
574 let parity = params
575 .get("parity")
576 .map(|v| match v.to_lowercase().as_str() {
577 "e" | "even" => crate::serial::Parity::Even,
578 "o" | "odd" => crate::serial::Parity::Odd,
579 _ => crate::serial::Parity::None,
580 })
581 .unwrap_or(crate::serial::Parity::None);
582
583 let stop_bits = params
584 .get("stopbits")
585 .and_then(|v| v.parse().ok())
586 .unwrap_or(1u8);
587
588 let preamble = params
589 .get("preamble")
590 .and_then(|v| v.parse().ok())
591 .unwrap_or(350u16);
592
593 let txtail = params
594 .get("txtail")
595 .and_then(|v| v.parse().ok())
596 .unwrap_or(20u16);
597
598 let persistence = params
599 .get("persistence")
600 .and_then(|v| v.parse().ok())
601 .unwrap_or(64u8);
602
603 let slottime = params
604 .get("slottime")
605 .and_then(|v| v.parse().ok())
606 .unwrap_or(20u16);
607
608 let flow_control = params
609 .get("flow_control")
610 .and_then(|v| crate::config::parse_bool_pub(v))
611 .unwrap_or(false);
612
613 let beacon_interval = params
614 .get("id_interval")
615 .or_else(|| params.get("beacon_interval"))
616 .and_then(|v| v.parse().ok());
617
618 let beacon_data = params
619 .get("id_callsign")
620 .or_else(|| params.get("beacon_data"))
621 .map(|v| v.as_bytes().to_vec());
622
623 Ok(Box::new(KissIfaceConfig {
624 name: name.to_string(),
625 port,
626 speed,
627 data_bits,
628 parity,
629 stop_bits,
630 preamble,
631 txtail,
632 persistence,
633 slottime,
634 flow_control,
635 beacon_interval,
636 beacon_data,
637 interface_id: id,
638 ax25_source: None,
639 interface_type_name: "KISSInterface".into(),
640 }))
641 }
642
643 fn start(
644 &self,
645 config: Box<dyn InterfaceConfigData>,
646 ctx: StartContext,
647 ) -> std::io::Result<StartResult> {
648 let kiss_config = *config
649 .into_any()
650 .downcast::<KissIfaceConfig>()
651 .map_err(|_| {
652 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
653 })?;
654
655 let id = kiss_config.interface_id;
656 let name = kiss_config.name.clone();
657 let interface_type_name = kiss_config.interface_type_name.clone();
658
659 let info = InterfaceInfo {
660 stamp_costs: Default::default(),
661 id,
662 name,
663 mode: ctx.mode,
664 gravity: ctx.gravity,
665 recursive_prs: ctx.recursive_prs,
666 announces_from_internal: ctx.announces_from_internal,
667 announces_to_internal: ctx.announces_to_internal,
668 out_capable: true,
669 in_capable: true,
670 bitrate: Some(1200),
671 airtime_profile: None,
672 announce_rate_target: None,
673 announce_rate_grace: 0,
674 announce_rate_penalty: 0.0,
675 announce_cap: rns_core::constants::ANNOUNCE_CAP,
676 is_local_client: false,
677 wants_tunnel: false,
678 tunnel_id: None,
679 mtu: rns_core::constants::MTU as u32,
680 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
681 ia_freq: 0.0,
682 ip_freq: 0.0,
683 op_freq: 0.0,
684 op_samples: 0,
685 started: crate::time::now(),
686 };
687
688 let (writer, control) = start_with_control(kiss_config, ctx.tx)?;
689
690 Ok(StartResult::Simple {
691 control: Some(control),
692 id,
693 info,
694 writer,
695 interface_type_name,
696 })
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703 use crate::serial::open_pty_pair;
704 use std::os::unix::io::{AsRawFd, FromRawFd};
705 use std::sync::mpsc;
706
707 fn poll_read(fd: i32, timeout_ms: i32) -> bool {
709 let mut pfd = libc::pollfd {
710 fd,
711 events: libc::POLLIN,
712 revents: 0,
713 };
714 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
715 ret > 0
716 }
717
718 fn thread_named(prefix: &str) -> bool {
719 std::fs::read_dir("/proc/self/task").unwrap().any(|task| {
720 let comm = task.unwrap().path().join("comm");
721 std::fs::read_to_string(comm).is_ok_and(|comm| comm.trim().starts_with(prefix))
722 })
723 }
724
725 fn tnc_reads(master: &mut std::fs::File, want: usize, timeout_ms: i32) -> Vec<Vec<u8>> {
728 let mut decoder = kiss::Decoder::new();
729 let mut frames = Vec::new();
730 let mut buf = [0u8; 1024];
731 while frames.len() < want && poll_read(master.as_raw_fd(), timeout_ms) {
732 let n = master.read(&mut buf).unwrap();
733 for event in decoder.feed(&buf[..n]) {
734 if let kiss::KissEvent::DataFrame(data) = event {
735 frames.push(data);
736 }
737 }
738 }
739 frames
740 }
741
742 #[test]
743 fn folded_kiss_port_runs_on_the_driver_loop() {
744 if !crate::engine::FOLD_IO {
745 return;
746 }
747 let tempdir = tempfile::tempdir().unwrap();
748 let port_path = tempdir.path().join("kiss-port");
749 let (master_fd, slave_fd) = open_pty_pair().unwrap();
750 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
751 let slave_path = std::fs::read_link(format!("/proc/self/fd/{slave_fd}")).unwrap();
752 std::os::unix::fs::symlink(slave_path, &port_path).unwrap();
753 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
754
755 let (tx, rx) = crate::event::channel();
756 let config = KissIfaceConfig {
757 name: "folded-kiss".into(),
758 port: port_path.display().to_string(),
759 interface_id: InterfaceId(75),
760 flow_control: true,
761 ax25_source: Some(Ax25Address {
762 callsign: "N0CALL".into(),
763 ssid: 1,
764 }),
765 ..Default::default()
766 };
767 let (writer, control) = start_with_control(config, tx.clone()).unwrap();
768 assert!(matches!(
769 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
770 Event::InterfaceUp(InterfaceId(75), None, None)
771 ));
772 assert!(writer.sends_inline());
773 let (mut writer, _) =
774 crate::interface::wrap_async_writer(writer, InterfaceId(75), "kiss", tx, 16);
775 assert!(!thread_named("kiss-reader-75"));
776 assert!(!thread_named("iface-writer-75"));
777
778 let source = Ax25Address {
780 callsign: "N0PEER".into(),
781 ssid: 0,
782 };
783 let payload: Vec<u8> = (0..32).collect();
784 let framed = kiss::frame(&super::super::ax25_kiss::encode_ui_frame(&source, &payload));
785 master.write_all(&framed[..10]).unwrap();
786 master.write_all(&framed[10..]).unwrap();
787 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
788 Event::Frame {
789 interface_id, data, ..
790 } => {
791 assert_eq!(interface_id, InterfaceId(75));
792 assert_eq!(&*data, &payload[..]);
793 }
794 other => panic!("expected Frame, got {other:?}"),
795 }
796
797 writer.send_frame(b"first frame").unwrap();
800 writer.send_frame(b"second frame").unwrap();
801 let sent = tnc_reads(&mut master, 2, 200);
802 assert_eq!(sent.len(), 1);
803 let decoded = super::super::ax25_kiss::decode_ui_frame(&sent[0]).unwrap();
804 assert_eq!(decoded, b"first frame");
805 master
806 .write_all(&kiss::command_frame(kiss::CMD_READY, &[0x01]))
807 .unwrap();
808 assert!(rx.recv_timeout(Duration::from_millis(200)).is_err());
809 let sent = tnc_reads(&mut master, 1, 2000);
810 let decoded = super::super::ax25_kiss::decode_ui_frame(&sent[0]).unwrap();
811 assert_eq!(decoded, b"second frame");
812
813 drop(master);
815 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
816 Event::InterfaceDown(id) => assert_eq!(id, InterfaceId(75)),
817 other => panic!("expected InterfaceDown, got {other:?}"),
818 }
819 control.request_stop();
820 drop(slave);
821 }
822
823 #[test]
824 fn kiss_data_roundtrip() {
825 let (master_fd, slave_fd) = open_pty_pair().unwrap();
826 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
827 let mut slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
828
829 let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
831 let framed = kiss::frame(&payload);
832 master_file.write_all(&framed).unwrap();
833 master_file.flush().unwrap();
834
835 assert!(poll_read(slave_file.as_raw_fd(), 2000));
837
838 let mut decoder = kiss::Decoder::new();
839 let mut buf = [0u8; 4096];
840 let n = slave_file.read(&mut buf).unwrap();
841 let events = decoder.feed(&buf[..n]);
842 assert_eq!(events.len(), 1);
843 assert_eq!(events[0], kiss::KissEvent::DataFrame(payload));
844 }
845
846 #[test]
847 fn detach_stops_idle_kiss_reader_without_reconnect() {
848 let (master_fd, slave_fd) = open_pty_pair().unwrap();
849 let master = unsafe { std::fs::File::from_raw_fd(master_fd) };
850 let flow_writer = unsafe { std::fs::File::from_raw_fd(libc::dup(slave_fd)) };
851 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
852 let (tx, rx) = crate::event::channel();
853 let control = ListenerControl::new();
854 let reader_control = control.clone();
855 let flow_state = Arc::new(Mutex::new(FlowState {
856 ready: true,
857 queue: VecDeque::new(),
858 lock_time: Instant::now(),
859 }));
860 let config = KissIfaceConfig {
861 name: "detach-test".into(),
862 interface_id: InterfaceId(72),
863 ..Default::default()
864 };
865
866 let reader = thread::spawn(move || {
867 reader_loop(
868 slave,
869 flow_writer,
870 InterfaceId(72),
871 config,
872 tx,
873 flow_state,
874 reader_control,
875 )
876 });
877 thread::sleep(Duration::from_millis(50));
878 control.request_stop();
879 reader.join().unwrap();
880 drop(master);
881 assert!(rx.try_recv().is_err());
882 }
883
884 #[test]
885 fn missing_kiss_port_recovers_after_it_appears() {
886 let tempdir = tempfile::tempdir().unwrap();
887 let port_path = tempdir.path().join("kiss-port");
888 let (tx, rx) = crate::event::channel();
889 let config = KissIfaceConfig {
890 name: "late-kiss".into(),
891 port: port_path.display().to_string(),
892 interface_id: InterfaceId(74),
893 ..Default::default()
894 };
895
896 let (_writer, control) = start_with_control(config, tx).unwrap();
897 assert!(rx.try_recv().is_err());
898
899 let (master_fd, slave_fd) = open_pty_pair().unwrap();
900 let master = unsafe { std::fs::File::from_raw_fd(master_fd) };
901 let slave_path = std::fs::read_link(format!("/proc/self/fd/{slave_fd}")).unwrap();
902 std::os::unix::fs::symlink(slave_path, &port_path).unwrap();
903 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
904
905 let event = rx.recv_timeout(Duration::from_secs(10)).unwrap();
906 assert!(matches!(
907 event,
908 Event::InterfaceUp(InterfaceId(74), Some(_), None)
909 ));
910 control.request_stop();
911 drop(slave);
912 drop(master);
913 }
914
915 #[test]
916 fn kiss_writer_frames() {
917 let (master_fd, slave_fd) = open_pty_pair().unwrap();
918
919 let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
920 let flow_state = Arc::new(Mutex::new(FlowState {
921 ready: true,
922 queue: VecDeque::new(),
923 lock_time: Instant::now(),
924 }));
925
926 let mut writer = KissWriter {
927 confirmed_pending: None,
928 file: writer_file,
929 flow_control: false,
930 flow_state,
931 ax25_source: None,
932 };
933
934 let payload = vec![0xC0, 0xDB, 0x01]; writer.send_frame(&payload).unwrap();
936
937 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
939 assert!(poll_read(master_file.as_raw_fd(), 2000));
940
941 let expected = kiss::frame(&payload);
942 let mut buf = [0u8; 256];
943 let n = master_file.read(&mut buf).unwrap();
944 assert_eq!(&buf[..n], &expected[..]);
945 }
946
947 #[test]
948 fn kiss_config_commands() {
949 use std::time::Instant;
950
951 let (master_fd, slave_fd) = open_pty_pair().unwrap();
952
953 let mut writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
954 let config = KissIfaceConfig {
955 preamble: 350,
956 txtail: 20,
957 persistence: 64,
958 slottime: 20,
959 ..Default::default()
960 };
961
962 configure_tnc(&mut writer_file, &config).unwrap();
963
964 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
966 let deadline = Instant::now() + Duration::from_secs(2);
967 let mut data = Vec::new();
968 let mut buf = [0u8; 1024];
969 while Instant::now() < deadline {
970 let remaining_ms = deadline
971 .saturating_duration_since(Instant::now())
972 .as_millis()
973 .min(i32::MAX as u128) as i32;
974 if remaining_ms <= 0 || !poll_read(master_file.as_raw_fd(), remaining_ms) {
975 break;
976 }
977
978 let n = master_file.read(&mut buf).unwrap();
979 if n == 0 {
980 break;
981 }
982 data.extend_from_slice(&buf[..n]);
983
984 let have_all = data.windows(4).any(|w| {
985 w[0] == kiss::FEND && w[1] == kiss::CMD_TXDELAY && w[2] == 35 && w[3] == kiss::FEND
986 }) && data.windows(4).any(|w| {
987 w[0] == kiss::FEND && w[1] == kiss::CMD_TXTAIL && w[2] == 2 && w[3] == kiss::FEND
988 }) && data.windows(4).any(|w| {
989 w[0] == kiss::FEND && w[1] == kiss::CMD_P && w[2] == 64 && w[3] == kiss::FEND
990 }) && data.windows(4).any(|w| {
991 w[0] == kiss::FEND && w[1] == kiss::CMD_SLOTTIME && w[2] == 2 && w[3] == kiss::FEND
992 });
993 if have_all {
994 break;
995 }
996 }
997
998 assert!(
1001 data.windows(4).any(|w| w[0] == kiss::FEND
1002 && w[1] == kiss::CMD_TXDELAY
1003 && w[2] == 35
1004 && w[3] == kiss::FEND),
1005 "should contain TXDELAY command"
1006 );
1007
1008 assert!(
1010 data.windows(4).any(|w| w[0] == kiss::FEND
1011 && w[1] == kiss::CMD_TXTAIL
1012 && w[2] == 2
1013 && w[3] == kiss::FEND),
1014 "should contain TXTAIL command"
1015 );
1016
1017 assert!(
1019 data.windows(4).any(|w| w[0] == kiss::FEND
1020 && w[1] == kiss::CMD_P
1021 && w[2] == 64
1022 && w[3] == kiss::FEND),
1023 "should contain P command"
1024 );
1025
1026 assert!(
1028 data.windows(4).any(|w| w[0] == kiss::FEND
1029 && w[1] == kiss::CMD_SLOTTIME
1030 && w[2] == 2
1031 && w[3] == kiss::FEND),
1032 "should contain SLOTTIME command"
1033 );
1034 }
1035
1036 #[test]
1037 fn kiss_flow_control_lock() {
1038 let flow_state = Arc::new(Mutex::new(FlowState {
1039 ready: true,
1040 queue: VecDeque::new(),
1041 lock_time: Instant::now(),
1042 }));
1043
1044 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1045 let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
1046
1047 let mut writer = KissWriter {
1048 confirmed_pending: None,
1049 file: writer_file,
1050 flow_control: true,
1051 flow_state: flow_state.clone(),
1052 ax25_source: None,
1053 };
1054
1055 writer.send_frame(b"hello").unwrap();
1057 assert!(!flow_state.lock().unwrap().ready);
1058
1059 writer.send_frame(b"world").unwrap();
1061 assert_eq!(flow_state.lock().unwrap().queue.len(), 1);
1062
1063 let mut flow_writer = unsafe { std::fs::File::from_raw_fd(libc::dup(master_fd)) };
1065 let mut first_tx = None;
1066 let config = KissIfaceConfig::default();
1067 process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
1068
1069 assert_eq!(flow_state.lock().unwrap().queue.len(), 0);
1071 assert!(!flow_state.lock().unwrap().ready);
1072
1073 process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
1075 assert!(flow_state.lock().unwrap().ready);
1076
1077 unsafe { libc::close(master_fd) };
1079 }
1080
1081 #[test]
1082 fn kiss_flow_control_timeout() {
1083 let flow_state = Arc::new(Mutex::new(FlowState {
1084 ready: false,
1085 queue: VecDeque::new(),
1086 lock_time: Instant::now() - Duration::from_secs(6), }));
1088
1089 let state = flow_state.lock().unwrap();
1091 assert!(!state.ready);
1092 assert!(state.lock_time.elapsed() > Duration::from_secs(5));
1093 }
1094
1095 #[test]
1096 fn confirmed_kiss_frame_waits_for_ready_and_flushes_once() {
1097 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1098 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
1099 let state = Arc::new(Mutex::new(FlowState {
1100 ready: false,
1101 queue: VecDeque::new(),
1102 lock_time: Instant::now(),
1103 }));
1104 let mut writer = KissWriter {
1105 confirmed_pending: None,
1106 file: unsafe { std::fs::File::from_raw_fd(slave_fd) },
1107 flow_control: true,
1108 flow_state: state.clone(),
1109 ax25_source: None,
1110 };
1111 assert_eq!(
1112 writer
1113 .send_frame_confirmed(b"confirmed")
1114 .unwrap_err()
1115 .kind(),
1116 io::ErrorKind::WouldBlock
1117 );
1118 assert!(state.lock().unwrap().queue.is_empty());
1119 assert!(!poll_read(master.as_raw_fd(), 20));
1120 state.lock().unwrap().ready = true;
1121 Writer::flush(&mut writer).unwrap();
1122 let mut actual = vec![0; kiss::frame(b"confirmed").len()];
1123 master.read_exact(&mut actual).unwrap();
1124 assert_eq!(actual, kiss::frame(b"confirmed"));
1125 Writer::flush(&mut writer).unwrap();
1126 assert!(!poll_read(master.as_raw_fd(), 20));
1127 }
1128
1129 #[test]
1130 fn kiss_fragmented() {
1131 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1132 let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
1133 let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
1134
1135 let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
1136 let framed = kiss::frame(&payload);
1137 let mid = framed.len() / 2;
1138
1139 let (tx, rx) = mpsc::channel::<kiss::KissEvent>();
1141 let reader_thread = thread::spawn(move || {
1142 let mut reader = slave_file;
1143 let mut decoder = kiss::Decoder::new();
1144 let mut buf = [0u8; 4096];
1145
1146 loop {
1147 match reader.read(&mut buf) {
1148 Ok(n) if n > 0 => {
1149 for event in decoder.feed(&buf[..n]) {
1150 let _ = tx.send(event.clone());
1151 if matches!(event, kiss::KissEvent::DataFrame(_)) {
1152 return;
1153 }
1154 }
1155 }
1156 _ => return,
1157 }
1158 }
1159 });
1160
1161 master_file.write_all(&framed[..mid]).unwrap();
1163 master_file.flush().unwrap();
1164
1165 thread::sleep(Duration::from_millis(50));
1166
1167 master_file.write_all(&framed[mid..]).unwrap();
1169 master_file.flush().unwrap();
1170
1171 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1172 assert_eq!(event, kiss::KissEvent::DataFrame(payload));
1173
1174 let _ = reader_thread.join();
1175 }
1176}