use lac::{DecodeError, decode_frame, encode_frame};
struct DecodeFixture {
name: &'static str,
samples: &'static [i32],
bytes: &'static [u8],
}
const DECODE_FIXTURES: &[DecodeFixture] = &[
DecodeFixture {
name: "single_zero",
samples: &[0],
bytes: &[0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04],
},
DecodeFixture {
name: "silence_4",
samples: &[0, 0, 0, 0],
bytes: &[0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x04, 0x07, 0x80],
},
DecodeFixture {
name: "silence_8",
samples: &[0, 0, 0, 0, 0, 0, 0, 0],
bytes: &[0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0xf8],
},
DecodeFixture {
name: "single_pos_one",
samples: &[1],
bytes: &[0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01],
},
DecodeFixture {
name: "single_neg_one",
samples: &[-1],
bytes: &[0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02],
},
DecodeFixture {
name: "single_full_scale_pos",
samples: &[(1 << 23) - 1],
bytes: &[
0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x01, 0xbb, 0xff, 0xff, 0xf8,
],
},
DecodeFixture {
name: "single_full_scale_neg",
samples: &[-((1 << 23) - 1)],
bytes: &[
0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x01, 0xbb, 0xff, 0xff, 0xf4,
],
},
DecodeFixture {
name: "dc_100_4",
samples: &[100, 100, 100, 100],
bytes: &[
0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3b, 0x21, 0x90, 0xc8, 0x64, 0x00,
],
},
DecodeFixture {
name: "alternating_small_4",
samples: &[1000, -1000, 1000, -1000],
bytes: &[
0x1a, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x04, 0x53, 0xe8, 0x3e, 0x7b, 0xe8, 0x3e, 0x78,
],
},
DecodeFixture {
name: "linear_ramp_8",
samples: &[0, 100, 200, 300, 400, 500, 600, 700],
bytes: &[
0x1a, 0xcc, 0x02, 0x02, 0x02, 0x00, 0x08, 0x40, 0x00, 0xe0, 0x00, 0x34, 0x01, 0x20,
0x18, 0x30, 0x60,
],
},
DecodeFixture {
name: "lfsr_noise_16",
samples: &[
21, -100, 42, -200, 51, -400, 71, -800, 90, -1600, 110, -3200, 130, -6400, 151, -12800,
],
bytes: &[
0x1a, 0xcc, 0x00, 0x01, 0x00, 0x00, 0x10, 0x44, 0xab, 0x8f, 0x54, 0x63, 0xec, 0xc2,
0x3f, 0x8e, 0x02, 0x7e, 0xc8, 0x5a, 0x71, 0xfe, 0x1b, 0x8c, 0x7f, 0xc4, 0x10, 0x47,
0xfe, 0x25, 0xc0, 0x4f, 0xfc,
],
},
];
struct RejectFixture {
name: &'static str,
bytes: &'static [u8],
expected: DecodeError,
}
const REJECT_FIXTURES: &[RejectFixture] = &[
RejectFixture {
name: "bad_sync_word",
bytes: &[0xFF, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x01],
expected: DecodeError::BadSyncWord { got: 0xFFCC },
},
RejectFixture {
name: "prediction_order_above_max",
bytes: &[0x1A, 0xCC, 0x21, 0x00, 0x00, 0x00, 0x01],
expected: DecodeError::InvalidPredictionOrder { got: 33 },
},
RejectFixture {
name: "partition_order_above_max",
bytes: &[0x1A, 0xCC, 0x00, 0x08, 0x00, 0x00, 0x01],
expected: DecodeError::InvalidPartitionOrder { got: 8 },
},
RejectFixture {
name: "coefficient_shift_above_max",
bytes: &[0x1A, 0xCC, 0x01, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00],
expected: DecodeError::InvalidCoefficientShift { got: 6 },
},
RejectFixture {
name: "coefficient_shift_without_order",
bytes: &[0x1A, 0xCC, 0x00, 0x00, 0x03, 0x00, 0x01],
expected: DecodeError::CoefficientShiftWithoutOrder { shift: 3 },
},
RejectFixture {
name: "zero_frame_sample_count",
bytes: &[0x1A, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00],
expected: DecodeError::InvalidParameter,
},
RejectFixture {
name: "frame_count_not_divisible_by_partition_count",
bytes: &[0x1A, 0xCC, 0x00, 0x03, 0x00, 0x00, 0x07],
expected: DecodeError::InvalidParameter,
},
RejectFixture {
name: "truncated_before_header_complete",
bytes: &[0x1A, 0xCC, 0x00],
expected: DecodeError::Truncated,
},
RejectFixture {
name: "truncated_before_coefficients",
bytes: &[0x1A, 0xCC, 0x02, 0x00, 0x00, 0x00, 0x04],
expected: DecodeError::Truncated,
},
RejectFixture {
name: "truncated_before_rice_bitstream",
bytes: &[0x1A, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x01],
expected: DecodeError::Truncated,
},
RejectFixture {
name: "rice_k_above_max",
bytes: &[0x1A, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF8],
expected: DecodeError::InvalidParameter,
},
];
#[test]
fn decode_fixtures() {
for f in DECODE_FIXTURES {
if f.bytes.is_empty() {
continue;
}
let decoded = decode_frame(f.bytes)
.unwrap_or_else(|e| panic!("fixture {}: decode failed with {e:?}", f.name));
assert_eq!(
decoded, f.samples,
"fixture {}: decoded samples mismatch",
f.name
);
}
}
#[test]
fn encode_matches_fixtures() {
for f in DECODE_FIXTURES {
if f.bytes.is_empty() {
continue;
}
let encoded = encode_frame(f.samples);
assert_eq!(
&encoded[..],
f.bytes,
"fixture {}: encoder output drifted from pinned bytes",
f.name
);
}
}
#[test]
fn reject_fixtures() {
for f in REJECT_FIXTURES {
match decode_frame(f.bytes) {
Ok(samples) => panic!(
"fixture {}: expected {:?}, got Ok with {} samples",
f.name,
f.expected,
samples.len()
),
Err(e) => assert_eq!(e, f.expected, "fixture {}: wrong error variant", f.name),
}
}
}
#[test]
fn reject_unary_run_above_cap() {
let mut bytes: Vec<u8> = vec![0x1A, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x01];
let mut rice: Vec<u8> = Vec::with_capacity(68);
let mut bit_accum: u32 = 0;
let mut bits_in_accum: u32 = 0;
let push_bits = |value: u32, count: u32, rice: &mut Vec<u8>, accum: &mut u32, n: &mut u32| {
for i in (0..count).rev() {
let bit = (value >> i) & 1;
*accum = (*accum << 1) | bit;
*n += 1;
if *n == 8 {
rice.push(*accum as u8);
*accum = 0;
*n = 0;
}
}
};
push_bits(23, 5, &mut rice, &mut bit_accum, &mut bits_in_accum); for _ in 0..512 {
push_bits(0, 1, &mut rice, &mut bit_accum, &mut bits_in_accum);
}
push_bits(1, 1, &mut rice, &mut bit_accum, &mut bits_in_accum); push_bits(0, 23, &mut rice, &mut bit_accum, &mut bits_in_accum); if bits_in_accum > 0 {
rice.push((bit_accum << (8 - bits_in_accum)) as u8);
}
bytes.extend_from_slice(&rice);
assert_eq!(bytes.len(), 7 + 68, "unexpected fixture length");
assert_eq!(
decode_frame(&bytes),
Err(DecodeError::InvalidParameter),
"q=512 with k=23 exceeds u32::MAX >> k = 511; decoder must reject \
with InvalidParameter per spec §4.2"
);
}
#[test]
#[ignore = "helper for refreshing the pinned byte literals"]
fn generate_vectors() {
for f in DECODE_FIXTURES {
let encoded = encode_frame(f.samples);
eprintln!(" DecodeFixture {{");
eprintln!(" name: {:?},", f.name);
eprint!(" samples: &[");
for (i, s) in f.samples.iter().enumerate() {
if i > 0 {
eprint!(", ");
}
eprint!("{s}");
}
eprintln!("],");
eprint!(" bytes: &[");
for (i, b) in encoded.iter().enumerate() {
if i > 0 {
eprint!(", ");
}
eprint!("{b:#04x}");
}
eprintln!("],");
eprintln!(" }},");
}
}