μCOBS
The COBS implementation for Rust.
Consistent Overhead Byte Stuffing
(COBS) encodes arbitrary bytes so that 0x00 never appears in the output,
allowing 0x00 to serve as an unambiguous frame delimiter. Overhead is exactly
1 byte per 254 input bytes (worst case).
Features
no_std, zero-alloc — runs anywhere, from 8-bit MCUs to servers- ~140 lines of implementation — small, auditable, easy to verify
- 100% spec-compliant — Cheshire & Baker, IEEE/ACM Transactions on Networking, 1999
- Proven interoperability — cross-validated against
corncobs,cobs, and Pythoncobs - Insanely tested — canonical vectors, property-based tests, fuzz targets, cross-crate interop, randomized payloads, and more coming
Performance
Benchmarked against the two other no_std COBS crates using
Criterion across three payload
patterns: all zeros (worst case — every byte triggers a code emit),
no zeros, and mixed (0x00–0xFF cycling). Throughput in MB/s, higher
is better.
Encode
| Payload | Pattern | μCOBS | cobs 0.3 |
corncobs 0.1 |
|---|---|---|---|---|
| 64 B | zeros | 595 | 748 | 180 |
| 64 B | mixed | 1,853 | 1,011 | 1,734 |
| 64 B | nonzero | 1,884 | 1,012 | 2,110 |
| 256 B | zeros | 654 | 817 | 184 |
| 256 B | mixed | 1,888 | 1,002 | 1,887 |
| 256 B | nonzero | 1,964 | 1,004 | 1,968 |
| 4096 B | zeros | 675 | 845 | 188 |
| 4096 B | mixed | 1,734 | 1,016 | 1,746 |
| 4096 B | nonzero | 1,775 | 1,020 | 2,245 |
Decode
| Payload | Pattern | μCOBS | cobs 0.3 |
corncobs 0.1 |
|---|---|---|---|---|
| 64 B | zeros | 595 | 440 | 732 |
| 64 B | mixed | 7,041 | 504 | 7,991 |
| 64 B | nonzero | 9,900 | 502 | 9,807 |
| 256 B | zeros | 656 | 472 | 742 |
| 256 B | mixed | 17,024 | 645 | 16,207 |
| 256 B | nonzero | 18,456 | 647 | 19,549 |
| 4096 B | zeros | 675 | 483 | 768 |
| 4096 B | mixed | 17,522 | 656 | 22,959 |
| 4096 B | nonzero | 37,987 | 623 | 40,005 |
Code size
Measured from .text section of release-optimized symbols (encode + decode
combined):
| Crate | Code size |
|---|---|
| μCOBS | 541 B |
cobs 0.3 |
537 B |
corncobs 0.1 |
637 B |
All three crates are tiny. The difference is negligible for any target.
Crate properties
| Property | μCOBS | cobs 0.3 |
corncobs 0.1 |
|---|---|---|---|
no_std |
always | opt-in[^1] | always |
| Zero-alloc | yes | opt-in[^2] | yes |
unsafe-free |
yes | yes | yes |
const fn encode |
yes | no | no |
| Implementation | ~120 LOC | ~790 LOC | ~645 LOC |
[^1]: Requires disabling the default std feature.
[^2]: alloc is enabled by default via the std feature.
Measured on Intel N100, Rust 1.93.1, Linux 6.12. Run
just benchto reproduce. Full Criterion reports are generated intarget/criterion/report/.
When to use μCOBS
| You need… | μCOBS | cobs 0.3 |
corncobs 0.1 |
|---|---|---|---|
| Minimal code to audit | ~120 LOC | ~790 LOC | ~645 LOC |
Compile-time (const fn) encode |
yes | no | no |
| Fastest encode (zero-heavy data) | 3–4× faster | baseline | slowest |
| Fastest decode (large payloads) | competitive | slow | slight edge |
| Dead-simple API (3 functions) | yes | yes | more surface area |
| In-place / streaming encode | no | no | yes |
no_std + zero-alloc by default |
yes | opt-in | yes |
| Thorough test suite | 106 tests, fuzz, proptest | basic | basic |
Choose μCOBS if you want the smallest, most auditable COBS implementation with a minimal API and strong performance — especially on embedded targets or payloads containing many zero bytes.
Choose corncobs if you need in-place encoding, an iterator-based API, or
your workload is dominated by large non-zero payloads where it holds a slight
throughput edge.
Choose cobs if you need std convenience features and don't care about
no_std or encode performance.
Examples
Basic encode and decode
// Encode: [0x11, 0x00, 0x33] → [0x02, 0x11, 0x02, 0x33]
let mut buf = ;
let n = encode.unwrap;
assert_eq!;
// Decode reverses it
let mut out = ;
let m = decode.unwrap;
assert_eq!;
Buffer sizing
Use max_encoded_len to determine the required destination buffer size:
let data = ;
let max = max_encoded_len; // 4 bytes
let mut buf = ;
let n = encode.unwrap;
assert_eq!; // fits exactly
If the destination is too small, encode returns None rather than panicking:
let mut tiny = ;
assert_eq!;
Framing for transport
Append a 0x00 sentinel after encoding to delimit frames on a wire. Strip
it before decoding:
let data = ;
// Encode and append sentinel
let mut frame = ;
let n = encode.unwrap;
frame = 0x00;
let wire = &frame;
// Receive: strip sentinel, then decode
let cobs_data = &wire;
let mut out = ;
let m = decode.unwrap;
assert_eq!;
Parsing a stream of frames
Split a byte stream on 0x00 to extract individual COBS frames:
// Two frames separated by 0x00 sentinels
let stream = ;
let mut out = ;
let frames: = stream.split
.filter
.collect;
let n = decode.unwrap;
assert_eq!;
let n = decode.unwrap;
assert_eq!;
Compile-time encoding
encode is const fn, so you can build COBS-encoded tables at compile
time with zero runtime cost:
const PING: = ;
const ACK: = ;
// Baked into the binary — no runtime encoding needed
assert_eq!;
assert_eq!;
Error handling
Both encode and decode return Option<usize> — None on failure,
never panic:
let mut buf = ;
// Destination too small
assert_eq!;
// Malformed input (zero byte in encoded stream)
assert_eq!;
// Truncated frame (code byte promises more data than exists)
assert_eq!;
// Empty input is valid
assert_eq!; // encodes to [0x01]
assert_eq!; // decodes to []
API
| Function | Signature | Description |
|---|---|---|
encode |
const fn(src: &[u8], dest: &mut [u8]) -> Option<usize> |
COBS-encode src into dest. Returns byte count or None if buffer too small. |
decode |
(src: &[u8], dest: &mut [u8]) -> Option<usize> |
Decode COBS data. Returns byte count or None if input is malformed. |
max_encoded_len |
const fn(src_len: usize) -> usize |
Maximum encoded size for a given input length. |
Note: The encoder does not append a trailing 0x00 sentinel. Append it
yourself when framing for transport.
Testing
μCOBS has one of the most thorough test suites of any COBS implementation — 106 tests across 7 categories:
- Canonical vectors — every example from Cheshire & Baker 1999 (the original COBS paper), plus the full Wikipedia vector set
- External corpora — vectors drawn from
cobs-c,nanocobs,cobs2-rs, Jacques Fortier's C implementation, and the Pythoncobspackage - Cross-crate interop — byte-for-byte validation against both
corncobsandcobscrates on randomized payloads up to 4 KB - Property-based tests — 6 proptest suites covering round-trip correctness, no-zero-in-output invariants, encoding properties, decoding properties, structural/algebraic properties, and boundary-region behavior
- Fuzz targets — 3
cargo-fuzzharnesses (decode, round-trip, small-dest) for continuous coverage of edge cases - Boundary tests — exhaustive coverage of 254/255-byte block boundaries, multi-block splits, buffer-exact fits, and off-by-one dest sizes
- Compile-time verification —
constassertions that validate encode correctness at compile time
# Unit + property + interop tests
# All quality gates (tests, clippy, fmt, doc, fuzz, miri)
# Fuzz testing only (requires nightly + cargo-fuzz)
License
MIT — Stephen Waits