Skip to main content

rns_net/
kiss.rs

1//! KISS framing for serial/radio interfaces.
2//!
3//! Matches Python `KISSInterface.py` KISS encoding/decoding.
4
5pub const FEND: u8 = 0xC0;
6pub const FESC: u8 = 0xDB;
7pub const TFEND: u8 = 0xDC;
8pub const TFESC: u8 = 0xDD;
9
10pub const CMD_DATA: u8 = 0x00;
11pub const CMD_TXDELAY: u8 = 0x01;
12pub const CMD_P: u8 = 0x02;
13pub const CMD_SLOTTIME: u8 = 0x03;
14pub const CMD_TXTAIL: u8 = 0x04;
15pub const CMD_FULLDUPLEX: u8 = 0x05;
16pub const CMD_SETHARDWARE: u8 = 0x06;
17pub const CMD_READY: u8 = 0x0F;
18pub const CMD_RETURN: u8 = 0xFF;
19pub const CMD_UNKNOWN: u8 = 0xFE;
20
21/// Escape data for KISS framing.
22/// Order matters: escape 0xDB first, then 0xC0 (same as Python).
23pub fn escape(data: &[u8]) -> Vec<u8> {
24    let mut out = Vec::with_capacity(data.len());
25    for &b in data {
26        match b {
27            FESC => {
28                out.push(FESC);
29                out.push(TFESC);
30            }
31            FEND => {
32                out.push(FESC);
33                out.push(TFEND);
34            }
35            _ => out.push(b),
36        }
37    }
38    out
39}
40
41/// Unescape KISS data.
42pub fn unescape(data: &[u8]) -> Vec<u8> {
43    let mut out = Vec::with_capacity(data.len());
44    let mut esc = false;
45    for &b in data {
46        if esc {
47            match b {
48                TFEND => out.push(FEND),
49                TFESC => out.push(FESC),
50                _ => out.push(b), // spec violation, pass through
51            }
52            esc = false;
53        } else if b == FESC {
54            esc = true;
55        } else {
56            out.push(b);
57        }
58    }
59    out
60}
61
62/// Wrap data as a KISS DATA frame: [FEND][CMD_DATA][escaped_data][FEND].
63pub fn frame(data: &[u8]) -> Vec<u8> {
64    let escaped = escape(data);
65    let mut out = Vec::with_capacity(escaped.len() + 3);
66    out.push(FEND);
67    out.push(CMD_DATA);
68    out.extend_from_slice(&escaped);
69    out.push(FEND);
70    out
71}
72
73/// Build a KISS command frame: [FEND][cmd][escaped_value][FEND].
74pub fn command_frame(cmd: u8, value: &[u8]) -> Vec<u8> {
75    let escaped = escape(value);
76    let mut out = Vec::with_capacity(escaped.len() + 3);
77    out.push(FEND);
78    out.push(cmd);
79    out.extend_from_slice(&escaped);
80    out.push(FEND);
81    out
82}
83
84/// Events yielded by the KISS Decoder.
85#[derive(Debug, Clone, PartialEq)]
86pub enum KissEvent {
87    /// A CMD_DATA frame was received with the decoded payload.
88    DataFrame(Vec<u8>),
89    /// A CMD_READY frame was received (flow control).
90    Ready,
91}
92
93/// Streaming KISS decoder. Feed bytes, yields decoded frames.
94///
95/// Matches the readLoop in `KISSInterface.py:290-356`.
96pub struct Decoder {
97    in_frame: bool,
98    escape: bool,
99    command: u8,
100    buffer: Vec<u8>,
101}
102
103impl Decoder {
104    pub fn new() -> Self {
105        Decoder {
106            in_frame: false,
107            escape: false,
108            command: CMD_UNKNOWN,
109            buffer: Vec::new(),
110        }
111    }
112
113    /// Feed raw bytes and return any decoded events.
114    pub fn feed(&mut self, bytes: &[u8]) -> Vec<KissEvent> {
115        let mut events = Vec::new();
116
117        for &byte in bytes {
118            if self.in_frame && byte == FEND && self.command == CMD_DATA {
119                // End of data frame
120                self.in_frame = false;
121                if !self.buffer.is_empty() {
122                    events.push(KissEvent::DataFrame(core::mem::take(&mut self.buffer)));
123                }
124            } else if byte == FEND {
125                // Start of new frame
126                self.in_frame = true;
127                self.command = CMD_UNKNOWN;
128                self.buffer.clear();
129                self.escape = false;
130            } else if self.in_frame {
131                if self.buffer.is_empty() && self.command == CMD_UNKNOWN {
132                    // First byte after FEND is the command, strip port nibble
133                    self.command = byte & 0x0F;
134                } else if self.command == CMD_DATA {
135                    if byte == FESC {
136                        self.escape = true;
137                    } else if self.escape {
138                        match byte {
139                            TFEND => self.buffer.push(FEND),
140                            TFESC => self.buffer.push(FESC),
141                            _ => self.buffer.push(byte),
142                        }
143                        self.escape = false;
144                    } else {
145                        self.buffer.push(byte);
146                    }
147                } else if self.command == CMD_READY {
148                    events.push(KissEvent::Ready);
149                    // Reset state so we don't fire Ready again for trailing bytes
150                    self.command = CMD_UNKNOWN;
151                    self.in_frame = false;
152                }
153            }
154        }
155
156        events
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn escape_fend() {
166        assert_eq!(escape(&[FEND]), vec![FESC, TFEND]);
167        assert_eq!(escape(&[0xC0]), vec![0xDB, 0xDC]);
168    }
169
170    #[test]
171    fn escape_fesc() {
172        assert_eq!(escape(&[FESC]), vec![FESC, TFESC]);
173        assert_eq!(escape(&[0xDB]), vec![0xDB, 0xDD]);
174    }
175
176    #[test]
177    fn escape_passthrough() {
178        let data = b"hello world";
179        assert_eq!(escape(data), data.to_vec());
180    }
181
182    #[test]
183    fn unescape_roundtrip() {
184        // All 256 byte values
185        let data: Vec<u8> = (0..=255).collect();
186        let escaped = escape(&data);
187        let recovered = unescape(&escaped);
188        assert_eq!(recovered, data);
189    }
190
191    #[test]
192    fn frame_data() {
193        let data = b"test";
194        let framed = frame(data);
195        assert_eq!(framed[0], FEND);
196        assert_eq!(framed[1], CMD_DATA);
197        assert_eq!(*framed.last().unwrap(), FEND);
198        // Middle should be escaped data
199        let middle = &framed[2..framed.len() - 1];
200        assert_eq!(middle, &escape(data)[..]);
201    }
202
203    #[test]
204    fn decoder_single_frame() {
205        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
206        let framed = frame(&data);
207
208        let mut decoder = Decoder::new();
209        let events = decoder.feed(&framed);
210        assert_eq!(events.len(), 1);
211        assert_eq!(events[0], KissEvent::DataFrame(data));
212    }
213
214    #[test]
215    fn decoder_ready_event() {
216        // Build a CMD_READY frame
217        let ready_frame = vec![FEND, CMD_READY, 0x01, FEND];
218
219        let mut decoder = Decoder::new();
220        let events = decoder.feed(&ready_frame);
221        assert_eq!(events.len(), 1);
222        assert_eq!(events[0], KissEvent::Ready);
223    }
224
225    #[test]
226    fn decoder_fragmented() {
227        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
228        let framed = frame(&data);
229
230        let mut decoder = Decoder::new();
231
232        // Feed byte by byte
233        let mut all_events = Vec::new();
234        for &byte in &framed {
235            all_events.extend(decoder.feed(&[byte]));
236        }
237
238        assert_eq!(all_events.len(), 1);
239        assert_eq!(all_events[0], KissEvent::DataFrame(data));
240    }
241}