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