Files
libfreemkv/src/sector/mod.rs
T
Matthew Jackson c610285910 test: constrain SectorSource speed forwarding and PassProgress percentages
Mutation testing left both unconstrained.

sector/mod.rs — set_speed on the Box<dyn> and &mut dyn forwarding impls
could be replaced with an empty body and nothing failed. This one hides
better than the read methods because the trait's own default body is
already a no-op, so a forwarder that swallowed the call is
indistinguishable from a source with no speed control. Consequence is a
silently absent value, not a wrong one: the recovery path lowers read
speed through a damaged region, and a swallowed call leaves the drive
at full speed while the caller believes it slowed down. Routed through
a generic S: SectorSource bound, since a direct call on a &mut dyn
receiver auto-derefs to the vtable and never enters the forwarding body.

progress.rs — 42 survivors. All four percentage accessors could return
a constant, read the wrong byte counter, or have their divide-by-zero
guard inverted. Added exact-value tests (25%, not 'some percentage'),
both sides of each guard, the overshoot clamp, and one test setting all
three disc counters to distinct values at once — without it, a swapped
field still passes every single-counter test.

The Progress blanket impl for closures could return a constant true.
That return value is the cancellation signal, so a constant-true body
makes every closure-based consumer uncancellable.

Each mutation applied, observed red, reverted.
2026-07-30 13:26:31 -07:00

547 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Sector-level read I/O traits.
//!
//! [`SectorSource`] reads 2048-byte sectors from a disc.
//!
//! - [`SectorSource`] is implemented by `Drive` (hardware) and
//! [`FileSectorSource`] (file-backed).
//! - [`DecryptingSectorSource`] is a decorator that wraps any
//! `SectorSource` and applies AACS / CSS in-place decrypt to
//! yield plaintext sectors.
pub mod decrypting;
pub mod prefetched;
use crate::error::Result;
/// Read 2048-byte sectors from a disc, image, or composed source.
///
/// Wrap the inner source in [`DecryptingSectorSource`] to get
/// plaintext sectors out of an encrypted disc.
pub trait SectorSource: Send {
/// Total capacity in sectors, if known. Default `0` = unknown
/// (e.g. live drives that haven't completed `READ CAPACITY` yet).
fn capacity_sectors(&self) -> u32 {
0
}
/// Read `count` sectors starting at `lba` into `buf`.
/// `buf` must be at least `count * 2048` bytes.
/// `recovery`: true = full retry/reset loop (ripping),
/// false = single attempt (verify). File-backed sources ignore
/// the flag.
///
/// Returns the number of bytes written into `buf` on success.
///
/// # Panics
///
/// Implementations may panic if `buf.len() < count * 2048`. This
/// is a caller contract enforced via `debug_assert!` in the
/// primary impl ([`FileSectorSource`]); in release builds an
/// undersized buffer panics on the slice. Callers must size `buf`
/// to at least `count * 2048` bytes.
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize>;
/// Like [`read_sectors`], but with an explicit Force Unit Access request.
///
/// `fua = true` asks the drive to bypass its readahead cache and physically
/// re-fetch the medium — a Pass-N marginal-sector lever: a cached hit can
/// mask a *stochastic* sector that would land differently off the platter on
/// each physical read, so FuaRetry re-reads it FUA. It is never blanket-
/// applied to the bulk path (forcing every sequential read past the cache
/// collapses streaming throughput ~10×).
///
/// The default ignores `fua` and delegates to [`read_sectors`]: only a live
/// [`Drive`] sets the CDB bit; file- / memory-backed sources have no drive
/// cache to bypass, so FUA is meaningless to them.
///
/// [`read_sectors`]: SectorSource::read_sectors
/// [`Drive`]: crate::drive::Drive
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
let _ = fua;
self.read_sectors(lba, count, buf, recovery)
}
/// Optional speed control for sources that map to a physical
/// drive. No-op for everything else.
fn set_speed(&mut self, _kbs: u16) {}
/// Set the base LBA an AACS unit-alignment gate measures against — the
/// `start_lba` of the extent/clip about to be read. Aligned AACS units
/// (6144 B / 3 sectors) are anchored at each clip's encrypted-region start,
/// so a decrypt-on-read source gates `lba` relative to this base, not
/// absolute disc LBA 0. Mux read paths call this when they advance to a new
/// extent. No-op for everything except [`DecryptingSectorSource`], the only
/// source that applies the unit-alignment gate.
///
/// [`DecryptingSectorSource`]: crate::sector::DecryptingSectorSource
fn set_unit_base(&mut self, _lba: u32) {}
}
// Forwarding impls so `Box<dyn SectorSource>` and `&mut dyn SectorSource`
// satisfy the `SectorSource` trait bound when wrapped by generic
// decorators like `DecryptingSectorSource<S: SectorSource>`.
impl SectorSource for Box<dyn SectorSource> {
fn capacity_sectors(&self) -> u32 {
(**self).capacity_sectors()
}
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
(**self).read_sectors(lba, count, buf, recovery)
}
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
(**self).read_sectors_fua(lba, count, buf, recovery, fua)
}
fn set_speed(&mut self, kbs: u16) {
(**self).set_speed(kbs)
}
fn set_unit_base(&mut self, lba: u32) {
(**self).set_unit_base(lba)
}
}
impl SectorSource for &mut (dyn SectorSource + '_) {
fn capacity_sectors(&self) -> u32 {
(**self).capacity_sectors()
}
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
(**self).read_sectors(lba, count, buf, recovery)
}
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
(**self).read_sectors_fua(lba, count, buf, recovery, fua)
}
fn set_speed(&mut self, kbs: u16) {
(**self).set_speed(kbs)
}
fn set_unit_base(&mut self, lba: u32) {
(**self).set_unit_base(lba)
}
}
pub use crate::io::file_sector_source::FileSectorSource;
pub use decrypting::{DecryptingSectorSource, KeyFetch, KeyFetchFn};
pub use prefetched::PrefetchedSectorSource;
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
/// A fully-instrumented SectorSource: records every read's
/// (lba, count, recovery), reports a known capacity, and records
/// set_speed calls. Lets the forwarding-impl tests prove each
/// trait method is delegated, not stubbed.
struct Spy {
capacity: u32,
reads: Arc<Mutex<Vec<(u32, u16, bool)>>>,
speeds: Arc<Mutex<Vec<u16>>>,
unit_bases: Arc<Mutex<Vec<u32>>>,
}
/// A `Spy` under test plus the handles recording its reads, speed sets,
/// and unit-base sets.
type SpyHarness = (
Spy,
Arc<Mutex<Vec<(u32, u16, bool)>>>,
Arc<Mutex<Vec<u16>>>,
Arc<Mutex<Vec<u32>>>,
);
impl Spy {
fn new(capacity: u32) -> SpyHarness {
let reads = Arc::new(Mutex::new(Vec::new()));
let speeds = Arc::new(Mutex::new(Vec::new()));
let unit_bases = Arc::new(Mutex::new(Vec::new()));
(
Self {
capacity,
reads: reads.clone(),
speeds: speeds.clone(),
unit_bases: unit_bases.clone(),
},
reads,
speeds,
unit_bases,
)
}
}
impl SectorSource for Spy {
fn capacity_sectors(&self) -> u32 {
self.capacity
}
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
self.reads.lock().unwrap().push((lba, count, recovery));
let bytes = count as usize * 2048;
buf[..bytes].fill(0xa5);
Ok(bytes)
}
fn set_speed(&mut self, kbs: u16) {
self.speeds.lock().unwrap().push(kbs);
}
fn set_unit_base(&mut self, lba: u32) {
self.unit_bases.lock().unwrap().push(lba);
}
}
/// Call `set_unit_base` through a generic `S: SectorSource` bound so the
/// `Box<dyn SectorSource>` / `&mut dyn SectorSource` FORWARDING impls are the
/// ones invoked (the generic monomorphizes to each forwarding body — the same
/// body a direct call on those receiver types also resolves to).
fn set_unit_base_generic<S: SectorSource>(mut s: S, base: u32) {
s.set_unit_base(base);
}
/// The default `capacity_sectors` is 0 (unknown). Grounding: trait
/// default body `fn capacity_sectors(&self) -> u32 { 0 }`.
#[test]
fn default_capacity_is_zero() {
struct Minimal;
impl SectorSource for Minimal {
fn read_sectors(
&mut self,
_lba: u32,
_count: u16,
_buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
Ok(0)
}
}
assert_eq!(Minimal.capacity_sectors(), 0);
}
/// The default `set_speed` is a no-op that must not panic.
/// Grounding: trait default body `fn set_speed(&mut self, _kbs) {}`.
#[test]
fn default_set_speed_is_noop() {
struct Minimal;
impl SectorSource for Minimal {
fn read_sectors(
&mut self,
_lba: u32,
_count: u16,
_buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
Ok(0)
}
}
let mut m = Minimal;
m.set_speed(12345); // must not panic
}
/// `Box<dyn SectorSource>` must forward ALL three trait methods to
/// the inner source (capacity, read_sectors args + return, speed) —
/// the blanket impl exists so boxed sources satisfy generic
/// decorator bounds. Grounding: `impl SectorSource for
/// Box<dyn SectorSource>` forwarding bodies.
#[test]
fn boxed_dyn_forwards_all_methods() {
let (spy, reads, speeds, unit_bases) = Spy::new(777);
let mut boxed: Box<dyn SectorSource> = Box::new(spy);
assert_eq!(boxed.capacity_sectors(), 777, "capacity must forward");
let mut buf = vec![0u8; 3 * 2048];
let n = boxed.read_sectors(99, 3, &mut buf, true).unwrap();
assert_eq!(n, 3 * 2048, "read return must forward");
assert!(buf.iter().all(|b| *b == 0xa5), "inner must have filled buf");
boxed.set_speed(5400);
assert_eq!(
*reads.lock().unwrap(),
vec![(99, 3, true)],
"read args (lba/count/recovery) must forward unchanged"
);
assert_eq!(
*speeds.lock().unwrap(),
vec![5400],
"set_speed must forward"
);
// set_unit_base through the generic bound exercises the forwarding impl
// (a direct `boxed.set_unit_base()` would vtable-dispatch instead). A
// missing forwarding body would silently no-op and record nothing.
set_unit_base_generic(boxed, 64);
assert_eq!(
*unit_bases.lock().unwrap(),
vec![64],
"set_unit_base must forward through Box<dyn>"
);
}
/// `&mut dyn SectorSource` must likewise forward every method.
/// Grounding: `impl SectorSource for &mut (dyn SectorSource + '_)`.
#[test]
fn mut_ref_dyn_forwards_all_methods() {
let (mut spy, reads, speeds, unit_bases) = Spy::new(123);
{
let r: &mut dyn SectorSource = &mut spy;
assert_eq!(r.capacity_sectors(), 123);
let mut buf = vec![0u8; 2 * 2048];
let n = r.read_sectors(7, 2, &mut buf, false).unwrap();
assert_eq!(n, 2 * 2048);
r.set_speed(8800);
}
// Pass `&mut dyn` as a generic S so the forwarding impl's set_unit_base
// is the one under test, not the vtable path.
let r2: &mut dyn SectorSource = &mut spy;
set_unit_base_generic(r2, 128);
assert_eq!(*reads.lock().unwrap(), vec![(7, 2, false)]);
assert_eq!(*speeds.lock().unwrap(), vec![8800]);
assert_eq!(
*unit_bases.lock().unwrap(),
vec![128],
"set_unit_base must forward through &mut dyn"
);
}
/// Records every read that reaches it, including whether it arrived on the
/// FUA path and with which `fua` bit — `Spy` above leaves `read_sectors_fua`
/// to the trait default, so it cannot tell the two entry points apart.
/// One recorded read: `(lba, count, recovery, fua)`, where `fua` is `None`
/// when the plain `read_sectors` entry point was the one reached.
type ReadLog = Arc<Mutex<Vec<(u32, u16, bool, Option<bool>)>>>;
#[derive(Default)]
struct ReadSpy {
calls: ReadLog,
fill: u8,
}
impl SectorSource for ReadSpy {
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
self.calls
.lock()
.unwrap()
.push((lba, count, recovery, None));
let bytes = count as usize * 2048;
buf[..bytes].fill(self.fill);
Ok(bytes)
}
fn read_sectors_fua(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
self.calls
.lock()
.unwrap()
.push((lba, count, recovery, Some(fua)));
let bytes = count as usize * 2048;
buf[..bytes].fill(self.fill);
Ok(bytes)
}
}
/// Read through a generic `S: SectorSource` bound so the FORWARDING impl is
/// what runs. A direct `r.read_sectors(..)` on a `&mut dyn SectorSource`
/// receiver auto-derefs to the vtable and never touches the forwarding body,
/// which is exactly why `set_unit_base` needed `set_unit_base_generic` too.
fn read_generic<S: SectorSource>(
mut s: S,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
s.read_sectors(lba, count, buf, recovery)
}
/// Same, for the speed lever. The trait's own `set_speed` default is a
/// no-op, so a forwarding body that also did nothing is indistinguishable
/// from the default unless the call is routed through the generic bound.
fn set_speed_generic<S: SectorSource>(mut s: S, kbs: u16) {
s.set_speed(kbs);
}
/// Same, for the FUA entry point.
fn read_fua_generic<S: SectorSource>(
mut s: S,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
fua: bool,
) -> Result<usize> {
s.read_sectors_fua(lba, count, buf, recovery, fua)
}
/// The `&mut dyn SectorSource` forwarding impl must actually DELEGATE
/// `read_sectors` — pass the args through unchanged, return the inner
/// source's byte count, and leave the inner source's bytes in the caller's
/// buffer. A body that returned a bare `Ok(n)` without calling the inner
/// source would be a delegating reader that reads NOTHING and reports
/// success: the caller sees `Ok` and consumes an untouched buffer.
///
/// `mut_ref_dyn_forwards_all_methods` above does not cover this: its
/// `r.read_sectors(..)` call on a `&mut dyn` receiver dispatches through the
/// vtable to `Spy`, not through the forwarding impl.
#[test]
fn mut_ref_dyn_forwards_read_sectors_to_the_inner_source() {
let calls = Arc::new(Mutex::new(Vec::new()));
let mut inner = ReadSpy {
calls: calls.clone(),
fill: 0x5C,
};
let mut buf = vec![0u8; 4 * 2048];
let r: &mut dyn SectorSource = &mut inner;
let n = read_generic(r, 0x1234, 4, &mut buf, true).expect("delegated read succeeds");
assert_eq!(
n,
4 * 2048,
"the forwarding impl must return the INNER source's byte count"
);
assert!(
buf.iter().all(|b| *b == 0x5C),
"the inner source's bytes must land in the caller's buffer; an \
undelegated read leaves it untouched and the caller muxes zeroes"
);
assert_eq!(
*calls.lock().unwrap(),
vec![(0x1234, 4, true, None)],
"lba/count/recovery must reach the inner source unchanged, on the \
non-FUA entry point"
);
}
/// The same for `read_sectors_fua`: the forwarding impl must reach the inner
/// source's FUA entry point (not silently downgrade to the plain read, and
/// not fabricate a count), carrying the `fua` bit through. FUA is the
/// Pass-N lever that re-fetches a stochastic sector past the drive cache
/// (MMC-6 READ(10) FUA bit); a forwarder that dropped it would make every
/// FUA retry re-read the same cached bytes and "confirm" the bad sector.
#[test]
fn mut_ref_dyn_forwards_read_sectors_fua_to_the_inner_source() {
let calls = Arc::new(Mutex::new(Vec::new()));
let mut inner = ReadSpy {
calls: calls.clone(),
fill: 0x3B,
};
let mut buf = vec![0u8; 2 * 2048];
let r: &mut dyn SectorSource = &mut inner;
let n = read_fua_generic(r, 42, 2, &mut buf, false, true).expect("delegated FUA read");
assert_eq!(n, 2 * 2048, "byte count must come from the inner source");
assert!(
buf.iter().all(|b| *b == 0x3B),
"the inner source's bytes must land in the caller's buffer"
);
assert_eq!(
*calls.lock().unwrap(),
vec![(42, 2, false, Some(true))],
"the FUA entry point must be the one reached, with fua=true intact"
);
}
/// The `&mut dyn SectorSource` forwarding impl must delegate `set_speed`.
///
/// This one hides better than the read methods, because the trait's own
/// default body is `fn set_speed(&mut self, _kbs: u16) {}` — so a forwarding
/// impl that dropped the call on the floor compiles, type-checks, and looks
/// exactly like a source that legitimately has no speed control. The
/// consequence is not a wrong value but a silently absent one: the recovery
/// path throttles a struggling drive by lowering its read speed, and a
/// forwarder that swallowed the call would leave the drive at full speed
/// through the damaged region while the caller believed it had slowed down.
#[test]
fn mut_ref_dyn_forwards_set_speed_to_the_inner_source() {
let (mut spy, _reads, speeds, _bases) = Spy::new(0);
let r: &mut dyn SectorSource = &mut spy;
set_speed_generic(r, 5540);
assert_eq!(
*speeds.lock().unwrap(),
vec![5540],
"the forwarding impl must pass set_speed through to the inner \
source; swallowing it is indistinguishable from the trait default \
and silently disables recovery-path throttling"
);
}
/// The same for `Box<dyn SectorSource>`, which is the receiver the mux read
/// paths actually hold.
#[test]
fn boxed_dyn_forwards_set_speed_to_the_inner_source() {
let (spy, _reads, speeds, _bases) = Spy::new(0);
let b: Box<dyn SectorSource> = Box::new(spy);
set_speed_generic(b, 11080);
assert_eq!(
*speeds.lock().unwrap(),
vec![11080],
"the boxed forwarding impl must pass set_speed through unchanged"
);
}
}