Skip to main content

pamoja_serial/
slip.rs

1//! SLIP framing, the Serial Line Internet Protocol of RFC 1055.
2//!
3//! SLIP is the oldest and simplest way to put packets on a serial line. A single byte,
4//! [`END`], marks the end of a packet. When that byte (or the escape byte [`ESC`]) appears
5//! in the payload it is replaced by a two-byte escape sequence, so the delimiter can never
6//! be mistaken for data. That is the whole protocol; its appeal is that it fits in a
7//! handful of bytes of code on the smallest microcontroller.
8//!
9//! The four byte values are fixed by RFC 1055: [`END`] is `0xC0`, [`ESC`] is `0xDB`, and
10//! the escape sequences are `ESC` `ESC_END` for a literal `END` and `ESC` `ESC_ESC` for a
11//! literal `ESC`.
12
13use crate::SerialError;
14
15/// The SLIP frame delimiter, RFC 1055 `END` (octal 300).
16pub const END: u8 = 0xC0;
17/// The SLIP escape byte, RFC 1055 `ESC` (octal 333).
18pub const ESC: u8 = 0xDB;
19/// The byte that follows [`ESC`] to encode a literal [`END`], RFC 1055 `ESC_END` (octal 334).
20pub const ESC_END: u8 = 0xDC;
21/// The byte that follows [`ESC`] to encode a literal [`ESC`], RFC 1055 `ESC_ESC` (octal 335).
22pub const ESC_ESC: u8 = 0xDD;
23
24/// Returns an output length that is always large enough to hold the SLIP encoding of a
25/// payload of `payload_len` bytes.
26///
27/// The worst case is a payload made entirely of [`END`] or [`ESC`] bytes, where every byte
28/// becomes a two-byte escape sequence, plus the one trailing [`END`] delimiter.
29///
30/// # Arguments
31///
32/// * `payload_len` - the length of the payload to be encoded.
33///
34/// # Returns
35///
36/// The maximum number of bytes [`encode`] can write for that payload.
37///
38/// # Examples
39///
40/// ```
41/// use pamoja_serial::slip;
42///
43/// assert_eq!(slip::max_encoded_len(10), 21);
44/// ```
45#[must_use]
46pub const fn max_encoded_len(payload_len: usize) -> usize {
47    payload_len * 2 + 1
48}
49
50/// Encodes a payload into a SLIP frame, terminated by an [`END`] byte.
51///
52/// Every [`END`] in the payload is written as `ESC` `ESC_END` and every [`ESC`] as `ESC`
53/// `ESC_ESC`; all other bytes pass through unchanged. A single [`END`] is appended to mark
54/// the end of the frame. A sender that wants RFC 1055's noise-flushing behaviour may
55/// prepend an extra [`END`] of its own; [`decode`] and [`SlipDecoder`] ignore it.
56///
57/// # Arguments
58///
59/// * `payload` - the bytes to frame.
60/// * `output` - the buffer the frame is written into; size it with [`max_encoded_len`].
61///
62/// # Returns
63///
64/// The number of bytes written to `output`.
65///
66/// # Errors
67///
68/// Returns [`SerialError::BufferTooSmall`] if `output` cannot hold the whole frame.
69///
70/// # Examples
71///
72/// ```
73/// use pamoja_serial::slip::{self, END, ESC, ESC_END};
74///
75/// // A payload containing the delimiter byte is escaped, never emitted raw.
76/// let mut frame = [0u8; 8];
77/// let n = slip::encode(&[0x01, END, 0x02], &mut frame)?;
78/// assert_eq!(&frame[..n], &[0x01, ESC, ESC_END, 0x02, END]);
79/// # Ok::<(), pamoja_serial::SerialError>(())
80/// ```
81pub fn encode(payload: &[u8], output: &mut [u8]) -> Result<usize, SerialError> {
82    let mut write = 0usize;
83    for &byte in payload {
84        match byte {
85            END => {
86                push(output, &mut write, ESC)?;
87                push(output, &mut write, ESC_END)?;
88            }
89            ESC => {
90                push(output, &mut write, ESC)?;
91                push(output, &mut write, ESC_ESC)?;
92            }
93            other => push(output, &mut write, other)?,
94        }
95    }
96    push(output, &mut write, END)?;
97    Ok(write)
98}
99
100/// Decodes a single SLIP frame, recovering the original payload.
101///
102/// A leading [`END`] (RFC 1055's flush byte) and any empty run before the payload are
103/// skipped; decoding stops at the [`END`] that closes the frame, or at the end of the
104/// slice if it carries no trailing delimiter.
105///
106/// # Arguments
107///
108/// * `frame` - the framed bytes, with or without the trailing [`END`].
109/// * `output` - the buffer the payload is written into; it never needs more room than
110///   `frame`.
111///
112/// # Returns
113///
114/// The number of payload bytes written to `output`.
115///
116/// # Errors
117///
118/// Returns [`SerialError::InvalidEscape`] if an [`ESC`] is followed by an unexpected byte,
119/// [`SerialError::TruncatedFrame`] if the frame ends in the middle of an escape sequence,
120/// and [`SerialError::BufferTooSmall`] if `output` cannot hold the payload.
121///
122/// # Examples
123///
124/// ```
125/// use pamoja_serial::slip::{self, END};
126///
127/// let mut payload = [0u8; 4];
128/// // A leading END is tolerated and the trailing one closes the frame.
129/// let n = slip::decode(&[END, b'h', b'i', END], &mut payload)?;
130/// assert_eq!(&payload[..n], b"hi");
131/// # Ok::<(), pamoja_serial::SerialError>(())
132/// ```
133pub fn decode(frame: &[u8], output: &mut [u8]) -> Result<usize, SerialError> {
134    let mut write = 0usize;
135    let mut in_escape = false;
136    for &byte in frame {
137        if byte == END {
138            if in_escape {
139                return Err(SerialError::TruncatedFrame);
140            }
141            // A delimiter with nothing buffered is a leading or repeated flush byte; keep
142            // reading. Otherwise it closes the frame.
143            if write == 0 {
144                continue;
145            }
146            return Ok(write);
147        }
148        if in_escape {
149            let decoded = match byte {
150                ESC_END => END,
151                ESC_ESC => ESC,
152                _ => return Err(SerialError::InvalidEscape),
153            };
154            push(output, &mut write, decoded)?;
155            in_escape = false;
156        } else if byte == ESC {
157            in_escape = true;
158        } else {
159            push(output, &mut write, byte)?;
160        }
161    }
162    if in_escape {
163        return Err(SerialError::TruncatedFrame);
164    }
165    Ok(write)
166}
167
168/// A streaming SLIP decoder that reassembles whole frames from a serial byte stream.
169///
170/// A serial read returns whatever bytes have arrived, which is rarely a whole packet, so a
171/// real receive loop feeds bytes in as they come and acts on each frame as it completes.
172/// `SlipDecoder` buffers up to `N` payload bytes; [`push`](SlipDecoder::push) returns the
173/// finished payload when an [`END`] closes the frame, and `None` while one is still being
174/// assembled.
175///
176/// # Examples
177///
178/// ```
179/// use pamoja_serial::slip::{SlipDecoder, END};
180///
181/// let mut decoder: SlipDecoder<32> = SlipDecoder::new();
182/// let mut frames = 0;
183/// // Two packets arrive back to back in one read.
184/// for &byte in &[b'o', b'k', END, b'g', b'o', END] {
185///     if let Some(frame) = decoder.push(byte)? {
186///         frames += 1;
187///         assert!(frame == b"ok" || frame == b"go");
188///     }
189/// }
190/// assert_eq!(frames, 2);
191/// # Ok::<(), pamoja_serial::SerialError>(())
192/// ```
193#[derive(Debug)]
194pub struct SlipDecoder<const N: usize> {
195    buffer: [u8; N],
196    len: usize,
197    in_escape: bool,
198    complete: bool,
199}
200
201impl<const N: usize> SlipDecoder<N> {
202    /// Creates an empty decoder with room for an `N`-byte payload.
203    ///
204    /// # Returns
205    ///
206    /// A decoder ready to receive the first byte.
207    #[must_use]
208    pub const fn new() -> Self {
209        Self {
210            buffer: [0u8; N],
211            len: 0,
212            in_escape: false,
213            complete: false,
214        }
215    }
216
217    /// Discards any partly assembled frame, returning the decoder to its initial state.
218    pub fn reset(&mut self) {
219        self.len = 0;
220        self.in_escape = false;
221        self.complete = false;
222    }
223
224    /// Feeds one byte from the stream into the decoder.
225    ///
226    /// # Arguments
227    ///
228    /// * `byte` - the next byte received on the serial line.
229    ///
230    /// # Returns
231    ///
232    /// `Some(payload)` when this byte completed a frame, or `None` while a frame is still
233    /// being assembled. An empty frame (a stray [`END`] with nothing buffered) is treated
234    /// as a flush and yields `None`.
235    ///
236    /// # Errors
237    ///
238    /// Returns [`SerialError::InvalidEscape`] if an [`ESC`] is followed by an unexpected
239    /// byte, [`SerialError::TruncatedFrame`] if an [`END`] arrives mid-escape, and
240    /// [`SerialError::BufferTooSmall`] if the payload exceeds `N` bytes. After any error
241    /// the partial frame is discarded and the decoder resumes at the next byte.
242    pub fn push(&mut self, byte: u8) -> Result<Option<&[u8]>, SerialError> {
243        if self.complete {
244            self.reset();
245        }
246        if byte == END {
247            if self.in_escape {
248                self.reset();
249                return Err(SerialError::TruncatedFrame);
250            }
251            if self.len == 0 {
252                return Ok(None);
253            }
254            self.complete = true;
255            return Ok(Some(&self.buffer[..self.len]));
256        }
257        if self.in_escape {
258            let decoded = match byte {
259                ESC_END => END,
260                ESC_ESC => ESC,
261                _ => {
262                    self.reset();
263                    return Err(SerialError::InvalidEscape);
264                }
265            };
266            self.in_escape = false;
267            self.store(decoded)?;
268        } else if byte == ESC {
269            self.in_escape = true;
270        } else {
271            self.store(byte)?;
272        }
273        Ok(None)
274    }
275
276    fn store(&mut self, byte: u8) -> Result<(), SerialError> {
277        if self.len >= N {
278            self.reset();
279            return Err(SerialError::BufferTooSmall);
280        }
281        self.buffer[self.len] = byte;
282        self.len += 1;
283        Ok(())
284    }
285}
286
287impl<const N: usize> Default for SlipDecoder<N> {
288    fn default() -> Self {
289        Self::new()
290    }
291}
292
293/// Writes one byte into `output` at `write`, advancing it, or reports the buffer is full.
294fn push(output: &mut [u8], write: &mut usize, byte: u8) -> Result<(), SerialError> {
295    if *write >= output.len() {
296        return Err(SerialError::BufferTooSmall);
297    }
298    output[*write] = byte;
299    *write += 1;
300    Ok(())
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn constants_match_rfc_1055() {
309        // RFC 1055 fixes these in octal: 300, 333, 334, 335.
310        assert_eq!(END, 0o300);
311        assert_eq!(ESC, 0o333);
312        assert_eq!(ESC_END, 0o334);
313        assert_eq!(ESC_ESC, 0o335);
314    }
315
316    #[test]
317    fn plain_payload_just_gets_a_trailing_end() {
318        let mut frame = [0u8; 8];
319        let n = encode(b"hi", &mut frame).unwrap();
320        assert_eq!(&frame[..n], &[b'h', b'i', END]);
321    }
322
323    #[test]
324    fn a_literal_end_byte_is_escaped() {
325        let mut frame = [0u8; 8];
326        let n = encode(&[END], &mut frame).unwrap();
327        assert_eq!(&frame[..n], &[ESC, ESC_END, END]);
328    }
329
330    #[test]
331    fn a_literal_esc_byte_is_escaped() {
332        let mut frame = [0u8; 8];
333        let n = encode(&[ESC], &mut frame).unwrap();
334        assert_eq!(&frame[..n], &[ESC, ESC_ESC, END]);
335    }
336
337    #[test]
338    fn both_specials_in_one_payload() {
339        let mut frame = [0u8; 16];
340        let n = encode(&[END, ESC, 0x01], &mut frame).unwrap();
341        assert_eq!(&frame[..n], &[ESC, ESC_END, ESC, ESC_ESC, 0x01, END]);
342    }
343
344    #[test]
345    fn round_trips_every_byte_value() {
346        let payload: [u8; 256] = core::array::from_fn(|i| i as u8);
347        let mut frame = [0u8; max_encoded_len(256)];
348        let n = encode(&payload, &mut frame).unwrap();
349        let mut out = [0u8; 256];
350        let m = decode(&frame[..n], &mut out).unwrap();
351        assert_eq!(&out[..m], &payload[..]);
352    }
353
354    #[test]
355    fn decode_skips_a_leading_flush_end() {
356        let mut out = [0u8; 4];
357        let n = decode(&[END, b'h', b'i', END], &mut out).unwrap();
358        assert_eq!(&out[..n], b"hi");
359    }
360
361    #[test]
362    fn decode_tolerates_a_missing_trailing_end() {
363        let mut out = [0u8; 4];
364        let n = decode(b"hi", &mut out).unwrap();
365        assert_eq!(&out[..n], b"hi");
366    }
367
368    #[test]
369    fn an_invalid_escape_is_rejected() {
370        let mut out = [0u8; 4];
371        assert_eq!(
372            decode(&[ESC, 0x01, END], &mut out),
373            Err(SerialError::InvalidEscape)
374        );
375    }
376
377    #[test]
378    fn an_escape_at_the_end_is_truncated() {
379        let mut out = [0u8; 4];
380        assert_eq!(
381            decode(&[0x01, ESC], &mut out),
382            Err(SerialError::TruncatedFrame)
383        );
384    }
385
386    #[test]
387    fn encode_reports_a_full_buffer() {
388        let mut frame = [0u8; 2];
389        assert_eq!(encode(&[END], &mut frame), Err(SerialError::BufferTooSmall));
390    }
391
392    #[test]
393    fn decode_reports_a_full_buffer() {
394        let mut out = [0u8; 1];
395        assert_eq!(decode(b"hi", &mut out), Err(SerialError::BufferTooSmall));
396    }
397
398    #[test]
399    fn streaming_decoder_splits_back_to_back_frames() {
400        let mut decoder: SlipDecoder<16> = SlipDecoder::new();
401        // Two packets in one read: "ok", then a single escaped END byte.
402        let stream = [b'o', b'k', END, ESC, ESC_END, END];
403        let expected: [&[u8]; 2] = [b"ok", &[END]];
404        let mut seen = 0;
405        for &byte in &stream {
406            if let Some(frame) = decoder.push(byte).unwrap() {
407                assert_eq!(frame, expected[seen]);
408                seen += 1;
409            }
410        }
411        assert_eq!(seen, 2);
412    }
413
414    #[test]
415    fn streaming_decoder_ignores_empty_frames() {
416        let mut decoder: SlipDecoder<8> = SlipDecoder::new();
417        // Repeated ENDs (line-noise flushes) yield no frames.
418        assert!(decoder.push(END).unwrap().is_none());
419        assert!(decoder.push(END).unwrap().is_none());
420        assert!(decoder.push(b'x').unwrap().is_none());
421        assert_eq!(decoder.push(END).unwrap(), Some(&b"x"[..]));
422    }
423
424    #[test]
425    fn streaming_decoder_reports_overflow_then_recovers() {
426        let mut decoder: SlipDecoder<2> = SlipDecoder::new();
427        assert!(decoder.push(b'a').unwrap().is_none());
428        assert!(decoder.push(b'b').unwrap().is_none());
429        assert_eq!(decoder.push(b'c'), Err(SerialError::BufferTooSmall));
430        // After the overflow the next frame decodes cleanly.
431        assert!(decoder.push(b'z').unwrap().is_none());
432        assert_eq!(decoder.push(END).unwrap(), Some(&b"z"[..]));
433    }
434
435    #[test]
436    fn a_payload_carrying_reserved_bytes_is_stuffed_as_rfc_1055_fixes() {
437        // RFC 1055 replaces an END byte inside a payload with ESC ESC_END, and an ESC byte
438        // with ESC ESC_ESC, then ends the frame with END.
439        let payload = [0x01, END, ESC, 0x02];
440        let mut frame = [0u8; max_encoded_len(4)];
441        let n = encode(&payload, &mut frame).expect("room for the frame");
442        assert_eq!(&frame[..n], &[0x01, ESC, ESC_END, ESC, ESC_ESC, 0x02, END]);
443
444        let mut restored = [0u8; 4];
445        let m = decode(&frame[..n], &mut restored).expect("a well-formed frame");
446        assert_eq!(&restored[..m], &payload);
447    }
448}