diff --git a/src/disc/mod.rs b/src/disc/mod.rs index f41fa81..61caad5 100644 --- a/src/disc/mod.rs +++ b/src/disc/mod.rs @@ -1372,6 +1372,7 @@ impl Disc { opts: &SweepOptions, ) -> Result { use crate::io::{DEFAULT_PIPELINE_DEPTH, Pipeline}; + use crate::sector::{DecryptingSectorSource, SectorSource}; use sweep::{ProgressSnapshot, SweepSink, WorkItem, try_recv_progress}; let total_bytes = self.capacity_sectors as u64 * 2048; @@ -1381,6 +1382,15 @@ impl Disc { crate::decrypt::DecryptKeys::None }; + // Wrap the producer-side reader once so every read_sectors call + // yields plaintext. `DecryptKeys::None` makes the decorator a + // pass-through, so the wrapping is cheap when --raw / unencrypted + // discs are being swept and we keep the pipeline shape uniform. + // Replaces the inline `decrypt::decrypt_sectors` calls that used + // to live in this loop and in the bisect inner loop below. + let mut reader = DecryptingSectorSource::new(reader, keys); + let reader = &mut reader; + // Mapfile: load if resuming, else wipe + recreate. let mapfile_path = self.mapfile_for(path); if !opts.resume { @@ -1528,14 +1538,10 @@ impl Disc { } read_ctx.bridge_degradation_count = 0; - // Decrypt on producer; consumer expects plaintext. - if opts.decrypt { - crate::decrypt::decrypt_sectors( - &mut buf[..block_bytes as usize], - &keys, - 0, - )?; - } + // Plaintext: the wrapped reader (DecryptingSectorSource) + // applied AACS / CSS in-place during read_sectors above. + // The consumer thread sees decrypted bytes; the + // pre-0.18 inline decrypt_sectors call lived here. // Move the batch into the channel via fresh // owned Vec. The producer's `buf` is reused @@ -1591,16 +1597,9 @@ impl Disc { ) { Ok(_) => { read_ctx.on_success(); - // Decrypt single sector before send. Pre-split - // bisect path silently skipped this — encrypted - // bytes were written for bisect-recovered sectors. - if opts.decrypt { - crate::decrypt::decrypt_sectors( - &mut sector_buf, - &keys, - 0, - )?; - } + // Plaintext via the wrapping + // DecryptingSectorSource — same + // decrypt path the batch read takes. if pipe .send(WorkItem::BisectGood { pos: write_pos, @@ -1959,6 +1958,7 @@ impl Disc { opts: &PatchOpts, ) -> Result { use crate::io::pipeline::{Pipeline, WRITE_THROUGH_DEPTH}; + use crate::sector::{DecryptingSectorSource, SectorSource}; use patch::{PatchItem, PatchSink}; const BRIDGE_DEGRADATION_PAUSE_SECS: u64 = 10; @@ -1987,6 +1987,15 @@ impl Disc { crate::decrypt::DecryptKeys::None }; + // Wrap the producer-side reader once so every read_sectors + // call (the main recovery read, the backtrack read, and the + // non-NOT_READY retry read) yields plaintext. Replaces three + // inline decrypt_sectors call sites that all keyed off the + // same `keys`. `DecryptKeys::None` keeps the unencrypted / + // --raw path a pass-through. + let mut reader = DecryptingSectorSource::new(reader, keys); + let reader = &mut reader; + let is_regular = std::fs::metadata(path) .map(|m| m.file_type().is_file()) .unwrap_or(false); @@ -2412,9 +2421,9 @@ impl Disc { "Read succeeded" ); } - if opts.decrypt { - crate::decrypt::decrypt_sectors(&mut buf[..bytes], &keys, 0)?; - } + // Plaintext: DecryptingSectorSource applied AACS / CSS + // in-place during the read_sectors call above. The + // pre-0.18 inline decrypt_sectors call lived here. let write_start = std::time::Instant::now(); tracing::debug!( target: "freemkv::disc", @@ -2503,13 +2512,9 @@ impl Disc { ) { Ok(_) => { blocks_read_ok += 1; - if opts.decrypt { - crate::decrypt::decrypt_sectors( - &mut buf[..bt_bytes], - &keys, - 0, - )?; - } + // Plaintext via DecryptingSectorSource + // wrapping; same path the main read + // takes above. send_or_abort( &pipe, PatchItem::Recovered { @@ -2652,13 +2657,8 @@ impl Disc { "Retry succeeded after non-NOT_READY error" ); - if opts.decrypt { - crate::decrypt::decrypt_sectors( - &mut buf[..bytes], - &keys, - 0, - )?; - } + // Plaintext via DecryptingSectorSource; + // same path the original read takes. let write_start = std::time::Instant::now(); tracing::debug!( target: "freemkv::disc", diff --git a/src/mux/disc.rs b/src/mux/disc.rs index 96e6629..2497265 100644 --- a/src/mux/disc.rs +++ b/src/mux/disc.rs @@ -8,7 +8,7 @@ use crate::disc::{Disc, DiscTitle, Extent}; use crate::drive::extract_scsi_context; use crate::event::{BatchSizeReason, Event, EventKind}; -use crate::sector::SectorReader; +use crate::sector::{DecryptingSectorSource, SectorReader, SectorSource}; use std::io; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -99,9 +99,19 @@ impl AdaptiveBatch { /// Sources: physical drive, ISO file, or any SectorReader. /// Decrypt, demux, and codec parsing happen internally. pub struct DiscStream { - reader: Box, + /// Underlying sector source wrapped in the 0.18 + /// [`DecryptingSectorSource`] decorator. Every `read_sectors` + /// call yields plaintext, so `fill_extents` no longer needs an + /// inline `decrypt::decrypt_sectors` step. `DecryptKeys::None` + /// (raw / unencrypted disc) makes the decorator a pass-through. + reader: DecryptingSectorSource>, title: DiscTitle, disc: Option, + /// Mirror of the keys handed in at construction. The decorator + /// owns the cryptographic state; this field is kept for + /// metadata-side callers (`info()` and friends) that want to + /// know whether the disc was encrypted, without reaching through + /// the wrapper. decrypt_keys: crate::decrypt::DecryptKeys, // Extents to read @@ -189,7 +199,11 @@ impl DiscStream { } Self { - reader, + // Wrap the input reader in DecryptingSectorSource so the + // internal fill_extents path sees plaintext bytes. For + // DecryptKeys::None (unencrypted / raw / test fixtures) + // the decorator is a pass-through. + reader: DecryptingSectorSource::new(reader, decrypt_keys.clone()), title, disc: None, decrypt_keys, @@ -241,9 +255,12 @@ impl DiscStream { } } - /// Skip decryption — return raw encrypted bytes. + /// Skip decryption — return raw encrypted bytes. Updates both + /// the metadata-side key field and the wrapped reader's keys so + /// subsequent `read_sectors` calls become a pass-through. pub fn set_raw(&mut self) { self.decrypt_keys = crate::decrypt::DecryptKeys::None; + self.reader.set_keys(crate::decrypt::DecryptKeys::None); } /// Get the scanned Disc (for listing all titles). @@ -420,11 +437,10 @@ impl crate::pes::Stream for DiscStream { } let bytes = self.buf_valid; - if let Err(e) = - crate::decrypt::decrypt_sectors(&mut self.read_buf[..bytes], &self.decrypt_keys, 0) - { - return Err(e.into()); - } + // Plaintext: the wrapped reader (DecryptingSectorSource) + // applied AACS / CSS in-place during fill_extents' + // read_sectors call. The pre-0.18 inline decrypt step + // lived here. if let Some(ref mut demuxer) = self.ts_demuxer { let packets = demuxer.feed(&self.read_buf[..bytes]); diff --git a/src/sector/decrypting.rs b/src/sector/decrypting.rs index 3410b71..1d086dd 100644 --- a/src/sector/decrypting.rs +++ b/src/sector/decrypting.rs @@ -53,6 +53,16 @@ impl DecryptingSectorSource { self } + /// Replace the configured keys without unwrapping the decorator. + /// Used by `DiscStream::set_raw()` to flip from encrypted-disc + /// decryption to a pass-through after the inner reader is already + /// owned by the wrapper. For new construction prefer [`new`]. + /// + /// [`new`]: Self::new + pub fn set_keys(&mut self, keys: DecryptKeys) { + self.keys = keys; + } + /// Borrow the inner source. Useful for tests and for adapters /// that want to introspect the underlying drive / file without /// unwrapping the decorator. diff --git a/src/sector/mod.rs b/src/sector/mod.rs index 720a701..d5213eb 100644 --- a/src/sector/mod.rs +++ b/src/sector/mod.rs @@ -144,6 +144,54 @@ impl SectorSource for T { } } +// Forwarding impls so callers can wrap `&mut dyn SectorReader` / +// `Box` in [`DecryptingSectorSource`] without +// having to unbox or re-borrow inside the lib's hot paths. The +// generic `&mut T` / `Box` 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 { + ::capacity(*self) + } + + fn read_sectors( + &mut self, + lba: u32, + count: u16, + buf: &mut [u8], + recovery: bool, + ) -> Result { + ::read_sectors(*self, lba, count, buf, recovery) + } + + fn set_speed(&mut self, kbs: u16) { + ::set_speed(*self, kbs) + } +} + +impl SectorSource for Box { + fn capacity_sectors(&self) -> u32 { + ::capacity(&**self) + } + + fn read_sectors( + &mut self, + lba: u32, + count: u16, + buf: &mut [u8], + recovery: bool, + ) -> Result { + ::read_sectors(&mut **self, lba, count, buf, recovery) + } + + fn set_speed(&mut self, kbs: u16) { + ::set_speed(&mut **self, kbs) + } +} + pub use decrypting::DecryptingSectorSource; pub use file::{FileSectorSink, FileSectorSource};