umsh_ulcp/
hdlc.rs

1//! HDLC-Lite framing for asynchronous serial links (UART, USB-CDC).
2//!
3//! Same discipline as Spinel's HDLC-Lite: frames are delimited by
4//! `0x7E` flag bytes, control bytes inside a frame are escaped with
5//! `0x7D` (XOR `0x20`), and each frame carries a trailing 16-bit FCS
6//! (CRC-16/X-25 per RFC 1662), least-significant byte first.
7
8/// Frame delimiter.
9pub const FLAG: u8 = 0x7E;
10/// Escape byte; the following byte is XORed with [`ESCAPE_XOR`].
11pub const ESCAPE: u8 = 0x7D;
12/// XOR applied to escaped bytes.
13pub const ESCAPE_XOR: u8 = 0x20;
14
15const XON: u8 = 0x11;
16const XOFF: u8 = 0x13;
17
18const fn needs_escape(byte: u8) -> bool {
19    matches!(byte, FLAG | ESCAPE | XON | XOFF)
20}
21
22/// RFC 1662 FCS-16 (CRC-16/X-25) over `data`.
23pub fn crc16(data: &[u8]) -> u16 {
24    let mut fcs = 0xFFFFu16;
25    for &byte in data {
26        fcs ^= u16::from(byte);
27        for _ in 0..8 {
28            if fcs & 1 != 0 {
29                fcs = (fcs >> 1) ^ 0x8408;
30            } else {
31                fcs >>= 1;
32            }
33        }
34    }
35    !fcs
36}
37
38/// Worst-case encoded size for a payload of `payload_len` bytes.
39///
40/// Two delimiting flags plus the payload and FCS with every byte
41/// escaped.
42pub const fn max_encoded_len(payload_len: usize) -> usize {
43    2 + (payload_len + 2) * 2
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum EncodeError {
48    BufferTooSmall,
49}
50
51struct Sink<'a> {
52    out: &'a mut [u8],
53    len: usize,
54}
55
56impl Sink<'_> {
57    fn push(&mut self, byte: u8) -> Result<(), EncodeError> {
58        if self.len >= self.out.len() {
59            return Err(EncodeError::BufferTooSmall);
60        }
61        self.out[self.len] = byte;
62        self.len += 1;
63        Ok(())
64    }
65
66    fn push_escaped(&mut self, byte: u8) -> Result<(), EncodeError> {
67        if needs_escape(byte) {
68            self.push(ESCAPE)?;
69            self.push(byte ^ ESCAPE_XOR)
70        } else {
71            self.push(byte)
72        }
73    }
74}
75
76/// Encode one frame, including both delimiting flags, into `out`.
77///
78/// Returns the number of bytes written. Size `out` with
79/// [`max_encoded_len`] to make overflow impossible.
80pub fn encode_frame(payload: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
81    let mut sink = Sink { out, len: 0 };
82    sink.push(FLAG)?;
83    for &byte in payload {
84        sink.push_escaped(byte)?;
85    }
86    for byte in crc16(payload).to_le_bytes() {
87        sink.push_escaped(byte)?;
88    }
89    sink.push(FLAG)?;
90    Ok(sink.len)
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum DecodeError {
95    /// The FCS did not match; the frame is corrupt.
96    Crc,
97    /// A complete frame was shorter than the two-byte FCS.
98    TooShort,
99    /// The frame exceeded the decoder's buffer capacity.
100    TooLong,
101    /// A flag byte arrived immediately after an escape byte.
102    AbortedEscape,
103}
104
105/// Streaming decoder with an internal reassembly buffer of `N` bytes.
106///
107/// `N` bounds the *unescaped* frame size including the two FCS bytes.
108/// Feed received bytes one at a time; a completed frame is returned
109/// with the FCS already verified and stripped. Errors report a
110/// discarded frame; the decoder resynchronizes on the next flag
111/// automatically.
112pub struct Decoder<const N: usize> {
113    buf: [u8; N],
114    len: usize,
115    escaped: bool,
116    overflow: bool,
117}
118
119impl<const N: usize> Default for Decoder<N> {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125impl<const N: usize> Decoder<N> {
126    pub const fn new() -> Self {
127        Self {
128            buf: [0; N],
129            len: 0,
130            escaped: false,
131            overflow: false,
132        }
133    }
134
135    /// Discard any partially received frame.
136    pub fn reset(&mut self) {
137        self.len = 0;
138        self.escaped = false;
139        self.overflow = false;
140    }
141
142    /// Process one received byte.
143    ///
144    /// Returns `Some(Ok(frame))` when `byte` completed a valid frame,
145    /// `Some(Err(_))` when it completed or aborted an invalid one, and
146    /// `None` otherwise.
147    pub fn push(&mut self, byte: u8) -> Option<Result<&[u8], DecodeError>> {
148        if byte == FLAG {
149            let escaped = core::mem::replace(&mut self.escaped, false);
150            let overflow = core::mem::replace(&mut self.overflow, false);
151            let len = core::mem::replace(&mut self.len, 0);
152            if escaped {
153                return Some(Err(DecodeError::AbortedEscape));
154            }
155            if len == 0 {
156                // Back-to-back or idle flags between frames.
157                return None;
158            }
159            if overflow {
160                return Some(Err(DecodeError::TooLong));
161            }
162            if len < 2 {
163                return Some(Err(DecodeError::TooShort));
164            }
165            let payload_len = len - 2;
166            let received_fcs = [self.buf[payload_len], self.buf[payload_len + 1]];
167            if crc16(&self.buf[..payload_len]).to_le_bytes() != received_fcs {
168                return Some(Err(DecodeError::Crc));
169            }
170            return Some(Ok(&self.buf[..payload_len]));
171        }
172
173        let byte = if byte == ESCAPE {
174            self.escaped = true;
175            return None;
176        } else if core::mem::replace(&mut self.escaped, false) {
177            byte ^ ESCAPE_XOR
178        } else {
179            byte
180        };
181
182        if self.len < N {
183            self.buf[self.len] = byte;
184            self.len += 1;
185        } else {
186            self.overflow = true;
187        }
188        None
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    /// Decode `bytes` expecting exactly one complete frame.
197    fn decode_all<const N: usize>(
198        decoder: &mut Decoder<N>,
199        bytes: &[u8],
200    ) -> Option<Result<Vec<u8>, DecodeError>> {
201        let mut result = None;
202        for &byte in bytes {
203            if let Some(outcome) = decoder.push(byte) {
204                assert!(result.is_none(), "more than one frame in input");
205                result = Some(outcome.map(<[u8]>::to_vec));
206            }
207        }
208        result
209    }
210
211    #[test]
212    fn crc16_check_value() {
213        // CRC catalog check value for CRC-16/X-25.
214        assert_eq!(crc16(b"123456789"), 0x906E);
215    }
216
217    #[test]
218    fn round_trip_plain() {
219        let payload = [0x81u8, 0x02, 0x00];
220        let mut wire = [0u8; max_encoded_len(3)];
221        let len = encode_frame(&payload, &mut wire).unwrap();
222        assert_eq!(wire[0], FLAG);
223        assert_eq!(wire[len - 1], FLAG);
224
225        let mut decoder = Decoder::<16>::new();
226        let frame = decode_all(&mut decoder, &wire[..len]).unwrap().unwrap();
227        assert_eq!(frame, payload);
228    }
229
230    #[test]
231    fn round_trip_escaped_bytes() {
232        let payload = [FLAG, ESCAPE, XON, XOFF, 0x00, 0xFF];
233        let mut wire = [0u8; max_encoded_len(6)];
234        let len = encode_frame(&payload, &mut wire).unwrap();
235        // Nothing between the flags may be a bare control byte.
236        for &byte in &wire[1..len - 1] {
237            assert_ne!(byte, FLAG);
238            assert!(!needs_escape(byte) || byte == ESCAPE);
239        }
240
241        let mut decoder = Decoder::<16>::new();
242        let frame = decode_all(&mut decoder, &wire[..len]).unwrap().unwrap();
243        assert_eq!(frame, payload);
244    }
245
246    #[test]
247    fn round_trip_empty_payload() {
248        let mut wire = [0u8; max_encoded_len(0)];
249        let len = encode_frame(&[], &mut wire).unwrap();
250        let mut decoder = Decoder::<8>::new();
251        let frame = decode_all(&mut decoder, &wire[..len]).unwrap().unwrap();
252        assert!(frame.is_empty());
253    }
254
255    #[test]
256    fn escaped_fcs_survives() {
257        // Find a payload whose FCS contains a byte needing escape, and
258        // make sure it round-trips. Payload [0x7A] -> FCS contains 0x7E
259        // for at least one of the candidates below.
260        for candidate in 0u8..=255 {
261            let payload = [candidate];
262            let fcs = crc16(&payload).to_le_bytes();
263            if fcs.iter().copied().any(needs_escape) {
264                let mut wire = [0u8; max_encoded_len(1)];
265                let len = encode_frame(&payload, &mut wire).unwrap();
266                let mut decoder = Decoder::<8>::new();
267                let frame = decode_all(&mut decoder, &wire[..len]).unwrap().unwrap();
268                assert_eq!(frame, payload);
269                return;
270            }
271        }
272        panic!("no candidate produced an FCS needing escape");
273    }
274
275    #[test]
276    fn back_to_back_frames_share_flag() {
277        // ...FLAG payload FLAG payload FLAG... with a single flag
278        // separating consecutive frames.
279        let mut wire = Vec::new();
280        let mut scratch = [0u8; 32];
281        for payload in [&[0x01u8][..], &[0x02u8][..]] {
282            let len = encode_frame(payload, &mut scratch).unwrap();
283            wire.extend_from_slice(&scratch[..len]);
284        }
285        // Also collapse the adjacent closing/opening flags to one.
286        let mut collapsed = wire.clone();
287        collapsed.dedup_by(|a, b| *a == FLAG && *b == FLAG);
288
289        for input in [wire, collapsed] {
290            let mut decoder = Decoder::<8>::new();
291            let mut frames = Vec::new();
292            for byte in input {
293                if let Some(outcome) = decoder.push(byte) {
294                    frames.push(outcome.unwrap().to_vec());
295                }
296            }
297            assert_eq!(frames, [[0x01].to_vec(), [0x02].to_vec()]);
298        }
299    }
300
301    #[test]
302    fn corrupt_frame_reports_crc_error() {
303        let mut wire = [0u8; max_encoded_len(3)];
304        let len = encode_frame(&[0x81, 0x02, 0x00], &mut wire).unwrap();
305        wire[1] ^= 0x01;
306        let mut decoder = Decoder::<16>::new();
307        assert_eq!(
308            decode_all(&mut decoder, &wire[..len]),
309            Some(Err(DecodeError::Crc))
310        );
311    }
312
313    #[test]
314    fn recovers_after_garbage() {
315        let mut decoder = Decoder::<16>::new();
316        // Garbage without flags is silently buffered, then aborted by
317        // the first flag (as a CRC/short error), after which a valid
318        // frame decodes normally.
319        for byte in [0xAAu8, 0xBB, 0xCC] {
320            assert_eq!(decoder.push(byte), None);
321        }
322        assert!(matches!(
323            decoder.push(FLAG),
324            Some(Err(DecodeError::Crc | DecodeError::TooShort))
325        ));
326
327        let mut wire = [0u8; max_encoded_len(1)];
328        let len = encode_frame(&[0x42], &mut wire).unwrap();
329        let frame = decode_all(&mut decoder, &wire[..len]).unwrap().unwrap();
330        assert_eq!(frame, [0x42]);
331    }
332
333    #[test]
334    fn oversized_frame_reports_too_long() {
335        let payload = [0u8; 16];
336        let mut wire = [0u8; max_encoded_len(16)];
337        let len = encode_frame(&payload, &mut wire).unwrap();
338        // Decoder buffer smaller than payload + FCS.
339        let mut decoder = Decoder::<8>::new();
340        assert_eq!(
341            decode_all(&mut decoder, &wire[..len]),
342            Some(Err(DecodeError::TooLong))
343        );
344        // And it recovers for the next frame.
345        let len = encode_frame(&[0x01], &mut wire).unwrap();
346        let frame = decode_all(&mut decoder, &wire[..len]).unwrap().unwrap();
347        assert_eq!(frame, [0x01]);
348    }
349
350    #[test]
351    fn escape_then_flag_aborts() {
352        let mut decoder = Decoder::<8>::new();
353        assert_eq!(decoder.push(FLAG), None);
354        assert_eq!(decoder.push(0x42), None);
355        assert_eq!(decoder.push(ESCAPE), None);
356        assert_eq!(decoder.push(FLAG), Some(Err(DecodeError::AbortedEscape)));
357    }
358
359    #[test]
360    fn encode_error_on_small_buffer() {
361        let mut wire = [0u8; 4];
362        assert_eq!(
363            encode_frame(&[0x01, 0x02, 0x03], &mut wire),
364            Err(EncodeError::BufferTooSmall)
365        );
366    }
367}