mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
Merge codeaudit/testutil (0e7e458)
This commit is contained in:
+26
-6
@@ -121,8 +121,8 @@ impl AacsStateBuilder {
|
||||
}
|
||||
|
||||
/// One stream file of an [`encrypted_bd_image`]: its path under the image root
|
||||
/// (e.g. `"BDMV/STREAM/00001.m2ts"`), its length in sectors (a multiple of 3 for
|
||||
/// whole aligned units), and the CPS unit key its units are encrypted with
|
||||
/// (e.g. `"BDMV/STREAM/00001.m2ts"`), its length in sectors (rounded up to a whole
|
||||
/// number of aligned units, the tail padded), and the CPS unit key its units are encrypted with
|
||||
/// (`None`: clear content, every Copy_permission_indicator `00₂`).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BdFile {
|
||||
@@ -200,12 +200,16 @@ impl SectorSource for MemSource {
|
||||
/// keyed file's units carry CPI `11₂` in every packet and are then encrypted
|
||||
/// (KS-3, KS-4). Panics on a fixture error; test use only.
|
||||
pub fn encrypted_bd_image(files: &[BdFile], uk_ro: &[u8]) -> EncryptedBdImage {
|
||||
let root = fixture_dir();
|
||||
let dir = FixtureDir(fixture_dir());
|
||||
let root = dir.0.clone();
|
||||
write(&root.join("AACS/Unit_Key_RO.inf"), uk_ro);
|
||||
let unit_sectors = (ALIGNED_UNIT_LEN / SECTOR_BYTES) as u32;
|
||||
for f in files {
|
||||
// A partial final unit is padded out to a whole aligned unit.
|
||||
let sectors = f.sectors.div_ceil(unit_sectors) * unit_sectors;
|
||||
write(
|
||||
&root.join(&f.path),
|
||||
&vec![0u8; f.sectors as usize * SECTOR_BYTES],
|
||||
&vec![0u8; sectors as usize * SECTOR_BYTES],
|
||||
);
|
||||
}
|
||||
let mut img = crate::DirImage::open(&root).expect("fixture DirImage");
|
||||
@@ -226,10 +230,10 @@ pub fn encrypted_bd_image(files: &[BdFile], uk_ro: &[u8]) -> EncryptedBdImage {
|
||||
.expect("fixture read");
|
||||
}
|
||||
drop(img);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
drop(dir);
|
||||
let mut plain = image.clone();
|
||||
let unit_sectors = (ALIGNED_UNIT_LEN / SECTOR_BYTES) as u32;
|
||||
for (f, &(start, sectors)) in files.iter().zip(&extents) {
|
||||
debug_assert_eq!(sectors % unit_sectors, 0, "{}: whole aligned units", f.path);
|
||||
for u in 0..sectors / unit_sectors {
|
||||
let lba = start + u * unit_sectors;
|
||||
let mut unit = content_unit(lba, f.key.is_some());
|
||||
@@ -261,6 +265,21 @@ fn content_unit(lba: u32, encrypted: bool) -> Vec<u8> {
|
||||
u
|
||||
}
|
||||
|
||||
// Removes its directory on drop, so a panicking fixture build doesn't leak it.
|
||||
struct FixtureDir(std::path::PathBuf);
|
||||
|
||||
impl Drop for FixtureDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
// The most recent fixture dir made on this thread, so tests can observe it.
|
||||
static LAST_FIXTURE_DIR: std::cell::RefCell<Option<std::path::PathBuf>> =
|
||||
const { std::cell::RefCell::new(None) };
|
||||
}
|
||||
|
||||
// A fresh, empty directory for one fixture (removed once the image is read).
|
||||
fn fixture_dir() -> std::path::PathBuf {
|
||||
static NEXT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
@@ -268,6 +287,7 @@ fn fixture_dir() -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!("libfreemkv-test-util-{}-{n}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).expect("fixture dir");
|
||||
LAST_FIXTURE_DIR.with(|d| *d.borrow_mut() = Some(dir.clone()));
|
||||
dir
|
||||
}
|
||||
|
||||
|
||||
+20
-5
@@ -310,10 +310,11 @@ impl FakeTransport {
|
||||
}
|
||||
|
||||
// Block until released (a new `release` generation) or `limit` passes.
|
||||
fn stall(&self, limit: Duration) -> bool {
|
||||
// `gen0` is the release generation read when the CDB was logged, so a release
|
||||
// between the log and this wait still counts.
|
||||
fn stall(&self, limit: Duration, gen0: u64) -> bool {
|
||||
let end = Instant::now() + limit;
|
||||
let mut st = self.shared.lock();
|
||||
let gen0 = st.released;
|
||||
while st.released == gen0 {
|
||||
let left = end.saturating_duration_since(Instant::now());
|
||||
if left.is_zero() {
|
||||
@@ -339,7 +340,7 @@ impl ScsiTransport for FakeTransport {
|
||||
timeout_ms: u32,
|
||||
) -> Result<ScsiResult> {
|
||||
self.last_progress = None;
|
||||
let (mode, limit) = {
|
||||
let (mode, limit, gen0) = {
|
||||
let mut st = self.shared.lock();
|
||||
let after_cancel = st.watch.as_ref().is_some_and(Halt::is_cancelled);
|
||||
st.log.push(FakeCdb {
|
||||
@@ -369,7 +370,7 @@ impl ScsiTransport for FakeTransport {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(mode, limit)
|
||||
(mode, limit, st.released)
|
||||
};
|
||||
let r = match mode {
|
||||
None => self.serve(cdb, dir, data, timeout_ms),
|
||||
@@ -381,7 +382,7 @@ impl ScsiTransport for FakeTransport {
|
||||
std::thread::sleep(d);
|
||||
self.serve(cdb, dir, data, timeout_ms)
|
||||
}
|
||||
Some(FakeMode::Stall) => match self.stall(limit) {
|
||||
Some(FakeMode::Stall) => match self.stall(limit, gen0) {
|
||||
true => self.serve(cdb, dir, data, timeout_ms),
|
||||
false => Err(transport_failure(cdb[0])),
|
||||
},
|
||||
@@ -420,3 +421,17 @@ fn note_ledger(l: &mut Ledger, cdb: &[u8], resp: &[u8], ok: bool) {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod stall_tests {
|
||||
use super::*;
|
||||
|
||||
// A release after the generation was read must end the stall even with a zero limit.
|
||||
#[test]
|
||||
fn stall_returns_true_when_released_after_gen_read() {
|
||||
let (t, h) = FakeTransport::new();
|
||||
let g = h.0.lock().released;
|
||||
h.release();
|
||||
assert!(t.stall(Duration::ZERO, g));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,3 +156,34 @@ fn decrypt_unit_inverts_encrypt_unit() {
|
||||
crate::test_util::decrypt_unit(&mut u, &K1);
|
||||
assert_eq!(u, plain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_dir_removed_when_build_panics() {
|
||||
let bad = [BdFile {
|
||||
path: "AACS/Unit_Key_RO.inf/x".into(),
|
||||
sectors: 3,
|
||||
key: None,
|
||||
}];
|
||||
let r = std::panic::catch_unwind(|| encrypted_bd_image(&bad, &[0u8; 4]));
|
||||
assert!(r.is_err());
|
||||
let dir = crate::test_util::LAST_FIXTURE_DIR
|
||||
.with(|d| d.borrow().clone())
|
||||
.expect("dir");
|
||||
assert!(!dir.exists(), "fixture dir leaked: {}", dir.display());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypted_bd_image_pads_a_partial_final_unit() {
|
||||
let inf = unit_key_ro(AacsVersion::V10, &[[0u8; 16]; 1], &[1]);
|
||||
let img = encrypted_bd_image(&[BdFile::new("BDMV/STREAM/00001.m2ts", 7, Some(K1))], &inf);
|
||||
let (start, sectors) = img.files[0];
|
||||
assert_eq!(sectors, 9, "7 sectors round up to 3 whole units");
|
||||
for u in 0..3 {
|
||||
let lba = start + u * 3;
|
||||
assert!(
|
||||
aacs_unit_encrypted(&unit(&img.image, lba), ContentFormat::BdTs),
|
||||
"unit {u}"
|
||||
);
|
||||
assert_ne!(unit(&img.image, lba), unit(&img.plain, lba), "unit {u}");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -122,7 +122,7 @@ fn install() {
|
||||
}
|
||||
|
||||
// Run `f` with every `tracing` event it emits on this thread captured; returns `f`'s value
|
||||
// alongside the events, in emission order. One global subscriber, not scoped `with_default`
|
||||
// alongside the events, in emission order. One global subscriber (see `install`), not scoped `with_default`.
|
||||
pub(crate) fn capture<T>(f: impl FnOnce() -> T) -> (T, Vec<CapturedEvent>) {
|
||||
install();
|
||||
let sink: Sink = Arc::default();
|
||||
@@ -134,7 +134,6 @@ pub(crate) fn capture<T>(f: impl FnOnce() -> T) -> (T, Vec<CapturedEvent>) {
|
||||
(out, events)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user