From e51fda9bcd9dba203c30c55643fa045f150baa99 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:48:30 -0700 Subject: [PATCH] docs: move drive/firmware internals notes to the private repo; drop their pointers [skip-media] --- src/css/mod.rs | 32 ++++++++++++----------------- src/ld/mod.rs | 11 +++++----- src/ld/platform/mt1959/mod.rs | 28 ++++++++++++------------- src/ld/platform/mt1959/variant_b.rs | 8 ++------ src/renesas/mod.rs | 16 +++++++-------- src/vid.rs | 2 +- 6 files changed, 41 insertions(+), 56 deletions(-) diff --git a/src/css/mod.rs b/src/css/mod.rs index 6ef8b1f..25702d1 100644 --- a/src/css/mod.rs +++ b/src/css/mod.rs @@ -14,7 +14,7 @@ use crate::scsi::{DataDirection, ScsiTransport}; // Issue ONE CSS bus-auth CDB, honouring the transport contract: treats a // non-zero status as CssAuthFailed and checks bytes_transferred >= min_bytes -// rather than trusting an unfilled buffer. See docs/css-mod.md#css_scsi. +// rather than trusting an unfilled buffer. fn css_scsi( scsi: &mut dyn ScsiTransport, cdb: &[u8], @@ -202,7 +202,7 @@ impl crate::Unlocker for DvdUnlocker { fn name(&self) -> &'static str { // "DVD" names the medium this bus-auth unlocks, not the CSS scheme: // the bus-auth runs on any DVD regardless of whether the content is - // actually scrambled. See docs/css-mod.md#dvdunlocker-name. + // actually scrambled. "DVD" } @@ -240,7 +240,7 @@ impl crate::Unlocker for DvdUnlocker { // Probes GET CONFIGURATION current-profile (DVD family 0x0010..=0x001F) so // DvdUnlocker can self-verify against the drive rather than trust the -// caller's DiscKind. See docs/css-mod.md#mounted_disc_is_dvd. +// caller's DiscKind. fn mounted_disc_is_dvd( scsi: &mut dyn ScsiTransport, ) -> std::result::Result { @@ -300,7 +300,6 @@ fn unlock_css_reads_inner(scsi: &mut dyn ScsiTransport, _lba: u32) -> Result<()> tracing::debug!(target: "freemkv::css", agid, "css unlock: bus authentication ok"); // Disc-key REPORT KEY: best-effort only, for firmware that ties part of // its read-unlock to it; bytes are unused and failure is non-fatal. - // See docs/css-mod.md#disc-key-report-key (7014 bug history). if let Err(e) = read_disc_key(scsi, agid) { tracing::debug!(target: "freemkv::css", error_code = e.code(), "css unlock: disc-key REPORT KEY skipped (non-fatal)"); } @@ -311,7 +310,7 @@ fn unlock_css_reads_inner(scsi: &mut dyn ScsiTransport, _lba: u32) -> Result<()> // ── Step 1: Bus Authentication ──────────────────────────────────────────── // Runs the CSS bus-auth handshake to set ASF=1 (invalidate AGIDs, allocate, -// challenge/response). Returns the AGID; no bus key is derived. See docs/css-mod.md. +// challenge/response). Returns the AGID; no bus key is derived. fn establish_authenticated_session(scsi: &mut dyn ScsiTransport) -> Result { // Invalidate all AGIDs via REPORT KEY format 0x3F. This is also what makes // an abandoned AGID self-heal — which is not a reason to abandon one. @@ -438,7 +437,7 @@ fn authenticate_with_agid(scsi: &mut dyn ScsiTransport, agid: u8) -> Result<()> // ── Step 2: Disc Key ────────────────────────────────────────────────────── // Issues READ DVD STRUCTURE format 0x02 (opcode 0xAD) purely for its -// bus-auth side effect; returned bytes are unused. See docs/css-mod.md#read_disc_key. +// bus-auth side effect; returned bytes are unused. fn read_disc_key(scsi: &mut dyn ScsiTransport, agid: u8) -> Result<()> { // READ DVD STRUCTURE, format 0x02 (disc key), 2048+4 bytes let alloc_len: u16 = 2048 + 4; @@ -464,7 +463,7 @@ fn read_disc_key(scsi: &mut dyn ScsiTransport, agid: u8) -> Result<()> { fn crypt_key(key_type: usize, variant: u8, challenge: &[u8; 10]) -> [u8; 5] { // key_type indexes PERM_CHALLENGE ([_;3]); variant indexes VARIANTS/ // PERM_VARIANT ([_;32]). Real (not debug-only) asserts catch an OOB index even - // in release; cost is nil on this once-per-disc path. See docs/css-mod.md. + // in release; cost is nil on this once-per-disc path. assert!(key_type < 3, "crypt_key: key_type out of range"); assert!((variant as usize) < 32, "crypt_key: variant out of range"); let perm = &PERM_CHALLENGE[key_type]; @@ -620,7 +619,7 @@ mod tests { // SECURITY REGRESSION GUARD: scans source files for a `tracing` field // binding a forbidden key name to a value expression (only a string - // literal or `_fp` field is allowed). See docs/css-mod.md#key-guard. + // literal or `_fp` field is allowed). #[test] fn no_key_bytes_in_instrumentation() { use std::path::Path; @@ -776,7 +775,7 @@ mod tests { // ── CSS constant-table integrity ─────────────────────────────────────── // Each PERM_CHALLENGE row must be a permutation of indices 0..10; a - // non-permutation would drop/duplicate bytes. See docs/css-mod.md#perm-challenge-rows. + // non-permutation would drop/duplicate bytes. #[test] fn perm_challenge_rows_are_permutations() { for (row, perm) in PERM_CHALLENGE.iter().enumerate() { @@ -795,7 +794,6 @@ mod tests { // Each PERM_VARIANT row must map the 32 variants to 32 distinct 5-bit // values; a collision would make two variants indistinguishable. - // See docs/css-mod.md#perm-variant-rows. #[test] fn perm_variant_rows_are_permutations_of_0_31() { for (row, perm) in PERM_VARIANT.iter().enumerate() { @@ -816,7 +814,7 @@ mod tests { // ── crypt_key behaviour ──────────────────────────────────────────────── // crypt_key's result must depend on every challenge byte: flipping any - // single byte must change the output. See docs/css-mod.md#crypt-key-byte-dependence. + // single byte must change the output. #[test] fn crypt_key_depends_on_every_challenge_byte() { let base: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; @@ -834,7 +832,7 @@ mod tests { // crypt_key(0, v, ..) must be distinct for each of the 32 variants: // bus-auth brute-forces the variant by matching against key1, so a - // collision could select the wrong one. See docs/css-mod.md. + // collision could select the wrong one. #[test] fn crypt_key_type0_distinct_per_variant() { let challenge: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; @@ -851,7 +849,6 @@ mod tests { // crypt_key's `key_type < 3` debug_assert must fire for key_type 3 // (which would otherwise index PERM_CHALLENGE out of bounds). - // See docs/css-mod.md#crypt-key-preconditions. #[test] #[should_panic] fn crypt_key_rejects_out_of_range_key_type() { @@ -861,7 +858,6 @@ mod tests { // crypt_key's `variant < 32` debug_assert must fire for variant 32 // (which would otherwise index VARIANTS/PERM_VARIANT out of bounds). - // See docs/css-mod.md#crypt-key-preconditions. #[test] #[should_panic] fn crypt_key_rejects_out_of_range_variant() { @@ -895,7 +891,6 @@ mod tests { // The key-format field is masked to 6 bits so a format with high bits // set (e.g. 0xFF) cannot corrupt the AGID bits of byte 10. - // See docs/css-mod.md#cdb-builders. #[test] fn report_key_cdb_masks_format_to_6_bits() { let cdb = report_key_cdb(0, 0xFF, 8); @@ -904,7 +899,7 @@ mod tests { // send_key_cdb encodes a 12-byte MMC SEND KEY (opcode 0xA3) CDB with the // parameter-list length at bytes 8-9 (big-endian) and AGID/format at - // byte 10. See docs/css-mod.md#cdb-builders. + // byte 10. #[test] fn send_key_cdb_matches_mmc_layout() { let cdb = send_key_cdb(0b11, 0x03, 0x000C); // AGID=3, format=3, param_len=12 @@ -921,7 +916,6 @@ mod tests { // Allocation length > 255 must split across bytes 8 (high) and 9 (low) // as a 16-bit big-endian field, e.g. 0x0804 (the disc-key block size). - // See docs/css-mod.md#cdb-builders. #[test] fn report_key_cdb_alloc_len_is_16bit_big_endian() { let cdb = report_key_cdb(0, 0x00, 0x0804); @@ -1012,7 +1006,7 @@ mod tests { // Defect-7 regression: a transport fault on the first probe command // must abort as Transport, not fall through to NotApplicable (which - // let the consumer keep probing a dead bus). See docs/css-mod.md. + // let the consumer keep probing a dead bus). #[test] fn transport_fault_probing_for_a_dvd_aborts() { let id = DriveId::default(); @@ -1039,7 +1033,7 @@ mod tests { // Defect-2 regression: a DVD is mounted, then the bus dies mid // bus-auth — must abort as Transport, not collapse to CssAuthFailed / - // NotApplicable. See docs/css-mod.md. + // NotApplicable. #[test] fn transport_fault_during_bus_auth_aborts() { let id = DriveId::default(); diff --git a/src/ld/mod.rs b/src/ld/mod.rs index 4b618c0..a20b2f5 100644 --- a/src/ld/mod.rs +++ b/src/ld/mod.rs @@ -8,7 +8,7 @@ // `cdb` is only the bdemu emulator's wire format (real unlocking uses profile // templates), gated behind `emulation`; also compiled under `cfg(test)` so its -// wire-format tests run in default-feature CI. See docs/ld-mod.md — cdb gating. +// wire-format tests run in default-feature CI. #[cfg(any(feature = "emulation", test))] mod cdb; mod error; @@ -21,7 +21,7 @@ use crate::{DriveId, UnlockCtx, UnlockError, Unlocked, Unlocker}; // ── Public profile catalog ────────────────────────────────────────────────── // Only the catalog is public (supported-drive lookup, used by bdemu); the -// unlock mechanism stays private. See docs/ld-mod.md — public catalog design. +// unlock mechanism stays private. pub use profile::{DriveProfile as Profile, Identity, Platform, ProfileMatch, Profiles}; @@ -122,7 +122,7 @@ impl LdUnlocker { impl LdUnlocker { // The MediaTek firmware unlock. Since it removes AACS at the drive (clear // content), this one op satisfies both features and bus-removal, so both - // trait methods delegate here. See docs/ld-mod.md — firmware_unlock contract. + // trait methods delegate here. fn firmware_unlock( &self, scsi: &mut dyn ScsiTransport, @@ -144,7 +144,7 @@ impl LdUnlocker { mt.init(scsi)?; // `init` only proves the handshake completed, not that the drive reached // extended-access state. Reporting unlocked off `init` alone shipped - // ciphertext at rc=0. See docs/ld-mod.md — half-unlock fallback. + // ciphertext at rc=0. if !mt.is_unlocked() { tracing::warn!( target: "freemkv::disc", @@ -159,7 +159,7 @@ impl LdUnlocker { if let Err(e) = mt.probe_disc(scsi) { // A transport fault here is a dead bus, not a calibration miss — most // profiles never touch the bus again, so this was the only dead-bus - // signal. See docs/ld-mod.md — probe_disc dead-bus classification. + // signal. if e.is_transport_failure() { tracing::warn!( target: "freemkv::disc", @@ -432,7 +432,6 @@ mod tests { // THE probe-disc dead-bus test: bus dies during speed calibration after a // full unlock; must abort with Transport, not report a successful unlock. - // See docs/ld-mod.md — probe_disc dead-bus test / mutation notes. #[test] fn transport_fault_during_probe_disc_is_transport_not_a_successful_unlock() { let id = known_vid_drive_id(); diff --git a/src/ld/platform/mt1959/mod.rs b/src/ld/platform/mt1959/mod.rs index 05d5588..f0a05fa 100644 --- a/src/ld/platform/mt1959/mod.rs +++ b/src/ld/platform/mt1959/mod.rs @@ -127,7 +127,7 @@ impl Mt1959 { let result = scsi.execute(&cdb, DataDirection::FromDevice, buf, 5_000)?; // A drive sense arrives as `Ok` with a non-zero status; a merely // SHORT response is the drive answering badly — neither is a dead - // bus. See docs/mt1959.md — short-probe-not-transport-failure. + // bus. if result.status != 0 || result.bytes_transferred != expected { return Err(Error::Scsi { opcode: SCSI_READ_BUFFER, @@ -181,7 +181,7 @@ impl Mt1959 { // `response` is a fixed 64-byte buffer; the meaningful bound is how // many bytes the drive actually delivered. Require enough bytes to - // validate before checking markers, up front. See docs/mt1959.md — min-unlock-response-length. + // validate before checking markers, up front. let n = result.bytes_transferred.min(response.len()); if n < MIN_UNLOCK_RESPONSE { tracing::debug!( @@ -206,7 +206,6 @@ impl Mt1959 { // Extended-access state requires the signature match AND both the // primary marker at [12..16] AND the secondary marker at [16..20]. - // See docs/mt1959.md — unlock-marker-gating. self.unlocked = response[FIRMWARE_MODE_OFFSET..FIRMWARE_MODE_OFFSET + 4] == FIRMWARE_MODE_SIG; @@ -282,7 +281,7 @@ impl Mt1959 { return Err(Error::UnlockFailed); } // A dead bus must abort here, not be retried three times as - // firmware reloads. See docs/mt1959.md — run-init-transport-fault. + // firmware reloads. Err(e) if e.is_transport_failure() => { tracing::warn!( target: "freemkv::disc", @@ -349,7 +348,7 @@ impl Mt1959 { } // Detect disc type from capacity to select probe mode (BD vs UHD - // init address). See docs/mt1959.md — probe-init-address-detection. + // init address). let cap_cdb = [ SCSI_READ_CAPACITY, 0x00, @@ -368,7 +367,7 @@ impl Mt1959 { let cap = scsi.execute(&cap_cdb, DataDirection::FromDevice, &mut cap_buf, 5_000)?; let disc_sectors = if cap.status == 0 && cap.bytes_transferred >= 4 { // last_lba + 1 = sector count; saturate on the 32-bit-overflow - // sentinel rather than wrap to 0. See docs/mt1959.md — capacity-saturation. + // sentinel rather than wrap to 0. u32::from_be_bytes([cap_buf[0], cap_buf[1], cap_buf[2], cap_buf[3]]).saturating_add(1) } else { 0 @@ -413,7 +412,7 @@ impl Mt1959 { { // A dead bus MUST keep its transport classification; a // short/rejected reply is the drive's own failure and stays - // `UnlockFailed`. See docs/mt1959.md — probe-failure-classification. + // `UnlockFailed`. if e.is_transport_failure() { return Err(e); } @@ -423,7 +422,6 @@ impl Mt1959 { } // Pass 2: continue past the coarse range (do not restart at 0). - // See docs/mt1959.md — probe-pass-overlap-fix. let mut addr: u32 = PROBE_COARSE_END as u32; while addr < PROBE_FINE_END { let mut resp = [0u8; PROBE_RESPONSE_SIZE as usize]; @@ -436,7 +434,7 @@ impl Mt1959 { ) { // Fine probing is best-effort — a short/rejected reply just ends // the sweep early, but a transport fault must keep its - // classification and abort. See docs/mt1959.md — probe-pass2-transport-fault. + // classification and abort. if e.is_transport_failure() { tracing::warn!( target: "freemkv::disc", @@ -696,7 +694,7 @@ mod tests { // THE defect-15 test: a zero-byte response used to skip every marker // check (each individually `n >= ..` guarded) and report a fully - // unlocked drive. See docs/mt1959.md — defect-15. + // unlocked drive. #[test] fn do_unlock_rejects_a_zero_length_response() { let sig = [0x99, 0x9E, 0xC3, 0x75]; @@ -776,7 +774,7 @@ mod tests { // THE defect-4 test: a dead bus must abort `run_init` at once, not // re-upload firmware on all three attempts and return the generic - // `UnlockFailed`. See docs/mt1959.md — defect-4. + // `UnlockFailed`. #[test] fn run_init_aborts_on_a_transport_fault_without_reloading_firmware() { let mut t = @@ -894,7 +892,7 @@ mod tests { // THE probe pass-1 dead-bus test: a bus that dies during the coarse // probe must keep its transport classification so the caller aborts - // with `Transport`. See docs/mt1959.md — pass1-dead-bus-mutation. + // with `Transport`. #[test] fn transport_fault_during_pass1_probe_stays_a_transport_failure() { let mut t = ProbeFaultsTransport { fault_at: 0 }; @@ -909,7 +907,7 @@ mod tests { // THE probe pass-2 dead-bus test: the loop used to `break` on ANY // error and return `Ok(())`, swallowing a dead bus mid-pass-2. It - // must now propagate. See docs/mt1959.md — pass2-dead-bus-mutation. + // must now propagate. #[test] fn transport_fault_during_pass2_probe_stays_a_transport_failure() { let mut t = ProbeFaultsTransport { @@ -952,7 +950,7 @@ mod tests { // variant_a's firmware-verify-read transport-abort branch: WRITE_BUFFER // succeeds, the 0x45 verify read faults — a dead bus must abort there, - // not be swallowed into the unlock retries. See docs/mt1959.md — variant-a-verify-read-transport-abort. + // not be swallowed into the unlock retries. #[test] fn variant_a_verify_read_transport_fault_aborts() { let mut profile = fixture_profile([0; 4]); @@ -1013,7 +1011,7 @@ mod tests { // variant_b's firmware-upload step (here the metadata read) hitting a // dead bus must abort via `trace_step`, not be swallowed into the - // unlock retries. See docs/mt1959.md — variant-b-upload-step-transport-abort. + // unlock retries. #[test] fn variant_b_upload_step_transport_fault_aborts() { let mut profile = fixture_profile([0; 4]); diff --git a/src/ld/platform/mt1959/variant_b.rs b/src/ld/platform/mt1959/variant_b.rs index bb36c1d..30d46eb 100644 --- a/src/ld/platform/mt1959/variant_b.rs +++ b/src/ld/platform/mt1959/variant_b.rs @@ -13,7 +13,6 @@ const FIRMWARE_EXTRA: [u8; 16] = [0; 16]; /// that drive — real profiles must supply their own (see `DriveProfile`). const VENDOR_VERIFY: [u8; 10] = [0xF1, 0x01, 0x02, 0x00, 0x0D, 0x30, 0x01, 0xF3, 0xAD, 0x23]; -// See docs/variant-b-firmware-upload.md — trace_step rationale. // Swallows everything except a transport fault; these steps are advisory, // the unlock retries below are the real gate. fn trace_step(phase: &'static str, r: crate::scsi::Result) -> Result<()> { @@ -58,7 +57,7 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re return Err(crate::ld::error::Error::UnlockFailed); } - // Step 1: Upload the firmware via MODE SELECT (see docs/variant-b-firmware-upload.md). + // Step 1: Upload the firmware via MODE SELECT. // MODE SELECT(10)'s parameter-list length is 16-bit, so reject only a // blob that can't be expressed in the CDB. let write_len = firmware.len(); @@ -124,8 +123,6 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re )?; // Step 4: Vendor verify (0xF1 — B-only, not standard SCSI), per-drive. - // See docs/variant-b-firmware-upload.md for why the outcome is traced - // rather than discarded. let verify_cdb = mt.profile.fw_verify_cdb.unwrap_or(VENDOR_VERIFY); let mut dummy = [0u8; 0]; trace_step( @@ -134,8 +131,7 @@ pub(super) fn load_firmware(mt: &mut Mt1959, scsi: &mut dyn ScsiTransport) -> Re )?; // Step 5: Unlock retries (up to 5, then a final fatal attempt). The - // confirmation pass after success is intentionally best-effort — see - // docs/variant-b-firmware-upload.md. + // confirmation pass after success is intentionally best-effort. for _attempt in 0..5 { match mt.do_unlock(scsi) { Ok(_) => { diff --git a/src/renesas/mod.rs b/src/renesas/mod.rs index 463e8f7..d6e4536 100644 --- a/src/renesas/mod.rs +++ b/src/renesas/mod.rs @@ -24,8 +24,7 @@ const RENESAS_MARKER_OFFSET: usize = 16; /// interface tag. A rejection (CHECK CONDITION or `Err` with a sense) is /// `Ok(false)`: not a Renesas drive. /// -/// `Err(Transport)` on a dead bus. See docs/renesas-mod.md for why a -/// transport fault must not be folded into `Ok(false)`. +/// `Err(Transport)` on a dead bus. pub fn is_renesas(scsi: &mut dyn ScsiTransport) -> std::result::Result { let mut buf = [0u8; RB_F1_LEN]; match scsi.execute(&RB_F1_CDB, DataDirection::FromDevice, &mut buf, 5_000) { @@ -71,8 +70,7 @@ impl Renesas { } // MakeMKV's vendor "open" sequence: primary read (A), and on refusal a - // knock + second read (B). See docs/renesas-mod.md for the full sequence - // and why we run A→knock→B rather than bailing after A. + // knock + second read (B). fn vendor_open(scsi: &mut dyn ScsiTransport) -> std::result::Result { const RB_B0_04_CDB: [u8; 10] = [0x3C, 0x02, 0xB0, 0x00, 0x00, 0x04, 0x00, 0x00, 0xA4, 0x00]; const KNOCK_A5AAAA_CDB: [u8; 10] = @@ -128,7 +126,7 @@ impl Unlocker for Renesas { /// best-effort `0xAD` reader. `Some` once the open succeeds — the drive /// serves clear content whether or not the bonus VID read did; `None` if it /// isn't a Renesas drive or the open is refused; `Err(Transport)` on a dead - /// bus. See docs/renesas-mod.md for the MakeMKV-parity rationale. + /// bus. fn unlock( &self, scsi: &mut dyn ScsiTransport, @@ -188,7 +186,7 @@ mod tests { } // Rejects like a MediaTek drive: ILLEGAL REQUEST with a sense, which is - // what distinguishes this from a dead bus. See docs/renesas-mod.md. + // what distinguishes this from a dead bus. struct RejectingTransport; impl ScsiTransport for RejectingTransport { fn execute( @@ -280,7 +278,7 @@ mod tests { } // Same rejection via a CONFORMING transport (`Ok` + CHECK CONDITION); - // must reach the same answer. See docs/renesas-mod.md. + // must reach the same answer. #[test] fn check_condition_is_not_a_renesas_drive() { use crate::scsi::mock::{MockTransport, Reply}; @@ -308,7 +306,7 @@ mod tests { // A recognized Renesas drive whose A read (RB 0xB0@0x04) is REFUSED but // whose B read (RB 0xB0@0x50) SUCCEEDS after the knock must unlock — and the - // knock CDB has to be issued BETWEEN A and B. See docs/renesas-mod.md. + // knock CDB has to be issued BETWEEN A and B. #[test] fn knock_runs_between_a_and_b_and_b_opens() { // The exact knock CDB the code issues (pinned wire format). @@ -395,7 +393,7 @@ mod tests { } // A recognized Renesas drive that REFUSES the vendor open read must - // defer to the next unlocker, not claim the drive. See docs/renesas-mod.md. + // defer to the next unlocker, not claim the drive. #[test] fn open_rejection_defers_to_next_unlocker() { struct GateOkOpenRefused; diff --git a/src/vid.rs b/src/vid.rs index db72d04..648c836 100644 --- a/src/vid.rs +++ b/src/vid.rs @@ -8,7 +8,7 @@ //! BEST-EFFORT: only a dead bus is an `Err(Transport)`. A CHECK CONDITION, a //! short response, or an all-zero VID all yield `Ok(None)` — a VID miss must //! never discard an unlock that already removed the bus (a key source can still -//! supply the key). See docs/freemkv-abi.md for the fixed-length reply rules. +//! supply the key). use crate::UnlockError; use crate::scsi::{DataDirection, ScsiTransport};