libfreemkv 0.31.2: comprehensive spec-grounded test suite (~950 tests)

Test-hardening release, no runtime changes. Adds spec-grounded unit tests
across the silent-corruption surfaces — UDF/MPLS/CLPI/IFO parsing, BD/DVD
title + extent assembly, AACS/CSS key handling, TS/PS demux + codec parsers,
MKV/EBML container output, the mux pipeline, sector prefetch + decrypt
decorator, drive/SCSI sense decoding, label extraction, and core I/O. Each
test is grounded in the format spec or real on-disc behavior and verified to
fail under a targeted source mutation. No behavior changed.
This commit is contained in:
Matthew Jackson
2026-06-07 22:28:29 -07:00
parent 2a55bab3ed
commit 8000bae177
85 changed files with 22998 additions and 1 deletions
+396
View File
@@ -1223,6 +1223,402 @@ mod tests {
assert_eq!(cp[18], 0xF8 | 2);
}
// --- BitReader unit tests (exp-Golomb + bit reads) ---
#[test]
fn bitreader_read_bits_msb_first() {
// 0b1011_0010 read 4 bits → 0b1011 = 11, then 4 → 0b0010 = 2.
let mut r = BitReader::new(&[0b1011_0010]);
assert_eq!(r.read_bits(4), Some(11));
assert_eq!(r.read_bits(4), Some(2));
// Past end → None.
assert_eq!(r.read_bit(), None);
}
#[test]
fn bitreader_ue_golomb_values() {
// Exp-Golomb ue(v): codeNum 0 = "1", 1 = "010", 2 = "011", 3 = "00100",
// 4 = "00101". (H.264/HEVC §9.1.) Pack "1 010 011" = 1010011x.
// Byte 0b1010_0110: read ue → 0 (leading "1"), then "010" → 1, then
// "011" → 2.
let mut r = BitReader::new(&[0b1010_0110]);
assert_eq!(r.read_ue(), Some(0));
assert_eq!(r.read_ue(), Some(1));
assert_eq!(r.read_ue(), Some(2));
}
#[test]
fn bitreader_ue_large_value() {
// codeNum 4 = "00101". Byte 0b0010_1000 → ue = 4.
let mut r = BitReader::new(&[0b0010_1000]);
assert_eq!(r.read_ue(), Some(4));
}
#[test]
fn bitreader_ue_runaway_zeros_bounded() {
// A corrupt all-zero stream has unbounded leading zeros; read_ue caps at
// 31 zeros and returns None rather than looping/overflowing.
let zeros = [0u8; 8]; // 64 zero bits
let mut r = BitReader::new(&zeros);
assert_eq!(r.read_ue(), None, "runaway zero-run is bounded → None");
}
#[test]
fn bitreader_skip_bits_past_end_is_none() {
let mut r = BitReader::new(&[0xFF]);
assert_eq!(r.skip_bits(8), Some(()));
assert_eq!(r.skip_bits(1), None, "skipping past the buffer end → None");
}
// --- strip_emulation_prevention (00 00 03 → 00 00) ---
#[test]
fn strip_ep_removes_third_byte_after_two_zeros() {
// 00 00 03 XX → 00 00 XX. The 0x03 is removed only after exactly two
// zeros. (H.264/HEVC §7.4.)
assert_eq!(
strip_emulation_prevention(&[0x00, 0x00, 0x03, 0x42]),
vec![0x00, 0x00, 0x42]
);
}
#[test]
fn strip_ep_leaves_03_after_single_zero() {
// A 0x03 preceded by only ONE zero is real data, not an EP byte.
assert_eq!(
strip_emulation_prevention(&[0x00, 0x03, 0x42]),
vec![0x00, 0x03, 0x42]
);
}
#[test]
fn strip_ep_handles_consecutive_sequences() {
// 00 00 03 00 00 03 → 00 00 00 00. After dropping the first 0x03 the run
// resets to 0, so the next two zeros re-arm and drop the second 0x03.
assert_eq!(
strip_emulation_prevention(&[0x00, 0x00, 0x03, 0x00, 0x00, 0x03]),
vec![0x00, 0x00, 0x00, 0x00]
);
}
#[test]
fn strip_ep_03_not_dropped_when_not_preceded_by_zeros() {
// 0x03 after non-zero bytes is kept verbatim.
assert_eq!(
strip_emulation_prevention(&[0xAA, 0xBB, 0x03, 0xCC]),
vec![0xAA, 0xBB, 0x03, 0xCC]
);
}
// --- parse_sps_chroma: chroma_format_idc edge values ---
#[test]
fn hvcc_chroma_monochrome_idc0() {
// chroma_format_idc = 0 (monochrome). bit depths 8-bit (minus8=0).
let sps = make_sps_with_chroma(0, 0, 0);
let cp = codec_private_from_sps(&sps);
// chromaFormat byte = 0xFC (6 reserved bits) | chroma_format_idc(0) = 0xFC.
assert_eq!(cp[16], 0xFC, "chroma_format_idc = 0 (monochrome)");
}
#[test]
fn hvcc_chroma_422_idc2() {
// chroma_format_idc = 2 (4:2:2), 10-bit.
let sps = make_sps_with_chroma(2, 2, 2);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 2, "chroma_format_idc = 2 (4:2:2)");
assert_eq!(cp[17], 0xF8 | 2);
}
#[test]
fn hvcc_asymmetric_bit_depths() {
// luma and chroma bit depths can differ; both must be parsed
// independently. luma minus8 = 2 (10-bit), chroma minus8 = 4 (12-bit).
let sps = make_sps_with_chroma(1, 2, 4);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[17], 0xF8 | 2, "bit_depth_luma_minus8 = 2");
assert_eq!(cp[18], 0xF8 | 4, "bit_depth_chroma_minus8 = 4");
}
/// Build a stored SPS NAL with sub-layers and a conformance window, so the
/// parser must skip sub-layer PTL and the 4 conformance-window ue(v) fields
/// before reaching the bit depths. max_sub_layers_minus1 controls the
/// sub-layer loop.
fn make_sps_full(
chroma_idc: u32,
bd_luma_m8: u32,
bd_chroma_m8: u32,
max_sub_layers_minus1: u32,
conformance_window: bool,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.put_bits(0, 4); // sps_video_parameter_set_id
w.put_bits(max_sub_layers_minus1, 3);
w.put_bit(1); // sps_temporal_id_nesting_flag
// general profile_tier_level: 96 bits.
for _ in 0..96 {
w.put_bit(0);
}
// Sub-layer flags + sub-layer PTL when max_sub_layers_minus1 > 0.
if max_sub_layers_minus1 > 0 {
let mut profile_present = Vec::new();
let mut level_present = Vec::new();
for _ in 0..max_sub_layers_minus1 {
// sub_layer_profile_present_flag, sub_layer_level_present_flag.
w.put_bit(1); // profile present
w.put_bit(1); // level present
profile_present.push(true);
level_present.push(true);
}
if max_sub_layers_minus1 < 8 {
for _ in max_sub_layers_minus1..8 {
w.put_bits(0, 2); // reserved_zero_2bits
}
}
for i in 0..max_sub_layers_minus1 as usize {
if profile_present[i] {
for _ in 0..88 {
w.put_bit(0); // sub-layer profile block
}
}
if level_present[i] {
w.put_bits(0, 8); // sub_layer_level_idc
}
}
}
w.put_ue(0); // sps_seq_parameter_set_id
w.put_ue(chroma_idc);
if chroma_idc == 3 {
w.put_bit(0); // separate_colour_plane_flag
}
w.put_ue(3840);
w.put_ue(2160);
if conformance_window {
w.put_bit(1); // conformance_window_flag
w.put_ue(0); // conf_win_left_offset
w.put_ue(0); // conf_win_right_offset
w.put_ue(0); // conf_win_top_offset
w.put_ue(0); // conf_win_bottom_offset
} else {
w.put_bit(0);
}
w.put_ue(bd_luma_m8);
w.put_ue(bd_chroma_m8);
let mut sps = hevc_nal_header(33).to_vec();
sps.extend_from_slice(&w.bytes);
sps
}
#[test]
fn hvcc_parses_chroma_through_sublayer_ptl() {
// With max_sub_layers_minus1 = 2 the parser must consume the sub-layer
// present-flag bits, reserved bits, and two sub-layer PTL blocks before
// reaching chroma_format_idc / bit depths. A wrong sub-layer skip would
// mis-read the bit depths.
let sps = make_sps_full(1, 2, 2, 2, false);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 1, "4:2:0 after sub-layer PTL skip");
assert_eq!(cp[17], 0xF8 | 2, "10-bit luma after sub-layer PTL skip");
assert_eq!(cp[18], 0xF8 | 2);
// byte 21: numTemporalLayers = max_sub_layers_minus1 + 1 = 3.
assert_eq!(
cp[21],
(3 << 3) | (1 << 2) | 0x03,
"numTemporalLayers = 3, temporalIdNested = 1, lengthSizeMinusOne = 3"
);
}
#[test]
fn hvcc_parses_chroma_through_conformance_window() {
// conformance_window_flag = 1 inserts 4 ue(v) fields the parser must skip
// before the bit depths. A correct skip lands on the right depths.
let sps = make_sps_full(1, 2, 2, 0, true);
let cp = codec_private_from_sps(&sps);
assert_eq!(
cp[17],
0xF8 | 2,
"10-bit luma after conformance-window skip"
);
assert_eq!(cp[18], 0xF8 | 2);
}
#[test]
fn hvcc_parses_444_with_separate_colour_plane() {
// chroma_format_idc = 3 (4:4:4) inserts separate_colour_plane_flag (1
// bit) that the parser must consume before pic dimensions. 12-bit.
let sps = make_sps_full(3, 4, 4, 0, false);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 3, "4:4:4");
assert_eq!(cp[17], 0xF8 | 4, "12-bit luma");
}
// --- hvcC array structure (VPS/SPS/PPS arrays) ---
#[test]
fn hvcc_array_headers_and_lengths() {
// After the 23-byte fixed header + numOfArrays the record holds three
// arrays. Each: (0x20 | nal_type), numNalus(=1, u16-BE), nalLength(u16),
// NAL bytes. Verify the SPS array's nal_type byte and length encode
// correctly. (ISO/IEC 14496-15 §8.3.3.1.)
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xA0, 0xA1, 0xA2]); // VPS, 5 bytes total
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]); // SPS, 11 bytes
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xC0, 0xC1]); // PPS, 4 bytes
parser.parse(&make_pes(data, Some(0)));
let cp = parser.codec_private().expect("hvcC");
// numOfArrays at index 22.
assert_eq!(cp[22], 3);
// VPS array begins at 23. array header byte = 0x20 | 32 = 0x40.
let mut o = 23;
assert_eq!(cp[o], 0x20 | 32, "VPS array nal_type byte");
assert_eq!(
u16::from_be_bytes([cp[o + 1], cp[o + 2]]),
1,
"numNalus VPS"
);
let vps_len = u16::from_be_bytes([cp[o + 3], cp[o + 4]]) as usize;
assert_eq!(vps_len, 5, "VPS NAL length = 2 hdr + 3 payload");
// skip to SPS array.
o += 5 + vps_len;
assert_eq!(cp[o], 0x20 | 33, "SPS array nal_type byte");
let sps_len = u16::from_be_bytes([cp[o + 3], cp[o + 4]]) as usize;
assert_eq!(sps_len, 11, "SPS NAL length = 2 hdr + 9 payload");
o += 5 + sps_len;
assert_eq!(cp[o], 0x20 | 34, "PPS array nal_type byte");
let pps_len = u16::from_be_bytes([cp[o + 3], cp[o + 4]]) as usize;
assert_eq!(pps_len, 4, "PPS NAL length = 2 hdr + 2 payload");
}
#[test]
fn hvcc_none_missing_vps() {
// VPS is required for hvcC; SPS + PPS only → None.
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1)); // slice
data.extend_from_slice(&[0x10, 0x20]);
parser.parse(&make_pes(data, Some(0)));
assert!(parser.codec_private().is_none(), "no VPS → None");
}
// --- IRAP keyframe boundary values ---
#[test]
fn irap_lower_boundary_type_16_is_keyframe() {
// BLA_W_LP = 16, the inclusive lower boundary of NAL_BLA_W_LP..=23.
let mut parser = HevcParser::new();
let mut data = vec![0x00, 0x00, 0x01];
data.extend_from_slice(&hevc_nal_header(16));
data.extend_from_slice(&[0x10, 0x20]);
let f = parser.parse(&make_pes(data, Some(0)));
assert!(f[0].keyframe);
}
#[test]
fn type_15_just_below_irap_not_keyframe() {
// Type 15 (RASL_R) is one below the IRAP range and must NOT be a keyframe.
let mut parser = HevcParser::new();
let mut data = vec![0x00, 0x00, 0x01];
data.extend_from_slice(&hevc_nal_header(15));
data.extend_from_slice(&[0x10, 0x20]);
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(!f[0].keyframe, "type 15 is below the IRAP range");
}
#[test]
fn type_24_just_above_irap_not_keyframe() {
// Type 24 (RSV_VCL24) is one above the IRAP range (..=23) → not keyframe.
let mut parser = HevcParser::new();
let mut data = vec![0x00, 0x00, 0x01];
data.extend_from_slice(&hevc_nal_header(24));
data.extend_from_slice(&[0x10, 0x20]);
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(!f[0].keyframe, "type 24 is above the IRAP range");
}
#[test]
fn hevc_nal_type_extraction_masks_correctly() {
// HEVC NAL type = (byte0 >> 1) & 0x3F. The forbidden_zero_bit (bit 7) and
// the low layer-id bit (bit 0) must not affect type. hevc_nal_header(19)
// = [(19<<1), 0x01] = [0x26, 0x01]; with the forbidden bit set (0xA6) it
// is still type 19.
let mut parser = HevcParser::new();
let data = vec![0x00, 0x00, 0x01, 0xA6, 0x01, 0x10, 0x20]; // 0xA6>>1&0x3F = 19
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(
f[0].keyframe,
"0xA6 decodes to NAL type 19 (IDR) → keyframe"
);
}
#[test]
fn hevc_dts_fallback_when_pts_absent() {
let mut parser = HevcParser::new();
let pes = PesPacket {
pid: 0x1011,
pts: None,
dts: Some(90000),
data: {
let mut d = vec![0x00, 0x00, 0x01];
d.extend_from_slice(&hevc_nal_header(1));
d.extend_from_slice(&[0x10, 0x20]);
d
},
};
let f = parser.parse(&pes);
assert_eq!(f.len(), 1);
assert_eq!(f[0].pts_ns, 1_000_000_000, "falls back to DTS");
}
#[test]
fn parse_sps_chroma_too_short_returns_none() {
// An SPS shorter than 3 bytes can't carry the 2-byte NAL header + RBSP →
// parse_sps_chroma returns None (caller falls back to 8-bit 4:2:0).
assert!(parse_sps_chroma(&[0x42]).is_none());
assert!(parse_sps_chroma(&[0x42, 0x01]).is_none());
}
#[test]
fn hvcc_falls_back_to_8bit_420_on_unparseable_sps() {
// An SPS whose RBSP is truncated mid-parse (can't reach the bit depths)
// must fall back to the 8-bit 4:2:0 default, not panic. A 3-byte stored
// SPS (header + 1 RBSP byte) can't complete the PTL skip.
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x00]); // 1 RBSP byte — unparseable
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD]);
parser.parse(&make_pes(data, Some(0)));
let cp = parser.codec_private().expect("hvcC");
assert_eq!(cp[16], 0xFC | 1, "fallback chroma_format_idc = 1 (4:2:0)");
assert_eq!(cp[17], 0xF8, "fallback 8-bit luma");
assert_eq!(cp[18], 0xF8, "fallback 8-bit chroma");
}
#[test]
fn hvcc_oversized_param_set_returns_none() {
// A param set larger than 65535 bytes cannot be length-encoded in hvcC's