Constrain five behaviours that mutation testing showed nothing constrained

Fifteen surviving mutants killed, from the highest-risk class: functions a
mutant could replace wholesale with a constant while all 2,555 tests
passed. None of the code was wrong. In every case a test was absent, which
is why eight rounds of reading never found any of them.

The one that generalises is in sector/mod.rs. Its existing test READS as
covering `read_sectors` on the `&mut dyn SectorSource` forwarding impl —
it takes a `&mut dyn`, calls the method, checks the spy. But the receiver
auto-derefs and dispatches through the vtable straight to the spy, so the
forwarding body is never entered. An earlier round hit this exact trap on
`set_unit_base` and fixed it with a generic helper; the read path kept the
test that looked right. Verified by stubbing the forwarding impl to Ok(0):
the new test fails, the old one passes. That makes a tenth distinct shape
of bad test in this audit, and the mutation list is how to find the rest —
any forwarding-impl method in it has the same problem.

decrypt.rs's two existing gate tests assert only `dropped == 0`, which is
precisely what the `Ok(0)` mutant returns; one asserts nothing else at all.
A wrapper that decrypts nothing therefore looked correct while the caller
muxed scrambled MPEG. Now pinned by descrambling a real CSS sector and
comparing against the plaintext it was built from — not against a
re-derived descramble, which would only assert the code agrees with
itself.

css/mod.rs's `is_scrambled_uncracked` turns out to have no production
callers at all; the enum is matched directly. Its three tests all assert
only the true direction, which is exactly why the `-> true` mutant
survived. It is public API, so a consumer routing on it would, under that
mutant, refuse to rip every clear DVD.

aacs/inf.rs's MKB drive read had no test whatsoever. Now pinned
byte-for-byte across multi-pack concatenation, the single-pack case, a
genuinely empty response, and error propagation — an unreadable MKB must
surface as an error, not as an empty one.

aacs/derive.rs's nine mutants are killed with planted MKBs built by
inverting the AACS relations, so no real key material is involved. The
assertions land on the derived Media Key rather than the intermediate
positions: a recovered position that does not actually walk to the planted
key is no better than None. A fixture-guard test asserts the planted MKB
parses, since an unparseable one would make every `-> None` body look
right.

2570 lib tests, debug and release.
This commit is contained in:
Matthew Jackson
2026-07-30 12:41:28 -07:00
parent 93e1436fc0
commit 8d4a6d54a4
5 changed files with 711 additions and 0 deletions
+65
View File
@@ -960,6 +960,71 @@ mod tests {
);
}
/// `is_scrambled_uncracked` is the predicate form of the Cracked /
/// Unencrypted / ScrambledUncracked split that round 7 introduced precisely
/// because conflating those cases made an uncrackable disc exit 0 with
/// garbage output. It is a public API predicate, so a consumer of this crate
/// can route on it in place of matching the enum.
///
/// Every existing use of it asserts only the TRUE direction (the
/// ScrambledUncracked case). Nothing anywhere asserted it is FALSE for the
/// other two variants, so a body that answered "yes, uncrackable" to
/// everything was indistinguishable: a genuinely clear DVD and a
/// successfully cracked one would both be routed to `CssNoDiscKey` /
/// `CssKeyMissing` and refuse to rip.
///
/// All three outcomes here come from real `crack_key_outcome` scans, not
/// hand-built enum values, so the predicate is checked against the verdicts
/// the scanner actually produces.
#[test]
fn is_scrambled_uncracked_is_true_for_that_case_and_false_for_the_other_two() {
let extents = [Extent {
start_lba: 1000,
sector_count: 50,
}];
// Cracked: a real Stevenson-crackable sector in an otherwise clear scan.
let title_key = [0x42, 0x13, 0x37, 0xBE, 0xEF];
let seed = [0x11, 0x22, 0x33, 0x44, 0x55];
let mut cracked_src = MockSource::new(0x00);
cracked_src.crackable = Some((1003, crackable_sector(&title_key, &seed, 8)));
let cracked = crack_key_outcome(&mut cracked_src, &extents, 4, None);
assert!(
matches!(cracked, CrackOutcome::Cracked(_)),
"fixture malformed — expected a real crack, got {cracked:?}"
);
assert!(
!cracked.is_scrambled_uncracked(),
"a disc whose key WAS recovered is not scrambled-uncracked; saying \
so aborts a rip that had its key in hand"
);
// Unencrypted: scramble flag never set across the scan.
let mut clear_src = MockSource::new(0x00);
let clear = crack_key_outcome(&mut clear_src, &extents, 4, None);
assert!(
matches!(clear, CrackOutcome::Unencrypted),
"fixture malformed — expected Unencrypted, got {clear:?}"
);
assert!(
!clear.is_scrambled_uncracked(),
"a genuinely plaintext disc is not scrambled-uncracked; saying so \
turns every unencrypted DVD into a hard CSS key error"
);
// ScrambledUncracked: scrambled sectors seen, no crackable crib.
let mut locked_src = MockSource::new(0x30);
let locked = crack_key_outcome(&mut locked_src, &extents, 4, None);
assert!(
matches!(locked, CrackOutcome::ScrambledUncracked),
"fixture malformed — expected ScrambledUncracked, got {locked:?}"
);
assert!(
locked.is_scrambled_uncracked(),
"scrambled sectors seen and no key recovered IS the hard-failure case"
);
}
/// `resolve_dvd_title_key` is the SINGLE shared per-title CSS step both read
/// paths (`build_iso_pipeline` multi-pass and `DiscStream::new` single-pass)
/// call, so these pin its full contract at the shared boundary.