initial commit
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
This commit is contained in:
commit
7862cb1d9d
2884 changed files with 16797 additions and 0 deletions
26
fuzz/fuzz_targets/decode_arbitrary.rs
Normal file
26
fuzz/fuzz_targets/decode_arbitrary.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//! Fuzzer for `decode_frame` against arbitrary byte input.
|
||||
//!
|
||||
//! Drives the frame parser and Rice bitstream decoder with random bytes.
|
||||
//! The decoder must never panic, allocate unboundedly, or enter an infinite
|
||||
//! loop on any input: success is "returns `Ok` with valid samples" or
|
||||
//! "returns `Err(DecodeError)`". Nothing else is acceptable.
|
||||
//!
|
||||
//! Paths exercised:
|
||||
//! - Every header validation branch (sync, order range, partition order
|
||||
//! range, truncation at each field boundary, partition count vs. sample
|
||||
//! count mismatch).
|
||||
//! - The Rice unary-decode loop with pathological run lengths (the
|
||||
//! `q > 2^26` guard in `rice::rice_decode` is specifically targeted).
|
||||
//! - The Q15 predictor accumulator on adversarial coefficient and residual
|
||||
//! values (overflow / wrap-around checks).
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Only the `Ok` / `Err` result is meaningful; panics are the failure
|
||||
// mode. The returned samples themselves are unchecked here — the
|
||||
// `roundtrip_arbitrary` target verifies encoder/decoder self-consistency.
|
||||
let _ = lac::decode_frame(data);
|
||||
});
|
||||
56
fuzz/fuzz_targets/roundtrip_arbitrary.rs
Normal file
56
fuzz/fuzz_targets/roundtrip_arbitrary.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! 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()
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue