Skip to main content

rns_net/interface/
kiss_iface.rs

1//! KISS interface with flow control and TNC configuration.
2//!
3//! Matches Python `KISSInterface.py` — opens a serial port,
4//! sends TNC configuration commands, handles KISS framing with
5//! optional flow control.
6
7use std::io::{self, Read, Write};
8use std::sync::{Arc, Mutex};
9use std::thread;
10use std::time::{Duration, Instant};
11use std::collections::VecDeque;
12
13use rns_core::transport::types::InterfaceId;
14
15use crate::event::{Event, EventSender};
16use crate::kiss;
17use crate::interface::Writer;
18use crate::serial::{Parity, SerialConfig, SerialPort};
19
20/// Configuration for a KISS interface.
21#[derive(Debug, Clone)]
22pub struct KissIfaceConfig {
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 preamble: u16,       // ms, default 350
30    pub txtail: u16,         // ms, default 20
31    pub persistence: u8,     // 0-255, default 64
32    pub slottime: u16,       // ms, default 20
33    pub flow_control: bool,  // default false
34    pub beacon_interval: Option<u32>,  // seconds
35    pub beacon_data: Option<Vec<u8>>,  // padded to 15 bytes
36    pub interface_id: InterfaceId,
37}
38
39impl Default for KissIfaceConfig {
40    fn default() -> Self {
41        KissIfaceConfig {
42            name: String::new(),
43            port: String::new(),
44            speed: 9600,
45            data_bits: 8,
46            parity: Parity::None,
47            stop_bits: 1,
48            preamble: 350,
49            txtail: 20,
50            persistence: 64,
51            slottime: 20,
52            flow_control: false,
53            beacon_interval: None,
54            beacon_data: None,
55            interface_id: InterfaceId(0),
56        }
57    }
58}
59
60/// Shared flow-control state between writer and reader threads.
61struct FlowState {
62    ready: bool,
63    queue: VecDeque<Vec<u8>>,
64    lock_time: Instant,
65}
66
67/// Writer that sends KISS-framed data over a serial port.
68/// Handles flow control: when enabled, queues packets until CMD_READY.
69struct KissWriter {
70    file: std::fs::File,
71    flow_control: bool,
72    flow_state: Arc<Mutex<FlowState>>,
73}
74
75impl Writer for KissWriter {
76    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
77        if self.flow_control {
78            let mut state = self.flow_state.lock().unwrap();
79            if state.ready {
80                state.ready = false;
81                state.lock_time = Instant::now();
82                drop(state);
83                self.file.write_all(&kiss::frame(data))
84            } else {
85                state.queue.push_back(data.to_vec());
86                Ok(())
87            }
88        } else {
89            self.file.write_all(&kiss::frame(data))
90        }
91    }
92}
93
94/// Start the KISS interface. Opens the port, configures TNC, spawns reader thread.
95pub fn start(config: KissIfaceConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
96    let serial_config = SerialConfig {
97        path: config.port.clone(),
98        baud: config.speed,
99        data_bits: config.data_bits,
100        parity: config.parity,
101        stop_bits: config.stop_bits,
102    };
103
104    let port = SerialPort::open(&serial_config)?;
105    let reader_file = port.reader()?;
106    let mut writer_file = port.writer()?;
107    let flow_writer_file = port.writer()?;
108
109    let id = config.interface_id;
110
111    // Initial 2-second delay for TNC initialization (matches Python)
112    thread::sleep(Duration::from_secs(2));
113
114    // Signal interface up
115    let _ = tx.send(Event::InterfaceUp(id, None, None));
116
117    // Send TNC configuration commands
118    configure_tnc(&mut writer_file, &config)?;
119
120    let flow_state = Arc::new(Mutex::new(FlowState {
121        ready: true,
122        queue: VecDeque::new(),
123        lock_time: Instant::now(),
124    }));
125
126    let reader_flow_state = flow_state.clone();
127
128    // Spawn reader thread
129    let reader_config = config.clone();
130    thread::Builder::new()
131        .name(format!("kiss-reader-{}", id.0))
132        .spawn(move || {
133            reader_loop(reader_file, flow_writer_file, id, reader_config, tx, reader_flow_state);
134        })?;
135
136    Ok(Box::new(KissWriter {
137        file: writer_file,
138        flow_control: config.flow_control,
139        flow_state,
140    }))
141}
142
143/// Send TNC configuration commands via KISS.
144/// Matches Python `KISSInterface.configure_device()`.
145fn configure_tnc(writer: &mut std::fs::File, config: &KissIfaceConfig) -> io::Result<()> {
146    log::info!("[{}] configuring KISS interface parameters", config.name);
147
148    // Preamble: value is ms/10, clamped to 0-255
149    let preamble_val = (config.preamble / 10).min(255) as u8;
150    writer.write_all(&kiss::command_frame(kiss::CMD_TXDELAY, &[preamble_val]))?;
151
152    // TX tail: value is ms/10, clamped to 0-255
153    let txtail_val = (config.txtail / 10).min(255) as u8;
154    writer.write_all(&kiss::command_frame(kiss::CMD_TXTAIL, &[txtail_val]))?;
155
156    // Persistence: raw value, clamped to 0-255
157    writer.write_all(&kiss::command_frame(kiss::CMD_P, &[config.persistence]))?;
158
159    // Slot time: value is ms/10, clamped to 0-255
160    let slottime_val = (config.slottime / 10).min(255) as u8;
161    writer.write_all(&kiss::command_frame(kiss::CMD_SLOTTIME, &[slottime_val]))?;
162
163    // Flow control: send CMD_READY with 0x01 (matches Python setFlowControl)
164    writer.write_all(&kiss::command_frame(kiss::CMD_READY, &[0x01]))?;
165
166    log::info!("[{}] KISS interface configured", config.name);
167    Ok(())
168}
169
170/// Reader loop: reads from serial, KISS-decodes, dispatches events.
171/// Also handles flow control unlocking and beacon transmission.
172fn reader_loop(
173    mut reader: std::fs::File,
174    mut flow_writer: std::fs::File,
175    id: InterfaceId,
176    config: KissIfaceConfig,
177    tx: EventSender,
178    flow_state: Arc<Mutex<FlowState>>,
179) {
180    let mut decoder = kiss::Decoder::new();
181    let mut buf = [0u8; 4096];
182    let mut first_tx: Option<Instant> = None;
183
184    loop {
185        match reader.read(&mut buf) {
186            Ok(0) => {
187                log::warn!("[{}] KISS port closed", config.name);
188                let _ = tx.send(Event::InterfaceDown(id));
189                match reconnect(&config, &tx, &flow_state) {
190                    Some((new_reader, new_flow_writer)) => {
191                        reader = new_reader;
192                        flow_writer = new_flow_writer;
193                        decoder = kiss::Decoder::new();
194                        continue;
195                    }
196                    None => return,
197                }
198            }
199            Ok(n) => {
200                for event in decoder.feed(&buf[..n]) {
201                    match event {
202                        kiss::KissEvent::DataFrame(data) => {
203                            if tx.send(Event::Frame { interface_id: id, data }).is_err() {
204                                return;
205                            }
206                        }
207                        kiss::KissEvent::Ready => {
208                            process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
209                        }
210                    }
211                }
212            }
213            Err(e) => {
214                log::warn!("[{}] KISS read error: {}", config.name, e);
215                let _ = tx.send(Event::InterfaceDown(id));
216                match reconnect(&config, &tx, &flow_state) {
217                    Some((new_reader, new_flow_writer)) => {
218                        reader = new_reader;
219                        flow_writer = new_flow_writer;
220                        decoder = kiss::Decoder::new();
221                        continue;
222                    }
223                    None => return,
224                }
225            }
226        }
227
228        // Flow control timeout check
229        if config.flow_control {
230            let state = flow_state.lock().unwrap();
231            if !state.ready && state.lock_time.elapsed() > Duration::from_secs(5) {
232                drop(state);
233                log::warn!("[{}] unlocking flow control due to timeout", config.name);
234                process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
235            }
236        }
237
238        // Beacon check
239        if let (Some(interval), Some(ref beacon_data)) = (config.beacon_interval, &config.beacon_data) {
240            if let Some(first) = first_tx {
241                if first.elapsed() > Duration::from_secs(interval as u64) {
242                    log::debug!("[{}] transmitting beacon data", config.name);
243                    // Pad to minimum 15 bytes
244                    let mut frame = beacon_data.clone();
245                    while frame.len() < 15 {
246                        frame.push(0x00);
247                    }
248                    let _ = flow_writer.write_all(&kiss::frame(&frame));
249                    first_tx = None;
250                }
251            }
252        }
253    }
254}
255
256/// Process the flow control queue: send next queued packet, mark ready.
257fn process_queue(
258    flow_state: &Arc<Mutex<FlowState>>,
259    writer: &mut std::fs::File,
260    first_tx: &mut Option<Instant>,
261    _config: &KissIfaceConfig,
262) {
263    let mut state = flow_state.lock().unwrap();
264    if let Some(data) = state.queue.pop_front() {
265        state.ready = false;
266        state.lock_time = Instant::now();
267        drop(state);
268        let _ = writer.write_all(&kiss::frame(&data));
269        if first_tx.is_none() {
270            *first_tx = Some(Instant::now());
271        }
272    } else {
273        state.ready = true;
274    }
275}
276
277/// Attempt to reconnect the serial port.
278fn reconnect(
279    config: &KissIfaceConfig,
280    tx: &EventSender,
281    flow_state: &Arc<Mutex<FlowState>>,
282) -> Option<(std::fs::File, std::fs::File)> {
283    loop {
284        thread::sleep(Duration::from_secs(5));
285        log::info!("[{}] attempting to reconnect KISS port {}...", config.name, config.port);
286
287        let serial_config = SerialConfig {
288            path: config.port.clone(),
289            baud: config.speed,
290            data_bits: config.data_bits,
291            parity: config.parity,
292            stop_bits: config.stop_bits,
293        };
294
295        match SerialPort::open(&serial_config) {
296            Ok(port) => {
297                match (port.reader(), port.writer(), port.writer()) {
298                    (Ok(reader), Ok(mut cfg_writer), Ok(flow_writer)) => {
299                        // 2-second init delay
300                        thread::sleep(Duration::from_secs(2));
301                        if let Err(e) = configure_tnc(&mut cfg_writer, config) {
302                            log::warn!("[{}] TNC config failed: {}", config.name, e);
303                            continue;
304                        }
305                        // Reset flow state
306                        let mut state = flow_state.lock().unwrap();
307                        state.ready = true;
308                        state.queue.clear();
309                        drop(state);
310
311                        let new_writer: Box<dyn Writer> = Box::new(KissWriter {
312                            file: cfg_writer,
313                            flow_control: config.flow_control,
314                            flow_state: flow_state.clone(),
315                        });
316                        let _ = tx.send(Event::InterfaceUp(config.interface_id, Some(new_writer), None));
317                        log::info!("[{}] KISS port reconnected", config.name);
318                        return Some((reader, flow_writer));
319                    }
320                    _ => {
321                        log::warn!("[{}] failed to get handles from serial port", config.name);
322                    }
323                }
324            }
325            Err(e) => {
326                log::warn!("[{}] KISS reconnect failed: {}", config.name, e);
327            }
328        }
329    }
330}
331
332// --- Factory implementation ---
333
334use std::collections::HashMap;
335use rns_core::transport::types::InterfaceInfo;
336use super::{InterfaceFactory, InterfaceConfigData, StartContext, StartResult};
337
338/// Factory for `KISSInterface`.
339pub struct KissFactory;
340
341impl InterfaceFactory for KissFactory {
342    fn type_name(&self) -> &str { "KISSInterface" }
343
344    fn default_ifac_size(&self) -> usize { 8 }
345
346    fn parse_config(
347        &self,
348        name: &str,
349        id: InterfaceId,
350        params: &HashMap<String, String>,
351    ) -> Result<Box<dyn InterfaceConfigData>, String> {
352        let port = params.get("port")
353            .cloned()
354            .ok_or_else(|| "KISSInterface requires 'port'".to_string())?;
355
356        let speed = params.get("speed")
357            .and_then(|v| v.parse().ok())
358            .unwrap_or(9600u32);
359
360        let data_bits = params.get("databits")
361            .and_then(|v| v.parse().ok())
362            .unwrap_or(8u8);
363
364        let parity = params.get("parity")
365            .map(|v| match v.to_lowercase().as_str() {
366                "e" | "even" => crate::serial::Parity::Even,
367                "o" | "odd" => crate::serial::Parity::Odd,
368                _ => crate::serial::Parity::None,
369            })
370            .unwrap_or(crate::serial::Parity::None);
371
372        let stop_bits = params.get("stopbits")
373            .and_then(|v| v.parse().ok())
374            .unwrap_or(1u8);
375
376        let preamble = params.get("preamble")
377            .and_then(|v| v.parse().ok())
378            .unwrap_or(350u16);
379
380        let txtail = params.get("txtail")
381            .and_then(|v| v.parse().ok())
382            .unwrap_or(20u16);
383
384        let persistence = params.get("persistence")
385            .and_then(|v| v.parse().ok())
386            .unwrap_or(64u8);
387
388        let slottime = params.get("slottime")
389            .and_then(|v| v.parse().ok())
390            .unwrap_or(20u16);
391
392        let flow_control = params.get("flow_control")
393            .and_then(|v| crate::config::parse_bool_pub(v))
394            .unwrap_or(false);
395
396        let beacon_interval = params.get("id_interval")
397            .or_else(|| params.get("beacon_interval"))
398            .and_then(|v| v.parse().ok());
399
400        let beacon_data = params.get("id_callsign")
401            .or_else(|| params.get("beacon_data"))
402            .map(|v| v.as_bytes().to_vec());
403
404        Ok(Box::new(KissIfaceConfig {
405            name: name.to_string(),
406            port,
407            speed,
408            data_bits,
409            parity,
410            stop_bits,
411            preamble,
412            txtail,
413            persistence,
414            slottime,
415            flow_control,
416            beacon_interval,
417            beacon_data,
418            interface_id: id,
419        }))
420    }
421
422    fn start(
423        &self,
424        config: Box<dyn InterfaceConfigData>,
425        ctx: StartContext,
426    ) -> std::io::Result<StartResult> {
427        let kiss_config = *config.into_any().downcast::<KissIfaceConfig>()
428            .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type"))?;
429
430        let id = kiss_config.interface_id;
431        let name = kiss_config.name.clone();
432
433        let info = InterfaceInfo {
434            id,
435            name,
436            mode: ctx.mode,
437            out_capable: true,
438            in_capable: true,
439            bitrate: Some(1200),
440            announce_rate_target: None,
441            announce_rate_grace: 0,
442            announce_rate_penalty: 0.0,
443            announce_cap: rns_core::constants::ANNOUNCE_CAP,
444            is_local_client: false,
445            wants_tunnel: false,
446            tunnel_id: None,
447            mtu: rns_core::constants::MTU as u32,
448            ingress_control: false,
449            ia_freq: 0.0,
450            started: crate::time::now(),
451        };
452
453        let writer = start(kiss_config, ctx.tx)?;
454
455        Ok(StartResult::Simple {
456            id,
457            info,
458            writer,
459            interface_type_name: "KISSInterface".to_string(),
460        })
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467    use crate::serial::open_pty_pair;
468    use std::os::unix::io::{AsRawFd, FromRawFd};
469    use std::sync::mpsc;
470
471    /// Helper: poll an fd for reading with timeout (ms).
472    fn poll_read(fd: i32, timeout_ms: i32) -> bool {
473        let mut pfd = libc::pollfd {
474            fd,
475            events: libc::POLLIN,
476            revents: 0,
477        };
478        let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
479        ret > 0
480    }
481
482    #[test]
483    fn kiss_data_roundtrip() {
484        let (master_fd, slave_fd) = open_pty_pair().unwrap();
485        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
486        let mut slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
487
488        // Write a KISS data frame to the master side
489        let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
490        let framed = kiss::frame(&payload);
491        master_file.write_all(&framed).unwrap();
492        master_file.flush().unwrap();
493
494        // Read from slave using KISS decoder
495        assert!(poll_read(slave_file.as_raw_fd(), 2000));
496
497        let mut decoder = kiss::Decoder::new();
498        let mut buf = [0u8; 4096];
499        let n = slave_file.read(&mut buf).unwrap();
500        let events = decoder.feed(&buf[..n]);
501        assert_eq!(events.len(), 1);
502        assert_eq!(events[0], kiss::KissEvent::DataFrame(payload));
503    }
504
505    #[test]
506    fn kiss_writer_frames() {
507        let (master_fd, slave_fd) = open_pty_pair().unwrap();
508
509        let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
510        let flow_state = Arc::new(Mutex::new(FlowState {
511            ready: true,
512            queue: VecDeque::new(),
513            lock_time: Instant::now(),
514        }));
515
516        let mut writer = KissWriter {
517            file: writer_file,
518            flow_control: false,
519            flow_state,
520        };
521
522        let payload = vec![0xC0, 0xDB, 0x01]; // includes bytes that need KISS escaping
523        writer.send_frame(&payload).unwrap();
524
525        // Read from master
526        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
527        assert!(poll_read(master_file.as_raw_fd(), 2000));
528
529        let expected = kiss::frame(&payload);
530        let mut buf = [0u8; 256];
531        let n = master_file.read(&mut buf).unwrap();
532        assert_eq!(&buf[..n], &expected[..]);
533    }
534
535    #[test]
536    fn kiss_config_commands() {
537        let (master_fd, slave_fd) = open_pty_pair().unwrap();
538
539        let mut writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
540        let config = KissIfaceConfig {
541            preamble: 350,
542            txtail: 20,
543            persistence: 64,
544            slottime: 20,
545            ..Default::default()
546        };
547
548        configure_tnc(&mut writer_file, &config).unwrap();
549
550        // Read all commands from master
551        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
552        assert!(poll_read(master_file.as_raw_fd(), 2000));
553
554        let mut buf = [0u8; 1024];
555        let n = master_file.read(&mut buf).unwrap();
556        let data = &buf[..n];
557
558        // Should contain TXDELAY command: FEND, CMD_TXDELAY, value, FEND
559        // preamble: 350/10 = 35
560        assert!(data.windows(4).any(|w|
561            w[0] == kiss::FEND && w[1] == kiss::CMD_TXDELAY && w[2] == 35 && w[3] == kiss::FEND
562        ), "should contain TXDELAY command");
563
564        // TXTAIL: 20/10 = 2
565        assert!(data.windows(4).any(|w|
566            w[0] == kiss::FEND && w[1] == kiss::CMD_TXTAIL && w[2] == 2 && w[3] == kiss::FEND
567        ), "should contain TXTAIL command");
568
569        // Persistence: 64
570        assert!(data.windows(4).any(|w|
571            w[0] == kiss::FEND && w[1] == kiss::CMD_P && w[2] == 64 && w[3] == kiss::FEND
572        ), "should contain P command");
573
574        // Slottime: 20/10 = 2
575        assert!(data.windows(4).any(|w|
576            w[0] == kiss::FEND && w[1] == kiss::CMD_SLOTTIME && w[2] == 2 && w[3] == kiss::FEND
577        ), "should contain SLOTTIME command");
578    }
579
580    #[test]
581    fn kiss_flow_control_lock() {
582        let flow_state = Arc::new(Mutex::new(FlowState {
583            ready: true,
584            queue: VecDeque::new(),
585            lock_time: Instant::now(),
586        }));
587
588        let (master_fd, slave_fd) = open_pty_pair().unwrap();
589        let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
590
591        let mut writer = KissWriter {
592            file: writer_file,
593            flow_control: true,
594            flow_state: flow_state.clone(),
595        };
596
597        // First send should go through (ready=true) and lock
598        writer.send_frame(b"hello").unwrap();
599        assert!(!flow_state.lock().unwrap().ready);
600
601        // Second send should be queued (ready=false)
602        writer.send_frame(b"world").unwrap();
603        assert_eq!(flow_state.lock().unwrap().queue.len(), 1);
604
605        // Simulate CMD_READY: process_queue
606        let mut flow_writer = unsafe { std::fs::File::from_raw_fd(libc::dup(master_fd)) };
607        let mut first_tx = None;
608        let config = KissIfaceConfig::default();
609        process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
610
611        // Queue should be empty now (dequeued "world"), but ready=false because it sent
612        assert_eq!(flow_state.lock().unwrap().queue.len(), 0);
613        assert!(!flow_state.lock().unwrap().ready);
614
615        // Process again with empty queue: should set ready=true
616        process_queue(&flow_state, &mut flow_writer, &mut first_tx, &config);
617        assert!(flow_state.lock().unwrap().ready);
618
619        // Clean up
620        unsafe { libc::close(master_fd) };
621    }
622
623    #[test]
624    fn kiss_flow_control_timeout() {
625        let flow_state = Arc::new(Mutex::new(FlowState {
626            ready: false,
627            queue: VecDeque::new(),
628            lock_time: Instant::now() - Duration::from_secs(6), // already timed out
629        }));
630
631        // Check that the timeout condition triggers
632        let state = flow_state.lock().unwrap();
633        assert!(!state.ready);
634        assert!(state.lock_time.elapsed() > Duration::from_secs(5));
635    }
636
637    #[test]
638    fn kiss_fragmented() {
639        let (master_fd, slave_fd) = open_pty_pair().unwrap();
640        let mut master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
641        let slave_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
642
643        let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
644        let framed = kiss::frame(&payload);
645        let mid = framed.len() / 2;
646
647        // Spawn reader thread first (it will block waiting for data)
648        let (tx, rx) = mpsc::channel::<kiss::KissEvent>();
649        let reader_thread = thread::spawn(move || {
650            let mut reader = slave_file;
651            let mut decoder = kiss::Decoder::new();
652            let mut buf = [0u8; 4096];
653
654            loop {
655                match reader.read(&mut buf) {
656                    Ok(n) if n > 0 => {
657                        for event in decoder.feed(&buf[..n]) {
658                            let _ = tx.send(event.clone());
659                            if matches!(event, kiss::KissEvent::DataFrame(_)) {
660                                return;
661                            }
662                        }
663                    }
664                    _ => return,
665                }
666            }
667        });
668
669        // Write first half
670        master_file.write_all(&framed[..mid]).unwrap();
671        master_file.flush().unwrap();
672
673        thread::sleep(Duration::from_millis(50));
674
675        // Write second half
676        master_file.write_all(&framed[mid..]).unwrap();
677        master_file.flush().unwrap();
678
679        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
680        assert_eq!(event, kiss::KissEvent::DataFrame(payload));
681
682        let _ = reader_thread.join();
683    }
684}