From 22a3e3fd01a5e41f80b4f138c3835621310e92c4 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:07:15 -0700 Subject: [PATCH] Distinguish a failed key source from one with no entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolve_and_apply_traced collapsed `Ok(_) | Err(_)` into a single KeyNode::NoEntry step, so a key source that FAILED — server unreachable, keydb unreadable, malformed entry — was recorded identically to one that simply had no entry for this disc. The front-end renders that trace, so it told the operator their disc is not in the database when the real cause was a fixable infrastructure problem. drive_unit_keys and drive_fmts_indexes were refactored this cycle to preserve exactly this distinction; this path had not been. KeyNode gains a SourceFailed variant and the two arms are split. freemkv's trace renderer matches KeyNode exhaustively with no catch-all, so its arm is added in the same change — otherwise the consumer would not build. Also made ETSI TS 102 114 the primary authority for DTS_AMODE_COUNT's comment rather than a reference decoder internal symbol, and pointed it at this crate's own cross-checked DTS_AMODE_LAYOUT / DTS_AMODE_CH tables. A round-2 finding asked for every a reference decoder and a reference decoder citation in the DTS parser to be stripped as a public-repo hygiene violation. Rejected: the project's rules (scan-secrets.sh, CLAUDE.md) prohibit internal infrastructure references and reverse-engineering material, and a reference-decoder citation is neither. The AMODE channel-count table is a factual table from the standard, not expression copied from an implementation. Citing the spec plus a corroborating implementation is how a decodability gate should be justified. --- src/aacs/trace.rs | 5 +++++ src/keysource.rs | 16 +++++++++++++--- src/mux/codec/dts.rs | 13 +++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/aacs/trace.rs b/src/aacs/trace.rs index 4699d99..c499593 100644 --- a/src/aacs/trace.rs +++ b/src/aacs/trace.rs @@ -77,6 +77,11 @@ pub enum KeyNode { MatchedDisc, /// The source had no entry for this disc. NoEntry, + /// The source FAILED — it errored rather than reporting no entry. Distinct + /// from [`Self::NoEntry`]: an unreachable key server or an unreadable keydb is + /// a fixable condition, whereas "this disc is not in the database" is not, and + /// collapsing the two told the front-end the wrong cause. + SourceFailed, /// Pre-decrypted unit keys were found. FoundUnitKeys, /// A VUK was found. diff --git a/src/keysource.rs b/src/keysource.rs index a7c3c16..5854c3b 100644 --- a/src/keysource.rs +++ b/src/keysource.rs @@ -350,15 +350,25 @@ pub fn resolve_and_apply_traced( outcome: KeyOutcome::NoKey, }); } - // Empty (no key here) or a source failure — both are "no key from - // this source"; move on to the next. - Ok(_) | Err(_) => { + // No key from this source either way, but WHY differs and the caller + // renders it: a source that errored is a fixable condition (server + // unreachable, keydb unreadable), while "no entry for this disc" is + // not. Recording both as NoEntry lost that distinction — the same one + // drive_unit_keys / drive_fmts_indexes were refactored to preserve. + Ok(_) => { trace.keys.push(KeyStep { who, path: vec![KeyNode::NoEntry], outcome: KeyOutcome::NoKey, }); } + Err(_) => { + trace.keys.push(KeyStep { + who, + path: vec![KeyNode::SourceFailed], + outcome: KeyOutcome::NoKey, + }); + } } } (false, trace) diff --git a/src/mux/codec/dts.rs b/src/mux/codec/dts.rs index 1d36714..79cc87f 100644 --- a/src/mux/codec/dts.rs +++ b/src/mux/codec/dts.rs @@ -690,12 +690,13 @@ fn dts_core_duration_ns(data: &[u8]) -> u64 { const DTS_PCMBLOCK_SAMPLES: u32 = 32; const DTS_SUBBAND_SAMPLES: u32 = 8; /// Number of LEGAL `AMODE` (channel-arrangement) codes. The 6-bit AMODE field -/// (ETSI TS 102 114 §5.3.1) has 16 defined channel arrangements, codes 0-15; -/// only 16-63 are reserved/user-defined and undecodable. ffmpeg's -/// `ff_dca_channels[16] = {1,2,2,2,2,3,3,4,4,5,6,6,6,7,8,8}` confirms all 16 are -/// decodable — codes 10-15 are the 6/7/8-channel layouts. A frame is dropped -/// only when `audio_mode >= DTS_AMODE_COUNT` (i.e. a truly reserved 16-63 code); -/// dropping a legal 10-15 multichannel core would silence recoverable audio. +/// (ETSI TS 102 114 §5.3.1) defines 16 channel arrangements, codes 0-15, of +/// which 10-15 are the 6/7/8-channel layouts; codes 16-63 are +/// reserved/user-defined and undecodable. A frame is dropped only when +/// `audio_mode >= DTS_AMODE_COUNT` (i.e. a truly reserved 16-63 code); dropping a +/// legal 10-15 multichannel core would silence recoverable audio. The per-AMODE +/// channel counts live in `DTS_AMODE_CH` in `mux/mp4/audio.rs`, cross-checked +/// against the speaker masks in `DTS_AMODE_LAYOUT` by an invariant test. const DTS_AMODE_COUNT: u32 = 16; const DTS_LFE_FLAG_INVALID: u32 = 3;