Skip to main content

wire_codec/
cobs.rs

1//! Consistent Overhead Byte Stuffing (COBS).
2//!
3//! COBS rewrites a payload so that it contains no `0x00` bytes, at a cost of
4//! at most one byte per 254 bytes of input (plus one). A zero byte can then
5//! mark frame boundaries unambiguously, which makes COBS a common choice for
6//! serial links, USB CDC, and radio packets where a receiver must be able to
7//! resynchronise after noise. It is the framing used by `postcard`.
8//!
9//! This module only does the byte stuffing. Combine it with
10//! [`Delimited`][`crate::framing::Delimited`] on a `[0x00]` delimiter to cut
11//! the stream into frames:
12//!
13//! ```
14//! use wire_codec::framing::{Delimited, Framer};
15//! use wire_codec::{cobs, WriteBuf};
16//!
17//! // Sender: stuff the payload, then terminate it with 0x00.
18//! let payload = [0x11, 0x00, 0x22, 0x00];
19//! let mut wire = [0u8; 16];
20//! let mut out = WriteBuf::new(&mut wire);
21//! cobs::encode(&payload, &mut out).unwrap();
22//! out.write_u8(0x00).unwrap();
23//! let n = out.position();
24//! assert_eq!(&wire[..n], &[0x02, 0x11, 0x02, 0x22, 0x01, 0x00]);
25//!
26//! // Receiver: split on 0x00, then unstuff in place.
27//! let framer = Delimited::bounded(&[0x00]).unwrap();
28//! let frame = framer.next_frame(&wire[..n]).unwrap().unwrap();
29//! let mut block = [0u8; 16];
30//! let block = &mut block[..frame.payload().len()];
31//! block.copy_from_slice(frame.payload());
32//! let len = cobs::decode_in_place(block).unwrap();
33//! assert_eq!(&block[..len], &payload);
34//! ```
35//!
36//! All functions are allocation-free and atomic: on error nothing is written
37//! to the output cursor.
38
39use crate::buf::WriteBuf;
40use crate::error::{Error, Result};
41
42/// Largest number of data bytes in one COBS block.
43const MAX_RUN: usize = 254;
44
45/// Upper bound on the encoded size of a `len`-byte payload, excluding the
46/// trailing `0x00` frame delimiter: `len + ceil(len / 254)`, and at least 1.
47///
48/// Saturates at `usize::MAX`.
49///
50/// # Example
51///
52/// ```
53/// use wire_codec::cobs;
54/// assert_eq!(cobs::max_encoded_len(0), 1);
55/// assert_eq!(cobs::max_encoded_len(254), 255);
56/// assert_eq!(cobs::max_encoded_len(255), 257);
57/// ```
58#[inline]
59#[must_use]
60pub const fn max_encoded_len(len: usize) -> usize {
61    let overhead = if len == 0 { 1 } else { len.div_ceil(MAX_RUN) };
62    len.saturating_add(overhead)
63}
64
65/// COBS-encode `src` into `out`. The output contains no `0x00` bytes and
66/// does not include a trailing delimiter.
67///
68/// A full 254-byte block at the end of the input is not followed by an
69/// empty block, so the output is never longer than
70/// [`max_encoded_len`]`(src.len())`. This matches the reference encoder of
71/// Cheshire and Baker and the `cobs` and `postcard` crates.
72///
73/// # Errors
74///
75/// Returns [`Error::BufferFull`] if `out` has insufficient room. Nothing is
76/// written on error.
77///
78/// # Example
79///
80/// ```
81/// use wire_codec::{cobs, WriteBuf};
82///
83/// let mut out = [0u8; 8];
84/// let mut buf = WriteBuf::new(&mut out);
85/// cobs::encode(&[0x00], &mut buf).unwrap();
86/// assert_eq!(buf.written(), &[0x01, 0x01]);
87/// ```
88pub fn encode(src: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
89    let dst = out.spare_mut();
90    // Check the room before touching the output. With room for the worst
91    // case no exact count is needed; otherwise a counting pass decides.
92    if dst.len() < max_encoded_len(src.len()) && dst.len() < encoded_len(src) {
93        return Err(Error::BufferFull);
94    }
95    // Index of the code byte of the block currently being filled.
96    let mut code_at = 0;
97    let mut write = 1;
98    let mut code: u8 = 1;
99    for (i, &byte) in src.iter().enumerate() {
100        if byte != 0 {
101            dst[write] = byte;
102            write += 1;
103            code += 1;
104        }
105        if byte == 0 || (code == 0xFF && i + 1 < src.len()) {
106            // Close the block and reserve a code byte for the next one.
107            dst[code_at] = code;
108            code_at = write;
109            write += 1;
110            code = 1;
111        }
112    }
113    dst[code_at] = code;
114    out.commit(write);
115    Ok(())
116}
117
118/// Exact size of the encoding [`encode`] produces for `src`.
119fn encoded_len(src: &[u8]) -> usize {
120    let mut len = 1;
121    let mut code: u8 = 1;
122    for (i, &byte) in src.iter().enumerate() {
123        if byte != 0 {
124            len += 1;
125            code += 1;
126        }
127        if byte == 0 || (code == 0xFF && i + 1 < src.len()) {
128            len += 1;
129            code = 1;
130        }
131    }
132    len
133}
134
135/// Decode one COBS block (without its `0x00` delimiter) from `src` into
136/// `out`.
137///
138/// Like the reference decoder, this accepts both encodings of a payload
139/// that ends with a full 254-byte run: with and without a trailing empty
140/// block (`0x01`). [`encode`] only ever produces the shorter form. If the
141/// encoded bytes must be canonical (for example because they are hashed or
142/// signed), re-encode the decoded payload and compare.
143///
144/// # Errors
145///
146/// - [`Error::InvalidEncoding`] if `src` is empty, contains a `0x00` byte, or
147///   a code byte points past the end of the block.
148/// - [`Error::BufferFull`] if `out` has insufficient room. The decoded size
149///   is always smaller than `src.len()`.
150///
151/// Nothing is written on error.
152///
153/// # Example
154///
155/// ```
156/// use wire_codec::{cobs, WriteBuf};
157///
158/// let mut out = [0u8; 8];
159/// let mut buf = WriteBuf::new(&mut out);
160/// cobs::decode(&[0x03, 0x11, 0x22, 0x02, 0x33], &mut buf).unwrap();
161/// assert_eq!(buf.written(), &[0x11, 0x22, 0x00, 0x33]);
162/// ```
163pub fn decode(src: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
164    // Validate and size the block first, so nothing is written on error.
165    let mut len = 0;
166    for_each_run(src, |run, zero_follows| {
167        len += run.len() + usize::from(zero_follows);
168        Ok(())
169    })?;
170    let Some(dst) = out.spare_mut().get_mut(..len) else {
171        return Err(Error::BufferFull);
172    };
173    let mut write = 0;
174    for_each_run(src, |run, zero_follows| {
175        dst[write..write + run.len()].copy_from_slice(run);
176        write += run.len();
177        if zero_follows {
178            dst[write] = 0;
179            write += 1;
180        }
181        Ok(())
182    })?;
183    out.commit(len);
184    Ok(())
185}
186
187/// Decode one COBS block in place and return the decoded length.
188///
189/// The decoded bytes occupy `buf[..len]`; the rest of `buf` is left in an
190/// unspecified state. Decoding never needs more room than the input, so this
191/// works on the receive buffer itself.
192///
193/// # Errors
194///
195/// Returns [`Error::InvalidEncoding`] under the same conditions as
196/// [`decode`]. The block is validated before it is rewritten, so `buf` is
197/// unchanged on error.
198///
199/// # Example
200///
201/// ```
202/// use wire_codec::cobs;
203///
204/// let mut block = [0x02, 0x11, 0x01, 0x01];
205/// let len = cobs::decode_in_place(&mut block).unwrap();
206/// assert_eq!(&block[..len], &[0x11, 0x00, 0x00]);
207/// ```
208pub fn decode_in_place(buf: &mut [u8]) -> Result<usize> {
209    validate(buf)?;
210    let mut read = 0;
211    let mut write = 0;
212    while read < buf.len() {
213        let code = usize::from(buf[read]);
214        let run_start = read + 1;
215        let run_end = read + code;
216        buf.copy_within(run_start..run_end, write);
217        write += code - 1;
218        read = run_end;
219        if code != 0xFF && read < buf.len() {
220            buf[write] = 0;
221            write += 1;
222        }
223    }
224    Ok(write)
225}
226
227/// Check that `src` is a structurally valid COBS block.
228fn validate(src: &[u8]) -> Result<()> {
229    for_each_run(src, |_, _| Ok(()))
230}
231
232/// Walk the runs of a COBS block, calling `emit(run, zero_follows)` for each.
233fn for_each_run<'a>(
234    src: &'a [u8],
235    mut emit: impl FnMut(&'a [u8], bool) -> Result<()>,
236) -> Result<()> {
237    if src.is_empty() {
238        return Err(Error::InvalidEncoding);
239    }
240    let mut read = 0;
241    while read < src.len() {
242        let code = usize::from(src[read]);
243        if code == 0 {
244            return Err(Error::InvalidEncoding);
245        }
246        let run = src
247            .get(read + 1..read + code)
248            .ok_or(Error::InvalidEncoding)?;
249        if run.contains(&0) {
250            return Err(Error::InvalidEncoding);
251        }
252        read += code;
253        emit(run, code != 0xFF && read < src.len())?;
254    }
255    Ok(())
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    fn encode_vec(src: &[u8]) -> Vec<u8> {
263        let mut out = vec![0u8; max_encoded_len(src.len())];
264        let mut buf = WriteBuf::new(&mut out);
265        encode(src, &mut buf).unwrap();
266        let n = buf.position();
267        out.truncate(n);
268        out
269    }
270
271    fn decode_vec(src: &[u8]) -> Result<Vec<u8>> {
272        let mut out = vec![0u8; src.len()];
273        let mut buf = WriteBuf::new(&mut out);
274        decode(src, &mut buf)?;
275        let n = buf.position();
276        out.truncate(n);
277        Ok(out)
278    }
279
280    #[test]
281    fn wikipedia_vectors() {
282        let cases: [(&[u8], &[u8]); 6] = [
283            (&[], &[0x01]),
284            (&[0x00], &[0x01, 0x01]),
285            (&[0x00, 0x00], &[0x01, 0x01, 0x01]),
286            (&[0x00, 0x11, 0x00], &[0x01, 0x02, 0x11, 0x01]),
287            (&[0x11, 0x22, 0x00, 0x33], &[0x03, 0x11, 0x22, 0x02, 0x33]),
288            (&[0x11, 0x22, 0x33, 0x44], &[0x05, 0x11, 0x22, 0x33, 0x44]),
289        ];
290        for (raw, encoded) in cases {
291            assert_eq!(encode_vec(raw), encoded, "encode {raw:?}");
292            assert_eq!(decode_vec(encoded).unwrap(), raw, "decode {encoded:?}");
293            let mut block = encoded.to_vec();
294            let len = decode_in_place(&mut block).unwrap();
295            assert_eq!(&block[..len], raw);
296        }
297    }
298
299    #[test]
300    fn long_runs() {
301        // 254 non-zero bytes: one full block, no trailing empty block.
302        let raw: Vec<u8> = (1..=254).map(|i| i as u8).collect();
303        let encoded = encode_vec(&raw);
304        assert_eq!(encoded.len(), 255);
305        assert_eq!(encoded[0], 0xFF);
306        assert_eq!(decode_vec(&encoded).unwrap(), raw);
307
308        // 255 non-zero bytes: a full block, then a one-byte block.
309        let raw: Vec<u8> = (0..255).map(|i| (i % 255) as u8 + 1).collect();
310        let encoded = encode_vec(&raw);
311        assert_eq!(encoded.len(), max_encoded_len(255));
312        assert_eq!(&encoded[255..], &[0x02, raw[254]]);
313        assert_eq!(decode_vec(&encoded).unwrap(), raw);
314
315        // A full block followed by a zero.
316        let mut raw: Vec<u8> = vec![0x7F; 254];
317        raw.push(0);
318        let encoded = encode_vec(&raw);
319        assert_eq!(decode_vec(&encoded).unwrap(), raw);
320    }
321
322    #[test]
323    fn round_trip_mixed_lengths() {
324        for len in [0usize, 1, 253, 254, 255, 508, 509, 1000] {
325            let raw: Vec<u8> = (0..len).map(|i| (i * 7 % 5) as u8).collect();
326            let encoded = encode_vec(&raw);
327            assert!(encoded.len() <= max_encoded_len(len));
328            assert!(!encoded.contains(&0));
329            assert_eq!(decode_vec(&encoded).unwrap(), raw);
330        }
331    }
332
333    #[test]
334    fn malformed_blocks() {
335        let cases: [&[u8]; 5] = [
336            &[],
337            &[0x00],
338            &[0x03, 0x11],
339            &[0x02, 0x00],
340            &[0x02, 0x11, 0x00, 0x01],
341        ];
342        for bad in cases {
343            assert_eq!(decode_vec(bad), Err(Error::InvalidEncoding), "{bad:?}");
344            let mut block = bad.to_vec();
345            assert_eq!(decode_in_place(&mut block), Err(Error::InvalidEncoding));
346        }
347    }
348
349    #[test]
350    fn encode_is_atomic_on_buffer_full() {
351        for (src, room) in [
352            (&[0x11u8, 0x22][..], 2usize),
353            (&[0x00, 0x00], 2),
354            (&[0x11], 0),
355        ] {
356            let mut out = vec![0xEEu8; room];
357            let mut buf = WriteBuf::new(&mut out);
358            assert_eq!(encode(src, &mut buf), Err(Error::BufferFull), "{src:?}");
359            assert_eq!(buf.position(), 0);
360            assert!(
361                out.iter().all(|&b| b == 0xEE),
362                "nothing written for {src:?}"
363            );
364        }
365        // Exactly the exact size (below the worst case) succeeds.
366        let raw: Vec<u8> = vec![0x00; 300];
367        let mut out = vec![0u8; encoded_len(&raw)];
368        assert!(out.len() < max_encoded_len(raw.len()) || raw.is_empty());
369        encode(&raw, &mut WriteBuf::new(&mut out)).unwrap();
370    }
371
372    #[test]
373    fn encoded_len_matches_encode() {
374        for len in [0usize, 1, 253, 254, 255, 508, 600] {
375            for zero_every in [1usize, 2, 7, 1000] {
376                let raw: Vec<u8> = (0..len).map(|i| u8::from(i % zero_every != 0)).collect();
377                let written = encode_vec(&raw).len();
378                assert_eq!(encoded_len(&raw), written, "len={len} every={zero_every}");
379            }
380        }
381    }
382
383    #[test]
384    fn non_canonical_trailing_block_is_accepted() {
385        // Documented: both encodings of a full trailing run decode equally.
386        let mut long = vec![0xFFu8];
387        long.extend(core::iter::repeat_n(0x07u8, 254));
388        let mut with_empty = long.clone();
389        with_empty.push(0x01);
390        assert_eq!(decode_vec(&long).unwrap(), decode_vec(&with_empty).unwrap());
391        assert_eq!(encode_vec(&decode_vec(&with_empty).unwrap()), long);
392    }
393
394    #[test]
395    fn decode_is_atomic_on_error() {
396        let mut out = [0xEEu8; 2];
397        let mut buf = WriteBuf::new(&mut out);
398        assert_eq!(decode(&[0x04, 1, 2, 3], &mut buf), Err(Error::BufferFull));
399        assert_eq!(
400            decode(&[0x02, 1, 0x02, 2], &mut buf),
401            Err(Error::BufferFull)
402        );
403        // Valid first run, malformed second run: rejected before writing.
404        assert_eq!(
405            decode(&[0x02, 1, 0x05], &mut buf),
406            Err(Error::InvalidEncoding)
407        );
408        assert_eq!(buf.position(), 0);
409        assert_eq!(out, [0xEE, 0xEE], "nothing is written on error");
410
411        let mut block = [0x02, 0x11, 0x03, 0x22];
412        assert_eq!(decode_in_place(&mut block), Err(Error::InvalidEncoding));
413        assert_eq!(
414            block,
415            [0x02, 0x11, 0x03, 0x22],
416            "in-place input unchanged on error"
417        );
418    }
419
420    #[test]
421    fn max_encoded_len_saturates() {
422        assert_eq!(max_encoded_len(usize::MAX), usize::MAX);
423    }
424}