test: constrain the AACS key-map gap fill, the PSI walk, and MP4 field offsets
Third pass over src/mux/. 40 survivors killed, no production change. resolve.rs — the deleted-statement cluster is now fully constrained. All 14 deletable statements probed; 9 were already caught, 5 survived: c.sort_unstable() in fill_base_key_gaps. Every existing case handed it cuts already in LBA order, but IndividualSegment.tbl is a record list. Verified on HEAD: deleting the sort passes all 54 resolve tests. The mutant lays a base-key fill straight over a forensic segment. last_idx = idx (FMTS gap fill) and last_idx = hit (multi-CPS cache hit). An extent with nothing to sample must inherit its neighbour's CPS unit; the mutants fall back to the first unit's key. Exactly the shape this file's own comments name — wrong key, no error, lost_bytes == 0. Both check_halt()? polls in probe_fmts_index_keys. These cannot be killed by outcome, since a later poll returns Halted too. The tests count reads instead, which is what the don't-hammer-a-struggling- drive rule actually says: after a Stop the drive is asked for zero content sectors. The four unresolved += 1 arms each got a test, and deleting each fails exactly one — one-to-one, so no fixture passes for the wrong reason. A control test pins that the baseline table resolves, so an expect_err cannot succeed for an unrelated reason. ts.rs::scan_streams was never entered. Six killed, two of which return wrong answers that look right: reading the PAT/PMT CRC as a table entry invents a stream on PID 546 out of CRC bytes, and dropping the ES_info_length skip decodes a descriptor as an entry and loses the one after it. Every existing PMT fixture declares ES_info_length = 0; a real BD PMT carries a registration descriptor on essentially every entry. Also ISO/IEC 13818-1 2.4.4.3 program_number == 0 is the network PID, not a program. mp4/read.rs — 13. Height read as the width beside it; channelcount; the 4-byte base-128 descriptor varint (every existing esds fixture uses a single byte); all three optional ES_Descriptor fields, whose loss is silent (an AAC track just loses its CodecPrivate); first-vs-last media edit, which is A/V desync of the difference; and the version-1 mvhd timescale offset, emitted by any writer whose duration exceeds 32 bits. dts.rs — 7, from a real cargo-mutants run over the file rather than guesswork. Including a buffer that IS the syncword, which is the state a sync split across PES packets lands in the moment its last byte arrives. Equivalents proven by application, not argued: the sample_encrypted_units guard pair is mutually redundant by construction (total*p/9 < total for p <= 8), so either alone is equivalent and both together are not; the PMT section_len guard is dead code where its PAT twin panics; three of the seven EXSS_HEADER_MIN_BYTES arithmetic mutants still sum to 10.
This commit is contained in:
@@ -2146,4 +2146,149 @@ mod tests {
|
||||
bytes.len().saturating_sub(out_bytes)
|
||||
);
|
||||
}
|
||||
|
||||
// ── Exact-boundary behaviour of the AU framer ────────────────────────────
|
||||
|
||||
/// `find_sync` scans `0..=len-4`, so a buffer that is EXACTLY the syncword
|
||||
/// still matches. The existing tests cover 0, 3 and "longer than 4", which
|
||||
/// leaves the `len == 4` boundary open — and that is the case a syncword split
|
||||
/// across PES packets lands on the moment its last byte arrives.
|
||||
#[test]
|
||||
fn find_sync_matches_a_buffer_that_is_exactly_the_syncword() {
|
||||
assert_eq!(
|
||||
find_sync(&DTS_CORE_SYNC, &DTS_CORE_SYNC),
|
||||
Some(0),
|
||||
"a four-byte buffer that IS the syncword matches at 0"
|
||||
);
|
||||
let mut five = vec![0x00u8];
|
||||
five.extend_from_slice(&DTS_CORE_SYNC);
|
||||
assert_eq!(
|
||||
find_sync(&five, &DTS_CORE_SYNC),
|
||||
Some(1),
|
||||
"and the last possible start offset is len - 4"
|
||||
);
|
||||
}
|
||||
|
||||
/// `CORE_HEADER_MIN_BYTES` is "enough bytes to DECODE the size field", so a
|
||||
/// buffer holding exactly that many must be decoded, not deferred. The
|
||||
/// distinction is visible precisely at the boundary: with 10 bytes of a core
|
||||
/// sync whose decoded size is sub-spec, the parser must recognise the false
|
||||
/// sync, drain past it and resync down to the 3-byte carry-over tail. Waiting
|
||||
/// instead leaves the false sync sitting at the front of the buffer, where it
|
||||
/// blocks every later real core behind it.
|
||||
#[test]
|
||||
fn a_core_header_of_exactly_the_minimum_length_is_decoded_not_deferred() {
|
||||
let mut parser = DtsParser::new();
|
||||
let mut d = vec![0u8; CORE_HEADER_MIN_BYTES];
|
||||
d[0..4].copy_from_slice(&DTS_CORE_SYNC);
|
||||
d[6] = 0x01; // fsize = 16 → core_size 17, below the 96-byte ETSI floor
|
||||
assert_eq!(d.len(), 10, "the fixture is exactly at the boundary");
|
||||
let out = parser.parse(&make_pes(d, Some(90_000)));
|
||||
assert!(out.is_empty(), "a false sync emits nothing");
|
||||
assert_eq!(
|
||||
parser.buf.len(),
|
||||
3,
|
||||
"the false sync was decoded, drained and resynced past — leaving only \
|
||||
the 3-byte split-sync carry-over"
|
||||
);
|
||||
assert_ne!(
|
||||
find_sync(&parser.buf, &DTS_CORE_SYNC),
|
||||
Some(0),
|
||||
"and the bogus sync is no longer at the front of the buffer"
|
||||
);
|
||||
}
|
||||
|
||||
/// The end-of-stream flush must emit a final access unit whose core is exactly
|
||||
/// as long as the buffer — the ordinary case, since the last AU is closed by
|
||||
/// end-of-stream rather than by a following core sync. Rejecting it at the
|
||||
/// boundary silently drops the last frame of every DTS track.
|
||||
#[test]
|
||||
fn flush_emits_a_core_that_exactly_fills_the_buffer() {
|
||||
let mut parser = DtsParser::new();
|
||||
let core = make_dts_core(512);
|
||||
parser.buf = core.clone();
|
||||
parser.pending_pts = 90_000;
|
||||
let out = parser.flush();
|
||||
assert_eq!(out.len(), 1, "the final AU is emitted, not dropped");
|
||||
assert_eq!(out[0].data, core, "and it is the whole core frame");
|
||||
// One byte short is still refused — the bound is not simply absent.
|
||||
let mut parser = DtsParser::new();
|
||||
parser.buf = core[..511].to_vec();
|
||||
parser.pending_pts = 90_000;
|
||||
assert!(
|
||||
parser.flush().is_empty(),
|
||||
"a core one byte short of its declared size is not emitted truncated"
|
||||
);
|
||||
}
|
||||
|
||||
/// The flush guard is a DISJUNCTION: a buffer that does not BEGIN with a core
|
||||
/// sync is discarded whatever its length. Requiring both conditions instead
|
||||
/// lets a long run of junk through to `dts_core_frame_size`, which happily
|
||||
/// decodes a 14-bit size out of arbitrary bytes — and the flush then emits an
|
||||
/// "access unit" that is not DTS at all.
|
||||
///
|
||||
/// The junk here is sized so that mis-decoding it yields a plausible core size
|
||||
/// that the buffer fully covers, which is exactly when the wrong answer looks
|
||||
/// like a right one.
|
||||
#[test]
|
||||
fn flush_discards_a_long_buffer_that_does_not_begin_with_a_core_sync() {
|
||||
// A well-formed core frame with ONE byte of its syncword corrupted: every
|
||||
// other field still decodes, and the decodability gate would pass it, so
|
||||
// only the leading-sync test stands between it and the output.
|
||||
let mut parser = DtsParser::new();
|
||||
let mut broken = make_dts_core(300);
|
||||
broken[0] ^= 0xFF; // no longer 0x7FFE8001 at offset 0
|
||||
assert_ne!(
|
||||
find_sync(&broken, &DTS_CORE_SYNC),
|
||||
Some(0),
|
||||
"the fixture really has no core sync at the front"
|
||||
);
|
||||
parser.buf = broken;
|
||||
parser.pending_pts = 90_000;
|
||||
assert!(
|
||||
parser.flush().is_empty(),
|
||||
"a buffer whose front is not a core sync is discarded, not size-decoded"
|
||||
);
|
||||
assert!(parser.buf.is_empty(), "and the junk is dropped");
|
||||
|
||||
// The other half of the disjunction: a buffer too short to size, whose
|
||||
// front IS a core sync, is discarded too.
|
||||
let mut parser = DtsParser::new();
|
||||
parser.buf = DTS_CORE_SYNC.to_vec();
|
||||
parser.pending_pts = 90_000;
|
||||
assert!(parser.flush().is_empty(), "a bare sync tail is not an AU");
|
||||
}
|
||||
|
||||
/// `EXSS_HEADER_MIN_BYTES` is the WORST-CASE header length — the long form's
|
||||
/// 43 bits after the syncword, rounded up to 6 bytes, plus the 4-byte sync.
|
||||
/// It gates whether an extension substream can be sized precisely, and that is
|
||||
/// what keeps a chance core syncword inside XLL payload from being mistaken
|
||||
/// for the next AU boundary; too large and every extension falls back to the
|
||||
/// payload scan, too small and the reader runs off a truncated header.
|
||||
///
|
||||
/// Pinned at both sides of the boundary rather than by value, so the field
|
||||
/// widths it is summed from stay honest.
|
||||
#[test]
|
||||
fn exss_frame_size_needs_the_worst_case_header_and_no_more() {
|
||||
assert_eq!(
|
||||
EXSS_HEADER_MIN_BYTES, 10,
|
||||
"4 sync bytes + ceil((8 + 2 + 1 + 12 + 20) / 8)"
|
||||
);
|
||||
let ext = make_exss(10, None);
|
||||
assert_eq!(
|
||||
exss_frame_size(&ext),
|
||||
Some(10),
|
||||
"exactly the minimum is enough to size a substream"
|
||||
);
|
||||
assert_eq!(
|
||||
exss_frame_size(&ext[..9]),
|
||||
None,
|
||||
"one byte short cannot be sized — the long form's fields are not all in"
|
||||
);
|
||||
assert_eq!(
|
||||
exss_frame_size(&ext[..4]),
|
||||
None,
|
||||
"the bare sync sizes nothing"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user