Benchmarking & comparison: - Add criterion benchmarks (ucobs vs cobs 0.3 vs corncobs 0.1) - Add Python cobs benchmark script - Add performance comparison section to README with full results Performance: - Rewrite encode to scan-and-bulk-copy via copy_from_slice - Rewrite decode to bulk-copy via copy_from_slice - ~2-4x throughput improvement; now matches corncobs on most workloads Quality hardening: - Add #![forbid(unsafe_code)], #![warn(missing_docs)], #[must_use] - Add [lints.rust] unsafe_code = "forbid" in Cargo.toml - Add rust-version (MSRV) = "1.56" - Add docs.rs metadata - 106 tests: 80 unit/corpus + 25 property tests (25k cases each = 625k total) - External test vectors from: Wikipedia, Cheshire & Baker paper, nanocobs, cobs-c, cobs2-rs, Jacques Fortier, Python cobs package - Add fuzz_small_dest fuzz target (encode/decode with undersized buffers) - Improve doc examples to show sentinel append/strip convention Infrastructure: - Add justfile with test (nextest), check, bench, fuzz, miri, fmt targets - cargo-nextest for parallel test execution - Update .gitignore for Python cache files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.9 KiB
μ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-in1 | always |
| Zero-alloc | yes | opt-in2 | yes |
unsafe-free |
yes | yes | yes |
| Implementation | ~120 LOC | ~790 LOC | ~645 LOC |
Measured on Intel N100, Rust 1.93.1, Linux 6.12. Run
cargo benchto reproduce. Full Criterion reports are generated intarget/criterion/report/.
Usage
// 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 n = ucobs::decode(&[0x02, 0x11, 0x02, 0x33], &mut out).unwrap();
assert_eq!(&out[..n], &[0x11, 0x00, 0x33]);
// Buffer sizing
let max = ucobs::max_encoded_len(256); // 258
API
| Function | Signature | Description |
|---|---|---|
encode |
(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
# Unit + property + interop tests
cargo test
# Fuzz testing (requires nightly + cargo-fuzz)
cd fuzz
cargo +nightly fuzz run fuzz_decode -- -max_total_time=60
cargo +nightly fuzz run fuzz_roundtrip -- -max_total_time=60
License
MIT — Stephen Waits