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
58 changes: 57 additions & 1 deletion src/data_reader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) mod track_information;
pub use sector_read_format::SectorReadFormat;

use crate::retry::RetryConfig;
use crate::{CdReaderError, Track};

/// Sector format and retry options for track and sector-range reads.
///
Expand Down Expand Up @@ -48,6 +49,22 @@ impl Default for ReadOptions {
}
}

pub(crate) fn validate_track_format(
track: &Track,
format: SectorReadFormat,
) -> Result<(), CdReaderError> {
// this checks both whether both are audio, or both are not audio
if track.is_audio == format.is_audio() {
return Ok(());
}

Err(CdReaderError::TrackFormatMismatch {
track_number: track.number,
track_is_audio: track.is_audio,
requested_format: format,
})
}

/// 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, format: SectorReadFormat) -> [u8; 12] {
Expand All @@ -64,7 +81,8 @@ pub(crate) fn build_read_cd_cdb(lba: u32, sectors: u32, format: SectorReadFormat

#[cfg(test)]
mod tests {
use super::{ReadOptions, SectorReadFormat, build_read_cd_cdb};
use super::{ReadOptions, SectorReadFormat, build_read_cd_cdb, validate_track_format};
use crate::{CdReaderError, Track};

#[test]
fn read_options_builders_override_individual_defaults() {
Expand All @@ -79,6 +97,44 @@ mod tests {
assert_eq!(options.retry().max_attempts, 9);
}

#[test]
fn validates_track_and_format_compatibility() {
let audio = Track {
number: 1,
start_lba: 0,
start_msf: (0, 2, 0),
is_audio: true,
};
let data = Track {
number: 2,
start_lba: 10_000,
start_msf: (2, 15, 25),
is_audio: false,
};

assert!(validate_track_format(&audio, SectorReadFormat::Audio).is_ok());
assert!(validate_track_format(&data, SectorReadFormat::Mode1Cooked).is_ok());
assert!(validate_track_format(&data, SectorReadFormat::Mode1Raw).is_ok());
assert!(validate_track_format(&data, SectorReadFormat::Mode2Raw).is_ok());

assert!(matches!(
validate_track_format(&audio, SectorReadFormat::Mode1Cooked),
Err(CdReaderError::TrackFormatMismatch {
track_number: 1,
track_is_audio: true,
requested_format: SectorReadFormat::Mode1Cooked,
})
));
assert!(matches!(
validate_track_format(&data, SectorReadFormat::Audio),
Err(CdReaderError::TrackFormatMismatch {
track_number: 2,
track_is_audio: false,
requested_format: SectorReadFormat::Audio,
})
));
}

#[test]
fn builds_read_cd_cdb() {
assert_eq!(
Expand Down
4 changes: 4 additions & 0 deletions src/data_reader/sector_read_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub enum SectorReadFormat {
}

impl SectorReadFormat {
pub(crate) fn is_audio(&self) -> bool {
matches!(self, Self::Audio)
}

/// Bytes returned per sector for this format.
pub fn sector_size(&self) -> usize {
match self {
Expand Down
23 changes: 23 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::fmt;

use crate::SectorReadFormat;

/// SCSI command groups issued by this library.
#[derive(Debug, Clone, Copy)]
pub enum ScsiOp {
Expand Down Expand Up @@ -44,6 +46,15 @@ pub enum CdReaderError {
Scsi(ScsiError),
/// Parsing failure for command payloads (TOC/CD-TEXT/subchannel parsing).
Parse(String),
/// The requested sector format is incompatible with the track type.
TrackFormatMismatch {
/// Track number from the TOC.
track_number: u8,
/// Whether the TOC identifies the track as audio.
track_is_audio: bool,
/// Format requested by the caller.
requested_format: SectorReadFormat,
},
/// The track's sector format could not be determined.
CannotDetectTrackFormat {
/// Track number from the TOC.
Expand All @@ -65,6 +76,17 @@ impl fmt::Display for CdReaderError {
err.op, err.scsi_status, err.lba, err.sectors, err.sense_key, err.asc, err.ascq
),
Self::Parse(msg) => write!(f, "parse error: {msg}"),
Self::TrackFormatMismatch {
track_number,
track_is_audio,
requested_format,
} => {
let track_type = if *track_is_audio { "audio" } else { "data" };
write!(
f,
"cannot read {track_type} track {track_number} using {requested_format:?}"
)
}
Self::CannotDetectTrackFormat {
track_number,
data_mode: Some(data_mode),
Expand All @@ -87,6 +109,7 @@ impl std::error::Error for CdReaderError {
Self::Io(error) => Some(error),
Self::Scsi(_)
| Self::Parse(_)
| Self::TrackFormatMismatch { .. }
| Self::CannotDetectTrackFormat { .. }
| Self::NoUsableDrive => None,
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ impl CdReader {
track_no: u8,
options: &ReadOptions,
) -> Result<Vec<u8>, CdReaderError> {
if let Some(track) = toc.tracks.iter().find(|track| track.number == track_no) {
data_reader::validate_track_format(track, options.format())?;
}

let (start_lba, sectors) =
utils::get_track_bounds(toc, track_no).map_err(CdReaderError::Io)?;
self.read_sector_range(start_lba, sectors, options)
Expand Down
5 changes: 5 additions & 0 deletions src/stream.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::cmp::min;

use crate::data_reader::validate_track_format;
use crate::{CdReader, CdReaderError, ReadOptions, RetryConfig, SectorReadFormat, Toc, utils};

/// Options for streamed track reads.
Expand Down Expand Up @@ -179,6 +180,10 @@ impl CdReader {
track_no: u8,
options: TrackStreamOptions,
) -> Result<TrackStream<'a>, CdReaderError> {
if let Some(track) = toc.tracks.iter().find(|track| track.number == track_no) {
validate_track_format(track, options.format)?;
}

let (start_lba, sectors) =
utils::get_track_bounds(toc, track_no).map_err(CdReaderError::Io)?;

Expand Down
Loading