Skip to main content

cd_da_reader/data_reader/
detect.rs

1use super::raw_sector::{RawSectorMode, RawSectorParseError, parse_raw_sector};
2use crate::{CdReader, CdReaderError, ReadOptions, SectorReadFormat, Track};
3
4impl CdReader {
5    /// Detect the default read format for a track.
6    ///
7    /// Audio tracks are identified directly from the TOC. Data tracks are
8    /// queried with MMC READ TRACK INFORMATION. If its Data Mode is
9    /// inconclusive, one raw sector is inspected as a fallback.
10    ///
11    /// # Errors
12    ///
13    /// - Returns [`CdReaderError::CannotDetectTrackFormat`] if neither the
14    ///   track metadata nor a raw sector identifies the data format.
15    /// - Returns [`CdReaderError::Io`], [`CdReaderError::Scsi`], or
16    ///   [`CdReaderError::Parse`] if querying the drive fails.
17    pub fn detect_track_format(&self, track: &Track) -> Result<SectorReadFormat, CdReaderError> {
18        if track.is_audio {
19            return Ok(SectorReadFormat::Audio);
20        }
21
22        let information = self.drive.read_track_information(track.number)?;
23        if let Some(format) = format_from_data_mode(information.data_mode) {
24            return Ok(format);
25        }
26
27        let options = ReadOptions::default().with_format(SectorReadFormat::Mode2Raw);
28        let raw_sector = self.read_sector_range(track.start_lba, 1, &options)?;
29        format_from_raw_sector(&raw_sector).map_err(|_| CdReaderError::CannotDetectTrackFormat {
30            track_number: track.number,
31            data_mode: Some(information.data_mode),
32        })
33    }
34}
35
36/// Map the MMC READ TRACK INFORMATION Data Mode field to an unambiguous
37/// default read format.
38///
39/// MMC reports `0x01` for Mode 1 and `0x02` for Mode 2. Track Information does
40/// not identify the per-sector Mode 2 form, so Mode 2 is exposed as complete
41/// raw sectors.
42fn format_from_data_mode(data_mode: u8) -> Option<SectorReadFormat> {
43    match data_mode {
44        0x01 => Some(SectorReadFormat::Mode1Cooked),
45        0x02 => Some(SectorReadFormat::Mode2Raw),
46        _ => None,
47    }
48}
49
50fn format_from_raw_sector(sector: &[u8]) -> Result<SectorReadFormat, RawSectorParseError> {
51    match parse_raw_sector(sector)?.mode {
52        RawSectorMode::Mode1 => Ok(SectorReadFormat::Mode1Cooked),
53        RawSectorMode::Mode2 => Ok(SectorReadFormat::Mode2Raw),
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    fn raw_sector(mode: u8) -> [u8; 2352] {
62        let mut sector = [0u8; 2352];
63        sector[0] = 0x00;
64        sector[1..11].fill(0xFF);
65        sector[11] = 0x00;
66        sector[15] = mode;
67        sector
68    }
69
70    #[test]
71    fn audio_tracks_do_not_require_drive_detection() {
72        let reader = CdReader::test_reader();
73        let track = Track {
74            number: 1,
75            start_lba: 0,
76            start_msf: (0, 2, 0),
77            is_audio: true,
78        };
79
80        assert_eq!(
81            reader.detect_track_format(&track).unwrap(),
82            SectorReadFormat::Audio
83        );
84    }
85
86    #[test]
87    fn maps_mode1_to_its_cooked_format() {
88        assert_eq!(
89            format_from_data_mode(0x01),
90            Some(SectorReadFormat::Mode1Cooked)
91        );
92    }
93
94    #[test]
95    fn maps_mode2_to_raw_sectors() {
96        assert_eq!(
97            format_from_data_mode(0x02),
98            Some(SectorReadFormat::Mode2Raw)
99        );
100    }
101
102    #[test]
103    fn maps_raw_sector_modes_to_supported_formats() {
104        assert_eq!(
105            format_from_raw_sector(&raw_sector(1)).unwrap(),
106            SectorReadFormat::Mode1Cooked
107        );
108        assert_eq!(
109            format_from_raw_sector(&raw_sector(2)).unwrap(),
110            SectorReadFormat::Mode2Raw
111        );
112    }
113
114    #[test]
115    fn rejects_unknown_or_reserved_mmc_data_modes() {
116        assert_eq!(format_from_data_mode(0x00), None);
117        for data_mode in 0x03..=0x0F {
118            assert_eq!(format_from_data_mode(data_mode), None);
119        }
120    }
121}