lac/fuzz/fuzz_targets/roundtrip_arbitrary.rs
Kamal Tufekcic 7862cb1d9d
All checks were successful
CI / lint (push) Successful in 5s
CI / fuzz-regression (push) Successful in 14s
CI / build (push) Successful in 4s
CI / test (push) Successful in 6m54s
CI / publish (push) Successful in 8s
initial commit
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
2026-04-23 14:58:32 +03:00

56 lines
2.1 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Fuzzer for encoder/decoder self-consistency.
//!
//! Interprets the fuzzer's byte input as a sequence of 24-bit signed PCM
//! samples, clamped to the LAC input range, encodes the frame, and
//! verifies the decoded output matches the original samples byte-for-byte.
//!
//! Catches any encoder bug that produces a bitstream the decoder can't
//! reconstruct exactly — which is the whole point of a lossless codec.
//!
//! This complements `decode_arbitrary`:
//! - `decode_arbitrary` asserts the decoder is robust to arbitrary input.
//! - `roundtrip_arbitrary` asserts encoder and decoder agree on meaning.
#![no_main]
use libfuzzer_sys::fuzz_target;
/// LAC's documented input range (spec §1): `|sample| ≤ 2^23 1`.
/// Fuzzer bytes are interpreted as i32 then clamped to this symmetric
/// range — note the lower bound is `-(2^23 1)`, NOT the two's-complement
/// `-2^23`; the encoder's debug-asserted input contract excludes the
/// extra negative value.
const SAMPLE_MIN: i32 = -((1 << 23) - 1);
const SAMPLE_MAX: i32 = (1 << 23) - 1;
fuzz_target!(|data: &[u8]| {
// Need at least one sample (4 bytes interpreted as i32) and a
// reasonable upper bound to keep each fuzz iteration fast. 16k samples
// at 48 kHz is ~340 ms of audio, more than enough to exercise every
// encoder path including the full MAX_PARTITION_ORDER search.
if data.len() < 4 || data.len() > 65_536 {
return;
}
// Read i32 chunks little-endian, clamp into LAC's 24-bit range.
let samples: Vec<i32> = data
.chunks_exact(4)
.map(|c| {
let raw = i32::from_le_bytes([c[0], c[1], c[2], c[3]]);
raw.clamp(SAMPLE_MIN, SAMPLE_MAX)
})
.collect();
if samples.is_empty() {
return;
}
let encoded = lac::encode_frame(&samples);
let decoded = lac::decode_frame(&encoded)
.expect("decoder rejected its own encoder's output");
assert_eq!(
decoded, samples,
"round-trip mismatch: {} samples, encoded {} bytes",
samples.len(),
encoded.len()
);
});