feat: const fn encode, decode optimization, comprehensive docs and examples
- Make `encode` const fn for compile-time COBS encoding (MSRV bumped to 1.83) - Optimize decode by removing unnecessary branch in inner loop - Add comprehensive rustdoc with examples for every function - Add crate-level doc sections: quick start, buffer sizing, framing, stream parsing, compile-time encoding, error handling - Expand README with decision matrix, examples section, testing depth - Bump version to 0.2.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
3 files changed
+379
-42
No files matched your search
@@ -76,6 +76,7 @@ All three crates are tiny. The difference is negligible for any target.
|
||||
| `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.
|
||||
@@ -84,28 +85,164 @@ All three crates are tiny. The difference is negligible for any target.
|
||||
> Measured on Intel N100, Rust 1.93.1, Linux 6.12. Run `just bench` to
|
||||
> reproduce. Full Criterion reports are generated in `target/criterion/report/`.
|
||||
|
||||
## Usage
|
||||
## 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
|
||||
|
||||
```rust
|
||||
// Encode
|
||||
// Encode: [0x11, 0x00, 0x33] → [0x02, 0x11, 0x02, 0x33]
|
||||
let mut buf = [0u8; 16];
|
||||
let n = ucobs::encode(&[0x11, 0x00, 0x33], &mut buf).unwrap();
|
||||
assert_eq!(&buf[..n], &[0x02, 0x11, 0x02, 0x33]);
|
||||
|
||||
// Decode
|
||||
// Decode reverses it
|
||||
let mut out = [0u8; 16];
|
||||
let n = ucobs::decode(&[0x02, 0x11, 0x02, 0x33], &mut out).unwrap();
|
||||
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:
|
||||
|
||||
```rust
|
||||
let data = [0x01, 0x02, 0x03];
|
||||
let max = ucobs::max_encoded_len(data.len()); // 4 bytes
|
||||
|
||||
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:
|
||||
|
||||
```rust
|
||||
let mut tiny = [0u8; 1];
|
||||
assert_eq!(ucobs::encode(&[0x01, 0x02], &mut tiny), None);
|
||||
```
|
||||
|
||||
### Framing for transport
|
||||
|
||||
Append a `0x00` sentinel after encoding to delimit frames on a wire. Strip
|
||||
it before decoding:
|
||||
|
||||
```rust
|
||||
let data = [0x11, 0x00, 0x33];
|
||||
|
||||
// Encode and append sentinel
|
||||
let mut frame = [0u8; 16];
|
||||
let n = ucobs::encode(&data, &mut frame).unwrap();
|
||||
frame[n] = 0x00;
|
||||
let wire = &frame[..n + 1];
|
||||
|
||||
// Receive: strip sentinel, then decode
|
||||
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
|
||||
|
||||
Split a byte stream on `0x00` to extract individual COBS frames:
|
||||
|
||||
```rust
|
||||
// Two frames separated by 0x00 sentinels
|
||||
let stream = [
|
||||
0x02, 0x11, 0x02, 0x33, 0x00, // frame 1: [0x11, 0x00, 0x33]
|
||||
0x03, 0xAA, 0xBB, 0x00, // frame 2: [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]);
|
||||
|
||||
// Buffer sizing
|
||||
let max = ucobs::max_encoded_len(256); // 258
|
||||
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 tables at compile
|
||||
time with zero runtime cost:
|
||||
|
||||
```rust
|
||||
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"),
|
||||
}
|
||||
};
|
||||
|
||||
// Baked into the binary — no runtime encoding needed
|
||||
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:
|
||||
|
||||
```rust
|
||||
let mut buf = [0u8; 8];
|
||||
|
||||
// Destination too small
|
||||
assert_eq!(ucobs::encode(&[1, 2, 3], &mut buf[..1]), None);
|
||||
|
||||
// Malformed input (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
|
||||
assert_eq!(ucobs::encode(&[], &mut buf), Some(1)); // encodes to [0x01]
|
||||
assert_eq!(ucobs::decode(&[], &mut buf), Some(0)); // decodes to []
|
||||
```
|
||||
|
||||
## 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. |
|
||||
| `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. |
|
||||
|
||||
@@ -114,6 +251,25 @@ 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 Python `cobs` package
|
||||
- **Cross-crate interop** — byte-for-byte validation against both `corncobs` and
|
||||
`cobs` crates 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-fuzz` harnesses (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** — `const` assertions that validate encode
|
||||
correctness at compile time
|
||||
|
||||
```sh
|
||||
# Unit + property + interop tests
|
||||
just test-unit
|
||||
|
||||
Reference in new issue
Block a user