Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee40455c5f
|
||
|
|
1eb7e09412
|
||
|
|
f805386bb8
|
||
|
|
f6778e20b8
|
||
|
|
aa09783cdd
|
No files matched your search
+4
-9
@@ -5,18 +5,13 @@ test_tool = "nextest"
|
||||
# are sufficient to catch mutants. Full proptests run separately via just test-unit.
|
||||
additional_cargo_test_args = ["-E", "not test(proptests)"]
|
||||
|
||||
# Excluded mutants (false positives only):
|
||||
# False positives excluded:
|
||||
#
|
||||
# - legacy-msrv const_copy: dead code under default features, only compiled
|
||||
# with --features legacy-msrv. Tested separately.
|
||||
# - `> 0` → `>= 0` or `< 0` on usize: tautological (usize always >= 0)
|
||||
# - `< 254` → `<= 254` in max_run: both branches yield 254 at the boundary
|
||||
#
|
||||
# - usize `> 0` → `>= 0` or `< 0`: tautological on usize (always >= 0).
|
||||
#
|
||||
# - `< 254` → `<= 254` in max_run: both branches yield 254 when remaining
|
||||
# is exactly 254, so the mutation is a semantic no-op.
|
||||
# The legacy-msrv const_copy is excluded via #[mutants::skip] in source.
|
||||
exclude_re = [
|
||||
"in const_copy",
|
||||
"replace const_copy with \\(\\)",
|
||||
"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
|
||||
+46
-6
@@ -1,14 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.2 — 2026-03-31
|
||||
## 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 passes clean** — 88 mutants tested, 59 caught, 0 missed.
|
||||
Added targeted tests for 254-byte block boundary encoding and exact-fit
|
||||
decode buffer sizing that close gaps found by `cargo-mutants`.
|
||||
- **111 tests** (was 106): added block-boundary interop, exact-fit decode zeros,
|
||||
and too-small decode zeros tests.
|
||||
- **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
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
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.
|
||||
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.
|
||||
4. **Fast** — benchmark against every competing implementation. Must be at least as fast, ideally fastest. Benchmarks are CI-enforced.
|
||||
5. **Tiny** — `no_std`, zero-alloc, zero runtime dependencies. ~120 lines of implementation. Runs on 8-bit MCUs and servers alike.
|
||||
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 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.
|
||||
6. **Trustworthy** — small enough to audit by hand. No unsafe. No panics in release. Fuzz-hardened.
|
||||
|
||||
## Architecture
|
||||
@@ -22,7 +22,7 @@ Single-file library: `src/lib.rs`
|
||||
### Public API (3 functions)
|
||||
|
||||
```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 const fn max_encoded_len(src_len: usize) -> usize
|
||||
```
|
||||
@@ -32,26 +32,29 @@ pub const fn max_encoded_len(src_len: usize) -> usize
|
||||
## Development
|
||||
|
||||
```sh
|
||||
# Run all tests
|
||||
cargo test
|
||||
# Run all quality gates (tests, clippy, fmt, doc, fuzz, miri, mutants)
|
||||
just test
|
||||
|
||||
# Unit + property + interop tests only
|
||||
just test-unit
|
||||
|
||||
# Benchmarks (iai-callgrind, requires valgrind)
|
||||
just bench
|
||||
|
||||
# Fuzz (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
|
||||
just test-fuzz
|
||||
```
|
||||
|
||||
## Dev Dependencies (test-only)
|
||||
|
||||
- `cobs` — cross-validation against the `cobs` 0.5 crate
|
||||
- `corncobs` — cross-validation against another COBS implementation
|
||||
- `iai-callgrind` — deterministic instruction-count benchmarks
|
||||
- `mutants` — mutation testing annotations
|
||||
- `proptest` — property-based testing
|
||||
- `rand` — randomized test payloads
|
||||
|
||||
## Roadmap
|
||||
|
||||
- More interop targets: `cobs` crate, Python `cobs` package
|
||||
- Benchmarks (criterion) vs corncobs, cobs, and others
|
||||
- quickcheck in addition to proptest
|
||||
- Even more fuzz targets and edge-case generators
|
||||
- MSRV policy and CI
|
||||
- `#[deny(unsafe_code)]` badge
|
||||
+4
-3
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "ucobs"
|
||||
version = "0.3.2"
|
||||
version = "0.3.5"
|
||||
edition = "2021"
|
||||
rust-version = "1.93"
|
||||
license = "MIT"
|
||||
description = "μCOBS — Consistent Overhead Byte Stuffing. no_std, zero-alloc, fastest, most-tested COBS implementation."
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
repository = "https://github.com/swaits/ucobs"
|
||||
homepage = "https://github.com/swaits/ucobs"
|
||||
repository = "https://git.swaits.com/swaits/ucobs"
|
||||
homepage = "https://git.swaits.com/swaits/ucobs"
|
||||
documentation = "https://docs.rs/ucobs"
|
||||
readme = "README.md"
|
||||
keywords = ["cobs", "framing", "no-std", "embedded", "codec"]
|
||||
@@ -27,6 +27,7 @@ all-features = true
|
||||
cobs = "0.5"
|
||||
corncobs = "0.1"
|
||||
iai-callgrind = "0.16"
|
||||
mutants = "0.0.4"
|
||||
proptest = "1"
|
||||
rand = "0.10"
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
[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,
|
||||
allowing `0x00` to serve as an unambiguous frame delimiter. Overhead is exactly
|
||||
1 byte per 254 input bytes (worst case).
|
||||
allowing `0x00` to serve as an unambiguous frame delimiter. Overhead is at most
|
||||
1 byte per 254 input bytes, plus 1.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -18,7 +18,7 @@ allowing `0x00` to serve as an unambiguous frame delimiter. Overhead is exactly
|
||||
- **100% spec-compliant** — Cheshire & Baker, IEEE/ACM Transactions on Networking, 1999
|
||||
- **Proven interoperability** — cross-validated against `corncobs`, `cobs`, and Python `cobs`
|
||||
- **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
|
||||
|
||||
## Performance
|
||||
|
||||
@@ -64,16 +64,16 @@ combined). Run `just bench-size` to reproduce.
|
||||
|
||||
| Crate | `encode` | `decode` | Total |
|
||||
|----------------|--------:|---------:|------:|
|
||||
| μCOBS | 429 B | 392 B | 821 B |
|
||||
| μ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 is the largest because 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"` which reduces μCOBS to
|
||||
~738 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
|
||||
|
||||
@@ -105,7 +105,7 @@ over speed can build with `opt-level = "s"` which reduces μCOBS to
|
||||
| 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, mutants** | basic | basic |
|
||||
| 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
|
||||
@@ -295,8 +295,8 @@ affected — it uses a byte loop instead of `copy_from_slice`/memcpy.
|
||||
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` verifies that the test suite catches
|
||||
injected bugs in every reachable code path
|
||||
- **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
|
||||
|
||||
@@ -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 = []
|
||||
@@ -30,3 +30,9 @@ 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);
|
||||
}
|
||||
});
|
||||
@@ -4,8 +4,8 @@
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# run ALL quality gates (tests, clippy, fmt check, doc, fuzz, miri, mutants)
|
||||
test: test-unit test-clippy test-fmt test-doc test-fuzz test-miri test-mutants
|
||||
# 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:
|
||||
@@ -14,11 +14,13 @@ 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:
|
||||
@@ -26,11 +28,16 @@ test-fmt:
|
||||
|
||||
# doc build (catches broken doc links)
|
||||
test-doc:
|
||||
cargo doc --no-deps
|
||||
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
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[tools]
|
||||
"cargo:cargo-nextest" = "latest"
|
||||
rust = "1.93"
|
||||
"cargo:cargo-deny" = "latest"
|
||||
"cargo:cargo-fuzz" = "latest"
|
||||
"cargo:cargo-mutants" = "latest"
|
||||
"cargo:cargo-nextest" = "latest"
|
||||
"cargo:iai-callgrind-runner" = "latest"
|
||||
+10
-10
@@ -167,6 +167,7 @@ const fn const_copy(dest: &mut [u8], di: usize, src: &[u8], n: usize) {
|
||||
}
|
||||
|
||||
/// Fallback const copy for toolchains older than Rust 1.93.
|
||||
#[cfg_attr(test, mutants::skip)] // dead code under default features
|
||||
#[inline(always)]
|
||||
#[cfg(feature = "legacy-msrv")]
|
||||
const fn const_copy(dest: &mut [u8], di: usize, src: &[u8], n: usize) {
|
||||
@@ -255,14 +256,10 @@ pub const fn encode(src: &[u8], dest: &mut [u8]) -> Option<usize> {
|
||||
}
|
||||
si += run;
|
||||
|
||||
if full_block {
|
||||
if si >= src.len() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if si >= src.len() {
|
||||
break;
|
||||
}
|
||||
if si >= src.len() {
|
||||
break;
|
||||
}
|
||||
if !full_block {
|
||||
si += 1; // skip the zero byte
|
||||
}
|
||||
}
|
||||
@@ -314,6 +311,9 @@ pub fn decode(src: &[u8], dest: &mut [u8]) -> Option<usize> {
|
||||
src = rest;
|
||||
count += 1;
|
||||
}
|
||||
// If more data follows, every 0x01 represents a zero byte.
|
||||
// If the stream ends here, the final 0x01 is the end-of-data
|
||||
// marker, not a zero — so emit one fewer.
|
||||
let zeros = if !src.is_empty() { count } else { count - 1 };
|
||||
if di + zeros > dest.len() {
|
||||
return None;
|
||||
@@ -842,7 +842,7 @@ mod tests {
|
||||
let encoded = enc(&input).unwrap();
|
||||
assert_eq!(encoded[0], 0xFF); // code byte for full 254-byte block
|
||||
assert_eq!(encoded.len(), 255); // 1 code byte + 254 data bytes
|
||||
// Verify it round-trips
|
||||
// Verify it round-trips
|
||||
let mut dec = vec![0u8; 256];
|
||||
let n = decode(&encoded, &mut dec).unwrap();
|
||||
assert_eq!(&dec[..n], &input[..]);
|
||||
@@ -1258,7 +1258,7 @@ mod proptests {
|
||||
shrink in 1usize..256,
|
||||
) {
|
||||
let max = max_encoded_len(data.len());
|
||||
let dest_size = if shrink >= max { 0 } else { max - shrink };
|
||||
let dest_size = max.saturating_sub(shrink);
|
||||
let mut buf = vec![0u8; dest_size];
|
||||
let _ = encode(&data, &mut buf); // may return None, must not panic
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user