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:
@@ -988,6 +988,65 @@ pub fn scan_streams(data: &[u8]) -> Option<Vec<crate::disc::Stream>> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The PSI scanner walks a BD-TS buffer one BYTE at a time until it finds a
|
||||
/// packet boundary, so `is_resync_point` is the only thing standing between
|
||||
/// it and a stray 0x47 in a TP_extra_header or a payload. Latching onto one
|
||||
/// puts every subsequent field read 1..191 bytes out of phase, so the PID and
|
||||
/// PUSI bits it then decodes belong to nothing — the scanner either invents a
|
||||
/// stream or loses the real PMT. Accepting every offset (the shape a constant
|
||||
/// `true` takes) guarantees it latches on the first offset it tries.
|
||||
#[test]
|
||||
fn resync_requires_a_corroborating_follower_not_just_one_sync_byte() {
|
||||
const P: usize = BD_SOURCE_PACKET_BYTES;
|
||||
|
||||
// Two well-formed source packets: sync at +4 of each.
|
||||
let mut two = vec![0u8; 2 * P];
|
||||
two[4] = SYNC_BYTE;
|
||||
two[P + 4] = SYNC_BYTE;
|
||||
assert!(
|
||||
is_resync_point(&two, 0),
|
||||
"a real boundary: sync here and 192 bytes on"
|
||||
);
|
||||
|
||||
// No sync byte at all → never a boundary.
|
||||
two[4] = 0x00;
|
||||
assert!(!is_resync_point(&two, 0));
|
||||
two[4] = SYNC_BYTE;
|
||||
|
||||
// Sync here, but the next 192-spaced position is not a sync byte: this is
|
||||
// a 0x47 that happens to sit inside a header or payload, not a boundary.
|
||||
two[P + 4] = 0x00;
|
||||
assert!(
|
||||
!is_resync_point(&two, 0),
|
||||
"an uncorroborated 0x47 must be rejected"
|
||||
);
|
||||
two[P + 4] = SYNC_BYTE;
|
||||
|
||||
// The concrete desync the corroboration prevents: a 0x47 byte sitting in
|
||||
// the payload of the first packet. Its own "sync" test passes, and the
|
||||
// scanner must still refuse the offset.
|
||||
let stray = 60usize;
|
||||
two[stray + 4] = SYNC_BYTE;
|
||||
assert!(
|
||||
!is_resync_point(&two, stray),
|
||||
"a stray 0x47 inside a payload is not a packet boundary"
|
||||
);
|
||||
// ...while the genuine boundaries either side still are.
|
||||
assert!(is_resync_point(&two, 0));
|
||||
|
||||
// A lone trailing packet has no follower to corroborate, so it is accepted
|
||||
// on its own sync byte — otherwise the last packet of every buffer would
|
||||
// be unreachable.
|
||||
let mut one = vec![0u8; P];
|
||||
one[4] = SYNC_BYTE;
|
||||
assert!(is_resync_point(&one, 0), "last packet in the buffer");
|
||||
one[4] = 0x00;
|
||||
assert!(
|
||||
!is_resync_point(&one, 0),
|
||||
"...but it still needs its own sync byte"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_timestamp() {
|
||||
// Example: PTS = 0 → encoded as 21 00 01 00 01
|
||||
|
||||
Reference in New Issue
Block a user