diff --git a/src/data_reader/mod.rs b/src/data_reader/mod.rs index ec2ff10..2a435f0 100644 --- a/src/data_reader/mod.rs +++ b/src/data_reader/mod.rs @@ -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. /// @@ -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] { @@ -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() { @@ -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!( diff --git a/src/data_reader/sector_read_format.rs b/src/data_reader/sector_read_format.rs index b3f308f..bb303d5 100644 --- a/src/data_reader/sector_read_format.rs +++ b/src/data_reader/sector_read_format.rs @@ -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 { diff --git a/src/errors.rs b/src/errors.rs index 3cd6fe9..6c99a4e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,7 @@ use std::fmt; +use crate::SectorReadFormat; + /// SCSI command groups issued by this library. #[derive(Debug, Clone, Copy)] pub enum ScsiOp { @@ -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. @@ -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), @@ -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, } diff --git a/src/lib.rs b/src/lib.rs index 44e5909..d577aef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,6 +253,10 @@ impl CdReader { track_no: u8, options: &ReadOptions, ) -> Result, 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) diff --git a/src/stream.rs b/src/stream.rs index a0696e3..9449962 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -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. @@ -179,6 +180,10 @@ impl CdReader { track_no: u8, options: TrackStreamOptions, ) -> Result, 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)?;