56 lines
2.1 KiB
Rust
56 lines
2.1 KiB
Rust
//! 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()
|
||
);
|
||
});
|