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:
Matthew Jackson
2026-06-25 23:39:03 -07:00
parent dc1d05985b
commit 05729f5dfe
16 changed files with 876 additions and 328 deletions
+81 -3
View File
@@ -65,8 +65,27 @@ pub struct Colour {
}
impl Colour {
/// Map the title's [`ColorSpace`] to CICP code points. Unknown colorimetry
/// maps to code point 2 ("unspecified"), the CICP convention.
/// Derive the FVI CICP code points from a full [`VideoStream`], using the
/// SAME precedence as the MKV muxer ([`crate::mux::mkv::cicp_for_video`]):
/// measured CICP (authoritative) → coarse `color_space` enum + HDR-driven
/// transfer override. This is what the sidecar must use so it never reports
/// an SDR transfer (14) for an HDR10 BT.2020 title while the MKV container
/// reports PQ (16) — the two sinks of one title must agree.
pub fn from_video(v: &VideoStream) -> Self {
let (matrix, transfer, primaries, range) = crate::mux::mkv::cicp_for_video(v);
Self {
primaries,
transfer,
matrix,
// Matroska/MeasuredCicp Range: 1 = limited (disc norm), 2 = full.
full_range: range == 2,
}
}
/// Map the title's [`ColorSpace`] alone to CICP code points (no HDR/measured
/// context). Retained for the no-video header fallback and unit coverage;
/// the title path uses [`Colour::from_video`]. Unknown colorimetry maps to
/// code point 2 ("unspecified"), the CICP convention.
pub fn from_color_space(cs: ColorSpace) -> Self {
// (primaries, transfer, matrix) per ITU-T H.273.
let (p, t, m) = match cs {
@@ -217,7 +236,7 @@ impl MapHeader {
} else {
Scan::Progressive
},
colour: Colour::from_color_space(v.color_space),
colour: Colour::from_video(v),
}
}
None => StreamInfo {
@@ -475,6 +494,65 @@ mod tests {
assert_eq!(Colour::from_color_space(ColorSpace::Unknown).primaries, 2);
}
/// Regression: the FVI sidecar must mirror the MKV muxer's colour precedence,
/// not blindly map `color_space` → the SDR transfer 14 for BT.2020. An HDR10
/// BT.2020 title's real transfer is PQ (16); a measured CICP triplet is
/// authoritative and copied through verbatim. Before the fix the FVI Colour
/// reported transfer=14 while the MKV container reported 16 — two sinks of
/// one title disagreeing on the colour code points.
#[test]
fn fvi_colour_follows_hdr_and_measured_cicp() {
use crate::disc::MeasuredCicp;
let mk = |hdr: HdrFormat, cs: ColorSpace, cicp: Option<MeasuredCicp>| VideoStream {
pid: 0x1011,
codec: Codec::Hevc,
resolution: Resolution::R2160p,
frame_rate: FrameRate::F23_976,
hdr,
color_space: cs,
display_aspect: None,
secondary: false,
label: String::new(),
measured_cicp: cicp,
};
// HDR10 BT.2020 with NO measured CICP → PQ transfer (16), NOT SDR 14.
let c = Colour::from_video(&mk(HdrFormat::Hdr10, ColorSpace::Bt2020, None));
assert_eq!(
c,
Colour {
primaries: 9,
transfer: 16, // PQ — not the SDR 14 the enum alone would give
matrix: 9,
full_range: false,
}
);
// HLG BT.2020 → transfer 18.
let c = Colour::from_video(&mk(HdrFormat::Hlg, ColorSpace::Bt2020, None));
assert_eq!(c.transfer, 18, "HLG transfer must be 18");
// Measured CICP is authoritative — copied through verbatim, incl. full
// range (2 → full_range = true), ignoring the coarse enum/HDR guess.
let measured = MeasuredCicp {
matrix: 9,
transfer: 16,
primaries: 9,
range: 2,
};
let c = Colour::from_video(&mk(HdrFormat::Sdr, ColorSpace::Bt709, Some(measured)));
assert_eq!(
c,
Colour {
primaries: 9,
transfer: 16,
matrix: 9,
full_range: true,
},
"measured CICP must override the coarse color_space enum"
);
}
#[test]
fn type_label_full_and_codec_agnostic_fallback() {
// coding present: full I/P/B from the agnostic coding_type().