- command_roundtrip: structure-aware (via arbitrary crate) Command
encode → parse → equal. Symmetric to cobs_roundtrip.
- cobs_decoder_multi_frame: chunked feeding with arbitrary split sizes;
asserts bounded payload and that command count never exceeds sentinel
count.
- cobs_chunk_invariant: same stream through two decoders (chunked vs
all-at-once) must produce the same command sequence.
Adds test-fuzz-long recipe (600s per target, ~1h 10m total) for CI
cron / pre-release. All seven targets smoke-tested at 100k runs each
with zero findings.
Scaled the proptest suite hard and added 13 new properties:
- cobs_decoder_multi_frame: N frames decode to N commands in order.
- cobs_decoder_chunk_invariant: arbitrary chunk splits yield the same
sequence as feed-all-at-once.
- cobs_decoder_byte_by_byte_equivalent: stronger form of chunking.
- cobs_decoder_recovers_after_garbage: decoder is not poisoned by a
leading garbage frame.
- cobs_decoder_ignores_sentinel_runs: arbitrary leading/trailing 0x00
runs do not affect decoding.
- cobs_decoder_oversized_discards: frames > MAX_FRAME are discarded;
subsequent frames still decode.
- response_full_roundtrip: encode + COBS + decode + re-parse yields the
original Response (via a test-only decode_response helper).
- response_encoding_deterministic: byte-identical output across runs.
- cobs_encoded_has_single_trailing_zero: COBS invariant check.
- bandwidth_u8_is_bijection: sweeps the whole u8 space.
- radio_config_write_to_stays_in_bounds: buffer-write sanity.
- resolve_then_validate_consistent: cross-field contract.
Split into two proptest! blocks: lightweight at 100,000 cases each, and
heavyweight (MAX_FRAME-sized buffers, multi-frame streams) at 10,000.
Total ~1.13M test cases per suite run, finishing in ~20 s.
Adds tests/mutation_killers.rs with six targeted tests derived from
cargo-mutants findings: cad_enabled true/false paths, Transmit
with-config boundary, CobsDecoder::reset behaviour, empty-frame
handling, and the cobs_encode_response success path.
Three mutants remain as provably equivalent — the justfile excludes
them by exact regex so any genuinely new mutant on the same line will
still surface. Updates test-miri to also run the integration tests
(proptest stays skipped because its file-persistence layer calls
getcwd). Gitignore picks up mutants.out / mutants.out.old.
cargo mutants: 89 caught, 9 unviable, 0 missed.
Hand-encoded byte sequences for RadioConfig, every Command tag, and
every Response tag, taken directly from the spec. Fields use hex-clean
values (freq_hz = 0x12345678, sync_word = 0xABCD, etc.) so each
little-endian byte is visually verifiable.
Locks firmware and host to the written spec — a refactor that quietly
changes a field layout fails here, not in the field.
Four libFuzzer targets:
- parse_command: Command::from_bytes never panics; bounded payload.
- parse_radio_config: RadioConfig::from_bytes never panics.
- cobs_decoder_feed: CobsDecoder::feed is panic-free across arbitrary
byte streams split into arbitrary chunks.
- cobs_roundtrip: structure-aware (via arbitrary crate) Response →
cobs_encode_response → ucobs::decode yields identical raw bytes.
All four smoke-tested at 50k runs each with no findings. Intended CI
cadence is a weekly cron with a larger time budget; per-PR would be
expensive. Requires nightly + cargo-fuzz.
Ten properties covering: RadioConfig roundtrip and validate/resolve
determinism; no-panic on arbitrary bytes for both Command::from_bytes
and RadioConfig::from_bytes; bounded-capacity for Transmit payloads
(guards the heapless::Vec size); Command roundtrip via a test-only
encoder (the library only decodes commands); COBS reversibility; and
two streaming CobsDecoder properties (no-panic on random byte streams
with random chunk boundaries, and a full encode→feed→decode roundtrip).
Adds Clone to Command and Response — benign public-API expansion that
unblocks proptest's Just strategy.
Imports CobsDecoder and cobs_encode_response from firmware's
src/host/framing.rs, adjusted so the defmt::warn on encode overflow is
gated on the crate's defmt feature rather than an unconditional import.
The route_command helper stays in firmware — it depends on embassy
channels, which have no business in a transport-agnostic crate.
Copied src/protocol.rs from firmware verbatim, added crate-level #![no_std],
migrated the defmt::Format derive gate from cfg_attr(not(test), ...) to
cfg_attr(feature = "defmt", ...) so host-side (no-default-features) builds
compile cleanly. Tests unchanged; all 35 pass.
Firmware still owns the file for now — the re-export swap lands in a
follow-up firmware commit to keep each change standalone.