From 18082d0df1d6a5112c0f1c63c325861549ddb636 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:40:54 -0700 Subject: [PATCH] audit: void empty-timeline duration, cover sniff overlap Round-11 findings from the 10-phase release audit (no real HIGH): - When a no-declared-duration source (HD-DVD) muxes a degenerate single frame at tick 0 with no per-frame duration, max_block_ticks stays 0 and the reserved DURATION placeholder was left as a literal 0.0 (players read that as a zero-length file). Void the element instead, so the Segment omits DURATION as an unknown-duration source did before the back-patch. - Add a regression test for the sniff_video_codec overlap fix (a picture_start_code whose payload begins 00 00 followed by a real start code) so the i+=4 marker skip can't silently regress to i+=3. --- src/disc/hddvd.rs | 16 ++++++++++++++++ src/mux/mkv.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/disc/hddvd.rs b/src/disc/hddvd.rs index e570c94..250a583 100644 --- a/src/disc/hddvd.rs +++ b/src/disc/hddvd.rs @@ -708,6 +708,22 @@ mod tests { // A slice/picture-only sample (no SPS/sequence) is indeterminate. assert_eq!(sniff_video_codec(&[0x00, 0x00, 0x01, 0x61, 0x9A]), None); assert_eq!(sniff_video_codec(&[0xDE, 0xAD, 0xBE, 0xEF]), None); + + // Overlap regression: a picture_start_code (0x00) whose payload begins + // with 00 00 must advance a full 4 bytes so the code byte isn't re-read + // as the start of a new marker. Here the picture is followed by a real + // MPEG-2 sequence header — the scan must reach it cleanly and return + // Mpeg2 (and, critically, not be confused by the 1-byte overlap). + assert_eq!( + sniff_video_codec(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xB3, 0x2D]), + Some(Codec::Mpeg2) + ); + // A lone picture_start_code with a 00-heavy payload and no following real + // start code stays indeterminate (the overlap must not fabricate one). + assert_eq!( + sniff_video_codec(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), + None + ); } #[test] diff --git a/src/mux/mkv.rs b/src/mux/mkv.rs index 5130c81..b1f4ca9 100644 --- a/src/mux/mkv.rs +++ b/src/mux/mkv.rs @@ -1365,6 +1365,19 @@ impl MkvMuxer { self.writer.seek(std::io::SeekFrom::Start(pos))?; self.writer .write_all(&(self.max_block_ticks as f64).to_be_bytes())?; + } else { + // The timeline never advanced past tick 0 (a degenerate + // single-frame recovery at t=0 with no per-frame duration): we + // can't derive a runtime, so DON'T leave a literal DURATION=0.0 + // (players read that as a zero-length/corrupt file). Void the + // whole 11-byte DURATION element (ID 2 + size 1 + 8-byte payload) + // so the Segment simply omits it, as an unknown-duration source + // did before the back-patch. `pos` is the payload start (+3 from + // the element start), so back up 3. + self.writer.seek(std::io::SeekFrom::Start(pos - 3))?; + ebml::write_id(&mut self.writer, ebml::VOID)?; + ebml::write_size(&mut self.writer, 9)?; // 11 - 1 (Void id) - 1 (size) + self.writer.write_all(&[0u8; 9])?; } } @@ -2907,6 +2920,23 @@ mod tests { .map(|b| f64::from_be_bytes(b.try_into().unwrap())) } + #[test] + fn duration_placeholder_voided_when_timeline_never_advances() { + // Degenerate recovery: a source with no declared duration muxes exactly + // one keyframe at tick 0 with no per-frame duration, so max_block_ticks + // stays 0. The reserved DURATION placeholder must be VOIDED (element + // omitted) rather than left as a bogus 0.0 that players read as a + // zero-length file. + let tracks = [make_video_track()]; + let one_frame = vec![(0usize, 0i64, true, vec![0xAAu8; 16])]; + let (data, _) = mux_to_bytes(&tracks, &[], &one_frame); + assert_eq!( + find_duration_ticks(&data), + None, + "no DURATION element (placeholder voided), not a 0.0 duration" + ); + } + #[test] fn duration_backpatched_from_timeline_when_source_gives_none() { // `mux_to_bytes` muxes with `duration_secs = 0.0` (as an HD-DVD title