Files
libfreemkv/tests/macos_drive_tests.rs
T
Matthew Jackson 4031513574 audit: module-by-module code audit fixes and test coverage
Lands the code-audit branches for aacs, css, keys, labels, udf, ifo and
dvd, bdnav/mpls/clpi, disc/bluray, extract, io, scsi, the mkv, mp4,
network and mpg muxers, the audio and video codecs, and CI. Fixes
include MKB and FMTS validation, UDF bounds, IFO fallback to the BUP,
DTS Express framing, MPEG-2 field pairing and mpg re-basing; the rest
pins existing behaviour with tests.
2026-09-29 20:45:05 -07:00

329 lines
12 KiB
Rust

//! Opt-in checks against an attached macOS optical drive.
//!
//! Run with:
//! `cargo test --test macos_drive_tests -- --ignored --nocapture`
#![cfg(target_os = "macos")]
use libfreemkv::{Drive, DriveStatus, list_drives, scsi::drive_has_disc};
use std::path::Path;
use std::path::PathBuf;
fn drive_name(drive: &libfreemkv::DriveInfo) -> String {
format!("{} {}", drive.vendor.trim(), drive.model.trim())
.trim()
.to_string()
}
/// Exercises the empty-tray path end to end: IOKit enumeration must retain an
/// IOBDServices drive without IOMedia, and the opaque selector must still open
/// that exact service and issue real SCSI commands.
#[test]
#[ignore = "requires an attached optical drive with an empty tray"]
fn empty_tray_drive_is_enumerated_opened_and_reports_no_disc() {
let drives = list_drives();
assert!(!drives.is_empty(), "no optical drive enumerated");
let empty = drives
.iter()
.find(|drive| matches!(drive_has_disc(Path::new(&drive.path)), Ok(false)))
.unwrap_or_else(|| {
panic!(
"no empty optical drive found among {} drive(s)",
drives.len()
)
});
assert!(
empty.path.starts_with("ioreg:"),
"empty-tray path should use its IOKit service selector, got {:?}",
empty.path
);
let mut drive = Drive::open(Path::new(&empty.path)).unwrap_or_else(|e| {
panic!(
"failed to open {} ({}) with empty tray: {e}",
drive_name(empty),
empty.path
)
});
assert_eq!(
drive.drive_id.raw_inquiry.first().map(|byte| byte & 0x1f),
Some(0x05),
"opened service must identify as an MMC optical drive"
);
assert_eq!(
drive.drive_status(),
DriveStatus::NoDisc,
"an empty tray should report NoDisc after a real SCSI status query"
);
// Opening the selected drive through the same session path as the GUI must
// reach the drive and fail because there is no medium, not because the
// optical drive was undiscoverable. Drop the first handle before reopening.
drop(drive);
use libfreemkv::{DeviceTarget, DiscSession, KeySpec, ScanOptions};
let mut session = DiscSession::open(
DeviceTarget::Path(PathBuf::from(&empty.path)),
KeySpec::default(),
)
.unwrap_or_else(|e| panic!("failed to create empty-tray session: {e}"));
assert!(
session.scan(ScanOptions::default()).is_err(),
"scanning an empty tray must fail as no media, not report an empty drive as a disc"
);
}
/// Reads real filesystem/title metadata from a disc, without muxing or saving
/// its content. Run only after inserting a retail Blu-ray or DVD.
#[test]
#[ignore = "requires an inserted Blu-ray/DVD; scan reads disc metadata"]
fn inserted_disc_scans_filesystem_and_finds_titles() {
use libfreemkv::{DeviceTarget, DiscSession, KeySpec, ScanOptions};
let drives = list_drives();
let media_checks: Vec<_> = drives
.iter()
.map(|drive| {
(
drive.path.as_str(),
drive.vendor.as_str(),
drive.model.as_str(),
drive_has_disc(Path::new(&drive.path)),
)
})
.collect();
let drive = drives
.iter()
.find(|drive| matches!(drive_has_disc(Path::new(&drive.path)), Ok(true)))
.unwrap_or_else(|| {
panic!("no optical drive with media detected; probes: {media_checks:?}")
});
println!(
"scanning {} ({}) {}",
drive.path,
drive_name(drive),
if drive.path.starts_with("ioreg:") {
"[IOKit service selector]"
} else {
""
}
);
let mut session = DiscSession::open(
DeviceTarget::Path(PathBuf::from(&drive.path)),
KeySpec::default(),
)
.unwrap_or_else(|e| panic!("failed to open {} ({}): {e}", drive_name(drive), drive.path));
let disc = session.scan(ScanOptions::default()).unwrap_or_else(|e| {
panic!(
"scan failed for {} ({}): {e}",
drive_name(drive),
drive.path
)
});
assert!(
!disc.titles.is_empty(),
"disc scan completed but found no titles on {} ({})",
drive_name(drive),
drive.path
);
}
/// Exercises the autodetect route used by bare `disc://` sources. Keeping this
/// separate from the explicit-path test catches discovery/open mismatches.
#[test]
#[ignore = "requires an inserted Blu-ray/DVD; opens the first detected drive"]
fn inserted_disc_autodetects_and_scans_titles() {
use libfreemkv::{DeviceTarget, DiscSession, KeySpec, ScanOptions};
let mut session = DiscSession::open(DeviceTarget::Autodetect, KeySpec::default())
.unwrap_or_else(|e| panic!("autodetect failed to open the inserted disc: {e}"));
let disc = session
.scan(ScanOptions::default())
.unwrap_or_else(|e| panic!("autodetected drive failed to scan the disc: {e}"));
assert!(
!disc.titles.is_empty(),
"autodetected drive scanned the disc but found no titles"
);
}
/// INQUIRY populates vendor / model / firmware for every enumerated drive.
/// Blank fields would indicate the IOKit walk dropped an INQUIRY-carrying
/// child, which would silently break drive-fingerprint keydb lookups.
#[test]
#[ignore = "requires an attached optical drive"]
fn every_enumerated_drive_reports_vendor_model_firmware() {
let drives = list_drives();
assert!(!drives.is_empty(), "no optical drive enumerated");
for d in &drives {
assert!(!d.vendor.trim().is_empty(), "empty vendor for {}", d.path);
assert!(!d.model.trim().is_empty(), "empty model for {}", d.path);
assert!(
!d.firmware.trim().is_empty(),
"empty firmware for {} ({} {})",
d.path,
d.vendor,
d.model
);
assert!(
!d.path.is_empty(),
"empty selector path for {} {}",
d.vendor,
d.model
);
}
}
/// Opening the same drive twice back-to-back must succeed. Catches leaked
/// exclusive-open state in the IOKit session path — a regression would
/// surface as "device busy" on the second open.
#[test]
#[ignore = "requires an attached optical drive"]
fn drive_can_be_reopened_after_close() {
let drives = list_drives();
let target = drives.first().expect("no optical drive enumerated");
for i in 0..3 {
let drive = Drive::open(Path::new(&target.path)).unwrap_or_else(|e| {
panic!(
"iter {i}: failed to reopen {} ({}): {e}",
drive_name(target),
target.path
)
});
drop(drive);
}
}
/// UHD disc scan populates the AACS 2.0 shape we advertise on the UI:
/// bus_encryption true, MKB version present, disc_hash populated, VID set,
/// at least one CPS unit key. Verifies the scan pipeline end-to-end for
/// the exact disc type most likely to regress.
#[test]
#[ignore = "requires an inserted UHD Blu-ray; scans AACS 2.0 disc metadata"]
fn inserted_uhd_disc_reports_full_aacs2_state() {
use libfreemkv::disc::DiscFormat;
use libfreemkv::{DeviceTarget, DiscSession, KeySpec, ScanOptions};
let mut session = DiscSession::open(DeviceTarget::Autodetect, KeySpec::default())
.unwrap_or_else(|e| panic!("autodetect failed to open the inserted disc: {e}"));
let disc = session
.scan(ScanOptions::default())
.unwrap_or_else(|e| panic!("scan failed: {e}"));
assert!(
matches!(disc.format, DiscFormat::Uhd),
"expected UHD DiscFormat, got {:?}",
disc.format
);
assert!(
disc.capacity_bytes >= 40_000_000_000,
"UHD capacity should be ≥ 40 GB, got {} bytes",
disc.capacity_bytes
);
assert!(
disc.layers >= 2,
"UHD disc should report ≥ 2 layers, got {}",
disc.layers
);
let aacs = disc.aacs.as_ref().expect("UHD disc must have AacsState");
assert_eq!(aacs.version, 2, "UHD disc must be AACS 2.0");
assert!(aacs.bus_encryption, "UHD disc must set bus_encryption");
assert!(
aacs.mkb_version.is_some_and(|v| v > 0),
"UHD disc must expose MKB version, got {:?}",
aacs.mkb_version
);
assert!(
aacs.disc_hash.starts_with("0x") && aacs.disc_hash.len() == 42,
"disc_hash should be 0x-prefixed 40 hex chars, got {:?}",
aacs.disc_hash
);
assert_ne!(
aacs.volume_id, [0u8; 16],
"VID must be populated after a successful AACS 2.0 handshake"
);
}
/// Scan is idempotent: rescanning the same session yields the same disc
/// hash and MKB version. Guards against stateful drift in the scan path.
#[test]
#[ignore = "requires an inserted Blu-ray/DVD; rescans twice"]
fn rescan_returns_stable_disc_identity() {
use libfreemkv::{DeviceTarget, DiscSession, KeySpec, ScanOptions};
let mut session = DiscSession::open(DeviceTarget::Autodetect, KeySpec::default())
.unwrap_or_else(|e| panic!("open failed: {e}"));
let first_snapshot = {
let first = session
.scan(ScanOptions::default())
.unwrap_or_else(|e| panic!("first scan failed: {e}"));
first
.aacs
.as_ref()
.map(|a| (a.disc_hash.clone(), a.mkb_version, a.volume_id))
};
let second_snapshot = {
let second = session
.scan(ScanOptions::default())
.unwrap_or_else(|e| panic!("second scan failed: {e}"));
second
.aacs
.as_ref()
.map(|a| (a.disc_hash.clone(), a.mkb_version, a.volume_id))
};
assert_eq!(
first_snapshot.is_some(),
second_snapshot.is_some(),
"AACS state appeared or vanished across rescans"
);
let (Some(a), Some(b)) = (first_snapshot, second_snapshot) else {
return; // clear/DVD disc: no AACS state to compare
};
assert_eq!(a.0, b.0, "disc_hash drifted across rescans");
assert_eq!(a.1, b.1, "mkb_version drifted across rescans");
assert_eq!(a.2, b.2, "VID drifted across rescans");
}
/// `drive_has_disc` matches what `DiscSession::open + scan` actually finds.
/// Every drive returning `Ok(true)` must be scannable; every `Ok(false)`
/// must fail to scan. Catches enumeration/probe divergence.
#[test]
#[ignore = "requires an attached optical drive; probes every enumerated drive"]
fn drive_has_disc_matches_scan_outcome() {
use libfreemkv::{DeviceTarget, DiscSession, KeySpec, ScanOptions};
let drives = list_drives();
assert!(!drives.is_empty(), "no optical drive enumerated");
for d in &drives {
match drive_has_disc(Path::new(&d.path)) {
Ok(true) => {
let mut session = DiscSession::open(
DeviceTarget::Path(PathBuf::from(&d.path)),
KeySpec::default(),
)
.unwrap_or_else(|e| panic!("open failed for {}: {e}", d.path));
let _ = session
.scan(ScanOptions::default())
.unwrap_or_else(|e| panic!("scan failed on drive_has_disc=true: {e}"));
}
Ok(false) => {
let mut session = DiscSession::open(
DeviceTarget::Path(PathBuf::from(&d.path)),
KeySpec::default(),
)
.unwrap_or_else(|e| panic!("open failed for empty tray {}: {e}", d.path));
assert!(
session.scan(ScanOptions::default()).is_err(),
"scan unexpectedly succeeded on drive_has_disc=false: {}",
d.path
);
}
Err(e) => panic!("drive_has_disc errored on {}: {e}", d.path),
}
}
}