Audit v3 fixes: all 3 tiers (19 findings)
Tier 1 (compilation + correctness): - Fix nightly-only is_multiple_of → % 2 != 0 (stable Rust compat) - Fix parse_sample_rate: check 192 before 96 (was returning wrong rate) - macOS drive discovery: split unix.rs → linux.rs + macos.rs - Linux: EACCES returns DevicePermission not DeviceNotFound - CLI pipe.rs: Ctrl+C signal handler added Tier 2 (correctness + security): - MkvStream: reset demuxer after scanning→streaming transition - Windows SPTI: zero data buffer before ioctl - AACS cert verification: documented why silently skipped - KEYDB: HOME + USERPROFILE fallback for Windows - Library modules: pub(crate) for internal modules - AACS: explicit re-exports, AES primitives pub(crate) Tier 3 (performance + polish): - IsoStream: batch 64-sector reads (was 1 sector at a time) - DiscStream: buffer swap instead of copy in decrypt_and_buffer - Vec capacity hints in TS/PS demuxer hot paths - NetworkStream: TLS warning documented - Batch rip: per-title progress display - cargo fmt: 0 violations 319 tests, 0 fmt violations.
This commit is contained in:
+9
-13
@@ -9,9 +9,8 @@
|
||||
|
||||
use super::IOStream;
|
||||
use crate::disc::{
|
||||
ContentFormat, Disc, DiscTitle, Extent,
|
||||
MIN_BATCH_SECTORS, RAMP_BATCH_AFTER, RAMP_SPEED_AFTER,
|
||||
SLOW_SPEED_AFTER, detect_max_batch_sectors,
|
||||
detect_max_batch_sectors, ContentFormat, Disc, DiscTitle, Extent, MIN_BATCH_SECTORS,
|
||||
RAMP_BATCH_AFTER, RAMP_SPEED_AFTER, SLOW_SPEED_AFTER,
|
||||
};
|
||||
use crate::drive::DriveSession;
|
||||
use crate::error::Error;
|
||||
@@ -187,8 +186,7 @@ impl DiscStream {
|
||||
if self.batch_sectors < self.max_batch_sectors
|
||||
&& self.ok_streak >= RAMP_BATCH_AFTER
|
||||
{
|
||||
self.batch_sectors =
|
||||
(self.batch_sectors * 2).min(self.max_batch_sectors);
|
||||
self.batch_sectors = (self.batch_sectors * 2).min(self.max_batch_sectors);
|
||||
self.ok_streak = 0;
|
||||
}
|
||||
|
||||
@@ -220,14 +218,12 @@ impl DiscStream {
|
||||
}
|
||||
|
||||
if self.batch_sectors > MIN_BATCH_SECTORS {
|
||||
self.batch_sectors =
|
||||
(self.batch_sectors / 2).max(MIN_BATCH_SECTORS);
|
||||
self.batch_sectors = (self.batch_sectors / 2).max(MIN_BATCH_SECTORS);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
} else {
|
||||
// At minimum batch -- retry once with longer pause
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
self.read_buf
|
||||
.resize(MIN_BATCH_SECTORS as usize * 2048, 0);
|
||||
self.read_buf.resize(MIN_BATCH_SECTORS as usize * 2048, 0);
|
||||
if self.read_sectors(lba, MIN_BATCH_SECTORS).is_ok() {
|
||||
self.error_streak = 0;
|
||||
self.current_offset += MIN_BATCH_SECTORS as u32;
|
||||
@@ -243,8 +239,7 @@ impl DiscStream {
|
||||
self.current_extent += 1;
|
||||
self.current_offset = 0;
|
||||
}
|
||||
self.read_buf
|
||||
.resize(crate::aacs::ALIGNED_UNIT_LEN, 0);
|
||||
self.read_buf.resize(crate::aacs::ALIGNED_UNIT_LEN, 0);
|
||||
self.read_buf.fill(0);
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -283,8 +278,9 @@ impl DiscStream {
|
||||
}
|
||||
// No encryption: read_buf is already plaintext
|
||||
|
||||
self.batch_buf.clear();
|
||||
self.batch_buf.extend_from_slice(&self.read_buf[..total_bytes]);
|
||||
// Swap buffers instead of copying — the old batch_buf becomes
|
||||
// read_buf and will be overwritten on the next read.
|
||||
std::mem::swap(&mut self.batch_buf, &mut self.read_buf);
|
||||
self.batch_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-10
@@ -18,6 +18,9 @@ use std::path::Path;
|
||||
|
||||
const SECTOR_SIZE: u64 = 2048;
|
||||
|
||||
/// Maximum sectors to batch-read at once (64 sectors = 128 KB).
|
||||
const BATCH_SECTORS: usize = 64;
|
||||
|
||||
/// File-backed sector reader for ISO images.
|
||||
pub struct IsoSectorReader {
|
||||
file: File,
|
||||
@@ -63,7 +66,8 @@ pub struct IsoStream {
|
||||
extents: Vec<(u32, u32)>,
|
||||
extent_idx: usize,
|
||||
sectors_remaining: u32,
|
||||
sector_buf: [u8; SECTOR_SIZE as usize],
|
||||
/// Batch buffer: holds up to BATCH_SECTORS sectors (128 KB) at once.
|
||||
batch_buf: Vec<u8>,
|
||||
buf_pos: usize,
|
||||
buf_len: usize,
|
||||
eof: bool,
|
||||
@@ -107,7 +111,7 @@ impl IsoStream {
|
||||
extents,
|
||||
extent_idx: 0,
|
||||
sectors_remaining,
|
||||
sector_buf: [0u8; SECTOR_SIZE as usize],
|
||||
batch_buf: vec![0u8; BATCH_SECTORS * SECTOR_SIZE as usize],
|
||||
buf_pos: 0,
|
||||
buf_len: 0,
|
||||
eof: false,
|
||||
@@ -130,7 +134,7 @@ impl IsoStream {
|
||||
extents: Vec::new(),
|
||||
extent_idx: 0,
|
||||
sectors_remaining: 0,
|
||||
sector_buf: [0u8; SECTOR_SIZE as usize],
|
||||
batch_buf: Vec::new(),
|
||||
buf_pos: 0,
|
||||
buf_len: 0,
|
||||
eof: false,
|
||||
@@ -163,7 +167,8 @@ impl IsoStream {
|
||||
self.disc.as_ref()
|
||||
}
|
||||
|
||||
fn read_next_sector(&mut self) -> io::Result<bool> {
|
||||
/// Read up to BATCH_SECTORS sectors at once into the batch buffer.
|
||||
fn read_next_batch(&mut self) -> io::Result<bool> {
|
||||
let reader = match self.reader.as_mut() {
|
||||
Some(r) => r,
|
||||
None => return Ok(false),
|
||||
@@ -177,13 +182,16 @@ impl IsoStream {
|
||||
let offset = total - self.sectors_remaining;
|
||||
let lba = start_lba + offset;
|
||||
|
||||
// Read up to BATCH_SECTORS, but no more than remaining in this extent
|
||||
let count = (self.sectors_remaining as usize).min(BATCH_SECTORS) as u16;
|
||||
|
||||
reader
|
||||
.read_sectors(lba, 1, &mut self.sector_buf)
|
||||
.read_sectors(lba, count, &mut self.batch_buf)
|
||||
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||
self.buf_pos = 0;
|
||||
self.buf_len = SECTOR_SIZE as usize;
|
||||
self.buf_len = count as usize * SECTOR_SIZE as usize;
|
||||
|
||||
self.sectors_remaining -= 1;
|
||||
self.sectors_remaining -= count as u32;
|
||||
if self.sectors_remaining == 0 {
|
||||
self.extent_idx += 1;
|
||||
if self.extent_idx < self.extents.len() {
|
||||
@@ -223,14 +231,14 @@ impl Read for IsoStream {
|
||||
|
||||
if self.buf_pos < self.buf_len {
|
||||
let n = (self.buf_len - self.buf_pos).min(buf.len());
|
||||
buf[..n].copy_from_slice(&self.sector_buf[self.buf_pos..self.buf_pos + n]);
|
||||
buf[..n].copy_from_slice(&self.batch_buf[self.buf_pos..self.buf_pos + n]);
|
||||
self.buf_pos += n;
|
||||
return Ok(n);
|
||||
}
|
||||
|
||||
if self.read_next_sector()? {
|
||||
if self.read_next_batch()? {
|
||||
let n = self.buf_len.min(buf.len());
|
||||
buf[..n].copy_from_slice(&self.sector_buf[..n]);
|
||||
buf[..n].copy_from_slice(&self.batch_buf[..n]);
|
||||
self.buf_pos = n;
|
||||
Ok(n)
|
||||
} else {
|
||||
|
||||
+3
-3
@@ -431,10 +431,10 @@ fn parse_resolution(s: &str) -> (u32, u32) {
|
||||
}
|
||||
|
||||
fn parse_sample_rate(s: &str) -> f64 {
|
||||
if s.contains("96") {
|
||||
96000.0
|
||||
} else if s.contains("192") {
|
||||
if s.contains("192") {
|
||||
192000.0
|
||||
} else if s.contains("96") {
|
||||
96000.0
|
||||
} else {
|
||||
48000.0
|
||||
}
|
||||
|
||||
@@ -333,10 +333,12 @@ fn begin_streaming(ws: &mut WriteState, dt: &DiscTitle) -> io::Result<()> {
|
||||
)?);
|
||||
ws.phase = WritePhase::Streaming;
|
||||
|
||||
// Re-parse buffered data through a fresh demuxer
|
||||
// Re-parse buffered data through a fresh demuxer, then reset the main
|
||||
// demuxer so stale PES assembler state from scanning doesn't cause
|
||||
// duplicate or incomplete packets during streaming.
|
||||
let pids: Vec<u16> = ws.pid_to_track.iter().map(|(pid, _)| *pid).collect();
|
||||
let buffered = ws.lookahead.drain();
|
||||
if !buffered.is_empty() {
|
||||
let pids: Vec<u16> = ws.pid_to_track.iter().map(|(pid, _)| *pid).collect();
|
||||
let mut temp = TsDemuxer::new(&pids);
|
||||
let packets = temp.feed(&buffered);
|
||||
if let Some(ref mut muxer) = ws.muxer {
|
||||
@@ -345,6 +347,7 @@ fn begin_streaming(ws: &mut WriteState, dt: &DiscTitle) -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.demuxer = TsDemuxer::new(&pids);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
//! NetworkStream — BD-TS over TCP with embedded metadata.
|
||||
//!
|
||||
//! **Security:** Data is transmitted over plain TCP with no encryption.
|
||||
//! Use only on trusted networks (LAN). TLS support is planned.
|
||||
//!
|
||||
//! Write side (sender): connects to a listener, sends FMKV header + BD-TS data.
|
||||
//! Read side (receiver): listens for a connection, reads FMKV header + BD-TS data.
|
||||
//!
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ impl PsDemuxer {
|
||||
|
||||
/// Scan the buffer for complete start-code-delimited units and parse them.
|
||||
fn extract_packets(&mut self) -> Vec<PsPacket> {
|
||||
let mut packets = Vec::new();
|
||||
let mut packets = Vec::with_capacity(4);
|
||||
let mut pos = 0;
|
||||
|
||||
loop {
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ impl TsDemuxer {
|
||||
/// Feed a chunk of BD transport stream data. Handles non-192-byte-aligned input
|
||||
/// by buffering leftover bytes between calls. Returns completed PES packets.
|
||||
pub fn feed(&mut self, data: &[u8]) -> Vec<PesPacket> {
|
||||
let mut completed = Vec::new();
|
||||
let mut completed = Vec::with_capacity(4);
|
||||
|
||||
// Prepend any remainder from previous call
|
||||
let work: &[u8];
|
||||
|
||||
Reference in New Issue
Block a user