Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee40455c5f
|
||
|
|
1eb7e09412
|
||
|
|
f805386bb8
|
||
|
|
f6778e20b8
|
||
|
|
aa09783cdd
|
||
|
|
b91fbb2ed9
|
||
|
|
777a37d4cd
|
||
|
|
ff883d6f8e
|
||
|
|
63ebaa0232
|
||
|
|
f4665a46cb
|
||
|
|
92021a2608
|
||
|
|
adb0db7ee5
|
||
|
|
3d674989ab
|
||
|
|
531f5311d8
|
||
|
|
a5e3e5f63b
|
No files matched your search
@@ -0,0 +1,18 @@
|
|||||||
|
# Use nextest for faster test execution
|
||||||
|
test_tool = "nextest"
|
||||||
|
|
||||||
|
# Skip slow proptests during mutation testing — the 60+ unit/interop tests
|
||||||
|
# are sufficient to catch mutants. Full proptests run separately via just test-unit.
|
||||||
|
additional_cargo_test_args = ["-E", "not test(proptests)"]
|
||||||
|
|
||||||
|
# False positives excluded:
|
||||||
|
#
|
||||||
|
# - `> 0` → `>= 0` or `< 0` on usize: tautological (usize always >= 0)
|
||||||
|
# - `< 254` → `<= 254` in max_run: both branches yield 254 at the boundary
|
||||||
|
#
|
||||||
|
# The legacy-msrv const_copy is excluded via #[mutants::skip] in source.
|
||||||
|
exclude_re = [
|
||||||
|
"replace > with < in encode",
|
||||||
|
"replace > with >= in encode",
|
||||||
|
"replace < with <= in encode",
|
||||||
|
]
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test (${{ matrix.rust }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
rust: [stable, "1.93"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
- run: cargo test
|
||||||
|
|
||||||
|
test-legacy-msrv:
|
||||||
|
name: Test (1.83, legacy-msrv)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: "1.83"
|
||||||
|
- run: cargo test --features legacy-msrv
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: Clippy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy
|
||||||
|
- run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
name: Format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
|
- run: cargo fmt -- --check
|
||||||
|
|
||||||
|
doc:
|
||||||
|
name: Doc
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: cargo doc --no-deps
|
||||||
|
|
||||||
|
deny:
|
||||||
|
name: Cargo Deny
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
@@ -2,4 +2,8 @@
|
|||||||
/fuzz/target
|
/fuzz/target
|
||||||
/fuzz/corpus
|
/fuzz/corpus
|
||||||
/fuzz/artifacts
|
/fuzz/artifacts
|
||||||
|
/mutants.out
|
||||||
|
/mutants.out.old
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.3.4 — 2026-04-01
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Encode shrunk by 41 bytes** — simplified branch structure in encoder exit
|
||||||
|
path. 388 B encode, 392 B decode, 780 B total (was 821 B). Zero benchmark
|
||||||
|
regressions.
|
||||||
|
- **Clarified decode invariant** — added inline comment explaining the subtle
|
||||||
|
trailing-0x01 COBS rule (end-of-data marker vs zero byte).
|
||||||
|
- **Clippy cleanup** — `saturating_sub` in proptest (cosmetic, test-only).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **GitHub Actions CI** — tests on stable + MSRV 1.93 + 1.83/legacy-msrv,
|
||||||
|
clippy, fmt, doc, and cargo-deny.
|
||||||
|
- **`cargo-deny` integration** — `deny.toml` with permissive-only license
|
||||||
|
policy, advisory checks, source auditing. Added `just test-deny` gate.
|
||||||
|
- **Encode-only fuzz target** — `fuzz_encode` exercises buffer-too-small
|
||||||
|
paths and validates output properties independently of decode.
|
||||||
|
- **Feature coverage** — `just test-unit` and `just test-clippy` now test
|
||||||
|
both default and `legacy-msrv` features. `just test-doc` builds with
|
||||||
|
`--all-features`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **README accuracy** — overhead description changed from "exactly" to
|
||||||
|
"at most ... plus 1", added miri to test mentions, updated code size
|
||||||
|
numbers.
|
||||||
|
- **CLAUDE.md rewrite** — updated stale API signatures, LOC count, roadmap,
|
||||||
|
dev-deps list, and development commands.
|
||||||
|
|
||||||
|
## 0.3.3 — 2026-04-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Fix compilation for downstream users** — `#[mutants::skip]` attribute was
|
||||||
|
not gated behind `cfg(test)`, causing unresolved module error when ucobs is
|
||||||
|
used as a dependency.
|
||||||
|
|
||||||
|
## 0.3.2 — 2026-04-01 (yanked)
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- **Mutation testing: 100% kill rate** — 89 mutants tested, 59 caught, 30
|
||||||
|
unviable (type system rejects them), 0 missed. Uses `cargo-mutants` v27
|
||||||
|
with nextest. Added `mutants` crate for `#[mutants::skip]` annotations.
|
||||||
|
- **111 tests** (was 106): added block-boundary interop verification against
|
||||||
|
corncobs, exact-fit and too-small decode buffer sizing for the batch zero
|
||||||
|
path.
|
||||||
|
|
||||||
|
## 0.3.1 — 2026-03-31
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- **Decode: rewritten with shrinking-slice pattern** — decode zeros is 14%
|
||||||
|
faster (25,018 vs 29,114 instructions at 4 KB), decode nonzero flipped from
|
||||||
|
5% behind corncobs to 2% ahead, decode mixed closed from 8% behind to tied.
|
||||||
|
ucobs now wins or ties 16 of 18 benchmarks.
|
||||||
|
- **Encode zeros: 6% faster** — zero fast path moved before `split_at` calls,
|
||||||
|
eliminating per-byte overhead. Now tied with cobs (previously 6% behind).
|
||||||
|
- **Code size reduced** — 821 B total (was 869 B). Encode 429 B, decode 392 B.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Replaced criterion with iai-callgrind** — benchmarks now measure
|
||||||
|
deterministic instruction counts via Valgrind instead of wall-clock time.
|
||||||
|
Zero run-to-run variance. Requires `valgrind` installed.
|
||||||
|
- **Expanded benchmark coverage** — 9 payload sizes (0–4096 B) × 3 patterns
|
||||||
|
× 3 crates × encode/decode = 162 benchmarks, all running in ~2 minutes.
|
||||||
|
- **Code size measurement** added to `just bench` via `just bench-size`.
|
||||||
|
- **Mutation testing** added via `cargo-mutants` (`just test-mutants`).
|
||||||
|
- Added `mise.toml` for dev tool management.
|
||||||
|
|
||||||
|
## 0.3.0 — 2026-03-31
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- **Encode nonzero/mixed: ~2x faster** — uses const `copy_from_slice` (memcpy at
|
||||||
|
runtime) via Rust 1.93's stabilization of const slice methods. Sub-slice scan
|
||||||
|
pattern helps LLVM eliminate bounds checks.
|
||||||
|
- **Encode zeros: ~50% faster** — fast path skips scan loop overhead for leading
|
||||||
|
zero bytes.
|
||||||
|
- **Decode zeros: ~4x faster** — batch consecutive `0x01` codes and fill zeros
|
||||||
|
via `fill(0)` (memset) instead of per-byte iteration.
|
||||||
|
- Encode now wins or ties corncobs on nonzero/mixed workloads.
|
||||||
|
- Decode zeros now leads all competitors by 3x.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `legacy-msrv` feature flag — enables compilation on Rust 1.83+ (e.g. Xtensa
|
||||||
|
toolchain) by replacing const `copy_from_slice` with a manual byte loop.
|
||||||
|
All other optimizations (sub-slice scan, zero fast path, decode batch fill)
|
||||||
|
work without this feature on any supported Rust version.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **MSRV bumped from 1.83 to 1.93** — required for const `copy_from_slice`.
|
||||||
|
Use the `legacy-msrv` feature for older toolchains.
|
||||||
|
- Dev-dependencies updated: `cobs` 0.3 to 0.5, `criterion` 0.5 to 0.8,
|
||||||
|
`rand` 0.8 to 0.10.
|
||||||
|
|
||||||
|
## 0.2.0 — 2026-03-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `const fn` encode — encode data at compile time with zero runtime cost.
|
||||||
|
- Comprehensive documentation with 7 usage examples (basic, buffer sizing,
|
||||||
|
framing, stream parsing, compile-time encoding, error handling).
|
||||||
|
- Initial encode/decode throughput optimizations.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- MSRV set to 1.83.
|
||||||
|
|
||||||
|
## 0.1.0 — 2026-03-29
|
||||||
|
|
||||||
|
- Initial release.
|
||||||
|
- `no_std`, zero-alloc, `unsafe`-free COBS encoder and decoder.
|
||||||
|
- 106 tests: canonical vectors, property-based (proptest), fuzz targets,
|
||||||
|
cross-crate interop with `corncobs` and `cobs`.
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
1. **Correct** — 100% COBS spec-compliant (Cheshire & Baker 1999). No shortcuts, no deviations.
|
1. **Correct** — 100% COBS spec-compliant (Cheshire & Baker 1999). No shortcuts, no deviations.
|
||||||
2. **Interoperable** — proven byte-for-byte compatibility with `corncobs`, `cobs` crates and Python `cobs` packages.
|
2. **Interoperable** — proven byte-for-byte compatibility with `corncobs`, `cobs` crates and Python `cobs` packages.
|
||||||
3. **Insanely tested** — canonical vectors, property-based tests (proptest), fuzz targets (cargo-fuzz), cross-crate interop, randomized payloads. If you think testing is done, you're 10% done.
|
3. **Insanely tested** — canonical vectors, property-based tests (proptest), fuzz targets (cargo-fuzz), cross-crate interop, miri, mutation testing, randomized payloads. If you think testing is done, you're 10% done.
|
||||||
4. **Fast** — benchmark against every competing implementation. Must be at least as fast, ideally fastest. Benchmarks are CI-enforced.
|
4. **Fast** — benchmark against every competing implementation. Must be at least as fast, ideally fastest. Benchmarks run via `just bench`.
|
||||||
5. **Tiny** — `no_std`, zero-alloc, zero runtime dependencies. ~140 lines of implementation. Runs on 8-bit MCUs and servers alike.
|
5. **Tiny** — `no_std`, zero-alloc, zero runtime dependencies. ~140 lines of implementation. Runs on 8-bit MCUs and servers alike.
|
||||||
6. **Trustworthy** — small enough to audit by hand. No unsafe. No panics in release. Fuzz-hardened.
|
6. **Trustworthy** — small enough to audit by hand. No unsafe. No panics in release. Fuzz-hardened.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Single-file library: `src/lib.rs`
|
|||||||
### Public API (3 functions)
|
### Public API (3 functions)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn encode(src: &[u8], dest: &mut [u8]) -> Option<usize>
|
pub const fn encode(src: &[u8], dest: &mut [u8]) -> Option<usize>
|
||||||
pub fn decode(src: &[u8], dest: &mut [u8]) -> Option<usize>
|
pub fn decode(src: &[u8], dest: &mut [u8]) -> Option<usize>
|
||||||
pub const fn max_encoded_len(src_len: usize) -> usize
|
pub const fn max_encoded_len(src_len: usize) -> usize
|
||||||
```
|
```
|
||||||
@@ -32,26 +32,29 @@ pub const fn max_encoded_len(src_len: usize) -> usize
|
|||||||
## Development
|
## Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Run all tests
|
# Run all quality gates (tests, clippy, fmt, doc, fuzz, miri, mutants)
|
||||||
cargo test
|
just test
|
||||||
|
|
||||||
|
# Unit + property + interop tests only
|
||||||
|
just test-unit
|
||||||
|
|
||||||
|
# Benchmarks (iai-callgrind, requires valgrind)
|
||||||
|
just bench
|
||||||
|
|
||||||
# Fuzz (requires nightly + cargo-fuzz)
|
# Fuzz (requires nightly + cargo-fuzz)
|
||||||
cd fuzz
|
just test-fuzz
|
||||||
cargo +nightly fuzz run fuzz_decode -- -max_total_time=60
|
|
||||||
cargo +nightly fuzz run fuzz_roundtrip -- -max_total_time=60
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dev Dependencies (test-only)
|
## Dev Dependencies (test-only)
|
||||||
|
|
||||||
|
- `cobs` — cross-validation against the `cobs` 0.5 crate
|
||||||
- `corncobs` — cross-validation against another COBS implementation
|
- `corncobs` — cross-validation against another COBS implementation
|
||||||
|
- `iai-callgrind` — deterministic instruction-count benchmarks
|
||||||
|
- `mutants` — mutation testing annotations
|
||||||
- `proptest` — property-based testing
|
- `proptest` — property-based testing
|
||||||
- `rand` — randomized test payloads
|
- `rand` — randomized test payloads
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- More interop targets: `cobs` crate, Python `cobs` package
|
|
||||||
- Benchmarks (criterion) vs corncobs, cobs, and others
|
|
||||||
- quickcheck in addition to proptest
|
- quickcheck in addition to proptest
|
||||||
- Even more fuzz targets and edge-case generators
|
- Even more fuzz targets and edge-case generators
|
||||||
- MSRV policy and CI
|
|
||||||
- `#[deny(unsafe_code)]` badge
|
|
||||||
+19
-4
@@ -1,12 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ucobs"
|
name = "ucobs"
|
||||||
version = "0.1.0"
|
version = "0.3.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.93"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "μCOBS — Consistent Overhead Byte Stuffing. no_std, zero-alloc, fastest, most-tested COBS implementation."
|
description = "μCOBS — Consistent Overhead Byte Stuffing. no_std, zero-alloc, fastest, most-tested COBS implementation."
|
||||||
authors = ["Stephen Waits <steve@waits.net>"]
|
authors = ["Stephen Waits <steve@waits.net>"]
|
||||||
repository = "https://github.com/swaits/ucobs"
|
repository = "https://git.swaits.com/swaits/ucobs"
|
||||||
homepage = "https://github.com/swaits/ucobs"
|
homepage = "https://git.swaits.com/swaits/ucobs"
|
||||||
documentation = "https://docs.rs/ucobs"
|
documentation = "https://docs.rs/ucobs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["cobs", "framing", "no-std", "embedded", "codec"]
|
keywords = ["cobs", "framing", "no-std", "embedded", "codec"]
|
||||||
@@ -14,8 +15,22 @@ categories = ["encoding", "no-std::no-alloc", "embedded"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
legacy-msrv = []
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
cobs = "0.5"
|
||||||
corncobs = "0.1"
|
corncobs = "0.1"
|
||||||
|
iai-callgrind = "0.16"
|
||||||
|
mutants = "0.0.4"
|
||||||
proptest = "1"
|
proptest = "1"
|
||||||
rand = "0.8"
|
rand = "0.10"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "bench"
|
||||||
|
harness = false
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
[Consistent Overhead Byte Stuffing](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing)
|
[Consistent Overhead Byte Stuffing](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing)
|
||||||
(COBS) encodes arbitrary bytes so that `0x00` never appears in the output,
|
(COBS) encodes arbitrary bytes so that `0x00` never appears in the output,
|
||||||
allowing `0x00` to serve as an unambiguous frame delimiter. Overhead is exactly
|
allowing `0x00` to serve as an unambiguous frame delimiter. Overhead is at most
|
||||||
1 byte per 254 input bytes (worst case).
|
1 byte per 254 input bytes, plus 1.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -18,46 +18,299 @@ allowing `0x00` to serve as an unambiguous frame delimiter. Overhead is exactly
|
|||||||
- **100% spec-compliant** — Cheshire & Baker, IEEE/ACM Transactions on Networking, 1999
|
- **100% spec-compliant** — Cheshire & Baker, IEEE/ACM Transactions on Networking, 1999
|
||||||
- **Proven interoperability** — cross-validated against `corncobs`, `cobs`, and Python `cobs`
|
- **Proven interoperability** — cross-validated against `corncobs`, `cobs`, and Python `cobs`
|
||||||
- **Insanely tested** — canonical vectors, property-based tests, fuzz targets,
|
- **Insanely tested** — canonical vectors, property-based tests, fuzz targets,
|
||||||
cross-crate interop, randomized payloads, and more coming
|
cross-crate interop, miri, mutation testing, randomized payloads
|
||||||
|
|
||||||
## Usage
|
## Performance
|
||||||
|
|
||||||
|
Benchmarked against the two other `no_std` COBS crates using
|
||||||
|
[iai-callgrind](https://github.com/iai-callgrind/iai-callgrind) (instruction
|
||||||
|
counts via Valgrind — deterministic, zero run-to-run variance). Three payload
|
||||||
|
patterns: **all zeros** (worst case), **no zeros**, and **mixed** (0x00–0xFF
|
||||||
|
cycling). Instruction count per call, lower is better.
|
||||||
|
Legend: 🥇 winner, 🥉 last, 🤝 tie (<3%).
|
||||||
|
|
||||||
|
### Encode
|
||||||
|
|
||||||
|
| Payload | Pattern | μCOBS | `cobs` 0.5 | `corncobs` 0.1 |
|
||||||
|
|---------|---------|------:|-----------:|---------------:|
|
||||||
|
| 64 B | zeros | 🤝 1,171 | 🤝 1,164 | 🥉 4,098 |
|
||||||
|
| 64 B | nonzero | 🤝 512 | 🥉 1,100 | 🤝 514 |
|
||||||
|
| 64 B | mixed | 🥇 523 | 🥉 1,101 | 570 |
|
||||||
|
| 256 B | zeros | 🤝 4,243 | 🤝 4,236 | 🥉 15,810 |
|
||||||
|
| 256 B | nonzero | 🤝 1,552 | 🥉 3,992 | 🤝 1,531 |
|
||||||
|
| 256 B | mixed | 🤝 1,561 | 🥉 3,993 | 🤝 1,585 |
|
||||||
|
| 4096 B | zeros | 🤝 65,904 | 🤝 65,897 | 🥉 250,271 |
|
||||||
|
| 4096 B | nonzero | 🤝 22,077 | 🥉 61,993 | 🤝 21,711 |
|
||||||
|
| 4096 B | mixed | 🤝 22,932 | 🥉 62,009 | 🤝 22,656 |
|
||||||
|
|
||||||
|
### Decode
|
||||||
|
|
||||||
|
| Payload | Pattern | μCOBS | `cobs` 0.5 | `corncobs` 0.1 |
|
||||||
|
|---------|---------|------:|-----------:|---------------:|
|
||||||
|
| 64 B | zeros | 🥇 499 | 🥉 2,354 | 1,507 |
|
||||||
|
| 64 B | nonzero | 🥇 179 | 🥉 1,778 | 185 |
|
||||||
|
| 64 B | mixed | 226 | 🥉 1,787 | 🥇 206 |
|
||||||
|
| 256 B | zeros | 🥇 1,473 | 🥉 8,882 | 5,539 |
|
||||||
|
| 256 B | nonzero | 🤝 240 | 🥉 6,607 | 🤝 247 |
|
||||||
|
| 256 B | mixed | 285 | 🥉 6,613 | 🥇 266 |
|
||||||
|
| 4096 B | zeros | 🥇 25,018 | 🥉 139,698 | 86,435 |
|
||||||
|
| 4096 B | nonzero | 🤝 1,315 | 🥉 103,253 | 🤝 1,337 |
|
||||||
|
| 4096 B | mixed | 🤝 2,086 | 🥉 103,394 | 🤝 2,067 |
|
||||||
|
|
||||||
|
### Code size
|
||||||
|
|
||||||
|
Measured from `.text` section of release-optimized symbols (`encode` + `decode`
|
||||||
|
combined). Run `just bench-size` to reproduce.
|
||||||
|
|
||||||
|
| Crate | `encode` | `decode` | Total |
|
||||||
|
|----------------|--------:|---------:|------:|
|
||||||
|
| μCOBS | 388 B | 392 B | 780 B |
|
||||||
|
| `cobs` 0.5 | 282 B | 494 B | 776 B |
|
||||||
|
| `corncobs` 0.1 | 375 B | 262 B | 637 B |
|
||||||
|
|
||||||
|
All three crates are under 1 KB. μCOBS and `cobs` are nearly tied;
|
||||||
|
`corncobs` is the smallest. μCOBS's safe `const fn` encoding with
|
||||||
|
`copy_from_slice`/memcpy requires `split_at` bounds checks that
|
||||||
|
generate panic paths — the cost of combining compile-time safety with
|
||||||
|
runtime speed. Embedded users targeting size over speed can build with
|
||||||
|
`opt-level = "s"` to shrink further.
|
||||||
|
|
||||||
|
### Crate properties
|
||||||
|
|
||||||
|
| Property | μCOBS | `cobs` 0.5 | `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 | ~140 LOC | ~790 LOC | ~645 LOC |
|
||||||
|
|
||||||
|
[^1]: Requires disabling the default `std` feature.
|
||||||
|
[^2]: `alloc` is enabled by default via the `std` feature.
|
||||||
|
|
||||||
|
> Benchmarked with iai-callgrind (Valgrind instruction counting) for
|
||||||
|
> deterministic, reproducible results. Run `just bench` to reproduce.
|
||||||
|
> Requires `valgrind` installed.
|
||||||
|
|
||||||
|
## When to use μCOBS
|
||||||
|
|
||||||
|
| You need… | μCOBS | `cobs` 0.5 | `corncobs` 0.1 |
|
||||||
|
|---|:---:|:---:|:---:|
|
||||||
|
| Minimal code to audit | **~140 LOC** | ~790 LOC | ~645 LOC |
|
||||||
|
| Compile-time (`const fn`) encode | **yes** | no | no |
|
||||||
|
| Fewest encode instructions | **ties cobs/corncobs** | zeros only | nonzero only |
|
||||||
|
| Fewest decode instructions (zero-heavy) | **yes (3–5×)** | no | 2nd |
|
||||||
|
| Fewest decode instructions (nonzero) | **yes/tied** | no | tied |
|
||||||
|
| Fewest decode instructions (mixed) | tied at 4 KB | no | **slight edge at small sizes** |
|
||||||
|
| 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 | **111 tests, fuzz, proptest, miri, mutants** | basic | basic |
|
||||||
|
|
||||||
|
**Choose μCOBS** if you want the smallest, most auditable COBS implementation
|
||||||
|
with a `const fn` encoder, a dead-simple 3-function API, and leading or tied
|
||||||
|
instruction efficiency across most workloads — plus dominant decode performance
|
||||||
|
on zero-heavy data (3–5× fewer instructions than alternatives). Wins or ties
|
||||||
|
16 of 18 benchmarks.
|
||||||
|
|
||||||
|
**Choose `corncobs`** if you need in-place encoding, an iterator-based API,
|
||||||
|
or your workload is dominated by small mixed-pattern decoding where it uses
|
||||||
|
~8% fewer instructions at 64–256 B.
|
||||||
|
|
||||||
|
**Choose `cobs`** if you need `std` convenience features or a streaming state
|
||||||
|
machine. Note: `cobs` uses 50–75× more instructions than μCOBS/corncobs for
|
||||||
|
decoding non-zero data.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic encode and decode
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Encode
|
// Encode: [0x11, 0x00, 0x33] → [0x02, 0x11, 0x02, 0x33]
|
||||||
let mut buf = [0u8; 16];
|
let mut buf = [0u8; 16];
|
||||||
let n = ucobs::encode(&[0x11, 0x00, 0x33], &mut buf).unwrap();
|
let n = ucobs::encode(&[0x11, 0x00, 0x33], &mut buf).unwrap();
|
||||||
assert_eq!(&buf[..n], &[0x02, 0x11, 0x02, 0x33]);
|
assert_eq!(&buf[..n], &[0x02, 0x11, 0x02, 0x33]);
|
||||||
|
|
||||||
// Decode
|
// Decode reverses it
|
||||||
let mut out = [0u8; 16];
|
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]);
|
assert_eq!(&out[..n], &[0x11, 0x00, 0x33]);
|
||||||
|
|
||||||
// Buffer sizing
|
let n = ucobs::decode(frames[1], &mut out).unwrap();
|
||||||
let max = ucobs::max_encoded_len(256); // 258
|
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
|
## API
|
||||||
|
|
||||||
| Function | Signature | Description |
|
| 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. |
|
| `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. |
|
| `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
|
**Note:** The encoder does *not* append a trailing `0x00` sentinel. Append it
|
||||||
yourself when framing for transport.
|
yourself when framing for transport.
|
||||||
|
|
||||||
|
## Minimum Supported Rust Version
|
||||||
|
|
||||||
|
The default build requires **Rust 1.93+** (for const `copy_from_slice`).
|
||||||
|
|
||||||
|
### `legacy-msrv` feature
|
||||||
|
|
||||||
|
If you're targeting a toolchain older than 1.93 (e.g. Xtensa), enable the
|
||||||
|
`legacy-msrv` feature to fall back to a manual byte copy in the encoder:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
ucobs = { version = "0.3", features = ["legacy-msrv"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
All other optimizations (sub-slice scan, zero fast path, decode batch fill)
|
||||||
|
are available regardless of this feature. Only the encode copy phase is
|
||||||
|
affected — it uses a byte loop instead of `copy_from_slice`/memcpy.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
μCOBS has one of the most thorough test suites of any COBS implementation —
|
||||||
|
111 tests across 8 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
|
||||||
|
- **Mutation testing** — `cargo-mutants` with 100% kill rate (59/59 viable
|
||||||
|
mutants caught; 30 more rejected by the type system)
|
||||||
|
- **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
|
```sh
|
||||||
# Unit + property + interop tests
|
# Unit + property + interop tests
|
||||||
cargo test
|
just test-unit
|
||||||
|
|
||||||
# Fuzz testing (requires nightly + cargo-fuzz)
|
# All quality gates (tests, clippy, fmt, doc, fuzz, miri, mutants)
|
||||||
cd fuzz
|
just test
|
||||||
cargo +nightly fuzz run fuzz_decode -- -max_total_time=60
|
|
||||||
cargo +nightly fuzz run fuzz_roundtrip -- -max_total_time=60
|
# Mutation testing only
|
||||||
|
just test-mutants
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Benchmark Python cobs encode/decode vs ucobs (reference timing only).
|
||||||
|
|
||||||
|
Requires: pip install cobs
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def payload_zeros(n: int) -> bytes:
|
||||||
|
return b"\x00" * n
|
||||||
|
|
||||||
|
|
||||||
|
def payload_nonzero(n: int) -> bytes:
|
||||||
|
return bytes((i % 255 + 1) for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def payload_mixed(n: int) -> bytes:
|
||||||
|
return bytes(i % 256 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def bench(func, data, iterations=10_000):
|
||||||
|
start = time.perf_counter()
|
||||||
|
for _ in range(iterations):
|
||||||
|
func(data)
|
||||||
|
elapsed = time.perf_counter() - start
|
||||||
|
return elapsed
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
from cobs import cobs as cobs_mod
|
||||||
|
except ImportError:
|
||||||
|
print("ERROR: python cobs package not installed. Run: pip install cobs")
|
||||||
|
return
|
||||||
|
|
||||||
|
sizes = [0, 1, 10, 64, 254, 255, 256, 1024, 4096]
|
||||||
|
payloads = [
|
||||||
|
("zeros", payload_zeros),
|
||||||
|
("nonzero", payload_nonzero),
|
||||||
|
("mixed", payload_mixed),
|
||||||
|
]
|
||||||
|
iterations = 10_000
|
||||||
|
|
||||||
|
print(f"{'operation':<30} {'size':>6} {'iters':>7} {'total_ms':>10} {'per_op_us':>10} {'MB/s':>10}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
for name, make in payloads:
|
||||||
|
for size in sizes:
|
||||||
|
data = make(size)
|
||||||
|
|
||||||
|
# Encode
|
||||||
|
elapsed = bench(cobs_mod.encode, data, iterations)
|
||||||
|
per_op_us = (elapsed / iterations) * 1_000_000
|
||||||
|
mbps = (size * iterations / elapsed / 1_000_000) if elapsed > 0 and size > 0 else 0
|
||||||
|
print(f"{'python-cobs encode/' + name:<30} {size:>6} {iterations:>7} {elapsed*1000:>10.2f} {per_op_us:>10.3f} {mbps:>10.1f}")
|
||||||
|
|
||||||
|
# Decode
|
||||||
|
encoded = cobs_mod.encode(data)
|
||||||
|
elapsed = bench(cobs_mod.decode, encoded, iterations)
|
||||||
|
per_op_us = (elapsed / iterations) * 1_000_000
|
||||||
|
mbps = (size * iterations / elapsed / 1_000_000) if elapsed > 0 and size > 0 else 0
|
||||||
|
print(f"{'python-cobs decode/' + name:<30} {size:>6} {iterations:>7} {elapsed*1000:>10.2f} {per_op_us:>10.3f} {mbps:>10.1f}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
|
||||||
|
use std::hint::black_box;
|
||||||
|
|
||||||
|
// ── Test payloads ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn zeros(n: usize) -> Vec<u8> {
|
||||||
|
vec![0u8; n]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonzero(n: usize) -> Vec<u8> {
|
||||||
|
(0..n).map(|i| (i % 255 + 1) as u8).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mixed(n: usize) -> Vec<u8> {
|
||||||
|
(0..n).map(|i| (i % 256) as u8).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Setup helpers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn setup_encode(data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let buf = vec![0u8; ucobs::max_encoded_len(data.len()) + 1];
|
||||||
|
(data, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_decode_ucobs(data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut enc = vec![0u8; ucobs::max_encoded_len(data.len()) + 1];
|
||||||
|
let n = ucobs::encode(&data, &mut enc).unwrap();
|
||||||
|
enc.truncate(n);
|
||||||
|
let dec = vec![0u8; data.len() + 1];
|
||||||
|
(enc, dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_encode_corncobs(data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let buf = vec![0u8; corncobs::max_encoded_len(data.len())];
|
||||||
|
(data, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_decode_corncobs(data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut enc = vec![0u8; corncobs::max_encoded_len(data.len())];
|
||||||
|
let n = corncobs::encode_buf(&data, &mut enc);
|
||||||
|
enc.truncate(n);
|
||||||
|
let dec = vec![0u8; data.len() + 1];
|
||||||
|
(enc, dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_encode_cobs(data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let buf = vec![0u8; cobs::max_encoding_length(data.len())];
|
||||||
|
(data, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_decode_cobs(data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut enc = vec![0u8; cobs::max_encoding_length(data.len())];
|
||||||
|
let n = cobs::encode(&data, &mut enc);
|
||||||
|
enc.truncate(n);
|
||||||
|
let dec = vec![0u8; data.len() + 1];
|
||||||
|
(enc, dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Encode benchmarks ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[library_benchmark]
|
||||||
|
#[bench::zeros_0(setup_encode(zeros(0)))]
|
||||||
|
#[bench::zeros_1(setup_encode(zeros(1)))]
|
||||||
|
#[bench::zeros_10(setup_encode(zeros(10)))]
|
||||||
|
#[bench::zeros_64(setup_encode(zeros(64)))]
|
||||||
|
#[bench::zeros_254(setup_encode(zeros(254)))]
|
||||||
|
#[bench::zeros_255(setup_encode(zeros(255)))]
|
||||||
|
#[bench::zeros_256(setup_encode(zeros(256)))]
|
||||||
|
#[bench::zeros_1024(setup_encode(zeros(1024)))]
|
||||||
|
#[bench::zeros_4096(setup_encode(zeros(4096)))]
|
||||||
|
#[bench::nonzero_0(setup_encode(nonzero(0)))]
|
||||||
|
#[bench::nonzero_1(setup_encode(nonzero(1)))]
|
||||||
|
#[bench::nonzero_10(setup_encode(nonzero(10)))]
|
||||||
|
#[bench::nonzero_64(setup_encode(nonzero(64)))]
|
||||||
|
#[bench::nonzero_254(setup_encode(nonzero(254)))]
|
||||||
|
#[bench::nonzero_255(setup_encode(nonzero(255)))]
|
||||||
|
#[bench::nonzero_256(setup_encode(nonzero(256)))]
|
||||||
|
#[bench::nonzero_1024(setup_encode(nonzero(1024)))]
|
||||||
|
#[bench::nonzero_4096(setup_encode(nonzero(4096)))]
|
||||||
|
#[bench::mixed_0(setup_encode(mixed(0)))]
|
||||||
|
#[bench::mixed_1(setup_encode(mixed(1)))]
|
||||||
|
#[bench::mixed_10(setup_encode(mixed(10)))]
|
||||||
|
#[bench::mixed_64(setup_encode(mixed(64)))]
|
||||||
|
#[bench::mixed_254(setup_encode(mixed(254)))]
|
||||||
|
#[bench::mixed_255(setup_encode(mixed(255)))]
|
||||||
|
#[bench::mixed_256(setup_encode(mixed(256)))]
|
||||||
|
#[bench::mixed_1024(setup_encode(mixed(1024)))]
|
||||||
|
#[bench::mixed_4096(setup_encode(mixed(4096)))]
|
||||||
|
fn encode_ucobs((data, mut buf): (Vec<u8>, Vec<u8>)) {
|
||||||
|
black_box(ucobs::encode(black_box(&data), &mut buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[library_benchmark]
|
||||||
|
#[bench::zeros_0(setup_encode_corncobs(zeros(0)))]
|
||||||
|
#[bench::zeros_1(setup_encode_corncobs(zeros(1)))]
|
||||||
|
#[bench::zeros_10(setup_encode_corncobs(zeros(10)))]
|
||||||
|
#[bench::zeros_64(setup_encode_corncobs(zeros(64)))]
|
||||||
|
#[bench::zeros_254(setup_encode_corncobs(zeros(254)))]
|
||||||
|
#[bench::zeros_255(setup_encode_corncobs(zeros(255)))]
|
||||||
|
#[bench::zeros_256(setup_encode_corncobs(zeros(256)))]
|
||||||
|
#[bench::zeros_1024(setup_encode_corncobs(zeros(1024)))]
|
||||||
|
#[bench::zeros_4096(setup_encode_corncobs(zeros(4096)))]
|
||||||
|
#[bench::nonzero_0(setup_encode_corncobs(nonzero(0)))]
|
||||||
|
#[bench::nonzero_1(setup_encode_corncobs(nonzero(1)))]
|
||||||
|
#[bench::nonzero_10(setup_encode_corncobs(nonzero(10)))]
|
||||||
|
#[bench::nonzero_64(setup_encode_corncobs(nonzero(64)))]
|
||||||
|
#[bench::nonzero_254(setup_encode_corncobs(nonzero(254)))]
|
||||||
|
#[bench::nonzero_255(setup_encode_corncobs(nonzero(255)))]
|
||||||
|
#[bench::nonzero_256(setup_encode_corncobs(nonzero(256)))]
|
||||||
|
#[bench::nonzero_1024(setup_encode_corncobs(nonzero(1024)))]
|
||||||
|
#[bench::nonzero_4096(setup_encode_corncobs(nonzero(4096)))]
|
||||||
|
#[bench::mixed_0(setup_encode_corncobs(mixed(0)))]
|
||||||
|
#[bench::mixed_1(setup_encode_corncobs(mixed(1)))]
|
||||||
|
#[bench::mixed_10(setup_encode_corncobs(mixed(10)))]
|
||||||
|
#[bench::mixed_64(setup_encode_corncobs(mixed(64)))]
|
||||||
|
#[bench::mixed_254(setup_encode_corncobs(mixed(254)))]
|
||||||
|
#[bench::mixed_255(setup_encode_corncobs(mixed(255)))]
|
||||||
|
#[bench::mixed_256(setup_encode_corncobs(mixed(256)))]
|
||||||
|
#[bench::mixed_1024(setup_encode_corncobs(mixed(1024)))]
|
||||||
|
#[bench::mixed_4096(setup_encode_corncobs(mixed(4096)))]
|
||||||
|
fn encode_corncobs((data, mut buf): (Vec<u8>, Vec<u8>)) {
|
||||||
|
black_box(corncobs::encode_buf(black_box(&data), &mut buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[library_benchmark]
|
||||||
|
#[bench::zeros_0(setup_encode_cobs(zeros(0)))]
|
||||||
|
#[bench::zeros_1(setup_encode_cobs(zeros(1)))]
|
||||||
|
#[bench::zeros_10(setup_encode_cobs(zeros(10)))]
|
||||||
|
#[bench::zeros_64(setup_encode_cobs(zeros(64)))]
|
||||||
|
#[bench::zeros_254(setup_encode_cobs(zeros(254)))]
|
||||||
|
#[bench::zeros_255(setup_encode_cobs(zeros(255)))]
|
||||||
|
#[bench::zeros_256(setup_encode_cobs(zeros(256)))]
|
||||||
|
#[bench::zeros_1024(setup_encode_cobs(zeros(1024)))]
|
||||||
|
#[bench::zeros_4096(setup_encode_cobs(zeros(4096)))]
|
||||||
|
#[bench::nonzero_0(setup_encode_cobs(nonzero(0)))]
|
||||||
|
#[bench::nonzero_1(setup_encode_cobs(nonzero(1)))]
|
||||||
|
#[bench::nonzero_10(setup_encode_cobs(nonzero(10)))]
|
||||||
|
#[bench::nonzero_64(setup_encode_cobs(nonzero(64)))]
|
||||||
|
#[bench::nonzero_254(setup_encode_cobs(nonzero(254)))]
|
||||||
|
#[bench::nonzero_255(setup_encode_cobs(nonzero(255)))]
|
||||||
|
#[bench::nonzero_256(setup_encode_cobs(nonzero(256)))]
|
||||||
|
#[bench::nonzero_1024(setup_encode_cobs(nonzero(1024)))]
|
||||||
|
#[bench::nonzero_4096(setup_encode_cobs(nonzero(4096)))]
|
||||||
|
#[bench::mixed_0(setup_encode_cobs(mixed(0)))]
|
||||||
|
#[bench::mixed_1(setup_encode_cobs(mixed(1)))]
|
||||||
|
#[bench::mixed_10(setup_encode_cobs(mixed(10)))]
|
||||||
|
#[bench::mixed_64(setup_encode_cobs(mixed(64)))]
|
||||||
|
#[bench::mixed_254(setup_encode_cobs(mixed(254)))]
|
||||||
|
#[bench::mixed_255(setup_encode_cobs(mixed(255)))]
|
||||||
|
#[bench::mixed_256(setup_encode_cobs(mixed(256)))]
|
||||||
|
#[bench::mixed_1024(setup_encode_cobs(mixed(1024)))]
|
||||||
|
#[bench::mixed_4096(setup_encode_cobs(mixed(4096)))]
|
||||||
|
fn encode_cobs((data, mut buf): (Vec<u8>, Vec<u8>)) {
|
||||||
|
black_box(cobs::encode(black_box(&data), &mut buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Decode benchmarks ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[library_benchmark]
|
||||||
|
#[bench::zeros_0(setup_decode_ucobs(zeros(0)))]
|
||||||
|
#[bench::zeros_1(setup_decode_ucobs(zeros(1)))]
|
||||||
|
#[bench::zeros_10(setup_decode_ucobs(zeros(10)))]
|
||||||
|
#[bench::zeros_64(setup_decode_ucobs(zeros(64)))]
|
||||||
|
#[bench::zeros_254(setup_decode_ucobs(zeros(254)))]
|
||||||
|
#[bench::zeros_255(setup_decode_ucobs(zeros(255)))]
|
||||||
|
#[bench::zeros_256(setup_decode_ucobs(zeros(256)))]
|
||||||
|
#[bench::zeros_1024(setup_decode_ucobs(zeros(1024)))]
|
||||||
|
#[bench::zeros_4096(setup_decode_ucobs(zeros(4096)))]
|
||||||
|
#[bench::nonzero_0(setup_decode_ucobs(nonzero(0)))]
|
||||||
|
#[bench::nonzero_1(setup_decode_ucobs(nonzero(1)))]
|
||||||
|
#[bench::nonzero_10(setup_decode_ucobs(nonzero(10)))]
|
||||||
|
#[bench::nonzero_64(setup_decode_ucobs(nonzero(64)))]
|
||||||
|
#[bench::nonzero_254(setup_decode_ucobs(nonzero(254)))]
|
||||||
|
#[bench::nonzero_255(setup_decode_ucobs(nonzero(255)))]
|
||||||
|
#[bench::nonzero_256(setup_decode_ucobs(nonzero(256)))]
|
||||||
|
#[bench::nonzero_1024(setup_decode_ucobs(nonzero(1024)))]
|
||||||
|
#[bench::nonzero_4096(setup_decode_ucobs(nonzero(4096)))]
|
||||||
|
#[bench::mixed_0(setup_decode_ucobs(mixed(0)))]
|
||||||
|
#[bench::mixed_1(setup_decode_ucobs(mixed(1)))]
|
||||||
|
#[bench::mixed_10(setup_decode_ucobs(mixed(10)))]
|
||||||
|
#[bench::mixed_64(setup_decode_ucobs(mixed(64)))]
|
||||||
|
#[bench::mixed_254(setup_decode_ucobs(mixed(254)))]
|
||||||
|
#[bench::mixed_255(setup_decode_ucobs(mixed(255)))]
|
||||||
|
#[bench::mixed_256(setup_decode_ucobs(mixed(256)))]
|
||||||
|
#[bench::mixed_1024(setup_decode_ucobs(mixed(1024)))]
|
||||||
|
#[bench::mixed_4096(setup_decode_ucobs(mixed(4096)))]
|
||||||
|
fn decode_ucobs((enc, mut buf): (Vec<u8>, Vec<u8>)) {
|
||||||
|
black_box(ucobs::decode(black_box(&enc), &mut buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[library_benchmark]
|
||||||
|
#[bench::zeros_0(setup_decode_corncobs(zeros(0)))]
|
||||||
|
#[bench::zeros_1(setup_decode_corncobs(zeros(1)))]
|
||||||
|
#[bench::zeros_10(setup_decode_corncobs(zeros(10)))]
|
||||||
|
#[bench::zeros_64(setup_decode_corncobs(zeros(64)))]
|
||||||
|
#[bench::zeros_254(setup_decode_corncobs(zeros(254)))]
|
||||||
|
#[bench::zeros_255(setup_decode_corncobs(zeros(255)))]
|
||||||
|
#[bench::zeros_256(setup_decode_corncobs(zeros(256)))]
|
||||||
|
#[bench::zeros_1024(setup_decode_corncobs(zeros(1024)))]
|
||||||
|
#[bench::zeros_4096(setup_decode_corncobs(zeros(4096)))]
|
||||||
|
#[bench::nonzero_0(setup_decode_corncobs(nonzero(0)))]
|
||||||
|
#[bench::nonzero_1(setup_decode_corncobs(nonzero(1)))]
|
||||||
|
#[bench::nonzero_10(setup_decode_corncobs(nonzero(10)))]
|
||||||
|
#[bench::nonzero_64(setup_decode_corncobs(nonzero(64)))]
|
||||||
|
#[bench::nonzero_254(setup_decode_corncobs(nonzero(254)))]
|
||||||
|
#[bench::nonzero_255(setup_decode_corncobs(nonzero(255)))]
|
||||||
|
#[bench::nonzero_256(setup_decode_corncobs(nonzero(256)))]
|
||||||
|
#[bench::nonzero_1024(setup_decode_corncobs(nonzero(1024)))]
|
||||||
|
#[bench::nonzero_4096(setup_decode_corncobs(nonzero(4096)))]
|
||||||
|
#[bench::mixed_0(setup_decode_corncobs(mixed(0)))]
|
||||||
|
#[bench::mixed_1(setup_decode_corncobs(mixed(1)))]
|
||||||
|
#[bench::mixed_10(setup_decode_corncobs(mixed(10)))]
|
||||||
|
#[bench::mixed_64(setup_decode_corncobs(mixed(64)))]
|
||||||
|
#[bench::mixed_254(setup_decode_corncobs(mixed(254)))]
|
||||||
|
#[bench::mixed_255(setup_decode_corncobs(mixed(255)))]
|
||||||
|
#[bench::mixed_256(setup_decode_corncobs(mixed(256)))]
|
||||||
|
#[bench::mixed_1024(setup_decode_corncobs(mixed(1024)))]
|
||||||
|
#[bench::mixed_4096(setup_decode_corncobs(mixed(4096)))]
|
||||||
|
fn decode_corncobs((enc, mut buf): (Vec<u8>, Vec<u8>)) {
|
||||||
|
let _ = black_box(corncobs::decode_buf(black_box(&enc), &mut buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[library_benchmark]
|
||||||
|
#[bench::zeros_0(setup_decode_cobs(zeros(0)))]
|
||||||
|
#[bench::zeros_1(setup_decode_cobs(zeros(1)))]
|
||||||
|
#[bench::zeros_10(setup_decode_cobs(zeros(10)))]
|
||||||
|
#[bench::zeros_64(setup_decode_cobs(zeros(64)))]
|
||||||
|
#[bench::zeros_254(setup_decode_cobs(zeros(254)))]
|
||||||
|
#[bench::zeros_255(setup_decode_cobs(zeros(255)))]
|
||||||
|
#[bench::zeros_256(setup_decode_cobs(zeros(256)))]
|
||||||
|
#[bench::zeros_1024(setup_decode_cobs(zeros(1024)))]
|
||||||
|
#[bench::zeros_4096(setup_decode_cobs(zeros(4096)))]
|
||||||
|
#[bench::nonzero_0(setup_decode_cobs(nonzero(0)))]
|
||||||
|
#[bench::nonzero_1(setup_decode_cobs(nonzero(1)))]
|
||||||
|
#[bench::nonzero_10(setup_decode_cobs(nonzero(10)))]
|
||||||
|
#[bench::nonzero_64(setup_decode_cobs(nonzero(64)))]
|
||||||
|
#[bench::nonzero_254(setup_decode_cobs(nonzero(254)))]
|
||||||
|
#[bench::nonzero_255(setup_decode_cobs(nonzero(255)))]
|
||||||
|
#[bench::nonzero_256(setup_decode_cobs(nonzero(256)))]
|
||||||
|
#[bench::nonzero_1024(setup_decode_cobs(nonzero(1024)))]
|
||||||
|
#[bench::nonzero_4096(setup_decode_cobs(nonzero(4096)))]
|
||||||
|
#[bench::mixed_0(setup_decode_cobs(mixed(0)))]
|
||||||
|
#[bench::mixed_1(setup_decode_cobs(mixed(1)))]
|
||||||
|
#[bench::mixed_10(setup_decode_cobs(mixed(10)))]
|
||||||
|
#[bench::mixed_64(setup_decode_cobs(mixed(64)))]
|
||||||
|
#[bench::mixed_254(setup_decode_cobs(mixed(254)))]
|
||||||
|
#[bench::mixed_255(setup_decode_cobs(mixed(255)))]
|
||||||
|
#[bench::mixed_256(setup_decode_cobs(mixed(256)))]
|
||||||
|
#[bench::mixed_1024(setup_decode_cobs(mixed(1024)))]
|
||||||
|
#[bench::mixed_4096(setup_decode_cobs(mixed(4096)))]
|
||||||
|
fn decode_cobs((enc, mut buf): (Vec<u8>, Vec<u8>)) {
|
||||||
|
black_box(
|
||||||
|
cobs::decode(black_box(&enc), &mut buf)
|
||||||
|
.map(|r| r.parsed_size())
|
||||||
|
.unwrap_or(0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Groups ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
library_benchmark_group!(
|
||||||
|
name = encode;
|
||||||
|
benchmarks = encode_ucobs, encode_corncobs, encode_cobs
|
||||||
|
);
|
||||||
|
|
||||||
|
library_benchmark_group!(
|
||||||
|
name = decode;
|
||||||
|
benchmarks = decode_ucobs, decode_corncobs, decode_cobs
|
||||||
|
);
|
||||||
|
|
||||||
|
main!(library_benchmark_groups = encode, decode);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
[graph]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[advisories]
|
||||||
|
yanked = "deny"
|
||||||
|
ignore = [
|
||||||
|
# bincode 1.3.3 — transitive dev-dep via iai-callgrind (latest 0.16.1).
|
||||||
|
# Unmaintained but stable and complete; no security issue. Dev-only, never
|
||||||
|
# ships in ucobs. Remove this ignore when iai-callgrind drops bincode.
|
||||||
|
"RUSTSEC-2025-0141",
|
||||||
|
]
|
||||||
|
|
||||||
|
[licenses]
|
||||||
|
# Permissive licenses only. All copyleft (GPL, LGPL, AGPL, MPL, etc.) is denied by omission.
|
||||||
|
allow = [
|
||||||
|
"MIT",
|
||||||
|
"Apache-2.0",
|
||||||
|
"BSD-2-Clause",
|
||||||
|
"BSD-3-Clause",
|
||||||
|
"ISC",
|
||||||
|
"Zlib",
|
||||||
|
"Unlicense",
|
||||||
|
"CC0-1.0",
|
||||||
|
"Unicode-3.0",
|
||||||
|
"Unicode-DFS-2016",
|
||||||
|
]
|
||||||
|
unused-allowed-license = "allow"
|
||||||
|
|
||||||
|
[bans]
|
||||||
|
multiple-versions = "warn"
|
||||||
|
wildcards = "deny"
|
||||||
|
|
||||||
|
[sources]
|
||||||
|
unknown-registry = "deny"
|
||||||
|
unknown-git = "deny"
|
||||||
|
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||||
|
allow-git = []
|
||||||
@@ -24,3 +24,15 @@ name = "fuzz_roundtrip"
|
|||||||
path = "fuzz_targets/fuzz_roundtrip.rs"
|
path = "fuzz_targets/fuzz_roundtrip.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_small_dest"
|
||||||
|
path = "fuzz_targets/fuzz_small_dest.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_encode"
|
||||||
|
path = "fuzz_targets/fuzz_encode.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//! Fuzz target: encode arbitrary input into buffers of varying sizes.
|
||||||
|
//! Exercises encode error paths (buffer too small) and validates output properties.
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
if data.len() > 1024 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let max = ucobs::max_encoded_len(data.len());
|
||||||
|
|
||||||
|
// Encode into a correctly-sized buffer — must always succeed.
|
||||||
|
let mut buf = vec![0u8; max];
|
||||||
|
let n = ucobs::encode(data, &mut buf)
|
||||||
|
.expect("encode failed with max_encoded_len buffer");
|
||||||
|
let encoded = &buf[..n];
|
||||||
|
|
||||||
|
// Output must never contain 0x00.
|
||||||
|
assert!(!encoded.contains(&0x00), "encoded output contains 0x00");
|
||||||
|
|
||||||
|
// Output length must be bounded by max_encoded_len.
|
||||||
|
assert!(n <= max, "encoded length exceeds max_encoded_len");
|
||||||
|
|
||||||
|
// Output must be at least 1 byte (even empty input encodes to [0x01]).
|
||||||
|
assert!(n >= 1, "encoded length is zero");
|
||||||
|
|
||||||
|
// Encode into every possible undersized buffer — must return None, never panic.
|
||||||
|
for size in 0..n {
|
||||||
|
let mut small = vec![0u8; size];
|
||||||
|
assert_eq!(ucobs::encode(data, &mut small), None);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//! Fuzz target: encode and decode with undersized destination buffers.
|
||||||
|
//! Must never panic — should return None when dest is too small.
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
if data.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split fuzz input: first byte selects dest size, rest is payload.
|
||||||
|
let dest_size = data[0] as usize;
|
||||||
|
let payload = &data[1..];
|
||||||
|
|
||||||
|
// Encode with a potentially too-small buffer.
|
||||||
|
let mut enc_buf = vec![0u8; dest_size];
|
||||||
|
let enc_result = ucobs::encode(payload, &mut enc_buf);
|
||||||
|
|
||||||
|
if let Some(enc_len) = enc_result {
|
||||||
|
let encoded = &enc_buf[..enc_len];
|
||||||
|
|
||||||
|
// Encoded output must never contain 0x00.
|
||||||
|
assert!(!encoded.contains(&0x00), "encoded contains sentinel");
|
||||||
|
|
||||||
|
// Decode with a potentially too-small buffer.
|
||||||
|
let mut dec_buf = vec![0u8; dest_size];
|
||||||
|
let dec_result = ucobs::decode(encoded, &mut dec_buf);
|
||||||
|
|
||||||
|
if let Some(dec_len) = dec_result {
|
||||||
|
assert_eq!(
|
||||||
|
&dec_buf[..dec_len], payload,
|
||||||
|
"round-trip mismatch with small buffers"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# μCOBS development tasks
|
||||||
|
|
||||||
|
# default: list all recipes
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
# run ALL quality gates (tests, clippy, fmt check, doc, deny, fuzz, miri, mutants)
|
||||||
|
test: test-unit test-clippy test-fmt test-doc test-deny test-fuzz test-miri test-mutants
|
||||||
|
|
||||||
|
# quick compile check (no tests)
|
||||||
|
check:
|
||||||
|
cargo check
|
||||||
|
|
||||||
|
# unit + integration + proptest tests (via nextest for parallelism)
|
||||||
|
test-unit:
|
||||||
|
cargo nextest run
|
||||||
|
cargo nextest run --features legacy-msrv
|
||||||
|
cargo test --doc
|
||||||
|
|
||||||
|
# clippy with all warnings as errors
|
||||||
|
test-clippy:
|
||||||
|
cargo clippy -- -D warnings
|
||||||
|
cargo clippy --features legacy-msrv -- -D warnings
|
||||||
|
|
||||||
|
# check formatting
|
||||||
|
test-fmt:
|
||||||
|
cargo fmt -- --check
|
||||||
|
|
||||||
|
# doc build (catches broken doc links)
|
||||||
|
test-doc:
|
||||||
|
cargo doc --no-deps --all-features
|
||||||
|
|
||||||
|
# license and advisory audit (requires cargo-deny)
|
||||||
|
test-deny:
|
||||||
|
cargo deny check
|
||||||
|
|
||||||
|
# fuzz all targets (60s each, requires nightly + cargo-fuzz)
|
||||||
|
test-fuzz:
|
||||||
|
cd fuzz && cargo +nightly fuzz run fuzz_decode -- -max_total_time=60
|
||||||
|
cd fuzz && cargo +nightly fuzz run fuzz_encode -- -max_total_time=60
|
||||||
|
cd fuzz && cargo +nightly fuzz run fuzz_roundtrip -- -max_total_time=60
|
||||||
|
cd fuzz && cargo +nightly fuzz run fuzz_small_dest -- -max_total_time=60
|
||||||
|
|
||||||
|
# run tests under miri (catches UB, requires nightly + miri component)
|
||||||
|
# proptest is excluded — its file-persistence layer calls getcwd which miri does not support
|
||||||
|
test-miri:
|
||||||
|
cargo +nightly miri test -- --skip proptests
|
||||||
|
|
||||||
|
# mutation testing (requires cargo-mutants)
|
||||||
|
test-mutants:
|
||||||
|
cargo mutants --no-shuffle
|
||||||
|
|
||||||
|
# run iai-callgrind benchmarks (deterministic instruction counts, requires valgrind)
|
||||||
|
bench: bench-iai bench-size
|
||||||
|
|
||||||
|
# run iai-callgrind benchmarks (ucobs vs cobs vs corncobs)
|
||||||
|
bench-iai:
|
||||||
|
cargo bench
|
||||||
|
|
||||||
|
# measure compiled code size (encode + decode .text bytes)
|
||||||
|
bench-size:
|
||||||
|
@cargo build --release 2>/dev/null
|
||||||
|
@echo ""
|
||||||
|
@echo "Code size (release, encode + decode .text):"
|
||||||
|
@nm -S target/release/libucobs.rlib 2>/dev/null \
|
||||||
|
| grep 'T _ZN5ucobs' \
|
||||||
|
| awk '{size=strtonum("0x"$2); name=$4; sub(/.*ucobs[0-9]*/, "", name); sub(/17h.*/, "", name); printf " ucobs %-7s %4d B\n", name, size}'
|
||||||
|
|
||||||
|
# run python cobs benchmarks (requires: pip install cobs)
|
||||||
|
bench-python:
|
||||||
|
python3 bench_python.py
|
||||||
|
|
||||||
|
# run all benchmarks (rust + python)
|
||||||
|
bench-all: bench bench-python
|
||||||
|
|
||||||
|
# format code
|
||||||
|
fmt:
|
||||||
|
cargo fmt
|
||||||
|
|
||||||
|
# build in release mode
|
||||||
|
build:
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# clean all build artifacts
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
rm -rf fuzz/target fuzz/corpus fuzz/artifacts
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[tools]
|
||||||
|
rust = "1.93"
|
||||||
|
"cargo:cargo-deny" = "latest"
|
||||||
|
"cargo:cargo-fuzz" = "latest"
|
||||||
|
"cargo:cargo-mutants" = "latest"
|
||||||
|
"cargo:cargo-nextest" = "latest"
|
||||||
|
"cargo:iai-callgrind-runner" = "latest"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc 00411472de4d6cdb296c04bc52fd533354bc25ba1765807da8b7994868fcb987 # shrinks to len = 1
|
||||||
|
cc 81ebf688f1dd5ea2a735756a7fb703b92c2a2033057ae91886577c6a63763cf5 # shrinks to data = [0]
|
||||||
|
cc 32d0d2d26e2be6ec66b432f1e9baee378777b1a00b959922cff7a5784292ec4b # shrinks to data = [0]
|
||||||
|
cc fe447e0e58f4c0b21f6e1b38a90bd33578c968967c956964a06013a4df977d68 # shrinks to data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
|
||||||
+1095
-105
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user