wire-codec 1.0.0

Binary frame codec and protocol codec toolkit. Length-prefixed, delimiter-based, and custom framing strategies. Built-in varint, zigzag, bitfield, and packed struct encoding. Runtime-agnostic foundation under network-protocol crates.
Documentation
# v0.9.0 - REPS Audit and Feature Freeze

`wire-codec` 0.9.0 is the pre-1.0 hardening release. The crate has been audited
end to end against [REPS.md](../../REPS.md), every finding has been resolved or
documented, and the public API is now considered frozen for 1.0.

This release contains one small breaking change to a constructor signature
(`Delimited::new` now returns `Result`); everything else is a tightening of
invariants, lints, and documentation. The wire format and behaviour of every
function are unchanged.

## Audit summary

The full REPS checklist (`.dev/ROADMAP.md` Phase 0.9.0) was worked through.
Findings and resolutions:

### Compiler and lint configuration

- **Finding**: `lib.rs` was missing three lints from the REPS-mandated set
  (`warnings`, `clippy::unreachable`, `clippy::undocumented_unsafe_functions`).
- **Resolution**: Lint header rewritten to match REPS verbatim. The lint
  `clippy::missing_safety_doc` is kept in place of REPS's
  `clippy::undocumented_unsafe_functions` because the latter does not exist
  in clippy; the former is the actual lint name with identical behaviour.

### Error handling

- **Finding**: `Delimited::new` panicked on an empty delimiter via
  `assert!`. REPS section 3 prohibits library panics for recoverable
  conditions.
- **Resolution**: `Delimited::new` now returns
  `Result<Self, Error>`. A new variant `Error::EmptyDelimiter` was added.
  All call sites in tests, benches, examples, and doctests have been
  updated. Empty-delimiter rejection is now exercised by a dedicated unit
  test (`empty_delimiter_rejected`).
- **Status**: This is the only API-breaking change in 0.9.0. Migration is a
  one-line `.unwrap()` (or `?`) at the construction site.

- **Finding**: Several `Result`-returning functions on `ReadBuf` and
  `WriteBuf` (`read_u16_be`, `write_u32_le`, etc.) lacked explicit
  `# Errors` doc sections.
- **Resolution**: Every `Result`-returning public function now documents
  its error contract.

### API stability

- **Finding**: `LengthWidth` enum lacked `#[non_exhaustive]`. The roadmap
  envisages adding wider prefix widths in the future.
- **Resolution**: `LengthWidth` is now `#[non_exhaustive]`. Adding new
  variants in a later minor release is non-breaking.

- **Finding**: `LengthPrefixed`, `Delimited`, `LengthWidth`, and `Endian`
  did not derive `PartialEq` / `Eq` / `Hash`. Comparison and hashing are
  expected ergonomics for configuration types.
- **Resolution**: All four now derive the standard equality and hashing
  traits.

- **Finding**: `Encode::encoded_size` returned `usize` without
  `#[must_use]`. Calling it and dropping the value is almost always a bug.
- **Resolution**: Added `#[must_use]` to the trait method.

- **Decision (no change)**: The codec and framing traits (`Encode`,
  `Decode`, `Framer`) are intentionally left unsealed. Downstream users
  must be able to implement custom codecs and framing strategies. This is
  the core extension point of the crate; sealing it would defeat the
  purpose.

### Documentation

- Every public type, trait, function, and constant has a rustdoc block.
- Every `Result`-returning function documents its `# Errors`.
- No `# Panics` sections are required because no public function panics
  (after the `Delimited::new` fix).
- `# Examples` sections are present on every non-trivial public item.
- `README.md`, `docs/API.md`, and `CHANGELOG.md` all reflect 0.9.0.
- `cargo doc --no-deps --all-features` builds with zero warnings under
  `RUSTDOCFLAGS=-D warnings`.

### Banned terms

- **Finding**: The REPS Documentation policy bans the words
  *comprehensive*, *robust*, *seamless*, and *leverage* in shipping docs.
- **Resolution**: None of these terms appear in any shipping file. (REPS.md
  itself uses the words when defining the policy; that file is treated as
  vendored, not authored content.)

### Reproducibility

- **Finding**: No `rust-toolchain.toml`. REPS section "Reproducibility &
  Build Determinism" requires the toolchain to be pinned by tooling, not
  by README convention.
- **Resolution**: Added `rust-toolchain.toml` pinning `channel = "stable"`
  with `components = ["rustfmt", "clippy"]`. MSRV remains pinned
  separately via `rust-version = "1.75"` in `Cargo.toml`.

### Supply chain

- **Finding**: No `cargo audit` or `cargo deny` enforcement in CI.
- **Resolution**: New CI job `supply-chain` runs both on every push and
  pull request via `rustsec/audit-check@v2` and
  `EmbarkStudios/cargo-deny-action@v2`. A `deny.toml` policy file ships
  with the crate: license allow-list, banned-crate registry, duplicate
  version warning, source restriction to crates.io.

### Test discipline

- **Finding**: Tests follow Rust idiom naming, not REPS's recommended
  `test_<subject>_<condition>_<expected>` form.
- **Decision (no change)**: The current naming is descriptive and
  human-readable. REPS phrases this as a convention rather than a hard
  rule; adopting the long form would obscure intent more than it would
  improve searchability. Documented here for transparency.

### Benchmarking

- **Finding**: REPS section "Testing" requires `criterion` for
  benchmarks. The 0.5.0 release ships a manual `std::time::Instant`
  harness instead.
- **Decision (no change)**: `criterion 0.5` transitively depends on a
  crate that requires `edition2024`, which is incompatible with MSRV
  1.75. The manual harness reports per-operation nanoseconds, integrates
  with `cargo bench`, and produces stable comparable numbers. When
  the MSRV moves past 1.85, replacing the harness with `criterion` is a
  drop-in change. Logged here as a tracked deviation.

### Anti-patterns scan

The REPS "Anti-Patterns: What NOT To Do" section was walked top to bottom.
Findings:

- **Performance**: No `clone()` on hot paths. No heap allocation on the
  encode/decode/frame fast path. No dynamic dispatch in tight loops.
- **Security**: No unchecked arithmetic on untrusted input
  (varint/zigzag/bitfield all use explicit width validation).
- **Correctness**: No `unwrap` / `expect` in non-test code. No silently
  ignored errors. No race conditions (no shared mutable state).
- **Architectural**: No god structs or god modules. No global state. No
  circular dependencies. No `RefCell` use.
- **Testing**: No happy-path-only tests. Property tests cover adversarial
  input. No flaky tests (seeded `proptest`).
- **Documentation**: No "Phase X" or "Step Y" headers in shipping docs.
  No emoji. No marketing-style adjectives. No stale doc references.
- **Dependencies**: Zero runtime dependencies. One dev-only dependency
  (`proptest`), pinned with justification (`>=1, <1.6`).

## Public API surface

The frozen 1.0 surface (subject only to bug fixes and non-breaking
additions in subsequent 0.9.x or 1.x releases):

| Module | Items |
| ------ | ----- |
| crate root (re-exports) | `Error`, `Result`, `ReadBuf`, `WriteBuf`, `Encode`, `Decode`, `BitReader`, `BitWriter`, `VERSION` |
| `error` | `Error` (`UnexpectedEof`, `BufferFull`, `VarintOverflow`, `FrameTooLarge`, `InvalidLengthPrefix`, `DelimiterNotFound`, `InvalidEncoding`, `BitOverflow`, `EmptyDelimiter`), `Result` |
| `buf` | `ReadBuf`, `WriteBuf` with `read_*_be/le`, `write_*_be/le`, `peek`, `advance`, `remaining`, `position` |
| `traits` | `Encode`, `Decode` |
| `varint` | `encode_u32`, `encode_u64`, `decode_u32`, `decode_u64`, `encoded_len_u32`, `encoded_len_u64`, `MAX_LEN_U16`, `MAX_LEN_U32`, `MAX_LEN_U64` |
| `zigzag` | `encode_i32`, `decode_i32`, `encode_i64`, `decode_i64` |
| `bitfield` | `BitReader`, `BitWriter`, `MAX_BIT_WIDTH` |
| `framing` | `Frame`, `Framer`, `LengthPrefixed`, `LengthWidth` (`#[non_exhaustive]`), `Endian`, `Delimited` |

## Breaking changes from 0.5.0

- `framing::Delimited::new(delimiter)` now returns
  `Result<Delimited<'_>, Error>` instead of `Self`. Migration:

  ```rust
  // before (0.5.0):
  let framer = Delimited::new(b"\n");

  // after (0.9.0):
  let framer = Delimited::new(b"\n")?;          // in a Result context
  let framer = Delimited::new(b"\n").unwrap();  // when the input is a constant
  ```

- `LengthWidth` is now `#[non_exhaustive]`. Exhaustive matches on it from
  outside the crate must add a `_ => ...` arm. Pattern matches against
  individual variants continue to work unchanged.

## Verification

Built and tested green:

- `cargo build` (default features)
- `cargo build --no-default-features`
- `cargo build --all-features`
- `cargo +1.75 build --all-features --tests`
- `cargo +1.75 test --all-features`
- `cargo fmt --all -- --check`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `cargo +1.75 clippy --all-targets --all-features -- -D warnings`
- `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features`
- `cargo bench --bench codec`
- `cargo bench --bench framing`

Test totals: 26 unit + 5 integration + 6 + 5 + 6 + 3 property + 3 smoke +
18 doctests = **72 tests** across stable and MSRV.

Supply-chain checks (`cargo audit`, `cargo deny`) wired into CI as a
separate `supply-chain` job.

## Compatibility

- Edition: `2021`
- MSRV: Rust `1.75`
- License: Apache-2.0 OR MIT
- Platforms: Linux, macOS, Windows (CI on all three, stable + 1.75)
- Runtime dependencies: none
- Dev dependencies: `proptest` (test-only)

## What is next

The remaining roadmap items for 1.0.0 are tracked in `.dev/ROADMAP.md`
under "Phase 0.9.x" and "Phase 1.0.0":

- Any findings raised by `cargo audit` or `cargo deny` once the new
  CI job is observed across a few PR cycles.
- Final benchmark capture committed alongside the 1.0.0 tag.
- `docs/release/v1.0.0.md` written.
- `Cargo.lock` commit (decided per REPS reproducibility guidance).
- Tag `v1.0.0` and publish to crates.io.

After 1.0.0, semver applies: breaking changes require a 2.x major release.