Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/read_data_track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/// Volume Descriptor — type byte `0x01` followed by `"CD001"`.
/// 3. Cooked vs raw: the cooked 2048 B must equal the user-data region of
/// the raw sector (offset 16 for Mode 1).
use cd_da_reader::{CdReader, ReadOptions, SectorReadMode};
use cd_da_reader::{CdReader, ReadOptions, SectorReadFormat};

// ISO 9660 places the Primary Volume Descriptor at logical sector 16.
const PVD_SECTOR_OFFSET: u32 = 16;
Expand All @@ -30,7 +30,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
data_track.number, data_track.start_lba, pvd_lba
);

let mut options = ReadOptions::default().with_mode(SectorReadMode::DataRaw);
let mut options = ReadOptions::default().with_format(SectorReadFormat::Mode1Raw);

// --- raw read (2352 B) -------------------------------------------------
let raw = reader.read_sector_range(pvd_lba, 1, &options)?;
Expand All @@ -56,9 +56,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("ISO 9660 'CD001' : {}", pass(iso_ok));

// --- cooked read (2048 B) ---------------------------------------------
// Our cooked mode maps to Mode 1; only meaningful to cross-check there.
// The cooked format is specifically Mode 1, so only cross-check it there.
if mode == 1 {
options = options.with_mode(SectorReadMode::DataCooked);
options = options.with_format(SectorReadFormat::Mode1Cooked);
let cooked = reader.read_sector_range(pvd_lba, 1, &options)?;
if cooked.len() != 2048 {
return Err(
Expand Down
90 changes: 45 additions & 45 deletions src/data_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use crate::retry::RetryConfig;
/// builder methods to override only the options you need.
#[derive(Debug, Clone)]
pub struct ReadOptions {
mode: SectorReadMode,
format: SectorReadFormat,
retry: RetryConfig,
}

impl ReadOptions {
/// Select the sector format requested from the drive.
pub fn with_mode(mut self, mode: SectorReadMode) -> Self {
self.mode = mode;
pub fn with_format(mut self, format: SectorReadFormat) -> Self {
self.format = format;
self
}

Expand All @@ -23,8 +23,8 @@ impl ReadOptions {
self
}

pub(crate) fn mode(&self) -> SectorReadMode {
self.mode
pub(crate) fn format(&self) -> SectorReadFormat {
self.format
}

pub(crate) fn retry(&self) -> &RetryConfig {
Expand All @@ -35,36 +35,36 @@ impl ReadOptions {
impl Default for ReadOptions {
fn default() -> Self {
Self {
mode: SectorReadMode::Audio,
format: SectorReadFormat::Audio,
retry: RetryConfig::default(),
}
}
}

/// Sector read mode for the READ CD (0xBE) command.
/// Sector format requested through the READ CD (0xBE) command.
///
/// Controls CDB byte 1 (Expected Sector Type) and byte 9 (Main Channel Selection)
/// to read different sector formats from the same READ CD command.
/// to return audio, cooked Mode 1 data, or complete Mode 1 sectors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectorReadMode {
pub enum SectorReadFormat {
/// Audio: 2352 bytes/sector, raw PCM.
/// CDB byte 1 = 0x00 (any type), byte 9 = 0x10 (user data).
Audio,
/// Data cooked: 2048 bytes/sector, user data only (no sync/header/EDC/ECC).
/// Mode 1 cooked data: 2048 bytes/sector, containing user data only.
/// CDB byte 1 = 0x08 (Mode 1), byte 9 = 0x10 (user data).
DataCooked,
/// Data raw: 2352 bytes/sector with sync + header + user data + EDC/ECC.
/// CDB byte 1 = 0x08 (Mode 1), byte 9 = 0xF8 (sync + header + user data + EDC/ECC).
DataRaw,
Mode1Cooked,
/// Complete Mode 1 sector: 2352 bytes with sync, header, user data, EDC, and ECC.
/// CDB byte 1 = 0x08 (Mode 1), byte 9 = 0xF8 (all main-channel fields).
Mode1Raw,
}

impl SectorReadMode {
/// Bytes per sector for this read mode.
impl SectorReadFormat {
/// Bytes per sector for this read format.
pub fn sector_size(&self) -> usize {
match self {
SectorReadMode::Audio => 2352,
SectorReadMode::DataCooked => 2048,
SectorReadMode::DataRaw => 2352,
SectorReadFormat::Audio => 2352,
SectorReadFormat::Mode1Cooked => 2048,
SectorReadFormat::Mode1Raw => 2352,
}
}

Expand All @@ -74,18 +74,18 @@ impl SectorReadMode {
/// `010b << 2 = 0x08`. (`0x04` would be `001b << 2`, i.e. CD-DA.)
pub fn cdb_byte1(&self) -> u8 {
match self {
SectorReadMode::Audio => 0x00,
SectorReadMode::DataCooked => 0x08,
SectorReadMode::DataRaw => 0x08,
SectorReadFormat::Audio => 0x00,
SectorReadFormat::Mode1Cooked => 0x08,
SectorReadFormat::Mode1Raw => 0x08,
}
}

/// CDB byte 9: Main Channel Selection bits.
pub fn cdb_byte9(&self) -> u8 {
match self {
SectorReadMode::Audio => 0x10,
SectorReadMode::DataCooked => 0x10,
SectorReadMode::DataRaw => 0xF8,
SectorReadFormat::Audio => 0x10,
SectorReadFormat::Mode1Cooked => 0x10,
SectorReadFormat::Mode1Raw => 0xF8,
}
}

Expand All @@ -110,63 +110,63 @@ impl SectorReadMode {

/// Build a READ CD (0xBE) command descriptor block for Linux and Windows.
#[cfg(any(target_os = "linux", target_os = "windows", test))]
pub(crate) fn build_read_cd_cdb(lba: u32, sectors: u32, mode: SectorReadMode) -> [u8; 12] {
pub(crate) fn build_read_cd_cdb(lba: u32, sectors: u32, format: SectorReadFormat) -> [u8; 12] {
let mut cdb = [0u8; 12];
cdb[0] = 0xBE;
cdb[1] = mode.cdb_byte1();
cdb[1] = format.cdb_byte1();
cdb[2..6].copy_from_slice(&lba.to_be_bytes());
cdb[6] = ((sectors >> 16) & 0xFF) as u8;
cdb[7] = ((sectors >> 8) & 0xFF) as u8;
cdb[8] = (sectors & 0xFF) as u8;
cdb[9] = mode.cdb_byte9();
cdb[9] = format.cdb_byte9();
cdb
}

#[cfg(test)]
mod tests {
use super::{ReadOptions, SectorReadMode, build_read_cd_cdb};
use super::{ReadOptions, SectorReadFormat, build_read_cd_cdb};

#[test]
fn read_options_builders_override_individual_defaults() {
assert_eq!(ReadOptions::default().mode(), SectorReadMode::Audio);
assert_eq!(ReadOptions::default().format(), SectorReadFormat::Audio);

let retry = crate::RetryConfig::default().with_max_attempts(9);
let options = ReadOptions::default()
.with_mode(SectorReadMode::DataRaw)
.with_format(SectorReadFormat::Mode1Raw)
.with_retry(retry);

assert_eq!(options.mode(), SectorReadMode::DataRaw);
assert_eq!(options.format(), SectorReadFormat::Mode1Raw);
assert_eq!(options.retry().max_attempts, 9);
}

#[test]
fn cdb_byte1_encodes_expected_sector_type() {
// Expected Sector Type lives in CDB byte 1, bits 4-2 (value << 2).
// Audio leaves it 0 (any type) and relies on byte 9; data reads must
// select Mode 1 = 010b << 2 = 0x08 (0x04 would wrongly mean CD-DA).
assert_eq!(SectorReadMode::Audio.cdb_byte1(), 0x00);
assert_eq!(SectorReadMode::DataCooked.cdb_byte1(), 0x08);
assert_eq!(SectorReadMode::DataRaw.cdb_byte1(), 0x08);
// Audio leaves it 0 (any type) and relies on byte 9; Mode 1 reads use
// 010b << 2 = 0x08 (0x04 would mean CD-DA).
assert_eq!(SectorReadFormat::Audio.cdb_byte1(), 0x00);
assert_eq!(SectorReadFormat::Mode1Cooked.cdb_byte1(), 0x08);
assert_eq!(SectorReadFormat::Mode1Raw.cdb_byte1(), 0x08);
}

#[test]
fn cdb_byte9_selects_main_channel() {
assert_eq!(SectorReadMode::Audio.cdb_byte9(), 0x10);
assert_eq!(SectorReadMode::DataCooked.cdb_byte9(), 0x10);
assert_eq!(SectorReadMode::DataRaw.cdb_byte9(), 0xF8);
assert_eq!(SectorReadFormat::Audio.cdb_byte9(), 0x10);
assert_eq!(SectorReadFormat::Mode1Cooked.cdb_byte9(), 0x10);
assert_eq!(SectorReadFormat::Mode1Raw.cdb_byte9(), 0xF8);
}

#[test]
fn sector_size_matches_mode() {
assert_eq!(SectorReadMode::Audio.sector_size(), 2352);
assert_eq!(SectorReadMode::DataCooked.sector_size(), 2048);
assert_eq!(SectorReadMode::DataRaw.sector_size(), 2352);
fn sector_size_matches_format() {
assert_eq!(SectorReadFormat::Audio.sector_size(), 2352);
assert_eq!(SectorReadFormat::Mode1Cooked.sector_size(), 2048);
assert_eq!(SectorReadFormat::Mode1Raw.sector_size(), 2352);
}

#[test]
fn builds_read_cd_cdb() {
assert_eq!(
build_read_cd_cdb(0x1234_5678, 0x0000_ABCD, SectorReadMode::DataRaw),
build_read_cd_cdb(0x1234_5678, 0x0000_ABCD, SectorReadFormat::Mode1Raw),
[
0xBE, 0x08, 0x12, 0x34, 0x56, 0x78, 0x00, 0xAB, 0xCD, 0xF8, 0x00, 0x00,
]
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ mod read_loop;
mod retry;
mod stream;
mod utils;
pub use data_reader::{ReadOptions, SectorReadMode};
pub use data_reader::{ReadOptions, SectorReadFormat};
pub use discovery::DriveInfo;
pub use errors::{CdReaderError, ScsiError, ScsiOp};
pub use retry::RetryConfig;
Expand Down Expand Up @@ -271,13 +271,13 @@ impl CdReader {
sectors: u32,
options: &ReadOptions,
) -> Result<Vec<u8>, CdReaderError> {
let mode = options.mode();
let format = options.format();
read_loop::read_sectors_chunked(
start_lba,
sectors,
mode,
format,
options.retry(),
|lba, chunk_sectors| self.drive.read_cd_chunk(lba, chunk_sectors, mode),
|lba, chunk_sectors| self.drive.read_cd_chunk(lba, chunk_sectors, format),
)
}
}
6 changes: 3 additions & 3 deletions src/platform/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod toc;

pub(crate) use device::{Drive, list_drive_paths};

use crate::{CdReaderError, SectorReadMode, Toc};
use crate::{CdReaderError, SectorReadFormat, Toc};

impl Drive {
pub(crate) fn read_toc(&self) -> Result<Toc, CdReaderError> {
Expand All @@ -16,8 +16,8 @@ impl Drive {
&self,
lba: u32,
sectors: u32,
mode: SectorReadMode,
format: SectorReadFormat,
) -> Result<Vec<u8>, CdReaderError> {
read_cd::read_cd_chunk(self, lba, sectors, mode)
read_cd::read_cd_chunk(self, lba, sectors, format)
}
}
8 changes: 4 additions & 4 deletions src/platform/linux/read_cd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::device::Drive;
use super::sg_io::{CommandContext, execute_read};
use crate::data_reader::{SectorReadMode, build_read_cd_cdb};
use crate::data_reader::{SectorReadFormat, build_read_cd_cdb};
use crate::{CdReaderError, ScsiOp};

const READ_CD_TIMEOUT_MS: u32 = 30_000;
Expand All @@ -9,10 +9,10 @@ pub(super) fn read_cd_chunk(
drive: &Drive,
lba: u32,
sectors: u32,
mode: SectorReadMode,
format: SectorReadFormat,
) -> Result<Vec<u8>, CdReaderError> {
let mut chunk = vec![0u8; sectors as usize * mode.sector_size()];
let mut cdb = build_read_cd_cdb(lba, sectors, mode);
let mut chunk = vec![0u8; sectors as usize * format.sector_size()];
let mut cdb = build_read_cd_cdb(lba, sectors, format);
let transferred = execute_read(
drive.fd(),
&mut cdb,
Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ unsafe extern "C" {
fd: libc::c_int,
lba: u32,
sectors: u32,
mode_id: u32,
format_id: u32,
out_buf: *mut *mut u8,
out_len: *mut u32,
out_err: *mut MacScsiError,
Expand Down
6 changes: 3 additions & 3 deletions src/platform/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod toc;

pub(crate) use device::{Drive, list_drive_paths};

use crate::{CdReaderError, SectorReadMode, Toc};
use crate::{CdReaderError, SectorReadFormat, Toc};

impl Drive {
pub(crate) fn read_toc(&self) -> Result<Toc, CdReaderError> {
Expand All @@ -16,8 +16,8 @@ impl Drive {
&self,
lba: u32,
sectors: u32,
mode: SectorReadMode,
format: SectorReadFormat,
) -> Result<Vec<u8>, CdReaderError> {
read_cd::read_cd_chunk(self, lba, sectors, mode)
read_cd::read_cd_chunk(self, lba, sectors, format)
}
}
23 changes: 12 additions & 11 deletions src/platform/macos/native/read_cd.c
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
#include "shim_common.h"

// Map our SectorReadMode discriminant to the macOS CD sector area/type and the
// resulting bytes-per-sector. Keeping the mapping here (rather than in Rust)
// means the IOKit constants only ever appear where their header is imported.
// Map our SectorReadFormat discriminant to the macOS CD sector area/type and
// the resulting bytes-per-sector. Keeping the mapping here (rather than in
// Rust) means the IOKit constants only ever appear where their header is
// imported.
//
// 0 = Audio -> user area, CDDA, 2352 B/sector
// 1 = DataCooked -> user area, Mode 1, 2048 B/sector
// 2 = DataRaw -> sync+header+user+aux, Mode 1, 2352 B/sector
static bool sector_layout_for_mode(uint32_t mode_id,
// 0 = Audio -> user area, CDDA, 2352 B/sector
// 1 = Mode1Cooked -> user area, Mode 1, 2048 B/sector
// 2 = Mode1Raw -> sync+header+user+aux, Mode 1, 2352 B/sector
static bool sector_layout_for_format(uint32_t format_id,
CDSectorArea *outArea,
CDSectorType *outType,
uint32_t *outSectorSize) {
switch (mode_id) {
switch (format_id) {
case 0:
*outArea = kCDSectorAreaUser;
*outType = kCDSectorTypeCDDA;
Expand All @@ -33,7 +34,7 @@ static bool sector_layout_for_mode(uint32_t mode_id,
}
}

bool read_cd_sectors(int fd, uint32_t lba, uint32_t sectors, uint32_t mode_id,
bool read_cd_sectors(int fd, uint32_t lba, uint32_t sectors, uint32_t format_id,
uint8_t **outBuf, uint32_t *outLen, CdScsiError *outErr) {
*outBuf = NULL;
*outLen = 0;
Expand All @@ -44,8 +45,8 @@ bool read_cd_sectors(int fd, uint32_t lba, uint32_t sectors, uint32_t mode_id,
CDSectorArea sectorArea;
CDSectorType sectorType;
uint32_t sectorSize;
if (!sector_layout_for_mode(mode_id, &sectorArea, &sectorType, &sectorSize)) {
fprintf(stderr, "[READ] unknown sector mode %u\n", mode_id);
if (!sector_layout_for_format(format_id, &sectorArea, &sectorType, &sectorSize)) {
fprintf(stderr, "[READ] unknown sector format %u\n", format_id);
goto fail;
}

Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/native/shim_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ typedef struct {
} CdDriveInfo;

bool cd_read_toc(int fd, uint8_t **outBuf, uint32_t *outLen, CdScsiError *outErr);
bool read_cd_sectors(int fd, uint32_t lba, uint32_t sectors, uint32_t mode_id, uint8_t **outBuf, uint32_t *outLen, CdScsiError *outErr);
bool read_cd_sectors(int fd, uint32_t lba, uint32_t sectors, uint32_t format_id, uint8_t **outBuf, uint32_t *outLen, CdScsiError *outErr);
void cd_free(void *p);

bool list_cd_drives(CdDriveInfo **outDrives, uint32_t *outCount);
Expand Down
Loading
Loading