refactor(decrypt): one orchestrator owns the no-key decision
There were TWO top-level decrypt paths: decrypt_sectors_impl for CSS and clear media, whose AACS arm was a bare `return Err` stub, and a wholly separate decrypt_sectors_mapped for AACS. Each scheme therefore decided its own answer to "there is no key for these bytes", and nothing held them to the same one. They drifted, in opposite directions, within a single release: css::descramble_region descrambled with a key the sector's own crib had just proven stale — garbage behind an intact clear header, reported Ok. the mapped path returned early for any LBA outside every range, before ever asking whether those bytes were ciphertext, so an unkeyable encrypted unit passed through and extract counted it as good. Both were fixed individually earlier today. This removes the shape that allowed them. decrypt_span is now the single orchestrator: it owns the loop, the refusal, and the loss count, and each scheme supplies only what genuinely differs. apply_aacs_map is a scheme step that reports what it could not open; it no longer decides what that means. The public wrappers (decrypt_sectors, _in_content, _mapped) are unchanged in signature and all funnel through it. Adding a scheme now means adding an arm here, which means answering the refusal question. That is the point. The new test asserts ONE verdict across all three schemes — AACS with no map, AACS with an encrypted unit outside every range, and CSS whose re-crack failed — plus that clear media is NOT a refusal. A per-scheme test cannot hold this: each would keep passing while the two disagreed. Flipping the AACS arm back to pass-through reds it. Also removes the last of the tracing-capture scaffolding. Serialising those captures crate-wide did not fix the 1-in-10 flake, and asserting the predicates directly made the helper, both capture subscribers and an unrelated dead OrderSink unused. Deleted rather than left behind.
This commit is contained in:
@@ -265,56 +265,3 @@ fn the_generators_actually_reach_the_parser_bodies() {
|
||||
);
|
||||
println!("mpls reach: {ok}/{total} cases parsed to completion");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Capturing-subscriber serialisation.
|
||||
//
|
||||
// Several tests install a capturing `tracing` subscriber to assert on a log
|
||||
// line's FIELDS — the only observable for a diagnostic that does not change a
|
||||
// return value. Doing that safely needs two things that pull in opposite
|
||||
// directions:
|
||||
//
|
||||
// * `dispatcher::set_default` / `subscriber::with_default` are THREAD-LOCAL.
|
||||
// * `tracing` caches per-callsite "is any subscriber interested?" GLOBALLY, the
|
||||
// first time each callsite fires. A callsite that already fired under the
|
||||
// process default (a no-op) is cached as "not interested" forever, so the
|
||||
// event never reaches a later capturing subscriber.
|
||||
//
|
||||
// The fix for the second is `rebuild_interest_cache()`. But that is global too,
|
||||
// so two tests doing this on different threads race: one rebuilds the cache to
|
||||
// "interested" for its own thread-local dispatch, the other rebuilds it back
|
||||
// while the first is mid-flight, and the first silently observes nothing.
|
||||
//
|
||||
// That is not hypothetical — it is a real intermittent failure of
|
||||
// `resolve_vid_only_bus_key_gate_reports_true_has_volume_id_when_vid_nonzero`,
|
||||
// which passes in isolation every time and fails under the full parallel suite.
|
||||
// The test carried a comment describing the hazard and a `rebuild_interest_cache`
|
||||
// call intended to fix it; the call is necessary but not sufficient.
|
||||
//
|
||||
// So capture is serialised process-wide here. Four tests across two modules were
|
||||
// each hand-rolling the same dance; one of them getting it subtly wrong is
|
||||
// exactly the drift a shared helper removes.
|
||||
static CAPTURE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
/// Run `f` with `subscriber` installed as the thread-local `tracing` dispatch,
|
||||
/// serialised against every other capture in this crate.
|
||||
///
|
||||
/// The interest cache is rebuilt on the way in (so a callsite already poisoned
|
||||
/// by an earlier no-op dispatch is reconsidered) and on the way out (so the
|
||||
/// next test does not inherit a cache built for a subscriber that is gone).
|
||||
pub(crate) fn with_captured_tracing<S, F, R>(subscriber: S, f: F) -> R
|
||||
where
|
||||
S: tracing::Subscriber + Send + Sync + 'static,
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
// Poisoning is irrelevant: the guard protects ordering, not data, and a
|
||||
// panicking test has already failed.
|
||||
let _lock = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let dispatch = tracing::Dispatch::new(subscriber);
|
||||
let guard = tracing::dispatcher::set_default(&dispatch);
|
||||
tracing::callsite::rebuild_interest_cache();
|
||||
let out = f();
|
||||
drop(guard);
|
||||
tracing::callsite::rebuild_interest_cache();
|
||||
out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user