decode_request feeds arbitrary bytes to decode_request; on a structural Open it also runs Params::decode on the encoded parameter bytes (both Ok/Err accepted, since structural Open decoding deliberately permits malformed params), and when hmac_present it runs verify_packet_hmac with a fixed fuzz key. Invariant: no panic.
decode_open_reply calls decode_open_reply(data, None) and decode_open_reply(data, Some(key)) on the raw bytes, then — when the input is structurally capable of carrying an HMAC field and its flags already indicate HMAC — recomputes the HMAC via the crate's own compute_hmac_in_place and decodes that derived packet too, so the post-HMAC parser gets sustained coverage rather than getting stuck on HmacPresenceMismatch/BadHmac every time.
decode_echo_reply derives a valid, bounded Params from a small control prefix of the fuzz input (received_stats/stamp_at/clock enum, a negotiated-length vocabulary around header boundaries up to 65507, and an HMAC toggle), builds a packet of the negotiated length from the remaining fuzz bytes (also exercising the upstream single-clock midpoint compatibility length), and asserts a successful decode's payload length never exceeds the packet length. The raw fuzz bytes are also decoded unmodified against the same Params for the plain no-panic invariant.
decode_params calls both Params::decode and Params::decode_with_presence (which share one parser) and asserts they agree; on success it re-encodes and re-decodes to assert semantic (not byte-for-byte) round-trip equality.
decode_varint calls decode_uvarint/decode_varint, asserts used bounds (>0, <= data.len(), <= 10), and asserts a canonical re-encode/decode round trip, without requiring noncanonical fuzz input to reproduce itself.
Isolation
fuzz/Cargo.toml declares its own [workspace], so fuzz/ is never a member of the root workspace in Cargo.toml.
libfuzzer-sys/arbitrary are fuzz-only dependencies that never enter any published crate's dependency graph; the root Cargo.lock is untouched (verified via git status/git diff — zero root-lockfile churn).
The normal stable/MSRV workspace is unaffected: cargo check/test/clippy/doc --workspace all pass unchanged, and fuzz/ requires nightly only for its own isolated workspace.
No protocol/production code changed; this PR is fuzzing infrastructure and seed corpora only.
Local smoke run per target, ~40s each, -timeout=5:
decode_request: 8,393,950 exec in 41s — no crashes
decode_open_reply: 4,676,635 exec in 41s — no crashes
decode_echo_reply: 3,325,893 exec in 41s — no crashes
decode_params: 9,887,763 exec in 41s — no crashes
decode_varint: 20,614,310 exec in 41s — no crashes
No crashes/panics found in any target.
Normal workspace: cargo fmt --all -- --check, cargo check/test/clippy --workspace --all-targets --all-features --locked, RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked, and cargo check -p irtt-proto --locked all pass.
CI: .github/workflows/fuzz.yml builds all five targets once, then runs each for -max_total_time=10 -timeout=5 sequentially in one job, on pull_request/push to main and workflow_dispatch.
## Summary
- add isolated cargo-fuzz/libFuzzer infrastructure for irtt-proto;
- fuzz request/open-reply/echo-reply/Params/varint decoders;
- add small clean-room seed corpora;
- add bounded nightly fuzz smoke CI.
## Coverage
- `decode_request` feeds arbitrary bytes to `decode_request`; on a structural `Open` it also runs `Params::decode` on the encoded parameter bytes (both Ok/Err accepted, since structural Open decoding deliberately permits malformed params), and when `hmac_present` it runs `verify_packet_hmac` with a fixed fuzz key. Invariant: no panic.
- `decode_open_reply` calls `decode_open_reply(data, None)` and `decode_open_reply(data, Some(key))` on the raw bytes, then — when the input is structurally capable of carrying an HMAC field and its flags already indicate HMAC — recomputes the HMAC via the crate's own `compute_hmac_in_place` and decodes that derived packet too, so the post-HMAC parser gets sustained coverage rather than getting stuck on `HmacPresenceMismatch`/`BadHmac` every time.
- `decode_echo_reply` derives a valid, bounded `Params` from a small control prefix of the fuzz input (received_stats/stamp_at/clock enum, a negotiated-length vocabulary around header boundaries up to 65507, and an HMAC toggle), builds a packet of the negotiated length from the remaining fuzz bytes (also exercising the upstream single-clock midpoint compatibility length), and asserts a successful decode's payload length never exceeds the packet length. The raw fuzz bytes are also decoded unmodified against the same Params for the plain no-panic invariant.
- `decode_params` calls both `Params::decode` and `Params::decode_with_presence` (which share one parser) and asserts they agree; on success it re-encodes and re-decodes to assert semantic (not byte-for-byte) round-trip equality.
- `decode_varint` calls `decode_uvarint`/`decode_varint`, asserts `used` bounds (`>0`, `<= data.len()`, `<= 10`), and asserts a canonical re-encode/decode round trip, without requiring noncanonical fuzz input to reproduce itself.
## Isolation
- `fuzz/Cargo.toml` declares its own `[workspace]`, so `fuzz/` is never a member of the root workspace in `Cargo.toml`.
- `libfuzzer-sys`/`arbitrary` are fuzz-only dependencies that never enter any published crate's dependency graph; the root `Cargo.lock` is untouched (verified via `git status`/`git diff` — zero root-lockfile churn).
- The normal stable/MSRV workspace is unaffected: `cargo check/test/clippy/doc --workspace` all pass unchanged, and `fuzz/` requires nightly only for its own isolated workspace.
- No protocol/production code changed; this PR is fuzzing infrastructure and seed corpora only.
## Verification
- cargo-fuzz 0.13.1, nightly toolchain (`cargo 1.99.0-nightly`).
- Local smoke run per target, ~40s each, `-timeout=5`:
- `decode_request`: 8,393,950 exec in 41s — no crashes
- `decode_open_reply`: 4,676,635 exec in 41s — no crashes
- `decode_echo_reply`: 3,325,893 exec in 41s — no crashes
- `decode_params`: 9,887,763 exec in 41s — no crashes
- `decode_varint`: 20,614,310 exec in 41s — no crashes
- No crashes/panics found in any target.
- Normal workspace: `cargo fmt --all -- --check`, `cargo check/test/clippy --workspace --all-targets --all-features --locked`, `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked`, and `cargo check -p irtt-proto --locked` all pass.
- CI: `.github/workflows/fuzz.yml` builds all five targets once, then runs each for `-max_total_time=10 -timeout=5` sequentially in one job, on `pull_request`/`push` to `main` and `workflow_dispatch`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Coverage
decode_requestfeeds arbitrary bytes todecode_request; on a structuralOpenit also runsParams::decodeon the encoded parameter bytes (both Ok/Err accepted, since structural Open decoding deliberately permits malformed params), and whenhmac_presentit runsverify_packet_hmacwith a fixed fuzz key. Invariant: no panic.decode_open_replycallsdecode_open_reply(data, None)anddecode_open_reply(data, Some(key))on the raw bytes, then — when the input is structurally capable of carrying an HMAC field and its flags already indicate HMAC — recomputes the HMAC via the crate's owncompute_hmac_in_placeand decodes that derived packet too, so the post-HMAC parser gets sustained coverage rather than getting stuck onHmacPresenceMismatch/BadHmacevery time.decode_echo_replyderives a valid, boundedParamsfrom a small control prefix of the fuzz input (received_stats/stamp_at/clock enum, a negotiated-length vocabulary around header boundaries up to 65507, and an HMAC toggle), builds a packet of the negotiated length from the remaining fuzz bytes (also exercising the upstream single-clock midpoint compatibility length), and asserts a successful decode's payload length never exceeds the packet length. The raw fuzz bytes are also decoded unmodified against the same Params for the plain no-panic invariant.decode_paramscalls bothParams::decodeandParams::decode_with_presence(which share one parser) and asserts they agree; on success it re-encodes and re-decodes to assert semantic (not byte-for-byte) round-trip equality.decode_varintcallsdecode_uvarint/decode_varint, assertsusedbounds (>0,<= data.len(),<= 10), and asserts a canonical re-encode/decode round trip, without requiring noncanonical fuzz input to reproduce itself.Isolation
fuzz/Cargo.tomldeclares its own[workspace], sofuzz/is never a member of the root workspace inCargo.toml.libfuzzer-sys/arbitraryare fuzz-only dependencies that never enter any published crate's dependency graph; the rootCargo.lockis untouched (verified viagit status/git diff— zero root-lockfile churn).cargo check/test/clippy/doc --workspaceall pass unchanged, andfuzz/requires nightly only for its own isolated workspace.Verification
cargo 1.99.0-nightly).-timeout=5:decode_request: 8,393,950 exec in 41s — no crashesdecode_open_reply: 4,676,635 exec in 41s — no crashesdecode_echo_reply: 3,325,893 exec in 41s — no crashesdecode_params: 9,887,763 exec in 41s — no crashesdecode_varint: 20,614,310 exec in 41s — no crashescargo fmt --all -- --check,cargo check/test/clippy --workspace --all-targets --all-features --locked,RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked, andcargo check -p irtt-proto --lockedall pass..github/workflows/fuzz.ymlbuilds all five targets once, then runs each for-max_total_time=10 -timeout=5sequentially in one job, onpull_request/pushtomainandworkflow_dispatch.