aacs: sample only index-1 forensic segments for the online key query; hoist MIN_SAMPLE_UNITS into the base crate
This commit is contained in:
@@ -23,6 +23,22 @@ use crate::aacs::types::{UnitKey, Vid};
|
||||
use crate::disc::Key;
|
||||
use crate::error::Error;
|
||||
|
||||
/// Minimum encrypted-content unit samples a single online key request must carry.
|
||||
///
|
||||
/// The key service identifies a key by which of the submitted units it decrypts,
|
||||
/// so too few samples — especially on FMTS, where a segment interleaves several
|
||||
/// variants at the unit level — can return a key that matches an incidental unit
|
||||
/// rather than the one asked about (a false positive). This many distinct units
|
||||
/// make the request unambiguous.
|
||||
///
|
||||
/// Canonical here (the base crate) so BOTH consumers agree on one value: the
|
||||
/// online source in `freemkv-keysources` (which refuses to send an under-sampled
|
||||
/// request) re-exports it, and libfreemkv's own FMTS forensic query
|
||||
/// ([`crate::mux`]) sizes its per-segment batch by it. Layering forbids the
|
||||
/// reverse import (keysources depends on libfreemkv, not vice versa), so the
|
||||
/// value lives at the lower layer both share.
|
||||
pub const MIN_SAMPLE_UNITS: usize = 8;
|
||||
|
||||
/// The public AACS inputs a key source needs to look a disc up. Captured at
|
||||
/// scan; contains no secrets — only the disc identity and the on-disc AACS
|
||||
/// structures a source or key server may key on.
|
||||
|
||||
+24
-4
@@ -691,17 +691,37 @@ fn resolve_fmts_key_map(
|
||||
// decrypt-and-check: the array position IS the index. The first readable
|
||||
// segment whose batch yields the full set wins; a short (e.g. 1-key,
|
||||
// base-UK-shaped) response means that batch wasn't forensic (a wrong
|
||||
// feature-title mapping), so try the next segment. ─────────────────────────
|
||||
// feature-title mapping), so try the next segment.
|
||||
//
|
||||
// ANCHOR RULE: the query MUST sample an INDEX-1 segment. The key service only
|
||||
// returns the full set for the canonical anchor sample (a unit that decrypts
|
||||
// under the index-1 key); a batch from any other forensic index is rejected
|
||||
// (a base-UK-shaped miss). The `index == 1` filter guarantees every batch we
|
||||
// send is an anchor — and the forensic tag cycles 1..32 in file order, so
|
||||
// ~1-in-32 segments qualify (~25 across the feature), leaving ample read-fault
|
||||
// fallback within the `MAX_ANCHOR_ATTEMPTS` budget. ─────────────────────────
|
||||
const N_INDEX: usize = 32;
|
||||
// Each forensic batch carries the server's minimum-samples count (the same
|
||||
// disambiguation floor the online source enforces), drawn as even-phase units
|
||||
// to land one clean variant half.
|
||||
const BATCH_UNITS: usize = crate::keysource::MIN_SAMPLE_UNITS;
|
||||
// Read-fault fallback budget: how many INDEX-1 (anchor) segments to attempt
|
||||
// before giving up. Only matters when the leading anchor segments are
|
||||
// unreadable; each attempt is one server round-trip, so it is bounded.
|
||||
const MAX_ANCHOR_ATTEMPTS: usize = 16;
|
||||
let mut index_keys: Vec<[u8; 16]> = Vec::new();
|
||||
for seg in segments.iter().take(16) {
|
||||
for seg in segments
|
||||
.iter()
|
||||
.filter(|s| s.index == 1)
|
||||
.take(MAX_ANCHOR_ATTEMPTS)
|
||||
{
|
||||
let mut batch: Vec<Vec<u8>> = Vec::new();
|
||||
for p in 0..8usize {
|
||||
for p in 0..BATCH_UNITS {
|
||||
if let Some(c) = read_unit(reader, seg, p * 2) {
|
||||
batch.push(c);
|
||||
}
|
||||
}
|
||||
if batch.len() < 8 {
|
||||
if batch.len() < BATCH_UNITS {
|
||||
continue; // read fault / short tail
|
||||
}
|
||||
let fresh = fetch(&batch);
|
||||
|
||||
Reference in New Issue
Block a user