Skip to main content

Crate ucobs

Crate ucobs 

Source
Expand description

COBS (Consistent Overhead Byte Stuffing) encoder and decoder.

Implements the algorithm from Cheshire & Baker, “Consistent Overhead Byte Stuffing,” IEEE/ACM Transactions on Networking, Vol. 7, No. 2, April 1999.

COBS transforms a byte sequence so that 0x00 never appears in the output, allowing 0x00 to be used as an unambiguous frame delimiter. Overhead is at most 1 byte per 254 input bytes plus 1; see max_encoded_len for the exact formula.

This crate is no_std, zero-alloc, and unsafe-free — it runs everywhere from 8-bit MCUs to servers.

§Feature flags

  • legacy-msrv — Enables compilation on Rust 1.83+ by replacing the const copy_from_slice (requires Rust 1.93) with a manual byte loop in the encoder. All other optimizations are unaffected.

§Quick start

// Encode
let mut buf = [0u8; 16];
let n = ucobs::encode(&[0x11, 0x00, 0x33], &mut buf).unwrap();
assert_eq!(&buf[..n], &[0x02, 0x11, 0x02, 0x33]);

// Decode
let mut out = [0u8; 16];
let m = ucobs::decode(&buf[..n], &mut out).unwrap();
assert_eq!(&out[..m], &[0x11, 0x00, 0x33]);

§Buffer sizing

Use max_encoded_len to determine the required destination buffer size before encoding:

let data = [0x01, 0x02, 0x03];
let max = ucobs::max_encoded_len(data.len()); // 4

let mut buf = [0u8; 4];
let n = ucobs::encode(&data, &mut buf).unwrap();
assert_eq!(n, 4); // fits exactly

If the destination is too small, encode returns None rather than panicking:

let mut tiny = [0u8; 1];
assert_eq!(ucobs::encode(&[0x01, 0x02], &mut tiny), None);

§Framing for transport

The encoder does not append a trailing 0x00 sentinel. The decoder expects input without a trailing sentinel. Append and strip the sentinel yourself when framing for transport:

let data = [0x11, 0x00, 0x33];

// Encode into a wire frame: [COBS bytes...] [0x00 sentinel]
let mut frame = [0u8; 16];
let n = ucobs::encode(&data, &mut frame).unwrap();
frame[n] = 0x00; // append sentinel
let wire = &frame[..n + 1];

// On the receiving end, strip the sentinel before decoding
let cobs_data = &wire[..wire.len() - 1];
let mut out = [0u8; 16];
let m = ucobs::decode(cobs_data, &mut out).unwrap();
assert_eq!(&out[..m], &data);

§Parsing a stream of frames

In a byte stream, split on 0x00 to extract individual COBS frames, then decode each one:

// Simulate a stream containing two frames separated by 0x00 sentinels
let stream = [
    0x02, 0x11, 0x02, 0x33, 0x00,  // frame 1: encodes [0x11, 0x00, 0x33]
    0x03, 0xAA, 0xBB, 0x00,        // frame 2: encodes [0xAA, 0xBB]
];

let mut out = [0u8; 16];
let frames: Vec<&[u8]> = stream.split(|&b| b == 0x00)
    .filter(|f| !f.is_empty())
    .collect();

let n = ucobs::decode(frames[0], &mut out).unwrap();
assert_eq!(&out[..n], &[0x11, 0x00, 0x33]);

let n = ucobs::decode(frames[1], &mut out).unwrap();
assert_eq!(&out[..n], &[0xAA, 0xBB]);

§Compile-time encoding

encode is const fn, so you can build COBS-encoded lookup tables or protocol headers at compile time with zero runtime cost:

// Pre-encode a command table at compile time
const PING: [u8; 2] = {
    let mut buf = [0u8; 2];
    match ucobs::encode(&[0x01], &mut buf) {
        Some(_) => buf,
        None => panic!("buffer too small"),
    }
};

const ACK: [u8; 3] = {
    let mut buf = [0u8; 3];
    match ucobs::encode(&[0x06, 0x00], &mut buf) {
        Some(_) => buf,
        None => panic!("buffer too small"),
    }
};

// No runtime encoding needed — these are baked into the binary
assert_eq!(PING, [0x02, 0x01]);
assert_eq!(ACK, [0x02, 0x06, 0x01]);

§Error handling

Both encode and decode return Option<usize>None on failure, never panic.

let mut buf = [0u8; 8];

// Destination too small for encode
assert_eq!(ucobs::encode(&[1, 2, 3], &mut buf[..1]), None);

// Malformed COBS data (zero byte in encoded stream)
assert_eq!(ucobs::decode(&[0x00], &mut buf), None);

// Truncated frame (code byte promises more data than exists)
assert_eq!(ucobs::decode(&[0x05, 0x11], &mut buf), None);

// Empty input is valid for both
assert_eq!(ucobs::encode(&[], &mut buf), Some(1)); // encodes to [0x01]
assert_eq!(ucobs::decode(&[], &mut buf), Some(0)); // decodes to []

Functions§

decode
Decode a COBS-encoded buffer.
encode
COBS-encode src into dest.
max_encoded_len
Maximum encoded size for a given source length (excluding sentinel).