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}