test: constrain MP4 composition timing, MLP substream directory, and codec-private absence

Mutation testing over src/mux/. No production change — 49 survivors
killed, all proven red before green.

The MP4 composition-time chain was entirely unconstrained: VideoTiming::ctts,
build_ctts and parse_ctts could each return a constant and the suite
stayed green. Confirmed on HEAD: build_ctts -> vec![] passes all 1,220
mux tests. A demuxed B-frame title presenting in decode order would
have shipped.

The cause is a test whose name asserts coverage its body does not
deliver — stts_and_ctts_expand builds an stts box and never touches
ctts, and write_then_read_round_trip asserts sample sizes and keyframe
flags but not one PTS. Same shape as the set_speed forwarding finding,
different disguise.

mlp_num_substreams / mlp_substr_header_size: every TrueHD fixture in
the crate uses one substream and no extraword, so both could return a
constant and agree with all of them. These position mlp_parity_ok's
window over the AU header, so a constant mis-windows the parity check
on exactly the multi-substream AUs that carry 7.1 and Atmos.

CodecPrivate absent vs empty: mkv.rs writes Some(bytes) verbatim and
omits the element on None (RFC 9559 5.1.4.1.24), so a zero-length Some
emits a track header asserting the config IS empty. Four parsers could
return Some(vec![]) before any frame.

Also: mandatory ISO/IEC 14496-12 boxes (tkhd, vmhd, smhd, dinf, mdhd)
could each build empty; HEVC num_extra_slice_header_bits (H.265 7.3.2.3)
was never non-zero in any fixture, so the slice-type offset skip was
unexercised; chapter names from the disc go straight into
<ChapterString> and the & escape must run first; a stray 0x47 in a
payload must not latch a TS resync.

Documented as equivalent rather than killed: CodecParser::flush and the
three parser flush bodies that differ from the mutant only by a tracing
call, and DropTally::log_summary.
This commit is contained in:
Matthew Jackson
2026-07-30 13:39:02 -07:00
parent 55b97ac576
commit 170fd0c064
14 changed files with 1027 additions and 0 deletions
+27
View File
@@ -190,6 +190,33 @@ mod tests {
);
}
/// A dropped ADTS frame is dropped BECAUSE its header failed validation, so
/// the very fields a duration would come from (sampling_frequency_index, and
/// the 1024-samples-per-AAC-frame constant applied to it) are the ones known
/// to be untrustworthy. This gate therefore reports the drop's duration as
/// zero rather than deriving a number from a header it has just rejected —
/// the honest answer, and the one the count alongside it must be read with.
/// A nonzero constant here would report silence that was never measured.
#[test]
fn dropped_frames_are_counted_but_their_duration_is_not_invented() {
let mut parser = AdtsParser::new();
// Three frames whose sampling_frequency_index is a reserved value (13),
// so `adts_verdict` rejects each one.
let mut bad = adts_frame(32);
bad[2] = (bad[2] & 0b1100_0011) | (13 << 2);
for i in 0..3 {
let out = parser.parse(&make_pes(bad.clone(), Some(i * 90_000)));
assert!(out.is_empty(), "an invalid ADTS frame is not emitted");
}
assert_eq!(parser.dropped_frames(), 3, "every drop is counted");
assert_eq!(
parser.dropped_duration_ns(),
0,
"the duration comes from the header that just failed validation, so \
it is reported as unmeasured rather than guessed"
);
}
#[test]
fn reserved_sample_rate_index_is_dropped() {
// sr_index = 13 (reserved). byte2 bits5..2 = 1101 → 0x34.
+25
View File
@@ -200,6 +200,31 @@ mod tests {
assert!(!t.is_poisoned());
}
/// The poison verdict is a RATIO — verified drops against every AU seen — so
/// the kept count is half of it. `does_not_poison_a_mostly_good_track` above
/// records its keeps AFTER the single drop, and `maybe_poison` only runs
/// inside `record_drop`, so the keeps are never in the denominator when the
/// verdict is actually computed: that test passes even with the kept count
/// never incremented. Interleaving them puts the kept count on the critical
/// path, where losing it turns the ratio into "verified drops vs verified
/// drops" — always >50% — and silently discards a healthy track.
#[test]
fn interleaved_keeps_are_in_the_poison_denominator() {
let mut t = DropTally::new("test");
// 2 kept per 1 dropped, well past the minimum-AU gate: a third of the
// track is undecodable, which is bad but nowhere near the >50% threshold.
for _ in 0..(TRACK_VERDICT_MIN_AUS * 3) {
t.record_kept();
t.record_kept();
t.record_drop(0, 1000, 512, "bad");
assert!(
!t.is_poisoned(),
"33% dropped must never poison, at any point in the run"
);
}
assert_eq!(t.dropped_frames(), TRACK_VERDICT_MIN_AUS * 3);
}
#[test]
fn collateral_drops_never_poison_the_track() {
// A TrueHD resync-forward run collaterally drops a long burst of AUs, but
+75
View File
@@ -1447,6 +1447,81 @@ mod tests {
assert_eq!(h.max_display_mastering_luminance, 10_000_000);
}
/// `num_extra_slice_header_bits` (H.265 §7.3.2.3) is a PPS field, and the
/// `slice_reserved_flag[i]` bits it counts sit BETWEEN
/// `slice_pic_parameter_set_id` and `slice_type` in the slice segment header
/// (§7.3.6.1). Every existing fixture used a PPS with the field == 0, so the
/// skip was never exercised: a parser that ignored the field entirely agreed
/// with all of them, and would then read `slice_type` from the wrong bit
/// offset on any real stream that sets it — mislabelling every picture's
/// coding type.
#[test]
fn nonzero_num_extra_slice_header_bits_shifts_the_slice_type_offset() {
use super::super::coding::CodingType;
// PPS body bits: pps_id ue=0 ('1'), sps_id ue=0 ('1'),
// dependent_slice_segments_enabled_flag 0, output_flag_present_flag 0,
// num_extra_slice_header_bits u(3).
let pps_body = |num_extra: u8| 0b1100_0000u8 | (num_extra << 1);
assert_eq!(pps_body(0), 0xC0, "matches the existing zero-extra fixture");
assert_eq!(pps_body(3), 0xC6);
let pps_nal = |num_extra: u8| {
let mut v = hevc_nal_header(NAL_PPS).to_vec();
v.push(pps_body(num_extra));
v
};
for n in 0..8u8 {
assert_eq!(
hevc_num_extra_slice_header_bits(&pps_nal(n)),
Some(n as u32),
"PPS must yield the value it encodes, for every u(3) code point"
);
}
// A PPS truncated to just its 2-byte NAL header carries no field to read,
// so the answer is absent — never a defaulted zero.
assert_eq!(
hevc_num_extra_slice_header_bits(&hevc_nal_header(NAL_PPS)),
None
);
// Slice segment header for a non-IRAP VCL NAL (TRAIL_R, type 1):
// first_slice_segment_in_pic_flag 1, slice_pic_parameter_set_id ue=0
// ('1'), then THREE reserved bits set to 101 (deliberately not zero, so a
// parser that reads them instead of skipping them cannot agree), then
// slice_type ue(v) = '011' → 2 → I.
let slice_body = 0b1_1_101_011u8;
assert_eq!(slice_body, 0xEB);
let nal = |t: u8, body: u8| {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(t));
v.push(body);
v
};
let mut data = nal(NAL_PPS, pps_body(3));
data.extend_from_slice(&nal(1, slice_body));
let frames = HevcParser::new().parse(&make_pes(data, Some(0)));
assert_eq!(frames.len(), 1);
assert_eq!(
frames[0].coding.expect("PictureInfo").coding_type(),
CodingType::I,
"with num_extra=3 the reserved bits are skipped and slice_type reads 2 (I)"
);
// Control: the SAME slice bytes under a PPS declaring num_extra=0 land on
// a different slice_type — proving the PPS field, not the slice bytes,
// decides the offset.
let mut data0 = nal(NAL_PPS, pps_body(0));
data0.extend_from_slice(&nal(1, slice_body));
let frames0 = HevcParser::new().parse(&make_pes(data0, Some(0)));
assert_eq!(
frames0[0].coding.expect("PictureInfo").coding_type(),
CodingType::B,
"num_extra=0 reads slice_type from bit 2 instead → 0 (B)"
);
}
#[test]
fn hevc_populates_measured_coding_type_and_source() {
use super::super::coding::CodingType;
+57
View File
@@ -284,6 +284,63 @@ mod tests {
}
}
/// A codec parser's `codec_private()` feeds `DiscStream::codec_private`,
/// which the MKV muxer turns directly into the track's `CodecPrivate`
/// element (RFC 9559 §5.1.4.1.24): `Some(bytes)` writes an element holding
/// exactly those bytes, `None` omits the element entirely. The two are NOT
/// interchangeable — a zero-length `CodecPrivate` asserts that the codec's
/// initialisation data IS empty, which is not true of any codec, and a
/// one-byte one asserts a config no decoder can parse.
///
/// The gating parsers (ADTS, MPEG audio, FLAC) and the passthrough parser
/// extract no configuration at all: they validate and forward frames whose
/// configuration is carried in band (ADTS headers, MPEG-1 audio frame
/// headers, FLAC frame headers) or supplied by the source container. Having
/// derived nothing, the only truthful answer they can give is "absent" —
/// any `Some` would be a value they invented. Nothing else in the suite
/// distinguished the two, so each of these impls could have returned a
/// fabricated `Some` and produced a malformed track header unnoticed.
#[test]
fn parsers_that_derive_no_config_report_absent_never_an_empty_codec_private() {
// Codecs whose parsers do no configuration extraction, paired with a
// payload that is a REAL frame of that codec so the gate takes its
// keep-path (a rejected frame proves nothing about the config answer).
let cases: [(Codec, Vec<u8>); 5] = [
// ADTS: syncword FFF1, MPEG-4 AAC-LC, 44.1 kHz, stereo, 7-byte frame.
(Codec::Aac, vec![0xFF, 0xF1, 0x50, 0x80, 0x00, 0xBF, 0xFC]),
// MPEG-1 Layer II, 44.1 kHz, 128 kbit/s, stereo.
(Codec::Mp2, vec![0xFF, 0xFD, 0x70, 0x00, 0x00, 0x00]),
// MPEG-1 Layer III, 44.1 kHz, 128 kbit/s, stereo.
(Codec::Mp3, vec![0xFF, 0xFB, 0x90, 0x00, 0x00, 0x00]),
// Not a FLAC frame sync, so the gate passes it through unvalidated —
// still the keep path, and still no configuration derived.
(Codec::Flac, vec![0x01, 0x02, 0x03, 0x04]),
// Opus rides the all-keyframe passthrough parser.
(Codec::Opus, vec![0x78, 0x01, 0x02, 0x03]),
];
for (codec, payload) in cases {
let mut parser = parser_for_codec(codec, None, false);
assert_eq!(
parser.codec_private(),
None,
"{codec:?}: no config before any frame"
);
parser.parse(&pes(Some(0), payload.clone()));
parser.parse(&pes(Some(90_000), payload));
assert_eq!(
parser.codec_private(),
None,
"{codec:?}: this parser derives no config, so it must report the \
CodecPrivate as ABSENT — an empty or invented Some would be \
written into the track header as if it were real"
);
// Flushing at end of stream must not conjure one either.
parser.flush();
assert_eq!(parser.codec_private(), None, "{codec:?}: after flush");
}
}
#[test]
fn audio_codecs_emit_keyframe_frames() {
// PES = frame audio: every frame is independently decodable → keyframe.
+24
View File
@@ -183,6 +183,30 @@ mod tests {
);
}
/// Same contract as the ADTS gate: an MPEG-audio frame is dropped because
/// its header is invalid, and the header is exactly where a frame duration
/// (samples-per-frame for the layer, divided by the sampling rate) would
/// have to come from — see `mpa_verdict`'s doc comment. So the drop's
/// duration is reported as zero, not derived from rejected fields.
#[test]
fn dropped_frames_are_counted_but_their_duration_is_not_invented() {
let mut parser = MpegAudioParser::new();
// Reserved layer field (00) — rejected per ISO/IEC 11172-3.
let mut bad = mp3_frame(32);
bad[1] &= !0b0000_0110;
for i in 0..3 {
let out = parser.parse(&make_pes(bad.clone(), Some(i * 90_000)));
assert!(out.is_empty(), "an invalid MPEG-audio frame is not emitted");
}
assert_eq!(parser.dropped_frames(), 3, "every drop is counted");
assert_eq!(
parser.dropped_duration_ns(),
0,
"the duration comes from the header that just failed validation, so \
it is reported as unmeasured rather than guessed"
);
}
#[test]
fn reserved_version_field_is_dropped() {
// version field = 01 (reserved) → rejected. byte1 = 111_01_01_1 = 0xEB
+34
View File
@@ -341,6 +341,40 @@ mod tests {
assert_eq!(display_set_is_forced(&[]), None);
}
/// `observed()` is the probe's "did I actually see any PGS content?" signal.
/// When it is false the track's forced state is UNKNOWN, and the probe leaves
/// whatever flag the disc's own metadata supplied alone; when it is true the
/// probe overwrites that flag with its own verdict. A tracker that always
/// claims to have observed something therefore lets an unread or undecrypted
/// subtitle track — where `is_forced()` is vacuously false — overwrite a
/// correct vendor "forced" flag with "not forced".
#[test]
fn observed_stays_false_until_a_real_display_set_is_seen() {
let mut t = ForcedTracker::new();
assert!(!t.observed(), "a fresh tracker has seen nothing");
assert!(!t.is_forced(), "and has no verdict to give");
// Blocks that carry no display set must not count as observation: a clear
// PCS (zero composition objects), a non-PCS segment, a truncated PCS, and
// an empty frame.
let mut clear = pcs_display(false);
clear[PCS_NUM_OBJECTS_OFFSET] = 0;
let mut ods = pcs_display(true);
ods[0] = 0x15;
for block in [clear, ods, pcs_display(true)[..15].to_vec(), Vec::new()] {
t.observe(&block);
assert!(
!t.observed(),
"a block with no display set leaves the verdict unknown"
);
}
// The first real display set is what flips it.
t.observe(&pcs_display(true));
assert!(t.observed());
assert!(t.is_forced(), "the only display set seen was forced");
}
fn make_pes(data: Vec<u8>, pts: Option<i64>) -> PesPacket {
PesPacket {
source: None,
+73
View File
@@ -752,6 +752,70 @@ mod tests {
}
}
/// The substream count and the substream-directory size are the two numbers
/// that position `mlp_parity_ok`'s check window over the AU header. Every
/// other fixture in this module uses the degenerate shape — one substream,
/// no extraword — so neither function's real behaviour was ever exercised:
/// a constant answer agreed with all of them, and would then mis-window the
/// parity check on the multi-substream AUs that carry 7.1 and Atmos, judging
/// clean audio corrupt (or corrupt audio clean).
#[test]
fn mlp_num_substreams_is_the_top_nibble_of_major_sync_byte_16() {
// Byte 16 of the major sync: top nibble is num_substreams, bottom nibble
// is a different field, so it must not leak into the answer.
for n in 0..16u8 {
let mut ms = vec![0u8; 17];
ms[16] = (n << 4) | 0x0F;
assert_eq!(
mlp_num_substreams(&ms),
Some(n),
"num_substreams is the high nibble only"
);
}
// Real counts: 1 substream for 2.0/5.1 core-only, 4 for the 7.1/Atmos
// layouts this crate has to mux.
let mut ms = vec![0u8; 20];
ms[16] = 0x40;
assert_eq!(mlp_num_substreams(&ms), Some(4));
// A major sync too short to contain byte 16 yields no answer — never a
// defaulted count, which would arm the parity check against garbage.
assert_eq!(mlp_num_substreams(&[0u8; 16]), None);
}
#[test]
fn mlp_substr_header_size_counts_the_extraword_entries() {
// Directory entry: 2 bytes, plus 2 more when the entry's top bit
// (extraword) is set. Build a 4-substream directory that mixes both.
const HDR: usize = 4;
let mut au = vec![0xAAu8; HDR];
au.extend_from_slice(&[0x00, 0x11]); // plain → 2
au.extend_from_slice(&[0x80, 0x22, 0x01, 0x02]); // extraword → 4
au.extend_from_slice(&[0x00, 0x33]); // plain → 2
au.extend_from_slice(&[0x80, 0x44, 0x03, 0x04]); // extraword → 4
au.extend_from_slice(&[0xFFu8; 8]); // payload past the directory
assert_eq!(
mlp_substr_header_size(&au, HDR, 4),
Some(12),
"2 + 4 + 2 + 4"
);
// Same AU, fewer declared substreams → only that many entries counted.
assert_eq!(mlp_substr_header_size(&au, HDR, 1), Some(2));
assert_eq!(mlp_substr_header_size(&au, HDR, 2), Some(6));
assert_eq!(mlp_substr_header_size(&au, HDR, 0), Some(0));
// An all-plain directory is 2 bytes per substream.
let plain = vec![0x00u8; HDR + 8];
assert_eq!(mlp_substr_header_size(&plain, HDR, 4), Some(8));
// A directory that runs past the AU has no answer: the parity window
// would otherwise be placed over bytes that are not there.
let truncated = &au[..HDR + 9];
assert_eq!(mlp_substr_header_size(truncated, HDR, 4), None);
assert_eq!(mlp_substr_header_size(&plain, HDR, 5), None);
}
fn make_truehd_unit(size_bytes: usize) -> Vec<u8> {
let words = size_bytes / 2;
let mut data = vec![0u8; size_bytes];
@@ -994,6 +1058,15 @@ mod tests {
frames.extend(parser.flush());
assert_eq!(frames.len(), 2, "the parity-broken AU is dropped");
assert_eq!(parser.dropped_frames(), 1);
// The drop is a SILENCE GAP, and its length is what the CLI reports as
// lost audio. One TrueHD access unit is 40 samples; at the 48 kHz base
// rate that is 40/48000 s = 833_333 ns. A count without a duration (or a
// duration that ignores the AU rate) understates or invents the loss.
assert_eq!(
parser.dropped_duration_ns(),
833_333,
"one dropped AU = 40 samples at 48 kHz"
);
}
#[test]