existing call sites — sweep producer and DiscStream demux Round 1 shipped the DecryptingSectorSource decorator (libfreemkv/src/sector/decrypting.rs) but the existing decrypt sites kept calling crate::decrypt::decrypt_sectors inline. This commit migrates both: - Disc::sweep (disc/mod.rs): producer wraps the input reader in DecryptingSectorSource::new(reader, keys) before the read loop. The inline decrypt_sectors call goes away — read_sectors yields plaintext directly. - DiscStream (mux/disc.rs): constructor wraps the underlying Box<dyn SectorReader> in DecryptingSectorSource so the internal fill_extents / read path sees plaintext bytes. The DecryptKeys field stays on DiscStream for metadata-side use; it just no longer drives decryption. Disc::patch carried the same inline decrypt step at three call sites (main read, backtrack read, non-NOT_READY retry read). All three migrated onto the same wrapping for a single audit surface. Two small support changes carry the migration without touching the round-1 decorator shape: - sector/mod.rs gains specific SectorSource impls for &mut dyn SectorReader and Box<dyn SectorReader>, mirroring std's Read forwarding pattern. Generic blankets would conflict with the existing SectorReader → SectorSource blanket under the orphan rule (downstream could impl SectorReader for &mut U), so the impls are scoped to the dyn-trait shape we actually consume. - sector/decrypting.rs gains DecryptingSectorSource::set_keys so DiscStream::set_raw() can flip the wrapped reader to a DecryptKeys::None pass-through without rebuilding the decorator (which would require moving the inner Box out from behind &mut self). After this commit, grep `decrypt_sectors` in src/ shows the function definition, its single use inside DecryptingSectorSource, plus comments only. One audit surface for AACS / CSS / passthrough correctness. Behaviour-preserving: same plaintext bytes flow through; the only difference is which type owns the decrypt step. See freemkv-private/memory/0_18_redesign.md. Single contributor: MattJackson.
203 lines
7.4 KiB
Rust
203 lines
7.4 KiB
Rust
//! Sector-level I/O traits.
|
|
//!
|
|
//! 0.18 splits the unidirectional read trait from a write trait at
|
|
//! the sector layer, so the type system catches "wrong direction"
|
|
//! mistakes at compile time instead of runtime. See
|
|
//! `freemkv-private/memory/0_18_redesign.md`.
|
|
//!
|
|
//! - [`SectorSource`] reads 2048-byte sectors. Implemented by
|
|
//! `Drive` (via the legacy [`SectorReader`] alias) and
|
|
//! [`FileSectorSource`] (ISO-backed).
|
|
//! - [`SectorSink`] writes 2048-byte sectors. Implemented by
|
|
//! [`FileSectorSink`] (ISO-backed) and, in later commits, by
|
|
//! sweep/patch consumer adapters.
|
|
//! - [`DecryptingSectorSource`] is a decorator that wraps any
|
|
//! `SectorSource` and applies the existing AACS / CSS in-place
|
|
//! decrypt to plaintext-out.
|
|
//!
|
|
//! [`SectorReader`] is the 0.17 read trait. It stays on through
|
|
//! the 0.18 migration window so existing call sites
|
|
//! (`Drive`, `IsoSectorReader`, `BufferedSectorReader`,
|
|
//! `DiscStream`, `verify`) compile unchanged. A blanket impl
|
|
//! forwards every `SectorReader` impl to `SectorSource`, so new
|
|
//! code should target `SectorSource` / `SectorSink` directly. The
|
|
//! formal `#[deprecated]` attribute lands once the internal
|
|
//! callers have migrated; see the comment on `SectorReader` for
|
|
//! why this commit holds it back.
|
|
|
|
pub mod decrypting;
|
|
pub mod file;
|
|
|
|
use crate::error::Result;
|
|
|
|
/// Read 2048-byte sectors from a disc, image, or composed source.
|
|
///
|
|
/// Direction-typed: a `SectorSource` cannot be written to. 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. Returns 0 when unknown
|
|
/// (e.g. live drives that haven't completed `READ CAPACITY` yet).
|
|
fn capacity_sectors(&self) -> u32;
|
|
|
|
/// 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.
|
|
fn read_sectors(
|
|
&mut self,
|
|
lba: u32,
|
|
count: u16,
|
|
buf: &mut [u8],
|
|
recovery: bool,
|
|
) -> Result<usize>;
|
|
|
|
/// Optional speed control for sources that map to a physical
|
|
/// drive. No-op for everything else.
|
|
fn set_speed(&mut self, _kbs: u16) {}
|
|
}
|
|
|
|
/// Write 2048-byte sectors to a disc image or composed sink.
|
|
///
|
|
/// Direction-typed: a `SectorSink` cannot be read from. The
|
|
/// terminal [`finish`] takes `Box<Self>` so it can run on `dyn
|
|
/// SectorSink` and consume the sink (`fsync` + close).
|
|
///
|
|
/// [`finish`]: SectorSink::finish
|
|
pub trait SectorSink: Send {
|
|
/// Write the sectors in `buf` starting at `lba`. `buf.len()`
|
|
/// must be a multiple of 2048; the implementation seeks to
|
|
/// `lba * 2048` before writing.
|
|
fn write_sectors(&mut self, lba: u32, buf: &[u8]) -> Result<()>;
|
|
|
|
/// Flush, fsync, and close. Consumes the sink. Always called
|
|
/// last; subsequent operations are not defined.
|
|
fn finish(self: Box<Self>) -> Result<()>;
|
|
}
|
|
|
|
/// 0.17 read trait. Slated for removal once internal call sites
|
|
/// migrate to [`SectorSource`] in follow-up commits; until then
|
|
/// it remains the trait that `Drive`, `IsoSectorReader`,
|
|
/// `BufferedSectorReader`, and existing `&mut dyn SectorReader`
|
|
/// signatures use unchanged.
|
|
///
|
|
/// New code should implement [`SectorSource`] directly. The
|
|
/// blanket impl below makes any `SectorReader` automatically
|
|
/// usable wherever a `SectorSource` is expected, so a one-way
|
|
/// migration off `SectorReader` is possible per-callsite without
|
|
/// touching the impls.
|
|
//
|
|
// NOTE: not marked `#[deprecated]` in this commit — `cargo clippy
|
|
// -- -D warnings` (the CI gauntlet) treats deprecation as an
|
|
// error, and the existing `Drive` / `udf::BufferedSectorReader` /
|
|
// `mux::DiscStream` / `verify` call sites all go through this
|
|
// trait. The deprecation attribute lands together with the
|
|
// migration commits that move those call sites to
|
|
// `SectorSource`. The behavioural contract — "this trait is
|
|
// going away in 0.18" — is documented above and tracked in
|
|
// `freemkv-private/memory/0_18_redesign.md`.
|
|
pub trait SectorReader: Send {
|
|
/// Read `count` sectors starting at `lba` into `buf`.
|
|
/// See [`SectorSource::read_sectors`] for semantics.
|
|
fn read_sectors(
|
|
&mut self,
|
|
lba: u32,
|
|
count: u16,
|
|
buf: &mut [u8],
|
|
recovery: bool,
|
|
) -> Result<usize>;
|
|
|
|
/// Total capacity in sectors, if known.
|
|
fn capacity(&self) -> u32 {
|
|
0
|
|
}
|
|
|
|
fn set_speed(&mut self, _kbs: u16) {}
|
|
}
|
|
|
|
// Blanket impl: anything implementing the legacy `SectorReader`
|
|
// trait automatically satisfies `SectorSource`. This is what keeps
|
|
// existing impls (`Drive`, `IsoSectorReader`, `BufferedSectorReader`,
|
|
// etc.) compiling without source changes during the migration. The
|
|
// reverse direction (impl SectorReader for SectorSource) is
|
|
// intentionally NOT provided — new code targets the new trait.
|
|
impl<T: SectorReader + ?Sized> SectorSource for T {
|
|
fn capacity_sectors(&self) -> u32 {
|
|
<T as SectorReader>::capacity(self)
|
|
}
|
|
|
|
fn read_sectors(
|
|
&mut self,
|
|
lba: u32,
|
|
count: u16,
|
|
buf: &mut [u8],
|
|
recovery: bool,
|
|
) -> Result<usize> {
|
|
<T as SectorReader>::read_sectors(self, lba, count, buf, recovery)
|
|
}
|
|
|
|
fn set_speed(&mut self, kbs: u16) {
|
|
<T as SectorReader>::set_speed(self, kbs)
|
|
}
|
|
}
|
|
|
|
// Forwarding impls so callers can wrap `&mut dyn SectorReader` /
|
|
// `Box<dyn SectorReader>` in [`DecryptingSectorSource`] without
|
|
// having to unbox or re-borrow inside the lib's hot paths. The
|
|
// generic `&mut T` / `Box<T>` blankets would conflict with the
|
|
// `SectorReader → SectorSource` blanket above (a downstream crate
|
|
// could `impl SectorReader for &mut U`); the specific
|
|
// `dyn SectorReader` instantiations are unambiguous because
|
|
// `SectorReader` is the very trait whose `dyn` we're targeting.
|
|
impl SectorSource for &mut (dyn SectorReader + '_) {
|
|
fn capacity_sectors(&self) -> u32 {
|
|
<dyn SectorReader as SectorReader>::capacity(*self)
|
|
}
|
|
|
|
fn read_sectors(
|
|
&mut self,
|
|
lba: u32,
|
|
count: u16,
|
|
buf: &mut [u8],
|
|
recovery: bool,
|
|
) -> Result<usize> {
|
|
<dyn SectorReader as SectorReader>::read_sectors(*self, lba, count, buf, recovery)
|
|
}
|
|
|
|
fn set_speed(&mut self, kbs: u16) {
|
|
<dyn SectorReader as SectorReader>::set_speed(*self, kbs)
|
|
}
|
|
}
|
|
|
|
impl SectorSource for Box<dyn SectorReader> {
|
|
fn capacity_sectors(&self) -> u32 {
|
|
<dyn SectorReader as SectorReader>::capacity(&**self)
|
|
}
|
|
|
|
fn read_sectors(
|
|
&mut self,
|
|
lba: u32,
|
|
count: u16,
|
|
buf: &mut [u8],
|
|
recovery: bool,
|
|
) -> Result<usize> {
|
|
<dyn SectorReader as SectorReader>::read_sectors(&mut **self, lba, count, buf, recovery)
|
|
}
|
|
|
|
fn set_speed(&mut self, kbs: u16) {
|
|
<dyn SectorReader as SectorReader>::set_speed(&mut **self, kbs)
|
|
}
|
|
}
|
|
|
|
pub use decrypting::DecryptingSectorSource;
|
|
pub use file::{FileSectorSink, FileSectorSource};
|
|
|
|
// Backwards-compat alias for the public API. `FileSectorReader` is
|
|
// the 0.17 name; new code uses `FileSectorSource`. Both point at
|
|
// the same type. The `#[deprecated]` attribute lands together with
|
|
// the migration commits that retire the alias from internal uses.
|
|
pub type FileSectorReader = FileSectorSource;
|