style: cargo fmt

This commit is contained in:
2026-04-24 12:23:42 -07:00
parent a67ed2635b
commit 3685c7a878
33 changed files with 127 additions and 99 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
//! AACS content decryption — AES primitives, unit decryption, bus encryption.
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit};
use aes::Aes128;
use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
// ── AACS constants ──────────────────────────────────────────────────────────
+1 -1
View File
@@ -674,8 +674,8 @@ fn generate_host_key_pair() -> ([u8; 20], [u8; 20], [u8; 20]) {
/// AES-128-CMAC over 16 bytes of data.
fn aes_cmac_16(data: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
let cipher = Aes128::new(GenericArray::from_slice(key));
+1 -5
View File
@@ -361,11 +361,7 @@ mod tests {
/// Get KEYDB path from KEYDB_PATH environment variable. Returns None if not set or not found.
fn keydb_path() -> Option<std::path::PathBuf> {
let path = std::path::PathBuf::from(std::env::var("KEYDB_PATH").ok()?);
if path.exists() {
Some(path)
} else {
None
}
if path.exists() { Some(path) } else { None }
}
#[test]
+2 -6
View File
@@ -661,18 +661,14 @@ pub fn resolve_keys(
#[cfg(test)]
mod tests {
use super::super::decrypt::{aes_ecb_encrypt, ALIGNED_UNIT_LEN};
use super::super::decrypt::{ALIGNED_UNIT_LEN, aes_ecb_encrypt};
use super::super::keydb::{DiscEntry, KeyDb};
use super::*;
/// Get KEYDB path from KEYDB_PATH environment variable. Returns None if not set or not found.
fn keydb_path() -> Option<std::path::PathBuf> {
let path = std::path::PathBuf::from(std::env::var("KEYDB_PATH").ok()?);
if path.exists() {
Some(path)
} else {
None
}
if path.exists() { Some(path) } else { None }
}
#[test]
+5 -5
View File
@@ -21,12 +21,12 @@ pub mod keys;
// Explicit re-exports — only items needed by external consumers and sibling crate modules.
// AES primitives (aes_ecb_encrypt, aes_ecb_decrypt, aes_cbc_decrypt) are pub(crate) in decrypt.rs.
pub use decrypt::{
decrypt_bus, decrypt_unit, decrypt_unit_full, decrypt_unit_try_keys, is_unit_encrypted,
ALIGNED_UNIT_LEN,
ALIGNED_UNIT_LEN, decrypt_bus, decrypt_unit, decrypt_unit_full, decrypt_unit_try_keys,
is_unit_encrypted,
};
pub use keydb::{DeviceKey, DiscEntry, HostCert, KeyDb};
pub use keys::{
decrypt_unit_key, derive_media_key_from_dk, derive_media_key_from_pk, derive_vuk, disc_hash,
disc_hash_hex, mkb_version, parse_content_cert, parse_unit_key_ro, read_mkb_from_drive,
resolve_keys, ContentCert, ResolvedKeys, UnitKeyFile,
ContentCert, ResolvedKeys, UnitKeyFile, decrypt_unit_key, derive_media_key_from_dk,
derive_media_key_from_pk, derive_vuk, disc_hash, disc_hash_hex, mkb_version,
parse_content_cert, parse_unit_key_ro, read_mkb_from_drive, resolve_keys,
};
+31 -7
View File
@@ -170,7 +170,9 @@ impl Mapfile {
pub fn open_or_create(path: &Path, total_size: u64, version: &str) -> io::Result<Self> {
match Self::load(path) {
Ok(mf) => Ok(mf),
Err(e) if e.kind() == io::ErrorKind::NotFound => Self::create(path, total_size, version),
Err(e) if e.kind() == io::ErrorKind::NotFound => {
Self::create(path, total_size, version)
}
Err(e) => Err(e),
}
}
@@ -290,12 +292,22 @@ impl Mapfile {
{
let file = std::fs::File::create(&tmp)?;
let mut w = std::io::BufWriter::new(file);
writeln!(w, "# Rescue Logfile. Created by libfreemkv v{}", self.version)?;
writeln!(
w,
"# Rescue Logfile. Created by libfreemkv v{}",
self.version
)?;
writeln!(w, "# Current pos / status / pass / pass_time")?;
writeln!(w, "0x000000000 ? 1 0")?;
writeln!(w, "# pos size status")?;
for e in &self.entries {
writeln!(w, "0x{:09x} 0x{:09x} {}", e.pos, e.size, e.status.to_char())?;
writeln!(
w,
"0x{:09x} 0x{:09x} {}",
e.pos,
e.size,
e.status.to_char()
)?;
}
w.flush()?;
}
@@ -347,9 +359,18 @@ mod tests {
mf.record(200, 100, SectorStatus::Finished).unwrap();
let es = mf.entries();
assert_eq!(es.len(), 3);
assert_eq!((es[0].pos, es[0].size, es[0].status), (0, 200, SectorStatus::NonTried));
assert_eq!((es[1].pos, es[1].size, es[1].status), (200, 100, SectorStatus::Finished));
assert_eq!((es[2].pos, es[2].size, es[2].status), (300, 700, SectorStatus::NonTried));
assert_eq!(
(es[0].pos, es[0].size, es[0].status),
(0, 200, SectorStatus::NonTried)
);
assert_eq!(
(es[1].pos, es[1].size, es[1].status),
(200, 100, SectorStatus::Finished)
);
assert_eq!(
(es[2].pos, es[2].size, es[2].status),
(300, 700, SectorStatus::NonTried)
);
let _ = std::fs::remove_file(&p);
}
@@ -363,7 +384,10 @@ mod tests {
// Entries: [0..100 NonTried, 100..300 Finished (merged), 300..1000 NonTried]
let es = mf.entries();
assert_eq!(es.len(), 3);
assert_eq!((es[1].pos, es[1].size, es[1].status), (100, 200, SectorStatus::Finished));
assert_eq!(
(es[1].pos, es[1].size, es[1].status),
(100, 200, SectorStatus::Finished)
);
let _ = std::fs::remove_file(&p);
}
+24 -12
View File
@@ -1243,20 +1243,26 @@ impl Disc {
if !opts.resume {
let _ = std::fs::remove_file(&mapfile_path);
}
let mut map = mapfile::Mapfile::open_or_create(&mapfile_path, total_bytes, env!("CARGO_PKG_VERSION"))
let mut map =
mapfile::Mapfile::open_or_create(&mapfile_path, total_bytes, env!("CARGO_PKG_VERSION"))
.map_err(|e| Error::IoError { source: e })?;
// ISO file: if resuming and mapfile has Finished ranges, open existing;
// otherwise create fresh and pre-size to total_bytes (sparse holes for
// non-tried regions).
let file = if opts.resume && std::fs::metadata(path).map(|m| m.len() > 0).unwrap_or(false) {
let file = if opts.resume
&& std::fs::metadata(path)
.map(|m| m.len() > 0)
.unwrap_or(false)
{
std::fs::OpenOptions::new()
.write(true)
.open(path)
.map_err(|e| Error::IoError { source: e })?
} else {
let f = std::fs::File::create(path).map_err(|e| Error::IoError { source: e })?;
f.set_len(total_bytes).map_err(|e| Error::IoError { source: e })?;
f.set_len(total_bytes)
.map_err(|e| Error::IoError { source: e })?;
f
};
@@ -1290,8 +1296,7 @@ impl Disc {
// Only process the first NonTried range per outer pass; skip_forward
// may turn others into NonTrimmed which we DO NOT re-enter here —
// Disc::patch handles those.
let Some((region_pos, region_size)) = map
.next_with(0, mapfile::SectorStatus::NonTried)
let Some((region_pos, region_size)) = map.next_with(0, mapfile::SectorStatus::NonTried)
else {
break;
};
@@ -1319,8 +1324,10 @@ impl Disc {
if opts.decrypt {
crate::decrypt::decrypt_sectors(&mut buf[..bytes], &keys, 0)?;
}
file.seek(SeekFrom::Start(pos)).map_err(|e| Error::IoError { source: e })?;
file.write_all(&buf[..bytes]).map_err(|e| Error::IoError { source: e })?;
file.seek(SeekFrom::Start(pos))
.map_err(|e| Error::IoError { source: e })?;
file.write_all(&buf[..bytes])
.map_err(|e| Error::IoError { source: e })?;
map.record(pos, block_bytes, mapfile::SectorStatus::Finished)
.map_err(|e| Error::IoError { source: e })?;
bytes_done = bytes_done.saturating_add(block_bytes);
@@ -1329,8 +1336,10 @@ impl Disc {
} else if opts.skip_on_error {
// Zero-fill this block, mark non-trimmed for later patch trim.
buf[..bytes].fill(0);
file.seek(SeekFrom::Start(pos)).map_err(|e| Error::IoError { source: e })?;
file.write_all(&buf[..bytes]).map_err(|e| Error::IoError { source: e })?;
file.seek(SeekFrom::Start(pos))
.map_err(|e| Error::IoError { source: e })?;
file.write_all(&buf[..bytes])
.map_err(|e| Error::IoError { source: e })?;
map.record(pos, block_bytes, mapfile::SectorStatus::NonTrimmed)
.map_err(|e| Error::IoError { source: e })?;
pos += block_bytes;
@@ -1453,7 +1462,8 @@ impl Disc {
use std::io::{Seek, SeekFrom, Write};
let mapfile_path = mapfile_path_for(path);
let mut map = mapfile::Mapfile::load(&mapfile_path).map_err(|e| Error::IoError { source: e })?;
let mut map =
mapfile::Mapfile::load(&mapfile_path).map_err(|e| Error::IoError { source: e })?;
let total_bytes = map.total_size();
let keys = if opts.decrypt {
self.decrypt_keys()
@@ -1507,8 +1517,10 @@ impl Disc {
if opts.decrypt {
crate::decrypt::decrypt_sectors(&mut buf[..bytes], &keys, 0)?;
}
file.seek(SeekFrom::Start(pos)).map_err(|e| Error::IoError { source: e })?;
file.write_all(&buf[..bytes]).map_err(|e| Error::IoError { source: e })?;
file.seek(SeekFrom::Start(pos))
.map_err(|e| Error::IoError { source: e })?;
file.write_all(&buf[..bytes])
.map_err(|e| Error::IoError { source: e })?;
map.record(pos, block_bytes, mapfile::SectorStatus::Finished)
.map_err(|e| Error::IoError { source: e })?;
} else {
+2 -2
View File
@@ -18,14 +18,14 @@ mod windows;
use crate::error::{Error, Result};
use crate::event::{Event, EventKind};
use crate::identity::DriveId;
use crate::platform::mt1959::Mt1959;
use crate::platform::PlatformDriver;
use crate::platform::mt1959::Mt1959;
use crate::profile::{self, DriveProfile};
use crate::scsi::ScsiTransport;
use crate::sector::SectorReader;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
/// Physical state of the drive tray and disc.
#[derive(Debug, Clone, Copy, PartialEq)]
+1 -1
View File
@@ -4,7 +4,7 @@
//! When both exist, language_streams.txt provides structured types while
//! menu_base.prop provides stream number → button name mapping.
use super::{vocab, LabelPurpose, LabelQualifier, StreamLabel, StreamLabelType};
use super::{LabelPurpose, LabelQualifier, StreamLabel, StreamLabelType, vocab};
use crate::sector::SectorReader;
use crate::udf::UdfFs;
use std::collections::HashMap;
+1 -1
View File
@@ -5,7 +5,7 @@
//!
//! Token format: `{lang}_{codec?}_{purpose?}_{region?}_`
use super::{vocab, LabelPurpose, LabelQualifier, StreamLabel, StreamLabelType};
use super::{LabelPurpose, LabelQualifier, StreamLabel, StreamLabelType, vocab};
use crate::sector::SectorReader;
use crate::udf::UdfFs;
+5 -5
View File
@@ -95,15 +95,15 @@ pub(crate) mod udf;
pub mod verify;
pub use drive::capture::{
capture_drive_data, mask_bytes, mask_string, CapturedFeature, DriveCapture,
CapturedFeature, DriveCapture, capture_drive_data, mask_bytes, mask_string,
};
pub use drive::{find_drive, find_drives, Drive, DriveStatus};
pub use drive::{Drive, DriveStatus, find_drive, find_drives};
pub use error::{Error, Result};
pub use event::{Event, EventKind};
pub use identity::DriveId;
pub use profile::DriveProfile;
// Platform trait is pub(crate) -- callers use Drive, not Platform directly
pub use decrypt::{decrypt_sectors, DecryptKeys};
pub use decrypt::{DecryptKeys, decrypt_sectors};
pub use disc::{
AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, Disc,
DiscFormat, DiscId, DiscTitle, Extent, FrameRate, HdrFormat, KeySource, Resolution, SampleRate,
@@ -115,8 +115,8 @@ pub use mux::MkvStream;
pub use mux::NetworkStream;
pub use mux::NullStream;
pub use mux::StdioStream;
pub use mux::{input, output, parse_url, InputOptions, StreamUrl};
pub use mux::{InputOptions, StreamUrl, input, output, parse_url};
pub use scsi::ScsiTransport;
pub use sector::{FileSectorReader, SectorReader};
pub use speed::DriveSpeed;
pub use udf::{read_filesystem, UdfFs};
pub use udf::{UdfFs, read_filesystem};
+1 -1
View File
@@ -4,7 +4,7 @@
//! Buffers across PES boundaries so frames that span two PES packets
//! are emitted complete, not truncated.
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
pub struct Ac3Parser {
/// Leftover bytes from previous PES (incomplete frame at end).
+1 -1
View File
@@ -5,7 +5,7 @@
//! Buffers across PES boundaries so frames spanning two PES packets
//! are emitted complete.
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
const DTS_CORE_SYNC: [u8; 4] = [0x7F, 0xFE, 0x80, 0x01];
const DTS_HD_EXT_SYNC: [u8; 4] = [0x64, 0x58, 0x20, 0x25];
+1 -1
View File
@@ -7,7 +7,7 @@
//! For MKV: codec ID "S_VOBSUB".
//! All frames are keyframes (each is a complete bitmap).
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
pub struct DvdSubParser {
/// Pre-formatted VobSub .idx palette header for codec_private.
+1 -1
View File
@@ -4,7 +4,7 @@
//! Detects keyframes (IDR slices).
//! Each PES packet = one access unit = one frame.
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
/// H.264 NAL unit types we care about.
const NAL_SLICE_IDR: u8 = 5;
+1 -1
View File
@@ -5,7 +5,7 @@
//! Each PES packet = one access unit = one frame.
use super::h264::{find_start_code, skip_start_code};
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
// HEVC NAL unit types
const NAL_VPS: u8 = 32;
+1 -1
View File
@@ -13,7 +13,7 @@
//! For MKV: codec ID "A_PCM/INT/BIG" (BD) or "A_PCM/INT/LIT" (DVD).
//! All frames are keyframes (uncompressed audio).
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
/// BD LPCM header size in bytes.
const BD_LPCM_HEADER_SIZE: usize = 4;
+1 -1
View File
@@ -9,7 +9,7 @@
//! - Sequence extension: 00 00 01 B5
//! - Picture header: 00 00 01 00
use super::{pts_to_ns, CodecParser, Frame};
use super::{CodecParser, Frame, pts_to_ns};
use crate::mux::ts::PesPacket;
/// Sequence header start code suffix.
+1 -1
View File
@@ -4,7 +4,7 @@
//! Each PES packet contains one or more segments.
//! All segments are keyframes (no inter-segment dependencies).
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
pub struct PgsParser;
+1 -1
View File
@@ -11,7 +11,7 @@
//! AC-3 frames (interleaved, same PID): start with sync word 0x0B77.
//! We skip AC-3 frames and only emit TrueHD access units.
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
/// Duration of one TrueHD access unit in nanoseconds (1/1200 second).
const AU_DURATION_NS: i64 = 833_333;
+1 -1
View File
@@ -5,7 +5,7 @@
//! Frame start = Frame header start code (0x0D).
//! I-frames (keyframes) are identified from the frame header.
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
const SC_SEQUENCE_HEADER: u8 = 0x0F;
const SC_ENTRY_POINT: u8 = 0x0E;
+3 -11
View File
@@ -9,8 +9,8 @@ use crate::disc::{Disc, DiscTitle, Extent};
use crate::event::{BatchSizeReason, Event, EventKind};
use crate::sector::SectorReader;
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
/// Ramp back up to the preferred batch size after this many sectors
/// of clean reading at the current (reduced) size. 100 MiB = 51,200 sectors.
@@ -26,22 +26,14 @@ const PROBE_THRESHOLD_SECTORS: u32 = 100 * 1024 * 1024 / 2048;
/// through 3 → 1 without intermediate unaligned sizes.
fn halve_batch_size(size: u16) -> u16 {
let h = (size / 2).max(1);
if h >= 6 {
h - (h % 3)
} else {
h
}
if h >= 6 { h - (h % 3) } else { h }
}
/// Double a batch size toward a preferred max, keeping 3-sector alignment
/// when the result is >= 6.
fn double_batch_size(size: u16, preferred: u16) -> u16 {
let d = size.saturating_mul(2).min(preferred);
if d >= 6 {
d - (d % 3)
} else {
d
}
if d >= 6 { d - (d % 3) } else { d }
}
/// Adaptive batch sizer. Shrinks on read failure, grows after a sustained
+1 -1
View File
@@ -4,7 +4,7 @@
//! Write: PES frames in → MKV mux → Matroska container.
use super::mkv::{MkvMuxer, MkvTrack};
use super::{ebml, WriteSeek};
use super::{WriteSeek, ebml};
type MkvHeaderResult = io::Result<(crate::disc::DiscTitle, Vec<(u16, Vec<u8>)>)>;
+1 -1
View File
@@ -38,7 +38,7 @@ pub use m2ts::M2tsStream;
pub use mkvstream::MkvStream;
pub use network::NetworkStream;
pub use null::NullStream;
pub use resolve::{input, output, parse_url, InputOptions, StreamUrl};
pub use resolve::{InputOptions, StreamUrl, input, output, parse_url};
pub use stdio::StdioStream;
use std::io::{Seek, Write};
+1 -5
View File
@@ -227,11 +227,7 @@ fn hvcc_to_annex_b(hvcc: &[u8]) -> Option<Vec<u8>> {
}
}
if out.is_empty() {
None
} else {
Some(out)
}
if out.is_empty() { None } else { Some(out) }
}
/// Convert length-prefixed NALUs (4-byte BE length + NAL) to Annex B
+3 -3
View File
@@ -101,7 +101,7 @@ impl UdfFs {
None => {
return Err(Error::UdfNotFound {
path: path.to_string(),
})
});
}
};
let entry = current
@@ -136,7 +136,7 @@ impl UdfFs {
None => {
return Err(Error::UdfNotFound {
path: path.to_string(),
})
});
}
};
let entry = current
@@ -375,7 +375,7 @@ impl UdfFs {
None => {
return Err(Error::UdfNotFound {
path: path.to_string(),
})
});
}
};
let entry = current
+4 -4
View File
@@ -73,8 +73,8 @@ fn css_is_scrambled_detection() {
/// then decrypt with decrypt_unit() and verify the plaintext matches.
#[test]
fn aacs_decrypt_unit_roundtrip() {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
let unit_key = [0xAAu8; 16];
let aacs_iv: [u8; 16] = [
@@ -207,8 +207,8 @@ fn aacs_disc_hash_deterministic() {
/// decrypt_unit_key recovers the original.
#[test]
fn aacs_decrypt_unit_key_roundtrip() {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
let vuk = [
0x11u8, 0x14, 0x36, 0x0B, 0x10, 0xEE, 0x6E, 0xAC, 0x78, 0xAA, 0x4A, 0xC0, 0xB7, 0x52, 0xEA,
@@ -309,8 +309,8 @@ fn aacs_decrypt_unit_unencrypted_passthrough() {
/// Independent AES-128-ECB encrypt (uses `aes` crate directly, NOT our library).
fn ref_aes_ecb_encrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
let cipher = Aes128::new(GenericArray::from_slice(key));
let mut block = GenericArray::clone_from_slice(data);
cipher.encrypt_block(&mut block);
@@ -321,8 +321,8 @@ fn ref_aes_ecb_encrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
/// Independent AES-128-CBC encrypt (uses `aes` crate directly, NOT our library).
fn ref_aes_cbc_encrypt(key: &[u8; 16], iv: &[u8; 16], data: &mut [u8]) {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
let cipher = Aes128::new(GenericArray::from_slice(key));
let mut prev = *iv;
let num_blocks = data.len() / 16;
+8 -2
View File
@@ -1,7 +1,7 @@
//! Disc scanning pipeline tests.
use libfreemkv::error::Result;
use libfreemkv::SectorReader;
use libfreemkv::error::Result;
use libfreemkv::{Disc, DiscTitle, ScanOptions};
use std::collections::HashMap;
@@ -21,7 +21,13 @@ impl MockSectorReader {
}
impl SectorReader for MockSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8], _recovery: bool) -> Result<usize> {
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
let total = count as usize * SECTOR_SIZE;
for i in 0..count as u32 {
let offset = i as usize * SECTOR_SIZE;
+8 -2
View File
@@ -1,7 +1,7 @@
//! UDF parser tests using a MockSectorReader.
use libfreemkv::error::Result;
use libfreemkv::{read_filesystem, SectorReader};
use libfreemkv::{SectorReader, read_filesystem};
use std::collections::HashMap;
const SECTOR_SIZE: usize = 2048;
@@ -39,7 +39,13 @@ impl MockSectorReader {
}
impl SectorReader for MockSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8], _recovery: bool) -> Result<usize> {
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
let total = count as usize * SECTOR_SIZE;
assert!(buf.len() >= total, "buffer too small");
for i in 0..count as u32 {