Skip to content

firechip/cobs_codec_rs

Repository files navigation

cobs_codec_rs

CI crates.io docs.rs License: MIT

#![no_std], dependency-free Consistent Overhead Byte Stuffing (COBS) and COBS/R for Rust — the Rust member of the Firechip COBS family (alongside the Dart cobs_codec and the Kotlin cobs_codec_kt), verified byte-identical against the shared conformance vectors.

COBS encodes an arbitrary byte sequence into one that contains no zero (0x00) byte, at a small and predictable cost: at most one extra byte per 254 bytes, plus one. That makes a single 0x00 a reliable packet delimiter for serial/UART, USB, TCP and other byte streams — ideal for embedded and robotics protocols.

Features

  • Basic COBS and COBS/R (Reduced) encode/decode.
  • no_std and zero dependencies — the core encode/decode work on caller-provided slices, allocating nothing.
  • Configurable sentinel*_with_sentinel variants frame with any delimiter byte, not just 0x00.
  • In-place decodingcobs::decode_in_place decodes without a second buffer.
  • Allocation-free streamingframing::StreamDecoder reassembles delimited frames into a fixed buffer in pure no_std; the alloc feature adds the owned-Vec FrameDecoder and *_to_vec conveniences.
  • const fn size helpers (max_encoded_len, encoding_overhead) for compile-time buffer sizing.
  • Optional serde / defmt — off by default; enable the matching feature to derive Serialize/Deserialize or defmt::Format for DecodeError.

Install

[dependencies]
cobs_codec_rs = "1.2"

# no_std, no allocator:
# cobs_codec_rs = { version = "1.2", default-features = false }

Usage

use cobs_codec_rs::{cobs, cobsr};

// With alloc (default):
let encoded = cobs::encode_to_vec(&[0x11, 0x22, 0x00, 0x33]);
assert_eq!(encoded, [0x03, 0x11, 0x22, 0x02, 0x33]); // no 0x00
assert_eq!(cobs::decode_to_vec(&encoded).unwrap(), [0x11, 0x22, 0x00, 0x33]);

// COBS/R often saves the trailing overhead byte:
assert_eq!(cobsr::encode_to_vec(b"12345"), b"51234"); // same length as input

no_std, into a fixed buffer:

use cobs_codec_rs::{cobs, max_encoded_len};

let src = [0x11, 0x00, 0x22];
let mut buf = [0u8; max_encoded_len(3)];
let n = cobs::encode(&src, &mut buf);
assert_eq!(&buf[..n], &[0x02, 0x11, 0x02, 0x22]);

With a custom sentinel byte, so a non-0x00 byte delimits frames (both cobs and cobsr, slice and *_to_vec variants):

use cobs_codec_rs::cobs;

// 0xAA delimits frames instead of 0x00; the encoded output never contains it.
// (`sentinel == 0` is identical to the plain codec.)
let encoded = cobs::encode_to_vec_with_sentinel(&[0x11, 0xAA, 0x22], 0xAA);
assert_eq!(encoded, [0xAE, 0xBB, 0x00, 0x88]); // no 0xAA byte
assert_eq!(
    cobs::decode_to_vec_with_sentinel(&encoded, 0xAA).unwrap(),
    [0x11, 0xAA, 0x22],
);

Decoding in place, without a second buffer (basic COBS only):

use cobs_codec_rs::cobs;

// COBS never expands on decode, so it can decode within the input buffer; the
// decoded bytes end up in `buf[..len]`.
let mut buf = [0x03, 0x11, 0x22, 0x02, 0x33];
let len = cobs::decode_in_place(&mut buf).unwrap();
assert_eq!(&buf[..len], &[0x11, 0x22, 0x00, 0x33]);

Reassembling a sentinel-delimited stream with no allocator, into a fixed buffer:

use cobs_codec_rs::cobs;
use cobs_codec_rs::framing::StreamDecoder;

// Encode a packet with sentinel 0xAA, then delimit it with an 0xAA byte.
let mut wire = [0u8; 16];
let n = cobs::encode_with_sentinel(&[0x11, 0x00, 0x22], &mut wire, 0xAA);
wire[n] = 0xAA;

// Reassemble it into a fixed scratch buffer — no allocation anywhere.
let mut scratch = [0u8; 8];
let mut decoder = StreamDecoder::new(&mut scratch).sentinel(0xAA); // .reduced(true) for COBS/R

let mut out = [0u8; 8];
let mut out_len = 0;
decoder.push(&wire[..n + 1], |frame| {
    let frame = frame.unwrap();
    out[..frame.len()].copy_from_slice(frame);
    out_len = frame.len();
});
assert_eq!(&out[..out_len], &[0x11, 0x00, 0x22]);

Reading a delimited serial stream (needs alloc):

use cobs_codec_rs::framing::{frame_to_vec, FrameDecoder};

let mut rx = FrameDecoder::new().max_frame_len(4096);
// `chunk` is any &[u8] read from the link; chunks need not align with frames.
# let chunk = frame_to_vec(&[0x01, 0x02]);
rx.push(&chunk, |frame| match frame {
    Ok(packet) => { /* handle packet */ }
    Err(err)   => { /* corrupt frame; keep receiving */ let _ = err; }
});

Framing Protocol Buffers over serial

COBS is the standard way to frame Protobuf on a UART/RS-485 link: protobuf serializes a message but doesn't delimit it, and COBS supplies the missing 0x00-delimited framing — with instant resync after line noise, unlike length-prefixing. See examples/protobuf_cobs.rs for a runnable device→host demo (cargo run --example protobuf_cobs) that survives a corrupted frame.

Overhead

COBS overhead is data-independent. Encoding an n-byte packet produces at most

$$ n + \left\lceil \frac{n}{254} \right\rceil $$

bytes (one extra byte per 254, rounded up), so the overhead is bounded by $\left\lceil n/254 \right\rceil$ and is always at least one byte. By contrast, escape-based schemes (PPP, SLIP, HDLC) can double the packet in the worst case. cobsr (COBS/R) can reach zero overhead. These bounds are what max_encoded_len and encoding_overhead return.

Benchmarks

Throughput on a 1 KiB payload (cargo bench, criterion), on an AMD Ryzen 7 3800XT under WSL2 — indicative, not a controlled benchmark:

Operation Throughput
COBS encode ~835 MiB/s
COBS decode ~1.40 GiB/s
COBS/R encode ~832 MiB/s

Integrations

The core stays no_std and dependency-free, so framework glue lives in your project rather than in the crate. INTEGRATIONS.md has verified copy-paste recipes — a tokio_util::codec for Framed streams, and the embedded-io shape for no_std targets — all built on the public API.

Background

Stuart Cheshire and Mary Baker, "Consistent Overhead Byte Stuffing", IEEE/ACM Transactions on Networking, Vol. 7, No. 2, April 1999. COBS/R is a variant by Craig McQueen.

License

MIT © 2026 Alexander Salas Bastidas (Firechip). See LICENSE.

About

no_std, dependency-free Consistent Overhead Byte Stuffing (COBS) and COBS/R codec for Rust. The Rust member of the Firechip COBS family.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages