Revert "unlock: drive-features capability (stock riplock lift for any disc)"

This reverts commit 00e56dbb7d.
This commit is contained in:
Matthew Jackson
2026-07-01 09:34:58 -07:00
parent 00e56dbb7d
commit 852cee1ea0
3 changed files with 7 additions and 194 deletions
-83
View File
@@ -125,33 +125,6 @@ impl Unlocker for LibreDrive {
ctx.kind == crate::DiscKind::Unknown && profile::find_bundled(ctx.drive_id).is_some()
}
/// Lift riplock on a profiled drive with STOCK MMC commands — no firmware
/// unlock, so it is safe for ANY disc including a CSS DVD (which must stay in
/// stock mode). We issue SET STREAMING (0xB6) — the modern command a slot-
/// loading BD combo honors when it ignores the legacy SET CD SPEED — and then
/// SET CD SPEED (0xBB) as a fallback for drives that only take the old one.
/// Self-gating: no bundled profile → no-op. Best-effort throughout: a drive
/// that rejects either command still rips, just slower, so every error is
/// swallowed (including a transport fault — the real reads that follow will
/// surface a dead bus).
fn apply_drive_features(
&self,
scsi: &mut dyn ScsiTransport,
ctx: &UnlockCtx,
) -> std::result::Result<(), UnlockError> {
if profile::find_bundled(ctx.drive_id).is_none() {
return Ok(());
}
// SET STREAMING (max): CDB + 28-byte performance descriptor data-out.
let (cdb, mut descriptor) = crate::scsi::build_set_streaming(0xFFFF_FFFF);
let _ = scsi.execute(&cdb, DataDirection::ToDevice, &mut descriptor, 5_000);
// SET CD SPEED (max): no data-out.
let cdb = crate::scsi::build_set_cd_speed(0xFFFF);
let mut empty = [0u8; 0];
let _ = scsi.execute(&cdb, DataDirection::None, &mut empty, 5_000);
Ok(())
}
/// Firmware-unlock the drive and report its OEM Volume ID. The unlocked drive
/// serves CLEAR content, so `drive_unlocked: true` and there is no bus key.
///
@@ -322,60 +295,4 @@ mod tests {
.expect_err("no profile → NotApplicable");
assert_eq!(err, UnlockError::NotApplicable);
}
/// A transport that records the opcode of every CDB it is handed.
struct RecordingTransport {
opcodes: Vec<u8>,
}
impl ScsiTransport for RecordingTransport {
fn execute(
&mut self,
cdb: &[u8],
_dir: DataDirection,
_data: &mut [u8],
_timeout_ms: u32,
) -> crate::scsi::Result<ScsiResult> {
self.opcodes.push(cdb[0]);
Ok(ScsiResult {
status: 0,
bytes_transferred: 0,
sense: [0u8; 32],
})
}
}
/// A profiled drive gets the stock speed commands (SET STREAMING first, then
/// SET CD SPEED) — the riplock lift, for any disc.
#[test]
fn drive_features_issues_stock_speed_commands_on_profiled_drive() {
let mut t = RecordingTransport { opcodes: vec![] };
LibreDrive::new()
.apply_drive_features(&mut t, &ctx(&known_vid_drive_id()))
.expect("best-effort ok");
assert!(
t.opcodes.contains(&crate::scsi::SCSI_SET_STREAMING),
"SET STREAMING (0xB6) issued"
);
assert!(
t.opcodes.contains(&crate::scsi::SCSI_SET_CD_SPEED),
"SET CD SPEED (0xBB) issued"
);
}
/// An unrecognised drive: drive-features is a no-op (self-gating), so the
/// consumer can call it on every unlocker without side effects.
#[test]
fn drive_features_is_noop_on_unprofiled_drive() {
let mut t = RecordingTransport { opcodes: vec![] };
LibreDrive::new()
.apply_drive_features(
&mut t,
&ctx(&make_drive_id("FAKE-VND", "9.99", "XX12345", "")),
)
.expect("best-effort ok");
assert!(
t.opcodes.is_empty(),
"no commands issued for an unrecognised drive"
);
}
}
+7 -30
View File
@@ -102,39 +102,16 @@ pub enum UnlockError {
Transport,
}
/// An unlocker provides drive/disc capabilities: **drive features** (speed /
/// riplock lift — a property of the DRIVE, applied for any disc) and **bus
/// removal** (AACS bus-decrypt + VID, or a CSS handshake — gated on the disc).
/// Implementors are the self-contained modules in this crate; the consumer only
/// ever sees the trait, via [`all_unlockers`]. (Each module owns its own
/// conversion from its internal error to [`UnlockError`].)
/// An unlocker removes a drive-level bus-encryption barrier. Implementors are
/// the self-contained modules in this crate; the consumer only ever sees the
/// trait, via [`all_unlockers`]. (Each module owns its own conversion from its
/// internal error to [`UnlockError`].)
///
/// The two capabilities are independent so a disc can take one without the
/// other. A CSS DVD, for example, wants a matched drive's [`apply_drive_features`]
/// (speed) but must NOT take its firmware bus-unlock, which would break stock CSS
/// auth. Keeping them separate lets the consumer apply only what the current
/// (drive, disc) context needs.
///
/// [`apply_drive_features`]: Unlocker::apply_drive_features
/// NOTE: drive tuning (e.g. SET CD SPEED to lift riplock) is deliberately NOT
/// here — that is the consumer's concern, not bus removal.
pub trait Unlocker: Send + Sync {
/// True if this unlocker's bus-removal [`unlock`](Unlocker::unlock) applies to
/// the given context (drive id + disc kind).
/// True if this unlocker applies to the given context (drive id + disc kind).
fn matches(&self, ctx: &UnlockCtx) -> bool;
/// Apply drive-level feature tuning (max read speed / riplock lift) that this
/// unlocker enables purely by virtue of the DRIVE — **safe for any disc,
/// touches no bus encryption**. Self-gating: an unlocker that doesn't
/// recognise the drive returns `Ok(())` (a no-op), so the consumer can call
/// this on every unlocker regardless of disc kind. Best-effort: a rejected
/// command must NOT fail the rip (a slow drive still rips). Default: no-op.
fn apply_drive_features(
&self,
_scsi: &mut dyn ScsiTransport,
_ctx: &UnlockCtx,
) -> std::result::Result<(), UnlockError> {
Ok(())
}
/// Remove the bus-encryption barrier, returning what was learned.
fn unlock(
&self,
-81
View File
@@ -74,7 +74,6 @@ pub(crate) const SCSI_STATUS_CHECK_CONDITION: u8 = 0x02;
// Common opcodes used by the unlocker modules.
pub(crate) const SCSI_SET_CD_SPEED: u8 = 0xBB;
pub(crate) const SCSI_SET_STREAMING: u8 = 0xB6;
pub(crate) const SCSI_SEND_KEY: u8 = 0xA3;
pub(crate) const SCSI_REPORT_KEY: u8 = 0xA4;
pub(crate) const SCSI_READ_DISC_STRUCTURE: u8 = 0xAD;
@@ -99,83 +98,3 @@ pub(crate) fn build_set_cd_speed(read_speed: u16) -> [u8; 12] {
0x00,
]
}
/// Length of a SET STREAMING Performance Descriptor (MMC-6 §6.42).
pub(crate) const SET_STREAMING_DESCRIPTOR_LEN: usize = 28;
/// Build a SET STREAMING (0xB6) CDB + its 28-byte Performance Descriptor
/// requesting maximum read performance across the whole disc. This is the modern
/// riplock lift: many drives (notably slot-loading BD combos over a USB bridge)
/// ignore the legacy SET CD SPEED (0xBB) but honor SET STREAMING, and unlike a
/// firmware unlock it is a STOCK MMC command — safe to issue on a non-unlocked
/// drive, so it never disturbs stock CSS auth.
///
/// `read_kbps` is the requested read size per 1000 ms window; `0xFFFF_FFFF` asks
/// the drive for its maximum. Returns `(cdb, descriptor)`; the caller sends the
/// descriptor as the CDB's data-out payload. The CDB's Parameter List Length
/// (bytes 910, big-endian) is the descriptor length.
pub(crate) fn build_set_streaming(
read_kbps: u32,
) -> ([u8; 12], [u8; SET_STREAMING_DESCRIPTOR_LEN]) {
let len = SET_STREAMING_DESCRIPTOR_LEN as u16;
let cdb = [
SCSI_SET_STREAMING,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
(len >> 8) as u8, // Parameter List Length (MSB)
len as u8, // Parameter List Length (LSB)
0x00,
];
let mut d = [0u8; SET_STREAMING_DESCRIPTOR_LEN];
// byte 0: flags — RDD=0 (SET performance, don't restore defaults), Exact=0,
// RA=0. bytes 13 reserved.
// bytes 4..8: Start LBA = 0.
// bytes 8..12: End LBA = 0xFFFFFFFF (apply across the whole disc).
d[8..12].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
// bytes 12..16: Read Size (kB per Read Time window).
d[12..16].copy_from_slice(&read_kbps.to_be_bytes());
// bytes 16..20: Read Time = 1000 ms.
d[16..20].copy_from_slice(&1000u32.to_be_bytes());
// bytes 20..24 / 24..28: Write Size / Write Time (mirror read; unused for a
// read-only rip but the descriptor requires them).
d[20..24].copy_from_slice(&read_kbps.to_be_bytes());
d[24..28].copy_from_slice(&1000u32.to_be_bytes());
(cdb, d)
}
#[cfg(test)]
mod tests {
use super::*;
/// SET STREAMING (0xB6) wire format: opcode, the 28-byte Parameter List
/// Length in CDB bytes 910, and the max-speed performance descriptor
/// (whole-disc End LBA + the requested read size).
#[test]
fn set_streaming_cdb_layout() {
let (cdb, d) = build_set_streaming(0xFFFF_FFFF);
assert_eq!(cdb[0], SCSI_SET_STREAMING);
assert_eq!(
u16::from_be_bytes([cdb[9], cdb[10]]) as usize,
SET_STREAMING_DESCRIPTOR_LEN,
"param list length = descriptor length"
);
assert_eq!(d.len(), SET_STREAMING_DESCRIPTOR_LEN);
assert_eq!(&d[8..12], &[0xFF, 0xFF, 0xFF, 0xFF], "whole-disc End LBA");
assert_eq!(
u32::from_be_bytes([d[12], d[13], d[14], d[15]]),
0xFFFF_FFFF,
"read size requests max"
);
assert_eq!(
u32::from_be_bytes([d[16], d[17], d[18], d[19]]),
1000,
"read time window = 1000 ms"
);
}
}