tests: read_aacs_inputs Long-AD coverage; scrub title names from comments
Add a precommit fixture proving Disc::read_aacs_inputs reads a Long-AD, multi-extent /AACS/Unit_Key_RO.inf in full — the exact input the online key-request path depends on (no disc/deploy needed). Make read_aacs_inputs_from_reader pub(crate) for the test. Reword internal comments/doc examples to generic descriptions instead of specific media titles.
This commit is contained in:
+1
-1
@@ -2113,7 +2113,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_keys_path2_5_mk_pool_brute_resolves_unkeyed_disc() {
|
||||
// The Dunkirk case: this disc's own hash/VID are NOT in keydb, but its
|
||||
// The keyless-disc case: this disc's own hash/VID are NOT in keydb, but its
|
||||
// Media Key IS — filed under a sibling disc that shares its MKB. Path
|
||||
// 2.5 must km_verifies that MK against the MKB and resolve.
|
||||
use super::super::decrypt::aes_ecb_encrypt as enc;
|
||||
|
||||
+1
-1
@@ -1299,7 +1299,7 @@ impl Disc {
|
||||
/// zero-padded, so reading either ships up to ~124 MiB of nothing —
|
||||
/// trim to the record stream so callers send/store a few MB, not
|
||||
/// 128 MiB.
|
||||
fn read_aacs_inputs_from_reader(
|
||||
pub(crate) fn read_aacs_inputs_from_reader(
|
||||
reader: &mut dyn SectorSource,
|
||||
udf_fs: &udf::UdfFs,
|
||||
) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ pub struct DiscInputs {
|
||||
/// [`crate::Disc::inputs`] leaves it empty for the caller to fill.
|
||||
pub samples: Vec<Vec<u8>>,
|
||||
/// The disc's human title — the UDF/ISO volume identifier (e.g.
|
||||
/// `WICKED_FOR_GOOD`), falling back to the BDMV `<di:name>` when present.
|
||||
/// `TITLE_2024`), falling back to the BDMV `<di:name>` when present.
|
||||
/// `None` when not captured. Identity only, no secret; a key service may
|
||||
/// record it (keyed by `disc_hash`) to build a hash→title catalog. Not used
|
||||
/// in any AACS derivation.
|
||||
|
||||
+3
-3
@@ -1358,7 +1358,7 @@ mod apply_tests {
|
||||
|
||||
#[test]
|
||||
fn apply_rejects_mismatched_codec_hint_and_uses_stream_codec() {
|
||||
// Paddington case: a TrueHD+Atmos main track the parser mislabeled
|
||||
// TrueHD+Atmos relabel case: a TrueHD+Atmos main track the parser mislabeled
|
||||
// "AC-3 2.0" (a compat-core hint bound to the wrong stream). The hint
|
||||
// contradicts the stream's real codec → discard it, use the stream's own.
|
||||
let mut titles = vec![title_with(vec![audio(
|
||||
@@ -1378,7 +1378,7 @@ mod apply_tests {
|
||||
|
||||
#[test]
|
||||
fn apply_unshuffles_cross_labeled_streams() {
|
||||
// Wicked case: hints fully cross-bound — a TrueHD stream wears "AC-3 5.1"
|
||||
// Cross-bound hints case: hints fully cross-bound — a TrueHD stream wears "AC-3 5.1"
|
||||
// and a DD+ stream wears "TrueHD 5.1". Each is corrected from its own
|
||||
// stream codec, eliminating the shuffle.
|
||||
let mut titles = vec![title_with(vec![
|
||||
@@ -1463,7 +1463,7 @@ mod apply_tests {
|
||||
|
||||
#[test]
|
||||
fn apply_normalizes_plain_consistent_hint_to_marketing() {
|
||||
// Wicked's French track: a DD+ stream whose hint "AC-3+ 5.1" is correct
|
||||
// A French DD+ track: a DD+ stream whose hint "AC-3+ 5.1" is correct
|
||||
// but short-form. A sibling DD+ track that fell back uses the marketing
|
||||
// form — keeping the short form here would read inconsistently, so a
|
||||
// plain (non-richer) consistent hint is normalized to the stream's own.
|
||||
|
||||
@@ -19,7 +19,7 @@ const DTS_HD_EXT_SYNC: [u8; 4] = [0x64, 0x58, 0x20, 0x25];
|
||||
/// a core frame plus all of its trailing DTS-HD extension substreams are
|
||||
/// emitted together as one access unit, delimited by the next valid core sync.
|
||||
/// This preserves the lossless extension data instead of downgrading to lossy
|
||||
/// core (the Dunkirk / Fight Club lossy-core bug).
|
||||
/// core (the lossy-core downgrade bug).
|
||||
pub struct DtsParser {
|
||||
buf: Vec<u8>,
|
||||
/// PTS of the access unit currently being assembled in `buf` (the unit
|
||||
@@ -140,8 +140,8 @@ impl CodecParser for DtsParser {
|
||||
// sync. Emitting on the core boundary keeps the core + every following
|
||||
// extension substream together (the lossless data), instead of the
|
||||
// old per-PES emit that dropped the extension PES packets and
|
||||
// downgraded the track to lossy DTS core (the Dunkirk / Fight Club
|
||||
// bug). The PTS is the core frame's PTS, captured when the unit began.
|
||||
// downgraded the track to lossy DTS core (the lossy-core
|
||||
// downgrade bug). The PTS is the core frame's PTS, captured when the unit began.
|
||||
// Capture the access unit's PTS base on a fresh buffer, or whenever a
|
||||
// prior forced (safety-valve) flush left it invalidated — in the
|
||||
// forced case the bytes still in `buf` are not a real core frame, so
|
||||
@@ -534,12 +534,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn keeps_dts_hd_extension_in_separate_pes_packets() {
|
||||
// The real Blu-ray layout (ground-truthed on Dunkirk): the DTS core
|
||||
// The real Blu-ray layout (ground-truthed on real UHD discs): the DTS core
|
||||
// arrives in one PES, then its DTS-HD MA extension substreams arrive
|
||||
// in SEPARATE following PES packets on the same PID. The parser must
|
||||
// stitch core + all trailing extensions into one access unit — not
|
||||
// emit a core-only (lossy) frame and drop the extension PES packets
|
||||
// (the Dunkirk / Fight Club lossy-core bug).
|
||||
// (the lossy-core downgrade bug).
|
||||
let mut parser = DtsParser::new();
|
||||
|
||||
// Frame 1: core (512) + two extension substreams (256 + 200).
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct HevcParser {
|
||||
// This is the ONLY copy the player gets out-of-band, and a player re-applies
|
||||
// it at every keyframe (ffmpeg's hvcC→Annex-B insertion). A stream may
|
||||
// redefine a parameter set mid-title under the SAME id with a different body
|
||||
// (Fight Club redefines PPS id 0 partway through). Any occurrence whose body
|
||||
// (some discs redefine PPS id 0 partway through). Any occurrence whose body
|
||||
// DIFFERS from this codecPrivate copy must therefore be emitted IN-BAND at
|
||||
// each point it appears (i.e. at every keyframe of the redefined segment) so
|
||||
// it overrides the re-applied codecPrivate set; otherwise those frames decode
|
||||
@@ -65,7 +65,7 @@ impl HevcParser {
|
||||
/// same id) → emitted IN-BAND (length-prefixed) at EVERY occurrence, so it
|
||||
/// overrides the hvcC copy the player re-applies at each keyframe. Emitting it
|
||||
/// only once is not enough — the next keyframe's hvcC re-insertion would revert
|
||||
/// it. This matches what a conforming muxer produces and fixes the Fight Club
|
||||
/// it. This matches what a conforming muxer produces and fixes mid-title
|
||||
/// PPS-id-0 redefinition.
|
||||
fn handle_param_set(first: &mut Option<Vec<u8>>, nal: &[u8], frame_data: &mut Vec<u8>) {
|
||||
match first {
|
||||
@@ -829,10 +829,10 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// --- parameter-set redefinition (Fight Club bug) ---
|
||||
// --- parameter-set redefinition (mid-title redefinition bug) ---
|
||||
|
||||
/// A parameter set REDEFINED mid-stream (same id, different body) must be
|
||||
/// emitted INLINE so the decoder re-activates it. Fight Club redefines PPS
|
||||
/// emitted INLINE so the decoder re-activates it. Some discs redefine PPS
|
||||
/// id 0 partway through the title; the old parser kept only the first PPS,
|
||||
/// so the second segment decoded against the wrong PPS (CABAC desync).
|
||||
#[test]
|
||||
|
||||
+51
-1
@@ -1461,7 +1461,7 @@ mod tests {
|
||||
// Regression for BOTH 0.31.0 bugs through `read_file`: this is the
|
||||
// exact path `Disc::read_aacs_inputs_from_reader` uses to read
|
||||
// `/AACS/MKB_RO.inf` + `Unit_Key_RO.inf`. A Long-AD, multi-extent
|
||||
// file (Dunkirk-class UHD layout) must return ALL its bytes. With the
|
||||
// file (large UHD/Blu-ray layout) must return ALL its bytes. With the
|
||||
// pre-fix Short-AD-only parser this read stopped after the first
|
||||
// extent, which (a) truncated the mux and (b) made autorip's
|
||||
// `key_files()` see a short/garbage AACS file → `MissingInputs` →
|
||||
@@ -1491,6 +1491,56 @@ mod tests {
|
||||
assert!(data[4096..].iter().all(|&b| b == 0xCC));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_aacs_inputs_reads_long_ad_files_in_full() {
|
||||
// PRECOMMIT proof for the autorip online-keyserver path (no disc / no
|
||||
// deploy). autorip's key request is gated on Disc::read_aacs_inputs
|
||||
// (keysource.rs key_files()): it reads /AACS/Unit_Key_RO.inf and
|
||||
// /AACS/MKB_RO.inf. On a Long-AD disc (UHD / large Blu-ray) the
|
||||
// pre-0.31.1 Short-AD-only reader truncated those files at their first
|
||||
// extent, breaking key derivation. This
|
||||
// fixture lays a Long-AD, multi-extent Unit_Key_RO.inf under /AACS and
|
||||
// asserts read_aacs_inputs returns its FULL content — i.e. the keyserver
|
||||
// inputs are complete, so the request is built correctly.
|
||||
let aacs = DirEntry {
|
||||
name: "AACS".to_string(),
|
||||
is_dir: true,
|
||||
meta_lba: 0,
|
||||
size: 0,
|
||||
entries: vec![
|
||||
file_entry("Unit_Key_RO.inf", 5, 4096), // Long-AD, 2 extents
|
||||
file_entry("MKB_RO.inf", 7, 2048),
|
||||
],
|
||||
};
|
||||
let root = DirEntry {
|
||||
name: String::new(),
|
||||
is_dir: true,
|
||||
meta_lba: 0,
|
||||
size: 0,
|
||||
entries: vec![aacs],
|
||||
};
|
||||
let mut reader = MapReader::new();
|
||||
// Unit_Key_RO.inf: Long-AD ICB with two recorded extents.
|
||||
reader.put(5, build_efe_long(4096, &[(0, 2048, 10), (0, 2048, 30)]));
|
||||
reader.put(10, [0xAA; 2048]);
|
||||
reader.put(30, [0xBB; 2048]);
|
||||
// MKB_RO.inf: single Long-AD extent (content is opaque to this test).
|
||||
reader.put(7, build_efe_long(2048, &[(0, 2048, 50)]));
|
||||
reader.put(50, [0xCC; 2048]);
|
||||
|
||||
let fs = fs_with(0, 0, root);
|
||||
let (inf, _mkb) = crate::disc::Disc::read_aacs_inputs_from_reader(&mut reader, &fs)
|
||||
.expect("read_aacs_inputs must succeed for a Long-AD disc");
|
||||
assert_eq!(
|
||||
inf.len(),
|
||||
4096,
|
||||
"Unit_Key_RO.inf (Long-AD, multi-extent) must read in full — the \
|
||||
pre-0.31.1 Short-AD parser truncated it to the first 2048-byte extent"
|
||||
);
|
||||
assert!(inf[..2048].iter().all(|&b| b == 0xAA));
|
||||
assert!(inf[2048..].iter().all(|&b| b == 0xBB));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_ranges_saturates_near_u32_max() {
|
||||
// Adjacent ranges near u32::MAX must not panic (debug) or wrap.
|
||||
|
||||
Reference in New Issue
Block a user