mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
aacs: a Stop while reading index.bdmv is Halted
The stride resolver's index.bdmv read, and the live UHD check, took a Stop for an unreadable index and went on with a default stride. The capture and the scan's version read now end as Halted, like their other reads.
This commit is contained in:
+64
-18
@@ -128,27 +128,35 @@ pub(crate) fn handshake_class_error(e: &Error) -> Option<Error> {
|
||||
}
|
||||
|
||||
// `Some(true)` for an `INDX0300` index.bdmv, `Some(false)` for another INDX version, `None`
|
||||
// when there is no readable index (HD DVD, damage).
|
||||
pub(super) fn index_is_uhd(udf_fs: &udf::UdfFs, reader: &mut dyn SectorSource) -> Option<bool> {
|
||||
let index = udf_fs
|
||||
.read_file_prefix(reader, "/BDMV/index.bdmv", 8)
|
||||
.ok()?;
|
||||
let version = index.get(..8)?.strip_prefix(b"INDX")?;
|
||||
Some(version == b"0300")
|
||||
// when there is no readable index (HD DVD, damage). `Err` only for a Stop.
|
||||
pub(super) fn index_is_uhd(
|
||||
udf_fs: &udf::UdfFs,
|
||||
reader: &mut dyn SectorSource,
|
||||
) -> Result<Option<bool>> {
|
||||
let index = match udf_fs.read_file_prefix(reader, "/BDMV/index.bdmv", 8) {
|
||||
Ok(index) => index,
|
||||
Err(Error::Halted) => return Err(Error::Halted),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
let version = index.get(..8).and_then(|v| v.strip_prefix(b"INDX"));
|
||||
Ok(version.map(|v| v == b"0300"))
|
||||
}
|
||||
|
||||
// Positive UHD evidence independent of the cert: index.bdmv version "0300"
|
||||
// (BD-ROM Part 3) or an AACS2 MKB. Unknown is `false`.
|
||||
fn disc_is_uhd(udf_fs: &udf::UdfFs, reader: &mut dyn SectorSource) -> bool {
|
||||
// (BD-ROM Part 3) or an AACS2 MKB. Unknown is `false`; `Err` only for a Stop.
|
||||
fn disc_is_uhd(udf_fs: &udf::UdfFs, reader: &mut dyn SectorSource) -> Result<bool> {
|
||||
use crate::aacs::mkb::{AacsVersion, mkb_type};
|
||||
if index_is_uhd(udf_fs, reader) == Some(true) {
|
||||
return true;
|
||||
if index_is_uhd(udf_fs, reader)? == Some(true) {
|
||||
return Ok(true);
|
||||
}
|
||||
udf_fs
|
||||
.read_file_prefix(reader, crate::aacs::PATH_MKB_RO, 64)
|
||||
.ok()
|
||||
.and_then(|m| mkb_type(&m).map(|t| t.generation()))
|
||||
.is_some_and(|g| matches!(g, AacsVersion::V20 | AacsVersion::V21))
|
||||
let mkb = match udf_fs.read_file_prefix(reader, crate::aacs::PATH_MKB_RO, 64) {
|
||||
Ok(mkb) => mkb,
|
||||
Err(Error::Halted) => return Err(Error::Halted),
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
Ok(mkb_type(&mkb)
|
||||
.map(|t| t.generation())
|
||||
.is_some_and(|g| matches!(g, AacsVersion::V20 | AacsVersion::V21)))
|
||||
}
|
||||
|
||||
/// A disc's AACS files, read before any AACS command is sent to the drive.
|
||||
@@ -217,7 +225,7 @@ pub(super) fn capture(
|
||||
// Fail safe: an unparseable cert, or none on a live disc known to be UHD, may mean bus encryption.
|
||||
let bus_encryption = match &cc {
|
||||
Some(c) => c.bus_encryption,
|
||||
None => cc_raw.is_some() || (live && disc_is_uhd(udf_fs, reader)),
|
||||
None => cc_raw.is_some() || (live && disc_is_uhd(udf_fs, reader)?),
|
||||
};
|
||||
// The bounded MKB reader, not `read_file` (the ~128 MiB padded MKB would fail).
|
||||
let mkb = match Disc::read_mkb_content(reader, udf_fs) {
|
||||
@@ -235,7 +243,7 @@ pub(super) fn capture(
|
||||
};
|
||||
// Stride version through the shared resolver (cert, then MKB type, then index.bdmv),
|
||||
// the same one `read_aacs_version` uses.
|
||||
let index = index_is_uhd(udf_fs, reader);
|
||||
let index = index_is_uhd(udf_fs, reader)?;
|
||||
let version =
|
||||
aacs::mkb::resolve_aacs_version(cc.as_ref().map(|c| c.version.major()), &mkb, index)
|
||||
.major();
|
||||
@@ -774,6 +782,44 @@ mod tests {
|
||||
state.ok_or_else(|| err.unwrap_or(Error::AacsNoKeys))
|
||||
}
|
||||
|
||||
// A Stop while reading index.bdmv (the stride's last resort, and the UHD check of a live
|
||||
// disc with no cert) ends the capture as Halted, not as an unknown index.
|
||||
#[test]
|
||||
fn a_stop_reading_the_index_is_halted() {
|
||||
struct StopAt<'a>(&'a mut MemDisc, u32);
|
||||
impl SectorSource for StopAt<'_> {
|
||||
fn read_sectors(
|
||||
&mut self,
|
||||
lba: u32,
|
||||
count: u16,
|
||||
buf: &mut [u8],
|
||||
recovery: bool,
|
||||
) -> Result<usize> {
|
||||
if lba == self.1 {
|
||||
return Err(Error::Halted);
|
||||
}
|
||||
self.0.read_sectors(lba, count, buf, recovery)
|
||||
}
|
||||
}
|
||||
for from in [CaptureFrom::Image, CaptureFrom::Live] {
|
||||
let mut disc = MemDisc::new();
|
||||
let uk = AacsFile {
|
||||
name: "Unit_Key_RO.inf",
|
||||
icb_lba: 60,
|
||||
data_lba: 5000,
|
||||
contents: vec![0xAB; 32],
|
||||
};
|
||||
let udf = build_aacs_fs_with_index(&mut disc, &[uk], Some(b"INDX0300"));
|
||||
let mut reader = StopAt(&mut disc, PART_START + 7000);
|
||||
let cap = capture(&mut reader, &udf, from);
|
||||
assert!(
|
||||
matches!(cap, Err(Error::Halted)),
|
||||
"live: {}",
|
||||
from == CaptureFrom::Live
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Missing Unit_Key_RO.inf (and its DUPLICATE) → Error::AacsNoKeys
|
||||
/// (encrypt.rs `.map_err(|_| Error::AacsNoKeys)`). Never panics.
|
||||
#[test]
|
||||
|
||||
+20
-11
@@ -2251,21 +2251,30 @@ impl Disc {
|
||||
|p| udf_fs.read_file(reader, p),
|
||||
)?;
|
||||
let mkb = Self::read_mkb_content(reader, udf_fs)?;
|
||||
let version = Self::read_aacs_version(reader, udf_fs, &mkb);
|
||||
let version = Self::read_aacs_version(reader, udf_fs, &mkb)?;
|
||||
Ok((inf, mkb, version))
|
||||
}
|
||||
|
||||
// AACS major version driving the Unit_Key_RO.inf parse stride: the content certificate,
|
||||
// then the MKB type, then index.bdmv, through the shared `resolve_aacs_version`.
|
||||
fn read_aacs_version(reader: &mut dyn SectorSource, udf_fs: &udf::UdfFs, mkb: &[u8]) -> u8 {
|
||||
let cert_major = crate::aacs::read_first(
|
||||
// then the MKB type, then index.bdmv, through the shared `resolve_aacs_version`. `Err` only
|
||||
// for a Stop.
|
||||
fn read_aacs_version(
|
||||
reader: &mut dyn SectorSource,
|
||||
udf_fs: &udf::UdfFs,
|
||||
mkb: &[u8],
|
||||
) -> Result<u8> {
|
||||
let cert = crate::aacs::read_first(
|
||||
&crate::aacs::role_paths(udf_fs, crate::aacs::AacsRole::ContentCert),
|
||||
|p| udf_fs.read_file(reader, p),
|
||||
)
|
||||
.ok()
|
||||
.as_deref()
|
||||
.and_then(crate::aacs::inf::parse_content_cert)
|
||||
.map(|c| c.version.major());
|
||||
);
|
||||
if matches!(cert, Err(Error::Halted)) {
|
||||
return Err(Error::Halted);
|
||||
}
|
||||
let cert_major = cert
|
||||
.ok()
|
||||
.as_deref()
|
||||
.and_then(crate::aacs::inf::parse_content_cert)
|
||||
.map(|c| c.version.major());
|
||||
if cert_major.is_none() {
|
||||
tracing::warn!(
|
||||
target: "freemkv::disc",
|
||||
@@ -2274,8 +2283,8 @@ impl Disc {
|
||||
or index.bdmv"
|
||||
);
|
||||
}
|
||||
let index = encrypt::index_is_uhd(udf_fs, reader);
|
||||
crate::aacs::mkb::resolve_aacs_version(cert_major, mkb, index).major()
|
||||
let index = encrypt::index_is_uhd(udf_fs, reader)?;
|
||||
Ok(crate::aacs::mkb::resolve_aacs_version(cert_major, mkb, index).major())
|
||||
}
|
||||
|
||||
// Reads the AACS MKB's real record stream — NOT its ~128 MiB zero padding. Reads a bounded,
|
||||
|
||||
Reference in New Issue
Block a user