initial commit
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

Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
This commit is contained in:
Kamal Tufekcic 2026-04-23 14:58:32 +03:00
commit 7862cb1d9d
No known key found for this signature in database
2884 changed files with 16797 additions and 0 deletions

View 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()
);
});