use super::IOStream;
use crate::disc::{
detect_max_batch_sectors, ContentFormat, Disc, DiscTitle, Extent, MIN_BATCH_SECTORS,
RAMP_BATCH_AFTER, RAMP_SPEED_AFTER, SLOW_SPEED_AFTER,
};
use crate::drive::Drive;
use crate::error::Error;
use crate::speed::DriveSpeed;
use std::io::{self, Read, Write};
struct AacsDecrypt {
unit_keys: Vec<(u32, [u8; 16])>,
read_data_key: Option<[u8; 16]>,
}
#[derive(Default)]
pub struct DiscOptions {
pub device: Option<std::path::PathBuf>,
pub keydb_path: Option<std::path::PathBuf>,
pub title_index: Option<usize>,
}
pub struct DiscStream {
disc_title: DiscTitle,
disc: Disc,
session: Drive,
batch_buf: Vec<u8>,
batch_pos: usize,
eof: bool,
extents: Vec<Extent>,
current_extent: usize,
current_offset: u32,
#[allow(dead_code)]
content_format: ContentFormat,
aacs: Option<AacsDecrypt>,
css: Option<crate::css::CssState>,
unit_key_idx: usize,
read_buf: Vec<u8>,
batch_sectors: u16,
max_batch_sectors: u16,
ok_streak: u32,
error_streak: u32,
pub errors: u32,
}
impl DiscStream {
pub fn open(opts: DiscOptions) -> Result<Self, Error> {
let mut session = match opts.device {
Some(ref d) => Drive::open(d)?,
None => crate::drive::find_drive().ok_or_else(|| Error::DeviceNotFound {
path: String::new(),
})?,
};
session.wait_ready()?;
let _ = session.init();
let _ = session.probe_disc();
let scan_opts = match opts.keydb_path {
Some(ref kp) => crate::disc::ScanOptions::with_keydb(kp.clone()),
None => crate::disc::ScanOptions::default(),
};
let disc = Disc::scan(&mut session, &scan_opts)?;
let title_index = opts.title_index.unwrap_or(0);
if title_index >= disc.titles.len() {
return Err(Error::DiscTitleRange {
index: title_index,
count: disc.titles.len(),
});
}
let disc_title = disc.titles[title_index].clone();
let extents = disc_title.extents.clone();
let content_format = disc_title.content_format;
let aacs = disc.aacs.as_ref().map(|a| AacsDecrypt {
unit_keys: a.unit_keys.clone(),
read_data_key: a.read_data_key,
});
let css = disc.css.clone();
let max_batch = detect_max_batch_sectors(session.device_path());
Ok(Self {
disc_title,
disc,
session,
batch_buf: Vec::new(),
batch_pos: 0,
eof: false,
extents,
current_extent: 0,
current_offset: 0,
content_format,
aacs,
css,
unit_key_idx: 0,
read_buf: Vec::with_capacity(max_batch as usize * 2048),
batch_sectors: max_batch,
max_batch_sectors: max_batch,
ok_streak: 0,
error_streak: 0,
errors: 0,
})
}
pub fn disc(&self) -> &Disc {
&self.disc
}
fn read_sectors(&mut self, lba: u32, count: u16) -> Result<(), Error> {
self.session.read_content(lba, count, &mut self.read_buf)?;
Ok(())
}
fn fill_buffer(&mut self) -> Result<bool, Error> {
loop {
if self.current_extent >= self.extents.len() {
return Ok(false);
}
let ext_start = self.extents[self.current_extent].start_lba;
let ext_sectors = self.extents[self.current_extent].sector_count;
let remaining = ext_sectors.saturating_sub(self.current_offset);
let sectors_to_read = remaining.min(self.batch_sectors as u32) as u16;
let sectors_to_read = sectors_to_read - (sectors_to_read % 3);
if sectors_to_read == 0 {
self.current_extent += 1;
self.current_offset = 0;
continue;
}
let lba = ext_start + self.current_offset;
let byte_count = sectors_to_read as usize * 2048;
self.read_buf.resize(byte_count, 0);
match self.read_sectors(lba, sectors_to_read) {
Ok(_) => {
self.current_offset += sectors_to_read as u32;
self.error_streak = 0;
if self.current_offset >= ext_sectors {
self.current_extent += 1;
self.current_offset = 0;
}
self.ok_streak += 1;
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.ok_streak = 0;
}
if self.batch_sectors == self.max_batch_sectors
&& self.ok_streak >= RAMP_SPEED_AFTER
{
self.session.set_speed(0xFFFF);
self.ok_streak = 0;
}
return Ok(true);
}
Err(_) => {
self.errors += 1;
self.error_streak += 1;
self.ok_streak = 0;
if self.error_streak == 1 {
let _ = self.session.init();
let _ = self.session.probe_disc();
}
if self.error_streak >= SLOW_SPEED_AFTER {
self.session.set_speed(DriveSpeed::BD2x.to_kbps());
self.error_streak = 0;
}
if self.batch_sectors > MIN_BATCH_SECTORS {
self.batch_sectors = (self.batch_sectors / 2).max(MIN_BATCH_SECTORS);
std::thread::sleep(std::time::Duration::from_millis(100));
} else {
std::thread::sleep(std::time::Duration::from_millis(500));
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;
if self.current_offset >= ext_sectors {
self.current_extent += 1;
self.current_offset = 0;
}
return Ok(true);
}
self.current_offset += 3;
if self.current_offset >= ext_sectors {
self.current_extent += 1;
self.current_offset = 0;
}
self.read_buf.resize(crate::aacs::ALIGNED_UNIT_LEN, 0);
self.read_buf.fill(0);
return Ok(true);
}
}
}
}
}
fn decrypt_and_buffer(&mut self) {
let unit_len = crate::aacs::ALIGNED_UNIT_LEN;
let total_bytes = self.read_buf.len();
if let Some(ref aacs) = self.aacs {
let uk = aacs
.unit_keys
.get(self.unit_key_idx)
.map(|(_, k)| *k)
.unwrap_or([0u8; 16]);
let rdk = aacs.read_data_key.as_ref();
let num_units = total_bytes / unit_len;
for i in 0..num_units {
let start = i * unit_len;
let end = start + unit_len;
let unit = &mut self.read_buf[start..end];
if crate::aacs::is_unit_encrypted(unit) {
crate::aacs::decrypt_unit_full(unit, &uk, rdk);
}
}
} else if let Some(ref css) = self.css {
for chunk in self.read_buf[..total_bytes].chunks_mut(2048) {
crate::css::lfsr::descramble_sector(&css.title_key, chunk);
}
}
std::mem::swap(&mut self.batch_buf, &mut self.read_buf);
self.batch_pos = 0;
}
}
impl IOStream for DiscStream {
fn info(&self) -> &DiscTitle {
&self.disc_title
}
fn finish(&mut self) -> io::Result<()> {
Ok(())
}
fn total_bytes(&self) -> Option<u64> {
Some(self.disc_title.size_bytes)
}
}
impl Read for DiscStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.batch_pos < self.batch_buf.len() {
let n = (self.batch_buf.len() - self.batch_pos).min(buf.len());
buf[..n].copy_from_slice(&self.batch_buf[self.batch_pos..self.batch_pos + n]);
self.batch_pos += n;
return Ok(n);
}
if self.eof {
return Ok(0);
}
let has_data = self
.fill_buffer()
.map_err(|e| io::Error::other(e.to_string()))?;
if !has_data {
self.eof = true;
return Ok(0);
}
self.decrypt_and_buffer();
let n = self.batch_buf.len().min(buf.len());
buf[..n].copy_from_slice(&self.batch_buf[..n]);
self.batch_pos = n;
Ok(n)
}
}
impl Write for DiscStream {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"disc is read-only",
))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::disc::Extent;
#[test]
fn state_advances_across_extents() {
let extents = [
Extent {
start_lba: 100,
sector_count: 6,
},
Extent {
start_lba: 200,
sector_count: 6,
},
];
let batch_sectors: u16 = 6;
let mut current_extent: usize = 0;
let mut current_offset: u32 = 0;
let mut lbas_read = Vec::new();
while current_extent < extents.len() {
let ext_start = extents[current_extent].start_lba;
let ext_sectors = extents[current_extent].sector_count;
let remaining = ext_sectors.saturating_sub(current_offset);
let sectors_to_read = remaining.min(batch_sectors as u32) as u16;
let sectors_to_read = sectors_to_read - (sectors_to_read % 3);
if sectors_to_read == 0 {
current_extent += 1;
current_offset = 0;
continue;
}
let lba = ext_start + current_offset;
lbas_read.push((lba, sectors_to_read));
current_offset += sectors_to_read as u32;
if current_offset >= ext_sectors {
current_extent += 1;
current_offset = 0;
}
}
assert_eq!(lbas_read.len(), 2, "should read two batches");
assert_eq!(lbas_read[0], (100, 6), "first batch starts at LBA 100");
assert_eq!(lbas_read[1], (200, 6), "second batch starts at LBA 200");
}
#[test]
fn unaligned_extent_is_skipped() {
let extents = [
Extent {
start_lba: 50,
sector_count: 2, },
Extent {
start_lba: 300,
sector_count: 9,
},
];
let batch_sectors: u16 = 9;
let mut current_extent: usize = 0;
let mut current_offset: u32 = 0;
let mut lbas_read = Vec::new();
while current_extent < extents.len() {
let ext_start = extents[current_extent].start_lba;
let ext_sectors = extents[current_extent].sector_count;
let remaining = ext_sectors.saturating_sub(current_offset);
let sectors_to_read = remaining.min(batch_sectors as u32) as u16;
let sectors_to_read = sectors_to_read - (sectors_to_read % 3);
if sectors_to_read == 0 {
current_extent += 1;
current_offset = 0;
continue;
}
let lba = ext_start + current_offset;
lbas_read.push((lba, sectors_to_read));
current_offset += sectors_to_read as u32;
if current_offset >= ext_sectors {
current_extent += 1;
current_offset = 0;
}
}
assert_eq!(lbas_read.len(), 1, "only second extent is readable");
assert_eq!(lbas_read[0], (300, 9));
}
#[test]
fn multiple_batches_within_one_extent() {
let extents = [Extent {
start_lba: 1000,
sector_count: 18, }];
let batch_sectors: u16 = 6;
let mut current_extent: usize = 0;
let mut current_offset: u32 = 0;
let mut lbas_read = Vec::new();
while current_extent < extents.len() {
let ext_start = extents[current_extent].start_lba;
let ext_sectors = extents[current_extent].sector_count;
let remaining = ext_sectors.saturating_sub(current_offset);
let sectors_to_read = remaining.min(batch_sectors as u32) as u16;
let sectors_to_read = sectors_to_read - (sectors_to_read % 3);
if sectors_to_read == 0 {
current_extent += 1;
current_offset = 0;
continue;
}
let lba = ext_start + current_offset;
lbas_read.push((lba, sectors_to_read));
current_offset += sectors_to_read as u32;
if current_offset >= ext_sectors {
current_extent += 1;
current_offset = 0;
}
}
assert_eq!(lbas_read.len(), 3, "three batches from one extent");
assert_eq!(lbas_read[0], (1000, 6));
assert_eq!(lbas_read[1], (1006, 6));
assert_eq!(lbas_read[2], (1012, 6));
}
}