AacsKeyMap: a positive map — no key for a sector means pass through

The key map is now purely "these sectors use this key": entry_for returns
Option and an LBA in no range is left untouched (no default-decrypt-
everything fallback). resolve_mux_key_map builds explicit content ranges
for every case — single-CPS keys each content extent, multi-CPS keys each
extent with the key that opens it, and FMTS fills the non-segment content
with the base Unit Key so a whole-disc read decrypts content and passes
nav/filesystem through. Combined with the fail-loud resolve, a map can
never silently apply a wrong key, and clear sectors are never scrambled.

decrypt_sectors_mapped skips a unit with no map entry; read_plan keeps an
unmapped unit (pass-through content) and drops only alternate-phase
forensic units. Tests updated to the Option semantics.
This commit is contained in:
Matthew Jackson
2026-07-23 12:58:35 -07:00
parent 1eb6910bdb
commit 279ba0dd7c
4 changed files with 122 additions and 98 deletions
+15 -7
View File
@@ -274,15 +274,23 @@ mod tests {
// sectors 937..=946 → LBA 1937..1947, key index 7.
assert_eq!(ranges[1], (1937, 1947, 7));
// The ranges drive an AacsKeyMap with the Unit Key (index 0) as default.
let map = crate::decrypt::AacsKeyMap::from_ranges(ranges, 0);
assert_eq!(map.key_idx_for(500), 0, "outside any segment → Unit Key");
assert_eq!(map.key_idx_for(1012), 5, "inside index-5 segment → key 5");
assert_eq!(map.key_idx_for(1940), 7, "inside index-7 segment → key 7");
// The ranges drive a positive AacsKeyMap: an LBA in no range has no key.
let map = crate::decrypt::AacsKeyMap::from_ranges(ranges);
assert_eq!(map.key_idx_for(500), None, "outside any segment → no key");
assert_eq!(
map.key_idx_for(1012),
Some(5),
"inside index-5 segment → key 5"
);
assert_eq!(
map.key_idx_for(1940),
Some(7),
"inside index-7 segment → key 7"
);
assert_eq!(
map.key_idx_for(1019),
0,
"segment end is exclusive → Unit Key"
None,
"segment end is exclusive → no key"
);
}