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(
123                        core::mem::take(&mut self.buffer),
124                    ));
125                }
126            } else if byte == FEND {
127                // Start of new frame
128                self.in_frame = true;
129                self.command = CMD_UNKNOWN;
130                self.buffer.clear();
131                self.escape = false;
132            } else if self.in_frame {
133                if self.buffer.is_empty() && self.command == CMD_UNKNOWN {
134                    // First byte after FEND is the command, strip port nibble
135                    self.command = byte & 0x0F;
136                } else if self.command == CMD_DATA {
137                    if byte == FESC {
138                        self.escape = true;
139                    } else if self.escape {
140                        match byte {
141                            TFEND => self.buffer.push(FEND),
142                            TFESC => self.buffer.push(FESC),
143                            _ => self.buffer.push(byte),
144                        }
145                        self.escape = false;
146                    } else {
147                        self.buffer.push(byte);
148                    }
149                } else if self.command == CMD_READY {
150                    events.push(KissEvent::Ready);
151                    // Reset state so we don't fire Ready again for trailing bytes
152                    self.command = CMD_UNKNOWN;
153                    self.in_frame = false;
154                }
155            }
156        }
157
158        events
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn escape_fend() {
168        assert_eq!(escape(&[FEND]), vec![FESC, TFEND]);
169        assert_eq!(escape(&[0xC0]), vec![0xDB, 0xDC]);
170    }
171
172    #[test]
173    fn escape_fesc() {
174        assert_eq!(escape(&[FESC]), vec![FESC, TFESC]);
175        assert_eq!(escape(&[0xDB]), vec![0xDB, 0xDD]);
176    }
177
178    #[test]
179    fn escape_passthrough() {
180        let data = b"hello world";
181        assert_eq!(escape(data), data.to_vec());
182    }
183
184    #[test]
185    fn unescape_roundtrip() {
186        // All 256 byte values
187        let data: Vec<u8> = (0..=255).collect();
188        let escaped = escape(&data);
189        let recovered = unescape(&escaped);
190        assert_eq!(recovered, data);
191    }
192
193    #[test]
194    fn frame_data() {
195        let data = b"test";
196        let framed = frame(data);
197        assert_eq!(framed[0], FEND);
198        assert_eq!(framed[1], CMD_DATA);
199        assert_eq!(*framed.last().unwrap(), FEND);
200        // Middle should be escaped data
201        let middle = &framed[2..framed.len() - 1];
202        assert_eq!(middle, &escape(data)[..]);
203    }
204
205    #[test]
206    fn decoder_single_frame() {
207        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
208        let framed = frame(&data);
209
210        let mut decoder = Decoder::new();
211        let events = decoder.feed(&framed);
212        assert_eq!(events.len(), 1);
213        assert_eq!(events[0], KissEvent::DataFrame(data));
214    }
215
216    #[test]
217    fn decoder_ready_event() {
218        // Build a CMD_READY frame
219        let ready_frame = vec![FEND, CMD_READY, 0x01, FEND];
220
221        let mut decoder = Decoder::new();
222        let events = decoder.feed(&ready_frame);
223        assert_eq!(events.len(), 1);
224        assert_eq!(events[0], KissEvent::Ready);
225    }
226
227    #[test]
228    fn decoder_fragmented() {
229        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
230        let framed = frame(&data);
231
232        let mut decoder = Decoder::new();
233
234        // Feed byte by byte
235        let mut all_events = Vec::new();
236        for &byte in &framed {
237            all_events.extend(decoder.feed(&[byte]));
238        }
239
240        assert_eq!(all_events.len(), 1);
241        assert_eq!(all_events[0], KissEvent::DataFrame(data));
242    }
243}