mux: distinguish FMTS phase-probe read fault from wrong key; cover multi-CPS
Fix 1 (correctness): resolve_fmts_key_map's per-index phase probe read a single representative segment via read_unit (whose read_sectors(...).ok()? swallows read errors into None) with no fault fallback. A transient live-drive read fault (e.g. NOT READY 2/04/3E on the BU40N) made every probe read return None, giving even==0 && odd==0 — indistinguishable from a genuine wrong key — so resolve_tie_phase returned FmtsKeyMissing and aborted the entire multi-title rip even though the forensic index keys were valid and in hand. Extract the probe into probe_index_phase, which returns Phase / WrongKey / ReadFault. It mirrors the anchor loop's tolerance: it tries multiple same-index segments and only concludes WrongKey once a read actually succeeded and decrypted to no clean parity. If every read of every same-index segment faults it returns ReadFault; the caller then leaves the phase unresolved so the range-builder defaults to Phase::All (decrypt both parities, demux drops the garbled alternate half) instead of aborting. A wrong key can never be masked as a read fault: ReadFault requires that not a single read succeeded, so zero decrypt evidence. Fix 2 (test coverage): resolve_mux_key_map's multi-CPS branch was only exercised with an all-zero source, so pick(), the KeyFetch cold path, and the fail-loud DecryptFailed guard never ran. Add tests over real AACS ciphertext (via aacs_encrypt_unit_for_test) covering pick() selecting the correct pool index, the DecryptFailed guard firing when a clean sample matches no held/fetched key, and the fetch cold path recovering a missing unit key. Mutation-verified. Fix 3 (doc): move the reader_event_fn EventKind->MuxEvents mapping doc off session_mux_keys onto reader_event_fn.
This commit is contained in:
+18
-18
@@ -443,24 +443,6 @@ pub fn mux_stream(
|
||||
)
|
||||
}
|
||||
|
||||
/// Translate libfreemkv's reader-side [`Event`]s into [`MuxEvents`] calls,
|
||||
/// producing the `'static` [`EventFn`](crate::sector::prefetched::EventFn) the
|
||||
/// file highway ([`build_iso_pipeline`]) and the live
|
||||
/// [`DiscStream`](crate::mux::DiscStream) constructors require. Cloning the
|
||||
/// `Arc` into the returned closure is precisely what lets a borrowed-lifetime
|
||||
/// consumer's events reach the highway's producer thread — a `&dyn MuxEvents`
|
||||
/// borrow cannot satisfy the `'static` bound.
|
||||
///
|
||||
/// Mapping (real [`EventKind`] variants):
|
||||
/// - `BytesRead { bytes, total }` → [`MuxEvents::on_read_progress`] (read-side;
|
||||
/// the file highway's only reader event — `total` is the extents' byte total)
|
||||
/// - `SectorSkipped { sector }` → [`MuxEvents::on_sector_skipped`] (live only)
|
||||
/// - `BatchSizeChanged { new_size, reason }` → [`MuxEvents::on_batch_size_changed`]
|
||||
/// (live only)
|
||||
/// - `ReadError { sector, .. }` → [`MuxEvents::on_read_error`]
|
||||
///
|
||||
/// Sector numbers are the library's `u64`; the `MuxEvents` LBA hooks take `u32`
|
||||
/// (the disc's LBA space), so they are narrowed with `as u32`.
|
||||
/// Decrypt keys for the live `Session` mux of `disc`.
|
||||
///
|
||||
/// A DVD is handed [`DecryptKeys::None`] so [`DiscStream::new`](crate::mux::DiscStream)
|
||||
@@ -530,6 +512,24 @@ fn resolve_inline_base_map(
|
||||
Ok(Some(Arc::new(map)))
|
||||
}
|
||||
|
||||
/// Translate libfreemkv's reader-side [`Event`]s into [`MuxEvents`] calls,
|
||||
/// producing the `'static` [`EventFn`](crate::sector::prefetched::EventFn) the
|
||||
/// file highway ([`build_iso_pipeline`]) and the live
|
||||
/// [`DiscStream`](crate::mux::DiscStream) constructors require. Cloning the
|
||||
/// `Arc` into the returned closure is precisely what lets a borrowed-lifetime
|
||||
/// consumer's events reach the highway's producer thread — a `&dyn MuxEvents`
|
||||
/// borrow cannot satisfy the `'static` bound.
|
||||
///
|
||||
/// Mapping (real [`EventKind`] variants):
|
||||
/// - `BytesRead { bytes, total }` → [`MuxEvents::on_read_progress`] (read-side;
|
||||
/// the file highway's only reader event — `total` is the extents' byte total)
|
||||
/// - `SectorSkipped { sector }` → [`MuxEvents::on_sector_skipped`] (live only)
|
||||
/// - `BatchSizeChanged { new_size, reason }` → [`MuxEvents::on_batch_size_changed`]
|
||||
/// (live only)
|
||||
/// - `ReadError { sector, .. }` → [`MuxEvents::on_read_error`]
|
||||
///
|
||||
/// Sector numbers are the library's `u64`; the `MuxEvents` LBA hooks take `u32`
|
||||
/// (the disc's LBA space), so they are narrowed with `as u32`.
|
||||
fn reader_event_fn(events: Arc<dyn MuxEvents>) -> crate::sector::prefetched::EventFn {
|
||||
Box::new(move |e: Event| match e.kind {
|
||||
EventKind::BytesRead { bytes, total } => events.on_read_progress(bytes, total),
|
||||
|
||||
Reference in New Issue
Block a user