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 constcopy_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 exactlyIf 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
srcintodest. - max_
encoded_ len - Maximum encoded size for a given source length (excluding sentinel).