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`
|
// `Some(true)` for an `INDX0300` index.bdmv, `Some(false)` for another INDX version, `None`
|
||||||
// when there is no readable index (HD DVD, damage).
|
// 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) -> Option<bool> {
|
pub(super) fn index_is_uhd(
|
||||||
let index = udf_fs
|
udf_fs: &udf::UdfFs,
|
||||||
.read_file_prefix(reader, "/BDMV/index.bdmv", 8)
|
reader: &mut dyn SectorSource,
|
||||||
.ok()?;
|
) -> Result<Option<bool>> {
|
||||||
let version = index.get(..8)?.strip_prefix(b"INDX")?;
|
let index = match udf_fs.read_file_prefix(reader, "/BDMV/index.bdmv", 8) {
|
||||||
Some(version == b"0300")
|
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"
|
// Positive UHD evidence independent of the cert: index.bdmv version "0300"
|
||||||
// (BD-ROM Part 3) or an AACS2 MKB. Unknown is `false`.
|
// (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) -> bool {
|
fn disc_is_uhd(udf_fs: &udf::UdfFs, reader: &mut dyn SectorSource) -> Result<bool> {
|
||||||
use crate::aacs::mkb::{AacsVersion, mkb_type};
|
use crate::aacs::mkb::{AacsVersion, mkb_type};
|
||||||
if index_is_uhd(udf_fs, reader) == Some(true) {
|
if index_is_uhd(udf_fs, reader)? == Some(true) {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
udf_fs
|
let mkb = match udf_fs.read_file_prefix(reader, crate::aacs::PATH_MKB_RO, 64) {
|
||||||
.read_file_prefix(reader, crate::aacs::PATH_MKB_RO, 64)
|
Ok(mkb) => mkb,
|
||||||
.ok()
|
Err(Error::Halted) => return Err(Error::Halted),
|
||||||
.and_then(|m| mkb_type(&m).map(|t| t.generation()))
|
Err(_) => return Ok(false),
|
||||||
.is_some_and(|g| matches!(g, AacsVersion::V20 | AacsVersion::V21))
|
};
|
||||||
|
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.
|
/// 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.
|
// Fail safe: an unparseable cert, or none on a live disc known to be UHD, may mean bus encryption.
|
||||||
let bus_encryption = match &cc {
|
let bus_encryption = match &cc {
|
||||||
Some(c) => c.bus_encryption,
|
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).
|
// The bounded MKB reader, not `read_file` (the ~128 MiB padded MKB would fail).
|
||||||
let mkb = match Disc::read_mkb_content(reader, udf_fs) {
|
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),
|
// Stride version through the shared resolver (cert, then MKB type, then index.bdmv),
|
||||||
// the same one `read_aacs_version` uses.
|
// 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 =
|
let version =
|
||||||
aacs::mkb::resolve_aacs_version(cc.as_ref().map(|c| c.version.major()), &mkb, index)
|
aacs::mkb::resolve_aacs_version(cc.as_ref().map(|c| c.version.major()), &mkb, index)
|
||||||
.major();
|
.major();
|
||||||
@@ -774,6 +782,44 @@ mod tests {
|
|||||||
state.ok_or_else(|| err.unwrap_or(Error::AacsNoKeys))
|
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
|
/// Missing Unit_Key_RO.inf (and its DUPLICATE) → Error::AacsNoKeys
|
||||||
/// (encrypt.rs `.map_err(|_| Error::AacsNoKeys)`). Never panics.
|
/// (encrypt.rs `.map_err(|_| Error::AacsNoKeys)`). Never panics.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+20
-11
@@ -2251,21 +2251,30 @@ impl Disc {
|
|||||||
|p| udf_fs.read_file(reader, p),
|
|p| udf_fs.read_file(reader, p),
|
||||||
)?;
|
)?;
|
||||||
let mkb = Self::read_mkb_content(reader, udf_fs)?;
|
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))
|
Ok((inf, mkb, version))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AACS major version driving the Unit_Key_RO.inf parse stride: the content certificate,
|
// 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`.
|
// then the MKB type, then index.bdmv, through the shared `resolve_aacs_version`. `Err` only
|
||||||
fn read_aacs_version(reader: &mut dyn SectorSource, udf_fs: &udf::UdfFs, mkb: &[u8]) -> u8 {
|
// for a Stop.
|
||||||
let cert_major = crate::aacs::read_first(
|
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),
|
&crate::aacs::role_paths(udf_fs, crate::aacs::AacsRole::ContentCert),
|
||||||
|p| udf_fs.read_file(reader, p),
|
|p| udf_fs.read_file(reader, p),
|
||||||
)
|
);
|
||||||
.ok()
|
if matches!(cert, Err(Error::Halted)) {
|
||||||
.as_deref()
|
return Err(Error::Halted);
|
||||||
.and_then(crate::aacs::inf::parse_content_cert)
|
}
|
||||||
.map(|c| c.version.major());
|
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() {
|
if cert_major.is_none() {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
target: "freemkv::disc",
|
target: "freemkv::disc",
|
||||||
@@ -2274,8 +2283,8 @@ impl Disc {
|
|||||||
or index.bdmv"
|
or index.bdmv"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let index = encrypt::index_is_uhd(udf_fs, reader);
|
let index = encrypt::index_is_uhd(udf_fs, reader)?;
|
||||||
crate::aacs::mkb::resolve_aacs_version(cert_major, mkb, index).major()
|
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,
|
// Reads the AACS MKB's real record stream — NOT its ~128 MiB zero padding. Reads a bounded,
|
||||||
|
|||||||
Reference in New Issue
Block a user