unlock: finalize Unlocker 3-capability contract

Rename the trait to a generic, drive-neutral capability contract so future
unlockers don't conform to LibreDrive specifics:

  - unlock(...)   -> unlock_drive(...)        (the one required capability)
  - read_vid(...) -> read_volume_id(...)      (no-op default)
  - add set_max_read_speed(...)               (no-op default)

The trait doc now states the contract in one place: unlockers are optional
drive-capability providers; the AACS layer is the always-present baseline and
falls back to the full cert handshake when no unlocker matches. Implement only
the capabilities your drive supports.

Registry: route_unlock now calls unlock_drive; unlocker_read_vid renamed to
unlocker_read_volume_id; add unlocker_set_max_read_speed (mirrors route_unlock
resolution, first matching unlocker, no-op if none match). drive::init calls
it on a matched drive in the post-unlock path; a speed-set failure is logged
and does not fail the rip. encrypt.rs handshake updated to the new VID helper.

Tests updated for the renames; added a set_max_read_speed routing test
(match invokes, no-match is a safe no-op).
This commit is contained in:
Matthew Jackson
2026-06-22 11:05:21 -07:00
parent 159e967760
commit 25acd09504
3 changed files with 141 additions and 49 deletions
+13
View File
@@ -399,6 +399,19 @@ impl Drive {
let r = match r {
Ok(Some(name)) => {
self.unlocker_name = Some(name);
// The matched unlocker may also be able to raise the drive to
// its maximum read speed. Best-effort: a failure here must NOT
// fail the rip — a slow drive still rips. Log and continue.
if let Err(e) =
crate::unlock::unlocker_set_max_read_speed(self.scsi.as_mut(), &self.drive_id)
{
tracing::warn!(
target: "freemkv::drive",
phase = "init",
error = ?e,
"unlocker set_max_read_speed failed; continuing at current speed"
);
}
Ok(())
}
// No unlocker matched: not an error — fall through to OEM route.