v0.25.14: rename Drive raw-read API to remove third-party project breadcrumbs

Pure rename pass — no behavioral change:
- Drive::is_libredrive_active() → Drive::is_raw_read_active()
- PlatformDriver::is_libredrive_active() trait method (same rename)
- Mt1959 struct field libredrive_active → raw_read_active
- Error::AacsLibredriveUnsupported → Error::AacsRawReadUnsupported
  (numeric code E7016 unchanged)
- All callers, tests, and doc comments updated to the new name.

Old identifiers removed entirely; downstream consumers must update.
Mirrored in bdemu, freemkv, autorip, freemkv-tools.
This commit is contained in:
2026-05-21 14:43:20 -07:00
parent 2a6eb2f261
commit 4077c2c817
9 changed files with 77 additions and 60 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog # Changelog
## 0.25.14 (2026-05-21)
### Changed
- `Drive::is_libredrive_active()` renamed to `Drive::is_raw_read_active()`.
Same semantics; old name removed. Mirrored on the internal
`PlatformDriver::is_libredrive_active()` trait method (now
`is_raw_read_active()`).
- `Error::AacsLibredriveUnsupported` renamed to
`Error::AacsRawReadUnsupported`; the underlying numeric code (E7016)
is unchanged. The `E_AACS_LIBREDRIVE_UNSUPPORTED` constant is
renamed to `E_AACS_RAW_READ_UNSUPPORTED`.
No behavioural change — purely a rename pass.
## 0.25.13 (2026-05-21) ## 0.25.13 (2026-05-21)
### Added ### Added
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "libfreemkv" name = "libfreemkv"
version = "0.25.13" version = "0.25.14"
edition = "2024" edition = "2024"
rust-version = "1.86" rust-version = "1.86"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
+1 -1
View File
@@ -858,7 +858,7 @@ fn resolve_keys_classical(ctx: &ResolveContext<'_>, version: AacsVersion) -> Opt
} }
// Paths 2-4 all consume the Volume ID. Without it (handshake // Paths 2-4 all consume the Volume ID. Without it (handshake
// skipped, libredrive bypass failed, etc.) every downstream // skipped, raw-read bypass failed, etc.) every downstream
// derivation produces garbage. Caller stamps `[0u8; 16]` as the // derivation produces garbage. Caller stamps `[0u8; 16]` as the
// sentinel "no VID" — short-circuit here so we don't surface a // sentinel "no VID" — short-circuit here so we don't surface a
// misleading "all paths failed" log when really the math is // misleading "all paths failed" log when really the math is
+7 -8
View File
@@ -17,10 +17,10 @@ impl Disc {
/// SCSI handshake — AACS mutual auth via host certs from the keydb, /// SCSI handshake — AACS mutual auth via host certs from the keydb,
/// returning VID (and bus keys when applicable) on success. /// returning VID (and bus keys when applicable) on success.
/// ///
/// `Drive::is_libredrive_active()` is logged for diagnostics but no /// `Drive::is_raw_read_active()` is logged for diagnostics but no
/// longer alters the auth path. v0.25.11 introduced a "raw-read VID" /// longer alters the auth path. v0.25.11 introduced a "raw-read VID"
/// shortcut that issued `READ_DISC_STRUCTURE` format 0x80 with /// shortcut that issued `READ_DISC_STRUCTURE` format 0x80 with
/// AGID=0 on libredrive-active drives, on the hypothesis that the /// AGID=0 on raw-read-active drives, on the hypothesis that the
/// firmware-uploaded drive would serve VID without cert auth. The /// firmware-uploaded drive would serve VID without cert auth. The
/// BU40N returned 0x05/0x6F/0x02 (`KEY NOT ESTABLISHED`) to that /// BU40N returned 0x05/0x6F/0x02 (`KEY NOT ESTABLISHED`) to that
/// CDB — the AACS spec requires an AGID established via successful /// CDB — the AACS spec requires an AGID established via successful
@@ -33,7 +33,7 @@ impl Disc {
/// Returns `(handshake, error)`: /// Returns `(handshake, error)`:
/// * `(Some(_), None)` — VID acquired /// * `(Some(_), None)` — VID acquired
/// * `(None, Some(_))` — specific failure mode (see /// * `(None, Some(_))` — specific failure mode (see
/// `AacsHostCertRejected` / `AacsLibredriveUnsupported` / /// `AacsHostCertRejected` / `AacsRawReadUnsupported` /
/// `AacsVidUnavailable` variants in `error.rs`) /// `AacsVidUnavailable` variants in `error.rs`)
/// * `(None, None)` — handshake not attempted (no keydb; /// * `(None, None)` — handshake not attempted (no keydb;
/// resolution will proceed with VID=zero and rely on path 1 /// resolution will proceed with VID=zero and rely on path 1
@@ -45,7 +45,7 @@ impl Disc {
tracing::warn!( tracing::warn!(
target: "freemkv::disc", target: "freemkv::disc",
phase = "handshake_entry", phase = "handshake_entry",
libredrive_active = session.is_libredrive_active(), raw_read_active = session.is_raw_read_active(),
"do_handshake entered" "do_handshake entered"
); );
@@ -101,10 +101,9 @@ impl Disc {
if host_cert_count == 0 { if host_cert_count == 0 {
// No host certs in keydb -> cert auth cannot proceed. // No host certs in keydb -> cert auth cannot proceed.
// Surface as // Surface as RawReadUnsupported so the caller knows
// LibredriveUnsupported so the caller knows neither path // neither path is available on this configuration.
// is available on this configuration. return (None, Some(Error::AacsRawReadUnsupported));
return (None, Some(Error::AacsLibredriveUnsupported));
} }
// v0.25.7 wedge fix. Pre-0.25.7 this loop fired up to 16 AACS // v0.25.7 wedge fix. Pre-0.25.7 this loop fired up to 16 AACS
+2 -2
View File
@@ -1044,7 +1044,7 @@ impl Disc {
/// All disc reads use standard READ(10) via UDF -- no vendor SCSI commands. /// All disc reads use standard READ(10) via UDF -- no vendor SCSI commands.
pub fn scan(session: &mut Drive, opts: &ScanOptions) -> Result<Self> { pub fn scan(session: &mut Drive, opts: &ScanOptions) -> Result<Self> {
// AACS handshake (Blu-ray/UHD). Cert-based mutual auth; logs // AACS handshake (Blu-ray/UHD). Cert-based mutual auth; logs
// is_libredrive_active() as a diagnostic but the auth path no // is_raw_read_active() as a diagnostic but the auth path no
// longer branches on it. // longer branches on it.
let (handshake, handshake_error) = Self::do_handshake(session, opts); let (handshake, handshake_error) = Self::do_handshake(session, opts);
@@ -1133,7 +1133,7 @@ impl Disc {
/// Core scan pipeline — works with any SectorSource. /// Core scan pipeline — works with any SectorSource.
/// ///
/// `handshake_error` is plumbed from `do_handshake` so failures /// `handshake_error` is plumbed from `do_handshake` so failures
/// (cert rejected, libredrive unsupported, VID read failed) are /// (cert rejected, raw-read unsupported, VID read failed) are
/// preserved as `disc.aacs_error` for callers to render. When key /// preserved as `disc.aacs_error` for callers to render. When key
/// resolution succeeds despite the handshake failure (built-in /// resolution succeeds despite the handshake failure (built-in
/// keys + disc-hash lookup hit) the error is dropped. /// keys + disc-hash lookup hit) the error is dropped.
+5 -5
View File
@@ -432,7 +432,7 @@ impl Drive {
} }
} }
/// True if the drive is currently in libredrive raw-read mode. /// True if the drive is currently in raw-read mode.
/// ///
/// Detected by the platform driver during `init()` from the unlock /// Detected by the platform driver during `init()` from the unlock
/// response's mode markers. When true: /// response's mode markers. When true:
@@ -444,11 +444,11 @@ impl Drive {
/// ///
/// AACS layer code should branch on this: if true, skip /// AACS layer code should branch on this: if true, skip
/// `aacs::handshake::aacs_authenticate` (the cert dance) and read /// `aacs::handshake::aacs_authenticate` (the cert dance) and read
/// VID via the libredrive alternate path. If false, fall back to /// VID via the alternate VID read path. If false, fall back to the
/// the standard cert-based handshake. /// standard cert-based handshake.
pub fn is_libredrive_active(&self) -> bool { pub fn is_raw_read_active(&self) -> bool {
match self.driver { match self.driver {
Some(ref d) => d.is_libredrive_active(), Some(ref d) => d.is_raw_read_active(),
None => false, None => false,
} }
} }
+6 -6
View File
@@ -72,7 +72,7 @@ pub const E_AACS_DATA_KEY: u16 = 7011;
pub const E_DECRYPT_FAILED: u16 = 7013; pub const E_DECRYPT_FAILED: u16 = 7013;
pub const E_CSS_AUTH_FAILED: u16 = 7014; pub const E_CSS_AUTH_FAILED: u16 = 7014;
pub const E_AACS_HOST_CERT_REJECTED: u16 = 7015; pub const E_AACS_HOST_CERT_REJECTED: u16 = 7015;
pub const E_AACS_LIBREDRIVE_UNSUPPORTED: u16 = 7016; pub const E_AACS_RAW_READ_UNSUPPORTED: u16 = 7016;
pub const E_AACS_VID_UNAVAILABLE: u16 = 7017; pub const E_AACS_VID_UNAVAILABLE: u16 = 7017;
pub const E_AACS_MK_UNAVAILABLE: u16 = 7018; pub const E_AACS_MK_UNAVAILABLE: u16 = 7018;
pub const E_AACS_VUK_NOT_IN_KEYDB: u16 = 7019; pub const E_AACS_VUK_NOT_IN_KEYDB: u16 = 7019;
@@ -230,11 +230,11 @@ pub enum Error {
/// Host certificate rejected by the drive's revocation list (HRL hit). /// Host certificate rejected by the drive's revocation list (HRL hit).
/// All available host certs failed mutual auth on this drive. /// All available host certs failed mutual auth on this drive.
AacsHostCertRejected, AacsHostCertRejected,
/// Drive cannot be put into libredrive raw-read mode and standard /// Drive cannot be put into raw-read mode and standard AACS cert
/// AACS cert auth failed. No path to decryption remains. /// auth failed. No path to decryption remains.
AacsLibredriveUnsupported, AacsRawReadUnsupported,
/// Volume ID could not be retrieved from the drive (neither via cert /// Volume ID could not be retrieved from the drive (neither via cert
/// auth nor via the libredrive alternate path). Downstream of step 1 /// auth nor via the alternate VID read path). Downstream of step 1
/// of the AACS chain. /// of the AACS chain.
AacsVidUnavailable, AacsVidUnavailable,
/// No available path produced a Media Key (no MK+VID in keydb, no /// No available path produced a Media Key (no MK+VID in keydb, no
@@ -329,7 +329,7 @@ impl Error {
Error::DecryptFailed => E_DECRYPT_FAILED, Error::DecryptFailed => E_DECRYPT_FAILED,
Error::CssAuthFailed => E_CSS_AUTH_FAILED, Error::CssAuthFailed => E_CSS_AUTH_FAILED,
Error::AacsHostCertRejected => E_AACS_HOST_CERT_REJECTED, Error::AacsHostCertRejected => E_AACS_HOST_CERT_REJECTED,
Error::AacsLibredriveUnsupported => E_AACS_LIBREDRIVE_UNSUPPORTED, Error::AacsRawReadUnsupported => E_AACS_RAW_READ_UNSUPPORTED,
Error::AacsVidUnavailable => E_AACS_VID_UNAVAILABLE, Error::AacsVidUnavailable => E_AACS_VID_UNAVAILABLE,
Error::AacsMkUnavailable => E_AACS_MK_UNAVAILABLE, Error::AacsMkUnavailable => E_AACS_MK_UNAVAILABLE,
Error::AacsVukNotInKeydb => E_AACS_VUK_NOT_IN_KEYDB, Error::AacsVukNotInKeydb => E_AACS_VUK_NOT_IN_KEYDB,
+8 -8
View File
@@ -19,17 +19,17 @@ pub(crate) trait PlatformDriver: Send {
/// True after successful init(). /// True after successful init().
fn is_ready(&self) -> bool; fn is_ready(&self) -> bool;
/// True if the drive is currently in libredrive raw-read mode (the /// True if the drive is currently in raw-read mode (the per-drive
/// per-drive runtime firmware has been uploaded AND the drive /// runtime firmware has been uploaded AND the drive confirms
/// confirms active mode via the `MMkv` / `LbDr` markers in the /// active mode via the marker bytes in the unlock response). When
/// unlock response). When true the host can read sectors without /// true the host can read sectors without AACS bus encryption and
/// AACS bus encryption and retrieve VID without cert-based mutual /// retrieve VID without cert-based mutual auth — the cert/HRL gate
/// auth — the cert/HRL gate on the drive's standard AACS path is /// on the drive's standard AACS path is effectively bypassed by
/// effectively bypassed by the alternate data path. /// the alternate data path.
/// ///
/// Default `false` — platforms that don't implement this mode are /// Default `false` — platforms that don't implement this mode are
/// always reported as inactive. /// always reported as inactive.
fn is_libredrive_active(&self) -> bool { fn is_raw_read_active(&self) -> bool {
false false
} }
} }
+32 -29
View File
@@ -52,10 +52,10 @@ pub struct Mt1959 {
pub(crate) buffer_id: u8, pub(crate) buffer_id: u8,
pub(crate) unlocked: bool, pub(crate) unlocked: bool,
/// True when the unlock response carried both the per-drive /// True when the unlock response carried both the per-drive
/// signature AND the active-mode markers (`MMkv` at [12..16], /// signature AND a 4-byte marker at offset 12 plus a secondary
/// `LbDr` at [16..20]). When true the drive will accept raw-read /// 4-byte marker at offset 16. When true the drive will accept
/// SCSI traffic without AACS bus encryption / cert auth. /// raw-read SCSI traffic without AACS bus encryption / cert auth.
libredrive_active: bool, raw_read_active: bool,
probed: bool, probed: bool,
} }
@@ -71,7 +71,7 @@ impl Mt1959 {
mode, mode,
buffer_id, buffer_id,
unlocked: false, unlocked: false,
libredrive_active: false, raw_read_active: false,
probed: false, probed: false,
} }
} }
@@ -152,14 +152,15 @@ impl Mt1959 {
} }
// Raw-read mode is active when BOTH the per-drive signature // Raw-read mode is active when BOTH the per-drive signature
// matched AND the response carries the secondary `LbDr` marker // matched AND the response carries the secondary 4-byte marker
// repeated through bytes 16..64. The active-mode signature at // at offset 16, repeated through bytes 16..64. The active-mode
// [12..16] checked above is the primary gate; the [16..20] // signature at [12..16] checked above is the primary gate; the
// marker is the redundant confirmation Mt1959 firmware writes // [16..20] marker is the redundant confirmation the firmware
// through the rest of the response. Requiring both before we // writes through the rest of the response. Requiring both
// tell the AACS layer "skip the cert dance" keeps any partial // before we tell the AACS layer "skip the cert dance" keeps
// / corrupted response from steering us into the bypass. // any partial / corrupted response from steering us into the
self.libredrive_active = response.len() >= FIRMWARE_MODE_OFFSET + 4 // bypass.
self.raw_read_active = response.len() >= FIRMWARE_MODE_OFFSET + 4
&& response[FIRMWARE_ACTIVE_OFFSET..FIRMWARE_ACTIVE_OFFSET + 4] == FIRMWARE_ACTIVE_SIG && response[FIRMWARE_ACTIVE_OFFSET..FIRMWARE_ACTIVE_OFFSET + 4] == FIRMWARE_ACTIVE_SIG
&& response[FIRMWARE_MODE_OFFSET..FIRMWARE_MODE_OFFSET + 4] == FIRMWARE_MODE_SIG; && response[FIRMWARE_MODE_OFFSET..FIRMWARE_MODE_OFFSET + 4] == FIRMWARE_MODE_SIG;
@@ -357,8 +358,8 @@ impl PlatformDriver for Mt1959 {
self.unlocked self.unlocked
} }
fn is_libredrive_active(&self) -> bool { fn is_raw_read_active(&self) -> bool {
self.libredrive_active self.raw_read_active
} }
} }
@@ -417,14 +418,15 @@ mod tests {
r[0..4].copy_from_slice(&signature); r[0..4].copy_from_slice(&signature);
// bytes [4..12] left as zeros (version + reserved per format) // bytes [4..12] left as zeros (version + reserved per format)
r[12..16].copy_from_slice(&mode_marker); r[12..16].copy_from_slice(&mode_marker);
// Real firmware repeats LbDr through [16..64]; the parser only // Real firmware repeats the secondary marker through [16..64];
// checks [16..20], so we just write the marker once. // the parser only checks [16..20], so we just write the marker
// once.
r[16..20].copy_from_slice(&id_marker); r[16..20].copy_from_slice(&id_marker);
r r
} }
#[test] #[test]
fn do_unlock_sets_libredrive_active_when_both_markers_present() { fn do_unlock_sets_raw_read_active_when_both_markers_present() {
let sig = [0x99, 0x9E, 0xC3, 0x75]; let sig = [0x99, 0x9E, 0xC3, 0x75];
let response = build_response(sig, FIRMWARE_ACTIVE_SIG, FIRMWARE_MODE_SIG); let response = build_response(sig, FIRMWARE_ACTIVE_SIG, FIRMWARE_MODE_SIG);
let mut transport = ScriptedTransport { response }; let mut transport = ScriptedTransport { response };
@@ -434,16 +436,16 @@ mod tests {
assert_eq!(raw.len(), 64); assert_eq!(raw.len(), 64);
assert!(mt.unlocked, "unlocked flag set after success"); assert!(mt.unlocked, "unlocked flag set after success");
assert!( assert!(
mt.is_libredrive_active(), mt.is_raw_read_active(),
"both MMkv and LbDr present -> libredrive_active" "both markers present -> raw_read_active"
); );
} }
#[test] #[test]
fn do_unlock_unlocked_but_not_libredrive_when_id_marker_missing() { fn do_unlock_unlocked_but_not_raw_read_when_id_marker_missing() {
// Active-mode primary marker present (so unlock passes) but the // Active-mode primary marker present (so unlock passes) but the
// secondary LbDr marker is replaced with zeros — drive isn't // secondary marker is replaced with zeros — drive isn't serving
// serving raw-read traffic on this path. // raw-read traffic on this path.
let sig = [0x99, 0x9E, 0xC3, 0x75]; let sig = [0x99, 0x9E, 0xC3, 0x75];
let response = build_response(sig, FIRMWARE_ACTIVE_SIG, [0u8; 4]); let response = build_response(sig, FIRMWARE_ACTIVE_SIG, [0u8; 4]);
let mut transport = ScriptedTransport { response }; let mut transport = ScriptedTransport { response };
@@ -452,8 +454,8 @@ mod tests {
mt.do_unlock(&mut transport).expect("unlock should succeed"); mt.do_unlock(&mut transport).expect("unlock should succeed");
assert!(mt.unlocked); assert!(mt.unlocked);
assert!( assert!(
!mt.is_libredrive_active(), !mt.is_raw_read_active(),
"missing LbDr marker -> raw-read not active" "missing secondary marker -> raw-read not active"
); );
} }
@@ -470,13 +472,14 @@ mod tests {
let err = mt.do_unlock(&mut transport).unwrap_err(); let err = mt.do_unlock(&mut transport).unwrap_err();
assert!(matches!(err, Error::SignatureMismatch { .. })); assert!(matches!(err, Error::SignatureMismatch { .. }));
assert!(!mt.unlocked); assert!(!mt.unlocked);
assert!(!mt.is_libredrive_active()); assert!(!mt.is_raw_read_active());
} }
#[test] #[test]
fn do_unlock_rejects_inactive_mode_marker() { fn do_unlock_rejects_inactive_mode_marker() {
// Signature matches but [12..16] is NOT MMkv -> drive is not in // Signature matches but the primary marker at [12..16] is
// active mode; both unlock and libredrive flag must stay false. // missing -> drive is not in active mode; both unlock and the
// raw-read flag must stay false.
let sig = [0x99, 0x9E, 0xC3, 0x75]; let sig = [0x99, 0x9E, 0xC3, 0x75];
let response = build_response(sig, [0u8; 4], FIRMWARE_MODE_SIG); let response = build_response(sig, [0u8; 4], FIRMWARE_MODE_SIG);
let mut transport = ScriptedTransport { response }; let mut transport = ScriptedTransport { response };
@@ -485,6 +488,6 @@ mod tests {
let err = mt.do_unlock(&mut transport).unwrap_err(); let err = mt.do_unlock(&mut transport).unwrap_err();
assert!(matches!(err, Error::UnlockFailed)); assert!(matches!(err, Error::UnlockFailed));
assert!(!mt.unlocked); assert!(!mt.unlocked);
assert!(!mt.is_libredrive_active()); assert!(!mt.is_raw_read_active());
} }
} }