mux: thread halt into live AACS key-map resolution; cover Session arm

Round-2 follow-ups to 6d6e60f (inline base-map resolve on the live
single-pass Session/Live mux arms).

Fix 1 (halt threading) — the inline resolve chain sampled ciphertext off
the LIVE drive with no cancel token, so an operator /api/stop during key
resolution was not honored (the FMTS probe can issue hundreds of reads,
each able to stall to the 60s SCSI recovery timeout — violating the
"don't hammer a struggling live drive" rule). Add an optional
`halt: Option<&Halt>` to `resolve_mux_key_map`, `resolve_fmts_key_map`,
`resolve_inline_base_map`, and `Disc::resolve_content_key_map`, and poll
it at each loop boundary (FMTS anchor + per-index probe loops, multi-CPS
extent loop) — returning Err(Halted) promptly. Live/Session arms pass the
driver's halt; sweep/patch pass their own token (via Halt::from_arc);
file-backed probe/ISO callers pass None. Tested with a pre-cancelled halt
(Err Halted, no extent sampling) and a None-halt no-abort case;
mutation-verified (dropping the extent-loop check → Ok, not Err).

Fix 2 (Session-arm coverage) — the MuxInput::Session arm ran the same
resolve→install→decrypt sequence as Live but had NO end-to-end test
(DiscSession only exposed open(), which needs live hardware). Add a
#[cfg(test)] DiscSession::from_parts_for_test (injected reader + scanned
disc, no Drive), an end-to-end AACS decrypt test through the Session arm
(mutation-verified: dropping with_key_map → mux aborts), and a
missing-reader clean-error (not panic) test.

Fix 3 (cleanups) — io_error_code: remove the unreachable typed-Error
downcast branch (From<Error> for io::Error stringifies; no path builds an
io::Error holding a typed Error), keeping the stringify parse is_halt /
is_skippable_title_stub rely on. Add a resolve_keys_for test covering the
largest-title sampling branch. Document the patch wedge-exit coverage gap
(TODO) in passn_handler_ab.rs.
This commit is contained in:
Matthew Jackson
2026-07-24 09:04:19 -07:00
parent 6d6e60fdf8
commit bf9a69ec80
8 changed files with 434 additions and 20 deletions
+11 -2
View File
@@ -2401,11 +2401,18 @@ impl Disc {
reader: &mut dyn SectorSource,
keys: &mut crate::decrypt::DecryptKeys,
fetch: Option<&crate::sector::KeyFetch>,
halt: Option<&crate::halt::Halt>,
) -> Result<crate::decrypt::AacsKeyMap> {
let mut ranges: Vec<(u32, u32, usize, crate::decrypt::Phase)> = Vec::new();
for title in &self.titles {
let map =
crate::mux::resolve_mux_key_map(reader, title, keys, fetch, self.content_format)?;
let map = crate::mux::resolve_mux_key_map(
reader,
title,
keys,
fetch,
self.content_format,
halt,
)?;
ranges.extend_from_slice(map.ranges());
}
Ok(crate::decrypt::AacsKeyMap::from_ranges_phased(
@@ -3206,10 +3213,12 @@ impl Disc {
// separate content gate is needed. CSS keeps the content-gated
// self-descramble path (the map path is AACS-only).
let key_map = if opts.decrypt && decrypt_is_aacs {
let halt = opts.halt.clone().map(crate::halt::Halt::from_arc);
Some(std::sync::Arc::new(self.resolve_content_key_map(
reader,
&mut keys,
opts.key_fetch.as_ref(),
halt.as_ref(),
)?))
} else {
None