fix(libfreemkv): rc6 hardening pass — mux timeline/colour/PCR, demux panic sentinel, parser robustness + doc accuracy
Surgical fixes (each with a regression test that fails without the change): mux/mkv.rs, mux/demux_sink.rs: drive the clip-boundary timeline epoch off the resolved PRIMARY VIDEO track, not the literal stream index 0. An M2TS/PMT title can list an audio ES before video, so streams[0] may be audio; a non-video epoch driver ratchets the frontier and inflates the timeline. mkv cluster-opening falls back to track 0 for audio-only titles so they still open clusters. mux/codec/ac3.rs: correct ACMOD_CHANNELS — acmod=5 (3/1) is 4 channels, not 3 (was undercounting a 3/1 stream); fix the A/52 Table 5.8 doc. disc/mod.rs: HDMV coding_type 0x91 (Interactive Graphics / menus) no longer maps to PGS subtitle — it falls through to Unknown so the PMT/STN walker drops it instead of surfacing a bogus subtitle track. mux/videomap.rs + mux/mkv.rs: FVI colour now mirrors the MKV muxer's CICP precedence (measured CICP authoritative; HDR-driven PQ/HLG transfer override) via a shared cicp_for_video helper, so the two sinks can't disagree (HDR10 BT.2020 no longer emits SDR transfer 14). mux/mkvstream.rs: saturating_add on cluster_ts + rel_ts so an adversarial CLUSTER_TIMESTAMP near i64::MAX can't overflow/panic before the existing saturating_mul. mux/timeline.rs: tighten the tail-straggler clamp so a normal new-epoch non-video frame leading the sparse video frontier by >3s is not demoted into the previous clip's epoch. mux/m2ts_mux/mod.rs: re-stamp PCR per video TS packet (mid-PES), not only at PES boundaries, so a large UHD I-frame can't open a multi-second PCR gap; modular 33-bit PTS rebasing so a real 90 kHz clock wrap is not collapsed to PTS 0 (pre-base frames still floor to 0). io/byte_prefetcher.rs, sector/prefetched.rs: wrap the producer feed loop in catch_unwind and emit a typed error sentinel on panic, so a mid-stream producer panic is not read as a clean EOF at the demux boundary (which would silently truncate the mux). mux/codec/h264.rs: extend HIGH_PROFILES to the full ISO/IEC 14496-15 set that mandates the avcC chroma/bit-depth extension (adds 244 et al.). Doc/comment accuracy: css/mod.rs (50000 sectors, not scrambled-sectors), aacs/decrypt.rs (decrypt_unit already-clear path), ifo.rs (TT_SRPT at 0xC4), css/lfsr.rs (LFSR0 24-bit; TAB1-then-XOR cipher; real scramble-flag predicate), disc/read_error.rs (for_sweep does bounded transient retries). Skipped: keydb.rs SSRF guard (low/latent, no live caller) — a hard loopback block breaks an existing behavioral test that exercises the header-EOF path over a loopback server; a clean fix needs a resolver test seam beyond this surgical pass. The sibling keydb_fetch.rs comment fix is out of scope (freemkv crate).
This commit is contained in:
+15
-9
@@ -269,12 +269,12 @@ fn frame_duration_ns(data: &[u8], bsid: u8) -> u64 {
|
||||
/// Index is the 3-bit acmod value; add 1 when `lfeon` is set.
|
||||
///
|
||||
/// ```text
|
||||
/// 0 = 1+1 (Ch1, Ch2) -> 2 4 = 3/0 (L,C,R) -> 3
|
||||
/// 1 = 1/0 (C, mono) -> 1 5 = 2/1 (L,R,S) -> 3
|
||||
/// 2 = 2/0 (L, R) -> 2 6 = 3/1 (L,C,R,S) -> 4
|
||||
/// 0 = 1+1 (Ch1, Ch2) -> 2 4 = 2/1 (L,R,S) -> 3
|
||||
/// 1 = 1/0 (C, mono) -> 1 5 = 3/1 (L,C,R,S) -> 4
|
||||
/// 2 = 2/0 (L, R) -> 2 6 = 2/2 (L,R,SL,SR) -> 4
|
||||
/// 3 = 3/0 (L,C,R) -> 3 7 = 3/2 (L,C,R,SL,SR) -> 5
|
||||
/// ```
|
||||
const ACMOD_CHANNELS: [u8; 8] = [2, 1, 2, 3, 3, 3, 4, 5];
|
||||
const ACMOD_CHANNELS: [u8; 8] = [2, 1, 2, 3, 3, 4, 4, 5];
|
||||
|
||||
/// Decode the channel count of an (E-)AC-3 frame from its bitstream `acmod` and
|
||||
/// `lfeon`, starting at the 0x0B77 syncword. Returns `None` when the frame is
|
||||
@@ -1129,12 +1129,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn acmod_channels_3_0_and_2_1() {
|
||||
// acmod=4 (3/0 L,C,R) → 3 (exercises cmixlev present, surmixlev absent).
|
||||
// Per A/52 Table 5.8: acmod 4 = 2/1, 5 = 3/1, 6 = 2/2.
|
||||
// acmod=4 (2/1 L,R,S) → 3 (surmixlev present, no centre → no cmixlev).
|
||||
assert_eq!(acmod_channels(&make_bsi(4, false)), Some(3));
|
||||
// acmod=5 (2/1 L,R,S) → 3 (surmixlev present, no centre).
|
||||
assert_eq!(acmod_channels(&make_bsi(5, false)), Some(3));
|
||||
// acmod=6 (3/1) + LFE → 5; lfeon position shifts after both
|
||||
// cmixlev (centre) and surmixlev (surround) 2-bit fields.
|
||||
// acmod=5 (3/1 L,C,R,S) → 4 (centre → cmixlev present, surround →
|
||||
// surmixlev present). This is the regression case: index 5 was wrongly
|
||||
// 3 in ACMOD_CHANNELS, undercounting a 3/1 stream by one channel.
|
||||
assert_eq!(acmod_channels(&make_bsi(5, false)), Some(4));
|
||||
// acmod=5 (3/1) + LFE → 5; lfeon position shifts after both cmixlev
|
||||
// (centre) and surmixlev (surround) 2-bit fields.
|
||||
assert_eq!(acmod_channels(&make_bsi(5, true)), Some(5));
|
||||
// acmod=6 (2/2 L,R,SL,SR) → 4 (surmixlev present, no centre); +LFE → 5.
|
||||
assert_eq!(acmod_channels(&make_bsi(6, false)), Some(4));
|
||||
assert_eq!(acmod_channels(&make_bsi(6, true)), Some(5));
|
||||
}
|
||||
|
||||
|
||||
+41
-6
@@ -295,13 +295,18 @@ impl CodecParser for H264Parser {
|
||||
record.push(pps.len() as u8);
|
||||
record.extend_from_slice(pps);
|
||||
|
||||
// ISO 14496-15 §5.3.3.1.2: for High-Profile and related profiles
|
||||
// (profile_idc 100, 110, 122, 144) the record has 4 trailing extension
|
||||
// bytes carrying chroma_format_idc and bit depths. Older parsers expect
|
||||
// the record to END after the PPS for Baseline/Main/Extended — do NOT
|
||||
// append for those (strict parsers reject the extra bytes).
|
||||
// ISO 14496-15 §5.3.3.1.2: for High-Profile and the related
|
||||
// chroma/bit-depth-extended profiles the record has 4 trailing extension
|
||||
// bytes carrying chroma_format_idc and the luma/chroma bit depths. The
|
||||
// full set that mandates the extension is profile_idc ∈ {100, 110, 122,
|
||||
// 144, 244 (High 4:4:4 Predictive), 44, 83, 86, 118, 128, 138, 139, 134,
|
||||
// 135}. Older parsers expect the record to END after the PPS for
|
||||
// Baseline/Main/Extended — do NOT append for those (strict parsers
|
||||
// reject the extra bytes).
|
||||
let profile_idc = sps[1];
|
||||
const HIGH_PROFILES: [u8; 4] = [100, 110, 122, 144];
|
||||
const HIGH_PROFILES: [u8; 14] = [
|
||||
100, 110, 122, 144, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135,
|
||||
];
|
||||
if HIGH_PROFILES.contains(&profile_idc) {
|
||||
if let Some((chroma_fmt, depth_luma, depth_chroma)) = parse_sps_high_profile_ext(sps) {
|
||||
// byte 0: 111111xx — reserved(6) + chroma_format_idc(2)
|
||||
@@ -1464,6 +1469,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// ISO 14496-15 §5.3.3.1.2 regression: profile_idc=244 (High 4:4:4
|
||||
/// Predictive) ALSO mandates the chroma/bit-depth extension. It was missing
|
||||
/// from HIGH_PROFILES, so a 244 stream took the Baseline/Main path and
|
||||
/// emitted an avcC with NO extension bytes — non-conforming, and strict
|
||||
/// parsers then assume 8-bit 4:2:0. The extension must be appended.
|
||||
#[test]
|
||||
fn avcc_profile_244_appends_extension_bytes() {
|
||||
// profile_idc=244, chroma_format_idc=3 (4:4:4), depths both 4 (12-bit).
|
||||
let sps = build_high_profile_sps(244, 3, 4, 4);
|
||||
let mut parser = H264Parser::new();
|
||||
feed_sps_pps(&mut parser, &sps);
|
||||
|
||||
let cp = parser.codec_private().expect("avcC must be present");
|
||||
let ext_off = sps.len() + 14;
|
||||
assert_eq!(
|
||||
cp.len(),
|
||||
ext_off + 4,
|
||||
"profile 244 avcC must have the 4 extension bytes (len={}, expected {})",
|
||||
cp.len(),
|
||||
ext_off + 4
|
||||
);
|
||||
assert_eq!(cp[ext_off] & 0x03, 3, "chroma_format_idc must be 3 (4:4:4)");
|
||||
assert_eq!(cp[ext_off + 1] & 0x07, 4, "bit_depth_luma_minus8 must be 4");
|
||||
assert_eq!(
|
||||
cp[ext_off + 2] & 0x07,
|
||||
4,
|
||||
"bit_depth_chroma_minus8 must be 4"
|
||||
);
|
||||
}
|
||||
|
||||
/// ISO 14496-15 §5.3.3.1.2 regression: a Main-Profile SPS (profile_idc=77)
|
||||
/// must NOT have the extension bytes — strict parsers reject trailing bytes
|
||||
/// for Baseline/Main/Extended profiles.
|
||||
|
||||
Reference in New Issue
Block a user