diff --git a/src/aacs/host_certs.rs b/src/aacs/host_certs.rs index 18233dd..3e11adf 100644 --- a/src/aacs/host_certs.rs +++ b/src/aacs/host_certs.rs @@ -10,8 +10,8 @@ pub fn collect_host_certs( opts: &crate::disc::ScanOptions, mkb: Option, -) -> Vec { - let mut host_certs: Vec = Vec::new(); +) -> Vec { + let mut host_certs: Vec = Vec::new(); if let Some(c) = &opts.credentials { host_certs.extend(c.host_certs.iter().cloned()); } diff --git a/src/aacs/mod.rs b/src/aacs/mod.rs index 655a391..991262b 100644 --- a/src/aacs/mod.rs +++ b/src/aacs/mod.rs @@ -50,62 +50,21 @@ pub const PATH_MKB_RW: &str = "/AACS/MKB_RW.inf"; pub const PATH_CONTENT_CERT: &str = "/AACS/Content000.cer"; pub const PATH_CONTENT_CERT_ALT: &str = "/AACS/Content001.cer"; -// Boil-down derivation primitives (thin newtypes + wrappers over the crypto). -pub use boil::{ - KeyCandidate, MediaKey, ProcessingKey, ResolvedChain, UnitKey, Vid, Vuk, mk_from_dk, - mk_from_pk, resolve_candidate, uk_from_vuk, vuk_from_mk, -}; -// Structured, English-free resolution trace. -pub use trace::{KeyNode, KeyOutcome, KeyStep, ResolutionTrace, UnlockOutcome, UnlockStep}; - -// Explicit re-exports — only items needed by external consumers and sibling crate modules. -// AES primitives (aes_ecb_encrypt, aes_ecb_decrypt, aes_cbc_decrypt) are pub(crate) in decrypt.rs. -pub use content::{ - ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, UnitKeyResult, aacs_unit_encrypted, - aacs_unit_needs_decrypt, aacs_unit_still_ciphertext, decrypt_bus, decrypt_unit, - decrypt_unit_checked, decrypt_unit_full, decrypt_unit_try_keys, fill_null_ts_unit, - is_unit_aligned, ts_packet_total, ts_sync_count, ts_sync_destroyed, unit_is_clean_ps, - unit_is_clean_ts, unit_key_validates, -}; -// `probe` is a reproduction-harness helper (see keys.rs), not part of the -// documented 1.0 surface; keep it reachable but off the rendered docs so we -// don't commit semver stability to test primitives. -#[doc(hidden)] -pub use derive::probe; -pub use derive::{decrypt_unit_key, derive_vuk}; -pub use derive::{ - derive_media_key_and_pk_from_dk, derive_media_key_from_dk, derive_media_key_from_pk, - recover_dk_position, -}; -pub use inf::{ - ContentCert, UnitKeyFile, disc_hash, disc_hash_hex, parse_content_cert, parse_unit_key_ro, - read_mkb_from_drive, -}; -pub use mkb::{ - AACS_MAJOR_BD, AACS_MAJOR_UHD, AacsVersion, MKB_20_CATEGORY_C, MKB_21_CATEGORY_C, - MKB_TYPE_3_RECORDABLE, MKB_TYPE_4_PRERECORDED, MKB_TYPE_10_CLASS_II, MkbRecord, MkbType, - mkb_content_len, mkb_is_uhd, mkb_type, mkb_type_raw, mkb_version, trim_mkb, walk_mkb, -}; -pub use provider::KeyProvider; -pub use resolve::{ - ResolveContext, ResolveFailure, ResolvedKeys, resolve_keys_v1, resolve_keys_v2, - resolve_keys_v21, resolve_keys_with_reason, -}; -pub use types::{DeviceKey, DiscEntry, HostCert}; -pub use variant::{ - KEY_CORRECTION_DATA_PLACEHOLDER, MediaKeyVariantError, ProcessingKeyMatch, - derive_media_key_variant, is_variant_mkb, variant_nonce, walk_processing_key, -}; +// No facade: the module structure IS the public API. Consumers import from the +// owning module directly — e.g. `aacs::content::decrypt_unit`, `aacs::mkb::MkbType`, +// `aacs::derive::derive_vuk`, `aacs::boil::mk_from_dk`, `aacs::resolve::resolve_keys_v2`. +// The `derive::probe` reproduction harness stays reachable via its module path. #[cfg(test)] mod tests { - //! Re-export surface guards. The module's public API is the set of - //! `pub use` items above. A regression that drops or renames an export - //! (the class of bug that shipped in 0.31.0 by silently changing a - //! surface) breaks compilation of these references, so they act as a - //! compile-time contract for the crate's AACS surface. + //! Surface guards. The public API is the module tree itself (no facade). + //! Touching one representative item per module keeps these as a + //! compile-time contract that the module paths stay stable. - use super::*; + use super::content::{ALIGNED_UNIT_LEN, ts_sync_destroyed}; + use super::inf::{disc_hash, disc_hash_hex}; + use super::mkb::{AacsVersion, mkb_content_len, walk_mkb}; + use super::variant::{KEY_CORRECTION_DATA_PLACEHOLDER, is_variant_mkb}; #[test] fn aligned_unit_len_is_three_2048_byte_sectors() { @@ -134,14 +93,13 @@ mod tests { } #[test] - fn public_helpers_are_callable_through_the_facade() { - // Touch a representative function from each re-export group so a - // dropped/renamed export fails to compile. These are smoke calls, not - // behavioural assertions (behaviour is covered in each module). + fn public_helpers_are_callable_by_module_path() { + // Touch a representative function from each module so a dropped/renamed + // item fails to compile. Smoke calls, not behavioural assertions. let _ = ts_sync_destroyed(&[0u8; ALIGNED_UNIT_LEN]); let _ = mkb_content_len(&[]); let _ = is_variant_mkb(&walk_mkb(&[])); let _ = disc_hash_hex(&disc_hash(b"x")); - let _ = mk_from_pk(&[[0u8; 16]], &[]); + let _ = super::boil::mk_from_pk(&[[0u8; 16]], &[]); } } diff --git a/src/aacs/resolve.rs b/src/aacs/resolve.rs index 87cc667..2ea1ec7 100644 --- a/src/aacs/resolve.rs +++ b/src/aacs/resolve.rs @@ -1068,7 +1068,7 @@ mod tests { disc_entry: None, }; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1111,7 +1111,7 @@ mod tests { }; let vid = [0u8; 16]; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1153,7 +1153,7 @@ mod tests { }; let vid = [0u8; 16]; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1193,7 +1193,7 @@ mod tests { }; let vid = [0u8; 16]; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1240,7 +1240,7 @@ mod tests { media_keys: vec![km], disc_entry: None, }; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1627,7 +1627,7 @@ mod tests { mkb.extend_from_slice(&[0x2f, 0x00, 0x00, 0x14]); mkb.extend_from_slice(&[0x55; 16]); - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1663,7 +1663,7 @@ mod tests { }), }; let mkb = vec![0x10, 0x00, 0x00, 0x08, 0, 0, 0, 1]; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1697,7 +1697,7 @@ mod tests { let mut cc = vec![0u8; 20]; cc[0] = 0x10; cc[1] = 0x80; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: Some(&cc), @@ -1731,7 +1731,7 @@ mod tests { unit_keys: Vec::new(), }), }; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &data, content_cert: None, @@ -1769,7 +1769,7 @@ mod tests { unit_keys: Vec::new(), }), }; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1786,7 +1786,7 @@ mod tests { fn resolve_keys_returns_none_when_no_provider_has_anything() { // Empty provider array + VID present + no MKB → all paths miss → None. let uk_ro = minimal_unit_key_ro(); - let providers: &[&dyn super::super::KeyProvider] = &[]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, @@ -1969,7 +1969,7 @@ mod tests { #[test] fn classify_processing_keys_zero_vid_is_vid_unavailable() { let prov = material_provider(Vec::new(), vec![[0u8; 16]]); - let providers: &[&dyn super::super::KeyProvider] = &[&prov]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&prov]; let uk_ro = minimal_unit_key_ro(); let ctx = ResolveContext { unit_key_ro: &uk_ro, @@ -1994,7 +1994,7 @@ mod tests { #[test] fn classify_vid_present_with_material_is_no_material_not_vid() { let prov = material_provider(vec![one_device_key()], vec![[0u8; 16]]); - let providers: &[&dyn super::super::KeyProvider] = &[&prov]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&prov]; let uk_ro = minimal_unit_key_ro(); let ctx = ResolveContext { unit_key_ro: &uk_ro, @@ -2017,7 +2017,7 @@ mod tests { #[test] fn classify_vid_present_no_material_is_no_material() { let prov = material_provider(Vec::new(), Vec::new()); - let providers: &[&dyn super::super::KeyProvider] = &[&prov]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&prov]; let uk_ro = minimal_unit_key_ro(); let ctx = ResolveContext { unit_key_ro: &uk_ro, @@ -2055,7 +2055,7 @@ mod tests { unit_keys: Vec::new(), }), }; - let providers: &[&dyn super::super::KeyProvider] = &[&keydb]; + let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb]; let ctx = ResolveContext { unit_key_ro: &uk_ro, content_cert: None, diff --git a/src/decrypt.rs b/src/decrypt.rs index 490272f..d26f133 100644 --- a/src/decrypt.rs +++ b/src/decrypt.rs @@ -244,7 +244,7 @@ fn decrypt_sectors_impl( // Strip CPS-unit IDs — the decrypt primitives only want the raw key bytes. let raw_keys: Vec<[u8; 16]> = unit_keys.iter().map(|(_, k)| *k).collect(); let rdk: Option<[u8; 16]> = *read_data_key; - let unit_len = aacs::ALIGNED_UNIT_LEN; + let unit_len = aacs::content::ALIGNED_UNIT_LEN; // AACS decrypts whole 6144-byte aligned units. The live mux path // (mux/disc.rs::fill_extents) issues 1- or 2-sector reads at every // extent tail, so a buffer is commonly NOT a multiple of the unit @@ -288,8 +288,8 @@ fn decrypt_sectors_impl( }; if partial_in_content { let partial = &buf[buf.len() - partial_len..]; - let packets = aacs::ts_packet_total(partial); - if packets > 0 && aacs::ts_sync_count(partial) <= packets / 2 { + let packets = aacs::content::ts_packet_total(partial); + if packets > 0 && aacs::content::ts_sync_count(partial) <= packets / 2 { return Err(crate::error::Error::DecryptFailed); } } @@ -326,7 +326,7 @@ fn decrypt_sectors_impl( // must happen first — it's a shared layer on top that is key-independent // across all CPS units on the disc. let decrypt_one = |chunk: &mut [u8]| { - if chunk.len() != unit_len || !aacs::aacs_unit_needs_decrypt(chunk) { + if chunk.len() != unit_len || !aacs::content::aacs_unit_needs_decrypt(chunk) { return; } // Save original bytes so we can restore if no key validates. @@ -335,7 +335,7 @@ fn decrypt_sectors_impl( // Build a bus-decrypted copy to try unit keys against, or work // in-place when there is no bus layer. if let Some(ref rdk_key) = rdk { - aacs::decrypt_bus(chunk, rdk_key); + aacs::content::decrypt_bus(chunk, rdk_key); } // Reorder the key iterator: try the cached hint first, then fall @@ -349,7 +349,7 @@ fn decrypt_sectors_impl( // Work on a per-key copy so a failing attempt doesn't // clobber the bus-decrypted base we'll retry on. let mut attempt: Vec = chunk.to_vec(); - if aacs::decrypt_unit(&mut attempt, key) { + if aacs::content::decrypt_unit(&mut attempt, key) { chunk.copy_from_slice(&attempt); last_key_idx.store(idx, Ordering::Relaxed); return; @@ -478,7 +478,7 @@ mod tests { /// not left scrambled. #[test] fn nav_file_unit_survives_decrypt_attempt() { - let mut unit = vec![0u8; aacs::ALIGNED_UNIT_LEN]; + let mut unit = vec![0u8; aacs::content::ALIGNED_UNIT_LEN]; unit[0] = b'M'; unit[1] = b'P'; unit[2] = b'L'; @@ -528,7 +528,7 @@ mod tests { let mut u = 0; while u < len { v[u] |= 0xC0; - u += aacs::ALIGNED_UNIT_LEN; + u += aacs::content::ALIGNED_UNIT_LEN; } v } @@ -561,7 +561,7 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let original = scrambled_region(aacs::ALIGNED_UNIT_LEN); + let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN); // base_lba 0, content = [(100,10)] ⇒ the unit at LBA 0 is OUTSIDE content. let mut buf = original.clone(); @@ -581,7 +581,7 @@ mod tests { decrypt_sectors_in_content(&mut buf2, &mut keys, 0, 100, &[(100, 10)]).unwrap(); assert_eq!( dropped2, - aacs::ALIGNED_UNIT_LEN, + aacs::content::ALIGNED_UNIT_LEN, "an undecryptable CONTENT unit IS counted as loss" ); } @@ -594,12 +594,12 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let mut buf = scrambled_region(2 * aacs::ALIGNED_UNIT_LEN); + let mut buf = scrambled_region(2 * aacs::content::ALIGNED_UNIT_LEN); // unit0 @ LBA 0 (clear/skip), unit1 @ LBA 3 (content). Content = [(3,3)]. let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(3, 3)]).unwrap(); assert_eq!( dropped, - aacs::ALIGNED_UNIT_LEN, + aacs::content::ALIGNED_UNIT_LEN, "only the in-content unit (unit1) is checked; clear unit0 is skipped" ); } @@ -613,7 +613,7 @@ mod tests { read_data_key: None, }; let mut keys_u = keys_g.clone(); - let original = scrambled_region(aacs::ALIGNED_UNIT_LEN); + let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN); let mut g = original.clone(); let mut u = original.clone(); let gated = decrypt_sectors_in_content(&mut g, &mut keys_g, 0, 0, &[(0, 3)]).unwrap(); @@ -662,7 +662,7 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let original = scrambled_region(aacs::ALIGNED_UNIT_LEN); + let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN); let mut buf = original.clone(); let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[]).unwrap(); assert_eq!( @@ -680,7 +680,7 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let original = clear_ts_region(aacs::ALIGNED_UNIT_LEN); + let original = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN); let mut buf = original.clone(); let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(0, 3)]).unwrap(); assert_eq!(dropped, 0, "a clear in-content unit is not ciphertext"); @@ -691,7 +691,7 @@ mod tests { #[test] fn content_gate_none_keys_is_noop() { let mut keys = DecryptKeys::None; - let original = scrambled_region(aacs::ALIGNED_UNIT_LEN); + let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN); let mut buf = original.clone(); let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(0, 3)]).unwrap(); assert_eq!(dropped, 0); @@ -721,7 +721,7 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let u = aacs::ALIGNED_UNIT_LEN; + let u = aacs::content::ALIGNED_UNIT_LEN; let mut buf = vec![0u8; 3 * u]; buf[..u].copy_from_slice(&scrambled_region(u)); // unit0 @ LBA0 scrambled buf[u..2 * u].copy_from_slice(&clear_ts_region(u)); // unit1 @ LBA3 clear @@ -738,10 +738,14 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let mut buf = scrambled_region(2 * aacs::ALIGNED_UNIT_LEN); + let mut buf = scrambled_region(2 * aacs::content::ALIGNED_UNIT_LEN); // unit0 @ LBA0 content, unit1 @ LBA3 out. Content = [(0,3)]. let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(0, 3)]).unwrap(); - assert_eq!(dropped, aacs::ALIGNED_UNIT_LEN, "only unit0 counts"); + assert_eq!( + dropped, + aacs::content::ALIGNED_UNIT_LEN, + "only unit0 counts" + ); } /// The trailing-partial reject is ALSO content-gated: a scrambled partial @@ -755,7 +759,7 @@ mod tests { }; // One full clear unit + a scrambled single-sector partial, all OUTSIDE // content → the partial must be tolerated (Ok), not DecryptFailed. - let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN); + let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN); buf.extend_from_slice(&scrambled_region(2048)); // content far away → both the full unit and the partial are non-content. let res = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(1000, 3)]); @@ -776,7 +780,7 @@ mod tests { read_data_key: None, }; // One full scrambled unit + a 2048-byte (single-sector) CLEAR tail. - let unit = scrambled_region(aacs::ALIGNED_UNIT_LEN); + let unit = scrambled_region(aacs::content::ALIGNED_UNIT_LEN); let tail = clear_ts_region(2048); let mut buf = unit; buf.extend_from_slice(&tail); @@ -784,7 +788,7 @@ mod tests { decrypt_sectors(&mut buf, &mut keys, 0).expect("clear trailing partial is Ok"); assert_eq!( - &buf[aacs::ALIGNED_UNIT_LEN..], + &buf[aacs::content::ALIGNED_UNIT_LEN..], &tail[..], "clear trailing partial unit must be left unchanged" ); @@ -801,7 +805,7 @@ mod tests { read_data_key: None, }; // One full unit + a 4096-byte (two-sector) SCRAMBLED tail. - let unit = clear_ts_region(aacs::ALIGNED_UNIT_LEN); + let unit = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN); let tail = scrambled_region(4096); let mut buf = unit; buf.extend_from_slice(&tail); @@ -835,7 +839,7 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN * 2); + let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN * 2); let snapshot = buf.clone(); decrypt_sectors(&mut buf, &mut keys, 0).expect("exact-multiple buffer is Ok"); @@ -1092,7 +1096,7 @@ mod tests { unit_keys: vec![(0, [0xAB; 16])], read_data_key: None, }; - let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN); + let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN); let err = decrypt_sectors(&mut buf, &mut keys, 5) .expect_err("unit_key_idx 5 is out of range for a 1-key list"); assert_eq!( @@ -1112,7 +1116,7 @@ mod tests { unit_keys: vec![], read_data_key: None, }; - let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN); + let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN); let err = decrypt_sectors(&mut buf, &mut keys, 0).expect_err("empty unit_keys must error"); assert_eq!(err.code(), crate::error::Error::DecryptFailed.code()); } @@ -1120,7 +1124,7 @@ mod tests { // ── Multi-CPS-unit key selection ────────────────────────────────────── /// Encrypt an aligned unit with the AACS algorithm run in reverse so that - /// `aacs::decrypt_unit` with the same key recovers the plaintext. Mirrors + /// `aacs::content::decrypt_unit` with the same key recovers the plaintext. Mirrors /// the `aacs_encrypt_unit` helper in `aacs::content::tests`. fn aacs_encrypt_unit_for_test(unit: &mut [u8], unit_key: &[u8; 16]) { use aes::Aes128; @@ -1136,7 +1140,7 @@ mod tests { } let cipher = Aes128::new(GenericArray::from_slice(&k)); let mut prev = crate::aacs::crypto::AACS_IV; - let num_blocks = (aacs::ALIGNED_UNIT_LEN - 16) / 16; + let num_blocks = (aacs::content::ALIGNED_UNIT_LEN - 16) / 16; for i in 0..num_blocks { let off = 16 + i * 16; for j in 0..16 { @@ -1153,9 +1157,9 @@ mod tests { /// (offset 4 + k*192) so `ts_sync_destroyed` reports false and /// `decrypt_unit` verifies it as clear after decryption. fn clear_ts_unit() -> Vec { - let mut unit = vec![0u8; aacs::ALIGNED_UNIT_LEN]; + let mut unit = vec![0u8; aacs::content::ALIGNED_UNIT_LEN]; let mut off = 4; - while off < aacs::ALIGNED_UNIT_LEN { + while off < aacs::content::ALIGNED_UNIT_LEN { unit[off] = 0x47; off += 192; } @@ -1171,7 +1175,7 @@ mod tests { /// garbage for content under key ≥ 1. The fix tries every key and /// accepts the one whose output passes the TS-sync verify. /// - /// Grounding: `for idx in try_order { … if aacs::decrypt_unit(&mut attempt, key) { … } }` + /// Grounding: `for idx in try_order { … if aacs::content::decrypt_unit(&mut attempt, key) { … } }` /// Mutation: revert to the pre-fix `decrypt_unit_full(chunk, &uk, …)` where /// `uk = raw_keys[unit_key_idx]` (always key 0) → the unit comes out as /// garbled bytes that still look scrambled, failing the `!ts_sync_destroyed` @@ -1185,7 +1189,7 @@ mod tests { let mut unit = clear_ts_unit(); aacs_encrypt_unit_for_test(&mut unit, &key1); assert!( - aacs::ts_sync_destroyed(&unit), + aacs::content::ts_sync_destroyed(&unit), "encrypted unit must look scrambled before decrypt" ); @@ -1199,13 +1203,13 @@ mod tests { decrypt_sectors(&mut buf, &mut keys, 0).expect("multi-CPS decrypt must succeed"); assert!( - !aacs::ts_sync_destroyed(&buf), + !aacs::content::ts_sync_destroyed(&buf), "unit encrypted under key1 must be fully decrypted (TS syncs restored)" ); // Every sync position must carry 0x47. assert_eq!( - aacs::ts_sync_count(&buf), - aacs::ts_packet_total(&buf), + aacs::content::ts_sync_count(&buf), + aacs::content::ts_packet_total(&buf), "all TS sync bytes must be restored after decrypting under key1" ); } @@ -1230,12 +1234,12 @@ mod tests { let mut buf = unit; decrypt_sectors(&mut buf, &mut keys, 0).expect("single-key disc must decrypt"); assert!( - !aacs::ts_sync_destroyed(&buf), + !aacs::content::ts_sync_destroyed(&buf), "single-key disc: TS syncs must be restored" ); assert_eq!( - aacs::ts_sync_count(&buf), - aacs::ts_packet_total(&buf), + aacs::content::ts_sync_count(&buf), + aacs::content::ts_packet_total(&buf), "all TS sync bytes must be restored for single-key disc" ); } @@ -1263,7 +1267,7 @@ mod tests { aacs_encrypt_unit_for_test(&mut unit, &real_key); let ciphertext = unit.clone(); assert!( - aacs::ts_sync_destroyed(&unit), + aacs::content::ts_sync_destroyed(&unit), "encrypted unit must look scrambled going in" ); @@ -1277,7 +1281,7 @@ mod tests { assert_eq!( dropped, - aacs::ALIGNED_UNIT_LEN, + aacs::content::ALIGNED_UNIT_LEN, "the whole scrambled unit must be reported as dropped when no key validates" ); assert_eq!( @@ -1306,7 +1310,7 @@ mod tests { aacs_encrypt_unit_for_test(&mut unit_b, &wrong); let unit_b_ciphertext = unit_b.clone(); - let mut buf = Vec::with_capacity(2 * aacs::ALIGNED_UNIT_LEN); + let mut buf = Vec::with_capacity(2 * aacs::content::ALIGNED_UNIT_LEN); buf.extend_from_slice(&unit_a); buf.extend_from_slice(&unit_b); @@ -1318,15 +1322,15 @@ mod tests { assert_eq!( dropped, - aacs::ALIGNED_UNIT_LEN, + aacs::content::ALIGNED_UNIT_LEN, "exactly one unit's worth of bytes must be reported dropped" ); assert!( - !aacs::ts_sync_destroyed(&buf[..aacs::ALIGNED_UNIT_LEN]), + !aacs::content::ts_sync_destroyed(&buf[..aacs::content::ALIGNED_UNIT_LEN]), "the decryptable unit must come out clear" ); assert_eq!( - &buf[aacs::ALIGNED_UNIT_LEN..], + &buf[aacs::content::ALIGNED_UNIT_LEN..], &unit_b_ciphertext[..], "the undecryptable unit must be restored to ciphertext" ); diff --git a/src/disc/encrypt.rs b/src/disc/encrypt.rs index 9479893..52c4e1e 100644 --- a/src/disc/encrypt.rs +++ b/src/disc/encrypt.rs @@ -86,9 +86,9 @@ impl AacsCertUnlocker<'_> { // MKB generation (best-effort) — forwarded to each source's // `host_certs(mkb)` so a source MAY select a generation-appropriate cert // (the default impl ignores it). A read failure leaves it `None`. - let mkb_gen = aacs::read_mkb_from_drive(session.scsi_mut()) + let mkb_gen = aacs::inf::read_mkb_from_drive(session.scsi_mut()) .ok() - .and_then(|m| aacs::mkb_version(&m)); + .and_then(|m| aacs::mkb::mkb_version(&m)); // Host certs are keysource-served, never compiled in — unioned from the // explicit `DriveCredentials` and the key-source layer. With ZERO certs @@ -152,10 +152,10 @@ fn unlock_error_to_error(e: &CertUnlockFailure) -> Error { } } -/// Map a [`CertUnlockFailure`] to a structured [`crate::aacs::UnlockOutcome`] +/// Map a [`CertUnlockFailure`] to a structured [`crate::aacs::trace::UnlockOutcome`] /// for the resolution trace (English-free). -fn cert_unlock_outcome(e: &CertUnlockFailure) -> crate::aacs::UnlockOutcome { - use crate::aacs::UnlockOutcome; +fn cert_unlock_outcome(e: &CertUnlockFailure) -> crate::aacs::trace::UnlockOutcome { + use crate::aacs::trace::UnlockOutcome; use freemkv_unlock::UnlockError; match e { CertUnlockFailure::NoHostCert { mkb } => UnlockOutcome::NoUsableHostCert { mkb: *mkb }, @@ -230,7 +230,10 @@ impl Disc { /// `mkb` is the disc's MKB generation when known, forwarded to each source's /// [`crate::KeySource::host_certs`] so a source MAY return only /// generation-appropriate certs (the default ignores it). - fn collect_host_certs(opts: &ScanOptions, mkb: Option) -> Vec { + fn collect_host_certs( + opts: &ScanOptions, + mkb: Option, + ) -> Vec { // Delegates to the shared cert primitive (the external freemkv-unlock-aacs // plugin uses the same one). Kept as a thin Disc method so the existing // collect_host_certs_* unit tests and call sites are unchanged. @@ -312,14 +315,14 @@ impl Disc { .read_file(reader, crate::aacs::PATH_UNIT_KEY_RO) .or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO_DUPLICATE)) .map_err(|_| Error::AacsNoKeys)?; - let dh = aacs::disc_hash(&uk_ro_data); + let dh = aacs::inf::disc_hash(&uk_ro_data); let cc = udf_fs .read_file(reader, crate::aacs::PATH_CONTENT_CERT) .or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT)) .ok() .as_deref() - .and_then(aacs::parse_content_cert); + .and_then(aacs::inf::parse_content_cert); let bus_encryption = cc.as_ref().map(|c| c.bus_encryption).unwrap_or(false); // No-cert default = UHD (V20 stride), matching `read_aacs_version` so the // scanned `AacsState.version` and the out-of-band fetch agree. A wrong @@ -328,7 +331,7 @@ impl Disc { let version = cc .as_ref() .map(|c| c.version.major()) - .unwrap_or(aacs::AACS_MAJOR_UHD); + .unwrap_or(aacs::mkb::AACS_MAJOR_UHD); // Bus-encryption gate (wrong-keys guard). A bus-encrypted disc (Content // Certificate bus-encryption bit set) carries bus encryption on its @@ -394,12 +397,12 @@ impl Disc { Vec::new() } }; - let mkb_ver = aacs::mkb_version(&mkb_bytes); + let mkb_ver = aacs::mkb::mkb_version(&mkb_bytes); tracing::debug!( target: "freemkv::disc", phase = "scan_aacs_vid_only", - disc_hash = %aacs::disc_hash_hex(&dh), + disc_hash = %aacs::inf::disc_hash_hex(&dh), version, bus_encryption, has_vid = handshake.is_some(), @@ -410,7 +413,7 @@ impl Disc { version, bus_encryption, mkb_version: mkb_ver, - disc_hash: aacs::disc_hash_hex(&dh), + disc_hash: aacs::inf::disc_hash_hex(&dh), key_source: KeyOrigin::ExternalUk, vuk: None, unit_keys: vec![], @@ -687,14 +690,14 @@ mod tests { let st = Disc::resolve_vid_only(&udf, &mut disc, None).expect("state"); assert_eq!( st.version, - aacs::AACS_MAJOR_UHD, + aacs::mkb::AACS_MAJOR_UHD, "no cert → default UHD (major 2)" ); assert!(!st.bus_encryption); } /// disc_hash is SHA1 of the Unit_Key_RO.inf bytes, hex with 0x prefix - /// and uppercase (aacs::disc_hash + disc_hash_hex). The state's + /// and uppercase (aacs::inf::disc_hash + disc_hash_hex). The state's /// disc_hash must match independently computing it over the same bytes. #[test] fn resolve_vid_only_disc_hash_is_sha1_of_unit_key_ro() { @@ -710,7 +713,7 @@ mod tests { }], ); let st = Disc::resolve_vid_only(&udf, &mut disc, None).expect("state"); - let expected = aacs::disc_hash_hex(&aacs::disc_hash(&uk)); + let expected = aacs::inf::disc_hash_hex(&aacs::inf::disc_hash(&uk)); assert_eq!(st.disc_hash, expected); assert!(st.disc_hash.starts_with("0x")); // uk_ro must be stashed verbatim for the external resolver. @@ -746,7 +749,7 @@ mod tests { // Real record stream is the single 16-byte type-0x10 record. assert_eq!( st.mkb.len(), - aacs::mkb_content_len(&mkb), + aacs::mkb::mkb_content_len(&mkb), "MKB must be trimmed to record-stream length, not the zero-pad" ); assert_eq!(st.mkb.len(), 16); @@ -943,7 +946,7 @@ mod tests { // disc_hash must be computed over the DUPLICATE bytes. assert_eq!( st.disc_hash, - aacs::disc_hash_hex(&aacs::disc_hash(&uk)), + aacs::inf::disc_hash_hex(&aacs::inf::disc_hash(&uk)), "fallback must hash the DUPLICATE Unit_Key_RO.inf" ); assert_eq!(st.uk_ro, uk); @@ -965,8 +968,8 @@ mod tests { // the route fails gracefully (AacsNoHostCert), never panics. // --------------------------------------------------------------- - fn fake_cert(tag: u8) -> aacs::HostCert { - aacs::HostCert { + fn fake_cert(tag: u8) -> aacs::types::HostCert { + aacs::types::HostCert { private_key: [tag; 20], certificate: vec![tag; 92], private_key_v2: None, @@ -975,15 +978,15 @@ mod tests { } /// A minimal in-test KeySource that yields no keys but a fixed cert list. - struct CertSource(Vec); + struct CertSource(Vec); impl crate::KeySource for CertSource { fn get_uk( &self, _ctx: &dyn crate::keysource::ResolveCtx, - ) -> Result> { + ) -> Result> { Ok(Vec::new()) } - fn host_certs(&self, _mkb: Option) -> Vec { + fn host_certs(&self, _mkb: Option) -> Vec { self.0.clone() } } @@ -1077,7 +1080,7 @@ mod tests { #[test] fn cert_unlock_outcome_maps_to_structured_trace_step() { - use crate::aacs::UnlockOutcome; + use crate::aacs::trace::UnlockOutcome; use freemkv_unlock::UnlockError; // The libfreemkv-side no-cert case carries the MKB generation. assert_eq!( diff --git a/src/disc/extract.rs b/src/disc/extract.rs index dd30a0e..b648b0a 100644 --- a/src/disc/extract.rs +++ b/src/disc/extract.rs @@ -1083,13 +1083,13 @@ mod tests { } /// Build a clear 6144-byte AACS unit (TS syncs at the 192-byte BD-TS - /// stride) then encrypt it under `unit_key` so `aacs::decrypt_unit` + /// stride) then encrypt it under `unit_key` so `aacs::content::decrypt_unit` /// recovers it cleanly (zero decrypt loss). Mirrors the encrypt helper in /// `sector/decrypting.rs` tests. `tag` distinguishes two units' payloads. fn encrypt_aacs_unit(unit_key: &[u8; 16], tag: u8) -> Vec { use aes::Aes128; use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray}; - let mut unit = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN]; + let mut unit = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN]; let mut off = 4; while off < unit.len() { unit[off] = 0x47; // TS sync @@ -1108,7 +1108,7 @@ mod tests { } let cipher = Aes128::new(GenericArray::from_slice(&k)); let mut prev = crate::aacs::crypto::AACS_IV; - let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16; + let blocks = (crate::aacs::content::ALIGNED_UNIT_LEN - 16) / 16; for i in 0..blocks { let o = 16 + i * 16; for j in 0..16 { @@ -1124,7 +1124,7 @@ mod tests { /// The plaintext that `encrypt_aacs_unit(_, tag)` decrypts back to. fn clear_aacs_unit(tag: u8) -> Vec { - let mut unit = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN]; + let mut unit = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN]; let mut off = 4; while off < unit.len() { unit[off] = 0x47; @@ -1600,10 +1600,10 @@ mod tests { /// own batch starts are always unit-aligned; anchoring a later extent /// against the FIRST extent's base mis-aligns whenever the extents' starts /// differ by a non-multiple of 3 sectors. This is the exact arithmetic the - /// decrypt-on-read gate (`aacs::is_unit_aligned`) performs. + /// decrypt-on-read gate (`aacs::content::is_unit_aligned`) performs. #[test] fn per_extent_base_is_aligned_first_extent_base_is_not() { - use crate::aacs::is_unit_aligned; + use crate::aacs::content::is_unit_aligned; let ext_a_start = 7000u32; // first extent abs LBA let ext_b_start = 7004u32; // second extent abs LBA (Δ4 — not mult of 3) diff --git a/src/disc/mod.rs b/src/disc/mod.rs index e765377..5fd2c91 100644 --- a/src/disc/mod.rs +++ b/src/disc/mod.rs @@ -1413,7 +1413,7 @@ impl KeyOrigin { #[derive(Default, Clone)] pub struct DriveCredentials { /// Host certificate(s) + private key(s) for the SCSI AACS handshake. - pub host_certs: Vec, + pub host_certs: Vec, } /// Options for disc scanning. @@ -1781,8 +1781,8 @@ impl Disc { Ok((inf, mkb, version)) } - /// AACS major version ([`crate::aacs::AACS_MAJOR_BD`] / - /// [`crate::aacs::AACS_MAJOR_UHD`]) from the content certificate. Drives the + /// AACS major version ([`crate::aacs::mkb::AACS_MAJOR_BD`] / + /// [`crate::aacs::mkb::AACS_MAJOR_UHD`]) from the content certificate. Drives the /// `Unit_Key_RO.inf` parse stride (48-byte V10 vs 64-byte V20/V21), so the /// out-of-band key-fetch path parses `enc_title_keys` at the right stride (a /// server VUK then derives the correct unit keys). @@ -1798,7 +1798,7 @@ impl Disc { .or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT)) .ok() .as_deref() - .and_then(crate::aacs::parse_content_cert) + .and_then(crate::aacs::inf::parse_content_cert) { Some(c) => c.version.major(), None => { @@ -1808,7 +1808,7 @@ impl Disc { "no readable AACS content certificate; defaulting to the V20/UHD \ Unit_Key_RO stride (a VUK-from-server path would otherwise mis-stride)" ); - crate::aacs::AACS_MAJOR_UHD + crate::aacs::mkb::AACS_MAJOR_UHD } } } @@ -1817,7 +1817,7 @@ impl Disc { /// /// `MKB_RO.inf` / `MKB_RW.inf` are allocated to a fixed ~128 MiB and /// zero-padded; the actual record stream is a few MiB. We read a bounded - /// prefix, find the record-stream length via [`crate::aacs::mkb_content_len`] + /// prefix, find the record-stream length via [`crate::aacs::mkb::mkb_content_len`] /// and return exactly that, growing the prefix if the records run past it. /// This avoids reading 100+ MiB of padding on every scan AND avoids the /// `read_file` `MAX_FILE_BYTES` cap that (since 0.31.0) rejected the padded @@ -1833,13 +1833,13 @@ impl Disc { .read_file_prefix(reader, crate::aacs::PATH_MKB_RO, want) .or_else(|_| udf_fs.read_file_prefix(reader, crate::aacs::PATH_MKB_RW, want)) .map_err(|_| Error::AacsNoKeys)?; - let n = crate::aacs::mkb_content_len(&buf); + let n = crate::aacs::mkb::mkb_content_len(&buf); // `n` strictly inside `buf` => the record walk reached the padding // boundary (full content captured). `buf` shorter than `want` => // the whole file is already read. Otherwise the records may run // past the prefix — grow and retry, bounded by MAX_BYTES. if (n > 0 && n < buf.len()) || buf.len() < want || want >= MAX_BYTES { - return Ok(crate::aacs::trim_mkb(buf)); + return Ok(crate::aacs::mkb::trim_mkb(buf)); } want = (want * 2).min(MAX_BYTES); } @@ -2274,7 +2274,7 @@ impl Disc { /// else (UDF filesystem, BDMV nav, PLAYLIST/CLIPINF) is always clear. /// /// The in-read decrypt-verify gate (`DecryptingSectorSource`) uses this so it - /// never consults [`ts_sync_destroyed`](crate::aacs::ts_sync_destroyed) about + /// never consults [`ts_sync_destroyed`](crate::aacs::content::ts_sync_destroyed) about /// non-content bytes — filesystem data has no TS sync and would otherwise be /// mistaken for ciphertext (the first-2-GB false-positive this fixes). /// @@ -2567,9 +2567,9 @@ impl Disc { } else if self.encrypted && self.css.is_none() { self.aacs = Some(AacsState { version: if self.format == DiscFormat::Uhd { - crate::aacs::AACS_MAJOR_UHD + crate::aacs::mkb::AACS_MAJOR_UHD } else { - crate::aacs::AACS_MAJOR_BD + crate::aacs::mkb::AACS_MAJOR_BD }, bus_encryption: self.format == DiscFormat::Uhd, mkb_version: None, @@ -2667,7 +2667,7 @@ impl Disc { Key::Processing(pks) => supplied.processing_keys = pks, Key::Media(mks) => supplied.media_keys = mks, Key::Volume(vuk) => { - supplied.disc_entry = Some(crate::aacs::DiscEntry { + supplied.disc_entry = Some(crate::aacs::types::DiscEntry { disc_hash: aacs.disc_hash.clone(), title: String::new(), media_key: None, @@ -2685,8 +2685,8 @@ impl Disc { let uk_ro = aacs.uk_ro.clone(); let version_u8 = aacs.version; - let provider_refs: [&dyn crate::aacs::KeyProvider; 1] = [&supplied]; - let ctx = crate::aacs::ResolveContext { + let provider_refs: [&dyn crate::aacs::provider::KeyProvider; 1] = [&supplied]; + let ctx = crate::aacs::resolve::ResolveContext { unit_key_ro: &uk_ro, content_cert: None, volume_id: &volume_id, @@ -2700,7 +2700,7 @@ impl Disc { // reason-preserving wrapper threads the no-key cause out so the // decrypt gate can report E7017 (had derivation material but no VID) // vs E7022 (no usable material) instead of a flat AacsKeyRejected. - let resolved = crate::aacs::resolve_keys_with_reason(&ctx, version_u8) + let resolved = crate::aacs::resolve::resolve_keys_with_reason(&ctx, version_u8) .map_err(|_reason| crate::error::Error::AacsKeyRejected)?; if resolved.unit_keys.is_empty() { @@ -3203,7 +3203,7 @@ impl Disc { // decrypts and is AACS-keyed. Region read-starts are aligned DOWN to a // unit boundary in the loop below; a fresh sweep starts at LBA 0 (already // aligned), so alignment only bites on resume NonTried regions. - const UNIT_SECTORS: u16 = (crate::aacs::ALIGNED_UNIT_LEN / 2048) as u16; // 3 + const UNIT_SECTORS: u16 = (crate::aacs::content::ALIGNED_UNIT_LEN / 2048) as u16; // 3 if decrypt_is_aacs && batch % UNIT_SECTORS != 0 { batch = batch.saturating_add(UNIT_SECTORS - (batch % UNIT_SECTORS)); } @@ -3285,7 +3285,7 @@ impl Disc { // sweep's NonTried region starts at 0, already unit-aligned; this only // shifts resume regions that begin mid-unit. let mut pos = if decrypt_is_aacs { - let unit_bytes = crate::aacs::ALIGNED_UNIT_LEN as u64; + let unit_bytes = crate::aacs::content::ALIGNED_UNIT_LEN as u64; region_pos - (region_pos % unit_bytes) } else { region_pos @@ -4240,8 +4240,8 @@ mod tests { /// `sector::decrypting::tests::aacs_unaligned_start_lba_rejected`. #[test] fn aacs_sweep_batch_and_region_are_unit_aligned() { - const UNIT_SECTORS: u16 = (crate::aacs::ALIGNED_UNIT_LEN / 2048) as u16; // 3 - let unit_bytes = crate::aacs::ALIGNED_UNIT_LEN as u64; // 6144 + const UNIT_SECTORS: u16 = (crate::aacs::content::ALIGNED_UNIT_LEN / 2048) as u16; // 3 + let unit_bytes = crate::aacs::content::ALIGNED_UNIT_LEN as u64; // 6144 // (a) Batch rounding: ecc_sectors() for UHD/BD is 32, not a multiple of 3. // The decrypting-AACS path rounds it up to the next multiple of 3 (33). @@ -4700,7 +4700,7 @@ mod tests { // The resolver classifies a device-keys-but-zero-VID context as // `VidUnavailable`; that reason rides on `aacs_error`. let supplied = crate::aacs::provider::SuppliedKey { - device_keys: vec![crate::aacs::DeviceKey { + device_keys: vec![crate::aacs::types::DeviceKey { key: [0x11; 16], node: 1, uv: 1, @@ -4710,14 +4710,14 @@ mod tests { media_keys: Vec::new(), disc_entry: None, }; - let provider_refs: [&dyn crate::aacs::KeyProvider; 1] = [&supplied]; + let provider_refs: [&dyn crate::aacs::provider::KeyProvider; 1] = [&supplied]; // A minimal but parseable Unit_Key_RO.inf (uk_pos=32, zero unit keys) // so resolution proceeds to the path-try logic and fails for lack of a // VID — not because the .inf failed to parse. let mut uk_ro = vec![0u8; 40]; uk_ro[0..4].copy_from_slice(&32u32.to_be_bytes()); // uk_pos = 32 // num_unit_keys = 0 (BE16) at uk_pos -> parses to an empty key file. - let ctx = crate::aacs::ResolveContext { + let ctx = crate::aacs::resolve::ResolveContext { unit_key_ro: &uk_ro, content_cert: None, volume_id: &[0u8; 16], // the "no VID" sentinel @@ -4725,8 +4725,8 @@ mod tests { mkb: None, }; assert_eq!( - crate::aacs::resolve_keys_with_reason(&ctx, 2).err(), - Some(crate::aacs::ResolveFailure::VidUnavailable), + crate::aacs::resolve::resolve_keys_with_reason(&ctx, 2).err(), + Some(crate::aacs::resolve::ResolveFailure::VidUnavailable), "device keys + zero VID must classify as VidUnavailable" ); @@ -4750,8 +4750,8 @@ mod tests { media_keys: Vec::new(), disc_entry: None, }; - let provider_refs_none: [&dyn crate::aacs::KeyProvider; 1] = [&supplied_none]; - let ctx_none = crate::aacs::ResolveContext { + let provider_refs_none: [&dyn crate::aacs::provider::KeyProvider; 1] = [&supplied_none]; + let ctx_none = crate::aacs::resolve::ResolveContext { unit_key_ro: &uk_ro, content_cert: None, volume_id: &[0u8; 16], @@ -4759,8 +4759,8 @@ mod tests { mkb: None, }; assert_eq!( - crate::aacs::resolve_keys_with_reason(&ctx_none, 2).err(), - Some(crate::aacs::ResolveFailure::NoMaterial), + crate::aacs::resolve::resolve_keys_with_reason(&ctx_none, 2).err(), + Some(crate::aacs::resolve::ResolveFailure::NoMaterial), "no key material must classify as NoMaterial" ); @@ -5030,8 +5030,8 @@ mod tests { let vuk = [0x5au8; 16]; let enc0 = [0x12u8; 16]; let enc1 = [0x34u8; 16]; - let exp0 = crate::aacs::decrypt_unit_key(&vuk, &enc0); - let exp1 = crate::aacs::decrypt_unit_key(&vuk, &enc1); + let exp0 = crate::aacs::derive::decrypt_unit_key(&vuk, &enc0); + let exp1 = crate::aacs::derive::decrypt_unit_key(&vuk, &enc1); let mut disc = make_test_disc(1000, "UHD"); disc.encrypted = true; diff --git a/src/disc/verify.rs b/src/disc/verify.rs index 009d8f7..0a80395 100644 --- a/src/disc/verify.rs +++ b/src/disc/verify.rs @@ -7,7 +7,7 @@ //! grid (clips can start off the 6144 grid and fragment across UDF extents). So //! this gate BUFFERS the disc-absolute read stream and re-ALIGNS it into //! clip-file units, then applies the standards-correct -//! [`crate::aacs::unit_is_clean_ts`] gate (libaacs `_verify_ts`, all-32 syncs). +//! [`crate::aacs::content::unit_is_clean_ts`] gate (libaacs `_verify_ts`, all-32 syncs). //! //! FAIL-SAFE CONTRACT (this sits in the middle of every read, so it must never //! break a good read): the gate can ONLY downgrade a unit it is *confident* is @@ -25,7 +25,8 @@ use std::collections::{HashMap, VecDeque}; -use crate::aacs::{self, ALIGNED_UNIT_LEN}; +use crate::aacs::content::ALIGNED_UNIT_LEN; +use crate::aacs::{self}; use crate::consts::SECTOR_BYTES_U64; use crate::decrypt::DecryptKeys; use crate::sector::KeyFetch; @@ -57,7 +58,7 @@ pub enum ContainerKind { Ts, /// HD-DVD `.evo` — MPEG-2 program stream (pack-start `00 00 01 BA`). /// NOT yet enabled by enumeration; present so adding HD-DVD is a one-mapping - /// change. See [`crate::aacs::unit_is_clean_ps`] for the (unvalidated) check. + /// change. See [`crate::aacs::content::unit_is_clean_ps`] for the (unvalidated) check. Ps, } @@ -187,8 +188,8 @@ impl UnitVerifier { /// The post-decrypt structural check for a clip's container. fn accept_for(&self, clip: u32) -> fn(&[u8]) -> bool { match self.containers[clip as usize] { - ContainerKind::Ts => aacs::unit_is_clean_ts, - ContainerKind::Ps => aacs::unit_is_clean_ps, + ContainerKind::Ts => aacs::content::unit_is_clean_ts, + ContainerKind::Ps => aacs::content::unit_is_clean_ps, } } @@ -301,8 +302,8 @@ impl UnitVerifier { } /// Can this fully-assembled unit be decrypted + verified? `accept` is the - /// container's strict structural check ([`aacs::unit_is_clean_ts`] for TS, - /// [`aacs::unit_is_clean_ps`] for PS) — the only format-specific part; the + /// container's strict structural check ([`aacs::content::unit_is_clean_ts`] for TS, + /// [`aacs::content::unit_is_clean_ps`] for PS) — the only format-specific part; the /// AACS crypto is container-agnostic. Returns the 3-state [`Decryptability`]. fn decryptability( &mut self, @@ -318,7 +319,7 @@ impl UnitVerifier { // ENCRYPTED units that no key opens are ever flagged. (A real bad READ of // clear content is still caught by the normal SCSI read-error path; this // gate just won't false-flag it.) - if !aacs::aacs_unit_encrypted(raw) { + if !aacs::content::aacs_unit_encrypted(raw) { return if accept(raw) { Decryptability::Decryptable } else { @@ -363,7 +364,7 @@ impl UnitVerifier { fn try_keys(&self, raw: &[u8; ALIGNED_UNIT_LEN], accept: fn(&[u8]) -> bool) -> bool { for k in &self.keys { let mut scratch = *raw; - if aacs::decrypt_unit_checked(&mut scratch, k, accept) { + if aacs::content::decrypt_unit_checked(&mut scratch, k, accept) { return true; } } diff --git a/src/keysource.rs b/src/keysource.rs index ed68539..ddf72af 100644 --- a/src/keysource.rs +++ b/src/keysource.rs @@ -3,12 +3,12 @@ //! libfreemkv performs NO key lookup. An application resolves a disc's keys //! through one or more [`KeySource`]s, each an adapter over a backing store (a //! keydb file, a key server, the mapfile cache). A source's job is to return the -//! disc's terminal **Unit Keys** ([`crate::aacs::UnitKey`]). It knows what +//! disc's terminal **Unit Keys** ([`crate::aacs::boil::UnitKey`]). It knows what //! material it holds (a DK / MK / VUK / pre-decrypted UK) and what it must fetch //! from the disc (VID, MKB, encrypted title keys, content samples) to get there; //! it orchestrates the derivation by calling libfreemkv's own boil-down crypto -//! primitives ([`crate::aacs::mk_from_dk`] / [`crate::aacs::vuk_from_mk`] / -//! [`crate::aacs::uk_from_vuk`]) through the [`ResolveCtx`] handed to it. +//! primitives ([`crate::aacs::boil::mk_from_dk`] / [`crate::aacs::boil::vuk_from_mk`] / +//! [`crate::aacs::boil::uk_from_vuk`]) through the [`ResolveCtx`] handed to it. //! //! libfreemkv still OWNS the crypto: the boil-down primitives and the AES live //! here. A source owns only PATH ORCHESTRATION — deciding which primitive to @@ -17,7 +17,8 @@ //! keeping key *policy* (which store, which order, online vs local) out of the //! library. -use crate::aacs::{HostCert, UnitKey, Vid}; +use crate::aacs::boil::{UnitKey, Vid}; +use crate::aacs::types::HostCert; use crate::disc::Key; use crate::error::Error; @@ -74,8 +75,8 @@ pub trait ResolveCtx { /// Raw MKB bytes (may be empty when not captured). fn mkb(&self) -> Result<&[u8], Error>; /// The disc's encrypted title keys, parsed from `Unit_Key_RO.inf` the same - /// way the library's resolver parses them ([`crate::aacs::parse_unit_key_ro`]), - /// in on-disc order. Feed straight into [`crate::aacs::uk_from_vuk`]. + /// way the library's resolver parses them ([`crate::aacs::inf::parse_unit_key_ro`]), + /// in on-disc order. Feed straight into [`crate::aacs::boil::uk_from_vuk`]. fn enc_title_keys(&self) -> Result<&[[u8; 16]], Error>; /// Up to `n` encrypted on-disc content sample units, for a source that /// validates a candidate server-side against real ciphertext. @@ -113,7 +114,8 @@ impl<'a> DiscInputsCtx<'a> { /// title keys — the parse failure is swallowed here, not surfaced as an /// error. pub fn new(inputs: &'a DiscInputs) -> Self { - use crate::aacs::{AacsVersion, parse_unit_key_ro}; + use crate::aacs::inf::parse_unit_key_ro; + use crate::aacs::mkb::AacsVersion; let enc_keys = if inputs.unit_key_ro.is_empty() { Vec::new() } else { @@ -164,8 +166,8 @@ impl ResolveCtx for DiscInputsCtx<'_> { /// holds, orchestrates the derivation down to Unit Keys using the library's /// boil-down crypto primitives — never re-implementing AES. A source that holds /// pre-decrypted Unit Keys returns them directly; one that holds a VUK calls -/// [`crate::aacs::uk_from_vuk`]; one that holds device keys calls -/// [`crate::aacs::mk_from_dk`] → [`crate::aacs::vuk_from_mk`] → `uk_from_vuk`. +/// [`crate::aacs::boil::uk_from_vuk`]; one that holds device keys calls +/// [`crate::aacs::boil::mk_from_dk`] → [`crate::aacs::boil::vuk_from_mk`] → `uk_from_vuk`. /// /// Returning an empty `Vec` means "no key for this disc from this source"; an /// `Err` means the source itself failed (I/O, parse, network). The caller @@ -209,7 +211,7 @@ pub fn resolve_and_apply( } /// Like [`resolve_and_apply`] but also returns a structured -/// [`crate::aacs::ResolutionTrace`] recording, per source, what happened — for +/// [`crate::aacs::trace::ResolutionTrace`] recording, per source, what happened — for /// applications to render. ZERO English; the trace is typed enums only. /// /// One-shot per source: each source's [`KeySource::get_uk`] is called exactly @@ -219,8 +221,8 @@ pub fn resolve_and_apply( /// success — so a wrong/partial key set is rejected and the loop continues. /// /// CPS-unit numbering: a source returns Unit Keys carrying the POSITIONAL index -/// from [`crate::aacs::uk_from_vuk`]; the library's canonical CPS-unit number is -/// `position + 1` (matching [`crate::aacs::parse_unit_key_ro`]'s `(i + 1)`), so +/// from [`crate::aacs::boil::uk_from_vuk`]; the library's canonical CPS-unit number is +/// `position + 1` (matching [`crate::aacs::inf::parse_unit_key_ro`]'s `(i + 1)`), so /// the committed `AacsState.unit_keys` is byte-identical to the library-resolved /// path. The number is cosmetic for descramble (the decrypt path strips it and /// tries every key) but is kept faithful to the resolver's convention. @@ -228,10 +230,10 @@ pub fn resolve_and_apply_traced( sources: &[Box], inputs: &DiscInputs, disc: &mut crate::Disc, -) -> (bool, crate::aacs::ResolutionTrace) { +) -> (bool, crate::aacs::trace::ResolutionTrace) { use crate::aacs::trace::{KeyNode, KeyOutcome, KeyStep}; - let mut trace = crate::aacs::ResolutionTrace::new(); + let mut trace = crate::aacs::trace::ResolutionTrace::new(); // The ctx parses Unit_Key_RO.inf at the stride for `inputs.version` (the // disc's own AACS major), so the stride is the disc's single source of truth. @@ -341,7 +343,7 @@ pub fn key_fetch( /// `start_lba`), which the library owns. A key source is *handed* these bytes /// via `DiscInputs.samples`; it never reads the disc itself. /// -/// "Encrypted" is decided by [`crate::aacs::ts_sync_destroyed`] — the SAME +/// "Encrypted" is decided by [`crate::aacs::content::ts_sync_destroyed`] — the SAME /// predicate the decrypt gate uses — so all sides agree. A clip opens with clear /// navigation units (PAT/PMT, menus); only the feature body is scrambled, and a /// clear unit proves nothing, so this collects only scrambled ones — probing @@ -352,7 +354,7 @@ pub fn read_encrypted_units( title: &crate::disc::DiscTitle, n: usize, ) -> Vec> { - use crate::aacs::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed}; + use crate::aacs::content::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed}; const CHUNK_UNITS: u32 = 15; // 45 sectors/read — under the drive transfer cap // Probe several evenly-spaced points across EACH extent rather than only the // midpoint-and-forward: a title whose encrypted feature starts late, or whose @@ -413,7 +415,7 @@ pub fn read_encrypted_units( #[cfg(test)] mod tests { use super::*; - use crate::aacs::UnitKey; + use crate::aacs::boil::UnitKey; use std::sync::{Arc, Mutex}; // ── KeySource default-method behaviour ──────────────────────────────────── @@ -454,7 +456,7 @@ mod tests { let inputs = DiscInputs { disc_hash: "0xABC".into(), volume_id: [0u8; 16], - version: crate::aacs::AACS_MAJOR_BD, + version: crate::aacs::mkb::AACS_MAJOR_BD, mkb: vec![1, 2, 3], unit_key_ro: uk_ro, samples: vec![vec![9u8; 4], vec![8u8; 4], vec![7u8; 4]], @@ -510,7 +512,7 @@ mod tests { let inputs = DiscInputs { disc_hash: "0x00".into(), volume_id: [0u8; 16], - version: crate::aacs::AACS_MAJOR_UHD, + version: crate::aacs::mkb::AACS_MAJOR_UHD, mkb: Vec::new(), unit_key_ro: Vec::new(), samples: Vec::new(), @@ -531,7 +533,7 @@ mod tests { DiscInputs { disc_hash: String::new(), volume_id: [0u8; 16], - version: crate::aacs::AACS_MAJOR_UHD, + version: crate::aacs::mkb::AACS_MAJOR_UHD, mkb: Vec::new(), unit_key_ro: Vec::new(), samples: Vec::new(), @@ -621,7 +623,7 @@ mod tests { }); let cb = key_fetch(empty_inputs(), make); - let samples = vec![vec![0xEEu8; crate::aacs::ALIGNED_UNIT_LEN]]; + let samples = vec![vec![0xEEu8; crate::aacs::content::ALIGNED_UNIT_LEN]]; let got = cb(&samples); assert_eq!( got, @@ -643,7 +645,7 @@ mod tests { /// finds the early scrambled band. #[test] fn read_encrypted_units_finds_scrambled_content_off_the_midpoint() { - use crate::aacs::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed}; + use crate::aacs::content::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed}; use crate::error::Result; use crate::sector::SectorSource; @@ -725,7 +727,7 @@ mod tests { /// prior single-key fixtures passed regardless of stride. #[test] fn disc_inputs_ctx_parses_unit_keys_at_the_version_stride() { - use crate::aacs::{AACS_MAJOR_BD, AACS_MAJOR_UHD}; + use crate::aacs::mkb::{AACS_MAJOR_BD, AACS_MAJOR_UHD}; const UK_POS: usize = 64; let mut inf = vec![0u8; 200]; inf[0..4].copy_from_slice(&(UK_POS as u32).to_be_bytes()); // uk_pos diff --git a/src/mux/ts.rs b/src/mux/ts.rs index 7c3b64c..95c3d3e 100644 --- a/src/mux/ts.rs +++ b/src/mux/ts.rs @@ -366,7 +366,7 @@ impl TsDemuxer { // P3/B1 CONCEALMENT MARKER. The decrypt layer fills an undecryptable // aligned unit with NULL-TS packets (PID 0x1FFF) that carry an - // adaptation-field discontinuity_indicator (see `aacs::fill_null_ts_unit`). + // adaptation-field discontinuity_indicator (see `aacs::content::fill_null_ts_unit`). // This is the authoritative loss signal — unlike a tracked PID's 4-bit // continuity_counter it is CC-INDEPENDENT, so it survives a loss that is // an exact multiple of 16 packets and a loss at the very start of a PID diff --git a/src/sector/decrypting.rs b/src/sector/decrypting.rs index 283926f..c73c879 100644 --- a/src/sector/decrypting.rs +++ b/src/sector/decrypting.rs @@ -146,7 +146,7 @@ pub struct DecryptingSectorSource { scratch: Vec, /// MUX loss-concealment switch (P3 / Edit-2). When `true`, a content unit /// that genuinely won't decrypt is NOT a read failure: it is overwritten with - /// valid NULL TS packets ([`crate::aacs::fill_null_ts_unit`]), tallied into + /// valid NULL TS packets ([`crate::aacs::content::fill_null_ts_unit`]), tallied into /// [`decrypt_dropped`](Self::decrypt_dropped), logged loud with its LBA, and /// the read returns `Ok` so the mux KEEPS GOING (it can never abort over an /// undecryptable unit). This is the spec's "decrypt-verify is a RIP gate, not @@ -197,7 +197,7 @@ impl DecryptingSectorSource { /// (sorted/merged `(start_lba, sector_count)` — see /// [`Disc::encrypted_content_ranges`](crate::Disc::encrypted_content_ranges)). /// Units outside content (UDF filesystem / BDMV nav) pass through untouched, - /// so [`ts_sync_destroyed`](crate::aacs::ts_sync_destroyed) is never consulted + /// so [`ts_sync_destroyed`](crate::aacs::content::ts_sync_destroyed) is never consulted /// about non-content bytes. Whole-disc readers (sweep / patch) set this; the /// mux leaves it unset because it only ever reads title extents. pub fn with_content_ranges(mut self, ranges: Arc<[(u32, u32)]>) -> Self { @@ -301,14 +301,14 @@ impl DecryptingSectorSource { content: Option<&[(u32, u32)]>, prev_dropped: usize, ) -> usize { - let unit_len = crate::aacs::ALIGNED_UNIT_LEN; + let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN; // Gather up to MAX_FETCH_SAMPLES still-scrambled aligned units — the // exact on-disc ciphertext no held key could open. A trailing partial // unit (chunks_exact remainder) can't be a whole scrambled unit, so // skipping it is correct. let mut samples: Vec> = Vec::new(); for chunk in buf.chunks_exact(unit_len) { - if crate::aacs::aacs_unit_needs_decrypt(chunk) { + if crate::aacs::content::aacs_unit_needs_decrypt(chunk) { samples.push(chunk.to_vec()); if samples.len() >= MAX_FETCH_SAMPLES { break; @@ -391,7 +391,7 @@ impl DecryptingSectorSource { content: Option<&[(u32, u32)]>, keys: &DecryptKeys, ) { - let unit_len = crate::aacs::ALIGNED_UNIT_LEN; + let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN; let unit_sectors = (unit_len / 2048) as u32; // Only AACS produces decrypt-verify failures; None / CSS never reach here // with a non-zero dropped count. @@ -415,12 +415,12 @@ impl DecryptingSectorSource { // A unit that decrypted is no longer sync-destroyed; a CPI-clear or // non-content unit is gated out. Only undecryptable in-content units // that are flagged encrypted carry signal. - if !in_content || !crate::aacs::aacs_unit_needs_decrypt(chunk) { + if !in_content || !crate::aacs::content::aacs_unit_needs_decrypt(chunk) { continue; } let all_zero = chunk.iter().all(|&b| b == 0); - let ts_sync = crate::aacs::ts_sync_count(chunk); - let ts_total = crate::aacs::ts_packet_total(chunk); + let ts_sync = crate::aacs::content::ts_sync_count(chunk); + let ts_total = crate::aacs::content::ts_packet_total(chunk); let mut seen = [false; 256]; for &b in chunk { seen[b as usize] = true; @@ -431,10 +431,10 @@ impl DecryptingSectorSource { for (_, k) in unit_keys.iter() { let mut attempt = chunk.to_vec(); if let Some(ref rdk_key) = rdk { - crate::aacs::decrypt_bus(&mut attempt, rdk_key); + crate::aacs::content::decrypt_bus(&mut attempt, rdk_key); } - crate::aacs::decrypt_unit(&mut attempt, k); - let s = crate::aacs::ts_sync_count(&attempt); + crate::aacs::content::decrypt_unit(&mut attempt, k); + let s = crate::aacs::content::ts_sync_count(&attempt); if s > best_sync { best_sync = s; } @@ -497,7 +497,7 @@ impl SectorSource for DecryptingSectorSource { // units (else its readable units are wrongly rejected → "Decryption // failed" on exactly those titles). if matches!(self.keys, DecryptKeys::Aacs { .. }) - && !crate::aacs::is_unit_aligned(lba, self.unit_base) + && !crate::aacs::content::is_unit_aligned(lba, self.unit_base) { return Err(crate::error::Error::DecryptFailed); } @@ -585,7 +585,7 @@ impl SectorSource for DecryptingSectorSource { // rip stays fail-loud. Ciphertext is never passed downstream: it is // replaced by null packets, not emitted. if self.tolerate_decrypt_loss && !self.verify_only { - let unit_len = crate::aacs::ALIGNED_UNIT_LEN; + let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN; let mut concealed = 0usize; let mut first_lba = lba; for (i, chunk) in buf[..n].chunks_mut(unit_len).enumerate() { @@ -604,11 +604,12 @@ impl SectorSource for DecryptingSectorSource { // un-restored ciphertext. In-content gating already happened in // `decrypt_buf`, which restored only failed units to ciphertext; // clear nav and decrypted tails pass through clean. - if crate::aacs::aacs_unit_still_ciphertext(chunk) { + if crate::aacs::content::aacs_unit_still_ciphertext(chunk) { if concealed == 0 { - first_lba = lba + (i as u32) * crate::aacs::ALIGNED_UNIT_SECTORS; + first_lba = + lba + (i as u32) * crate::aacs::content::ALIGNED_UNIT_SECTORS; } - crate::aacs::fill_null_ts_unit(chunk); + crate::aacs::content::fill_null_ts_unit(chunk); concealed += 1; } } @@ -631,8 +632,10 @@ impl SectorSource for DecryptingSectorSource { // flags, loudly. Cryptographically unreachable in practice. let mut forced = 0usize; for chunk in buf[..n].chunks_mut(unit_len) { - if chunk.len() == unit_len && crate::aacs::aacs_unit_needs_decrypt(chunk) { - crate::aacs::fill_null_ts_unit(chunk); + if chunk.len() == unit_len + && crate::aacs::content::aacs_unit_needs_decrypt(chunk) + { + crate::aacs::content::fill_null_ts_unit(chunk); forced += 1; } } @@ -1253,12 +1256,12 @@ mod tests { } /// Build a clear 6144-byte AACS unit (TS syncs at the BD-TS stride) then - /// encrypt it under `unit_key` so `aacs::decrypt_unit` recovers it. Mirrors + /// encrypt it under `unit_key` so `aacs::content::decrypt_unit` recovers it. Mirrors /// the encrypt helper in `crate::decrypt`'s tests. fn encrypt_aacs_unit(unit_key: &[u8; 16]) -> Vec { use aes::Aes128; use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray}; - let mut unit = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN]; + let mut unit = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN]; let mut off = 4; while off < unit.len() { unit[off] = 0x47; @@ -1274,7 +1277,7 @@ mod tests { } let cipher = Aes128::new(GenericArray::from_slice(&k)); let mut prev = crate::aacs::crypto::AACS_IV; - let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16; + let blocks = (crate::aacs::content::ALIGNED_UNIT_LEN - 16) / 16; for i in 0..blocks { let o = 16 + i * 16; for j in 0..16 { @@ -1347,7 +1350,7 @@ mod tests { ); assert_eq!( loss.load(Ordering::Relaxed), - crate::aacs::ALIGNED_UNIT_LEN as u64, + crate::aacs::content::ALIGNED_UNIT_LEN as u64, "the undecryptable unit is tallied as loss before the read errors" ); @@ -1358,7 +1361,7 @@ mod tests { ); assert_eq!( loss.load(Ordering::Relaxed), - 2 * crate::aacs::ALIGNED_UNIT_LEN as u64, + 2 * crate::aacs::content::ALIGNED_UNIT_LEN as u64, "loss must accumulate across reads" ); @@ -1392,7 +1395,7 @@ mod tests { // One unit encrypted under real_key, plus one trailing CLEAR (TS-sync) // unit so we can confirm conceal touches ONLY the undecryptable unit. let enc = encrypt_aacs_unit(&real_key); - let mut clear = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN]; + let mut clear = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN]; let mut o = 4; while o < clear.len() { clear[o] = 0x47; @@ -1441,13 +1444,13 @@ mod tests { // The undecryptable unit is tallied as loss. assert_eq!( loss.load(Ordering::Relaxed), - crate::aacs::ALIGNED_UNIT_LEN as u64, + crate::aacs::content::ALIGNED_UNIT_LEN as u64, "the concealed unit is still counted as loss" ); // Unit 0 is now valid NULL TS packets — sync 0x47 at every 192-byte // stride (offset 4), PID 0x1FFF — and carries no ciphertext. - let unit0 = &buf[..crate::aacs::ALIGNED_UNIT_LEN]; + let unit0 = &buf[..crate::aacs::content::ALIGNED_UNIT_LEN]; let mut off = 0; while off + 192 <= unit0.len() { assert_eq!(unit0[off + 4], 0x47, "null packet sync at {off}"); @@ -1456,12 +1459,13 @@ mod tests { off += 192; } assert!( - !crate::aacs::ts_sync_destroyed(unit0), + !crate::aacs::content::ts_sync_destroyed(unit0), "concealed unit reads as well-formed TS, not scrambled" ); // Unit 1 (clear) passed through untouched. - let unit1 = &buf[crate::aacs::ALIGNED_UNIT_LEN..2 * crate::aacs::ALIGNED_UNIT_LEN]; + let unit1 = &buf + [crate::aacs::content::ALIGNED_UNIT_LEN..2 * crate::aacs::content::ALIGNED_UNIT_LEN]; assert_eq!(unit1, &clear[..], "the clear unit is left exactly as read"); } @@ -1496,7 +1500,7 @@ mod tests { // The byte-exact expected post-decrypt form of unit B (independent decrypt). let mut expected_tail = good_tail.clone(); assert!( - crate::aacs::decrypt_unit(&mut expected_tail, &good_key), + crate::aacs::content::decrypt_unit(&mut expected_tail, &good_key), "padding-tail must decrypt under good_key" ); @@ -1542,12 +1546,12 @@ mod tests { // ONLY the genuinely-undecryptable unit A is tallied / concealed. assert_eq!( loss.load(Ordering::Relaxed), - crate::aacs::ALIGNED_UNIT_LEN as u64, + crate::aacs::content::ALIGNED_UNIT_LEN as u64, "exactly one unit (the undecryptable one) is counted as loss" ); // Unit A → NULL TS (concealed). - let unit0 = &buf[..crate::aacs::ALIGNED_UNIT_LEN]; + let unit0 = &buf[..crate::aacs::content::ALIGNED_UNIT_LEN]; let mut off = 0; while off + 192 <= unit0.len() { assert_eq!(unit0[off + 4], 0x47, "unit A null packet sync at {off}"); @@ -1562,7 +1566,8 @@ mod tests { // Unit B → the GOOD decrypted padding tail, byte-for-byte intact (NOT // overwritten with NULL TS). This is the silent-data-loss the old // majority-vote predicate caused. - let unit1 = &buf[crate::aacs::ALIGNED_UNIT_LEN..2 * crate::aacs::ALIGNED_UNIT_LEN]; + let unit1 = &buf + [crate::aacs::content::ALIGNED_UNIT_LEN..2 * crate::aacs::content::ALIGNED_UNIT_LEN]; assert_eq!( unit1, &expected_tail[..], @@ -1587,8 +1592,8 @@ mod tests { /// `mux::ts` reads as a concealed gap. #[test] fn null_ts_fill_is_well_formed_and_invisible_to_real_pids() { - let mut unit = vec![0xAAu8; crate::aacs::ALIGNED_UNIT_LEN]; - crate::aacs::fill_null_ts_unit(&mut unit); + let mut unit = vec![0xAAu8; crate::aacs::content::ALIGNED_UNIT_LEN]; + crate::aacs::content::fill_null_ts_unit(&mut unit); // 32 packets, each: sync 0x47, PID 0x1FFF, adaptation-only (0b10) with a // discontinuity_indicator in the adaptation field. let mut off = 0; @@ -1679,7 +1684,7 @@ mod tests { "callback must be invoked once with the failing unit" ); assert!( - crate::aacs::ts_sync_destroyed(&got[0]), + crate::aacs::content::ts_sync_destroyed(&got[0]), "the sample handed to the callback is the still-scrambled ciphertext" ); assert_eq!( @@ -1703,7 +1708,7 @@ mod tests { ); assert_eq!( nocb_loss.load(Ordering::Relaxed), - crate::aacs::ALIGNED_UNIT_LEN as u64, + crate::aacs::content::ALIGNED_UNIT_LEN as u64, "without a fetch callback the undecryptable unit is loss" ); } @@ -1784,7 +1789,7 @@ mod tests { "fetch fired for BOTH units — the dry result for A did not latch off B" ); assert!( - !crate::aacs::ts_sync_destroyed(&buf2), + !crate::aacs::content::ts_sync_destroyed(&buf2), "unit B is decrypted after its on-demand fetch" ); } @@ -1899,7 +1904,7 @@ mod tests { let mut u = 0; while u < bytes { buf[u] |= 0xC0; - u += crate::aacs::ALIGNED_UNIT_LEN; + u += crate::aacs::content::ALIGNED_UNIT_LEN; } Ok(bytes) } @@ -1998,7 +2003,7 @@ mod tests { let mut u = 0; while u < b { buf[u] |= 0xC0; // CPI bits → reads as encrypted - u += crate::aacs::ALIGNED_UNIT_LEN; + u += crate::aacs::content::ALIGNED_UNIT_LEN; } Ok(b) } diff --git a/src/unlock_bridge.rs b/src/unlock_bridge.rs index eae789f..ad35c6a 100644 --- a/src/unlock_bridge.rs +++ b/src/unlock_bridge.rs @@ -89,7 +89,7 @@ impl fu::scsi::ScsiTransport for ScsiAdapter<'_> { } /// Map libfreemkv's host certs (keysource-collected) to the unlock contract's. -pub(crate) fn map_host_certs(certs: &[crate::aacs::HostCert]) -> Vec { +pub(crate) fn map_host_certs(certs: &[crate::aacs::types::HostCert]) -> Vec { certs .iter() .map(|c| fu::HostCert {