cd_da_reader/data_reader/sector_read_format.rs
1/// Selects the type and layout of sectors returned when reading from an optical drive.
2///
3/// This is the crate's platform-independent representation of the sector type and
4/// main-channel fields requested by the MMC `READ CD` command (`0xBE`) or the
5/// equivalent platform API.
6///
7/// [`ReadOptions`](crate::ReadOptions) defaults to [`Audio`](Self::Audio), so
8/// callers reading audio tracks normally do not need to select a format. For a
9/// data track, call
10/// [`CdReader::detect_track_format`](crate::CdReader::detect_track_format) and
11/// pass the result to [`ReadOptions::with_format`](crate::ReadOptions::with_format). Detection
12/// chooses [`Mode1Cooked`](Self::Mode1Cooked) for Mode 1 tracks and
13/// [`Mode2Raw`](Self::Mode2Raw) for Mode 2 tracks.
14///
15/// Selecting a format does not convert the sectors. It tells the drive what
16/// sector type and fields to return, so the selection must match the track being
17/// read. A mismatched format may be rejected by the library or the drive.
18///
19/// The `Raw` variants return the complete 2,352-byte main-channel sector. They
20/// do not include subchannel data or C2 error information. Use
21/// [`sector_size`](Self::sector_size) to obtain the number of bytes returned per
22/// sector for any variant.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SectorReadFormat {
25 /// CD-DA audio as 2,352 bytes of headerless PCM per sector.
26 ///
27 /// The samples are signed 16-bit little-endian stereo at 44.1 kHz. Each
28 /// sector contains 588 stereo sample frames and represents 1/75 second of
29 /// audio. The returned bytes can be passed directly to [`create_wav`](crate::create_wav).
30 Audio,
31
32 /// The 2,048-byte user-data field from a Mode 1 sector.
33 ///
34 /// The drive omits the sync pattern, sector header, Error Detection Code
35 /// (EDC), reserved bytes, and Error Correction Code (ECC). This is usually
36 /// the preferred representation for reading filesystems; concatenating the
37 /// cooked sectors of a typical ISO 9660 track produces a directly usable
38 /// disc image.
39 Mode1Cooked,
40
41 /// A complete 2,352-byte Mode 1 main-channel sector.
42 ///
43 /// This includes the 12-byte sync pattern, 4-byte header, 2,048-byte user
44 /// data field, EDC, reserved bytes, and ECC. Use this when preserving or
45 /// inspecting the original sector framing. For normal filesystem access,
46 /// [`Mode1Cooked`](Self::Mode1Cooked) is usually more convenient.
47 Mode1Raw,
48
49 /// A complete 2,352-byte Mode 2 main-channel sector.
50 ///
51 /// This is the only Mode 2 representation provided by the crate. Mode 2 XA
52 /// tracks can mix Form 1 and Form 2 sectors within the same track: Form 1
53 /// carries 2,048 bytes of user data with stronger error correction, while
54 /// Form 2 carries 2,324 bytes of user data.
55 ///
56 /// The form is recorded in each sector's XA subheader. The crate does not
57 /// expose a cooked Mode 2 reader or a public XA payload parser, so callers
58 /// must inspect each sector and extract the appropriate payload themselves.
59 Mode2Raw,
60}
61
62impl SectorReadFormat {
63 pub(crate) fn is_audio(&self) -> bool {
64 matches!(self, Self::Audio)
65 }
66
67 /// Bytes returned per sector for this format.
68 pub fn sector_size(&self) -> usize {
69 match self {
70 Self::Audio | Self::Mode1Raw | Self::Mode2Raw => 2352,
71 Self::Mode1Cooked => 2048,
72 }
73 }
74
75 /// CDB byte 1: Expected Sector Type in bits 4–2.
76 #[cfg(any(target_os = "linux", target_os = "windows", test))]
77 pub(crate) fn cdb_byte1(&self) -> u8 {
78 match self {
79 Self::Audio => 0x04,
80 Self::Mode1Cooked | Self::Mode1Raw => 0x08,
81 // Mode 2 forms can be interleaved, so let the drive determine the
82 // actual sector type while returning the complete sector.
83 Self::Mode2Raw => 0x00,
84 }
85 }
86
87 /// CDB byte 9: Main Channel Selection.
88 #[cfg(any(target_os = "linux", target_os = "windows", test))]
89 pub(crate) fn cdb_byte9(&self) -> u8 {
90 match self {
91 Self::Audio | Self::Mode1Cooked => 0x10,
92 Self::Mode1Raw | Self::Mode2Raw => 0xF8,
93 }
94 }
95
96 /// Maximum sectors per single `READ CD` command.
97 ///
98 /// Transfers are kept at approximately 64 KiB for compatibility with
99 /// optical-drive firmware and USB bridges.
100 pub(crate) fn max_sectors_per_xfer(&self) -> u32 {
101 (64 * 1024 / self.sector_size() as u32).max(1)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::SectorReadFormat;
108
109 #[test]
110 fn expected_sector_types_are_encoded_in_cdb_byte1() {
111 assert_eq!(SectorReadFormat::Audio.cdb_byte1(), 0x04);
112 assert_eq!(SectorReadFormat::Mode1Cooked.cdb_byte1(), 0x08);
113 assert_eq!(SectorReadFormat::Mode1Raw.cdb_byte1(), 0x08);
114 assert_eq!(SectorReadFormat::Mode2Raw.cdb_byte1(), 0x00);
115 }
116
117 #[test]
118 fn main_channel_fields_are_encoded_in_cdb_byte9() {
119 assert_eq!(SectorReadFormat::Audio.cdb_byte9(), 0x10);
120 assert_eq!(SectorReadFormat::Mode1Cooked.cdb_byte9(), 0x10);
121 assert_eq!(SectorReadFormat::Mode1Raw.cdb_byte9(), 0xF8);
122 assert_eq!(SectorReadFormat::Mode2Raw.cdb_byte9(), 0xF8);
123 }
124
125 #[test]
126 fn sector_sizes_match_mmc_layouts() {
127 assert_eq!(SectorReadFormat::Audio.sector_size(), 2352);
128 assert_eq!(SectorReadFormat::Mode1Cooked.sector_size(), 2048);
129 assert_eq!(SectorReadFormat::Mode1Raw.sector_size(), 2352);
130 assert_eq!(SectorReadFormat::Mode2Raw.sector_size(), 2352);
131 }
132
133 #[test]
134 fn transfer_caps_stay_within_64_kib() {
135 for format in [
136 SectorReadFormat::Audio,
137 SectorReadFormat::Mode1Cooked,
138 SectorReadFormat::Mode1Raw,
139 SectorReadFormat::Mode2Raw,
140 ] {
141 let bytes = format.max_sectors_per_xfer() as usize * format.sector_size();
142 assert!(bytes <= 64 * 1024);
143 }
144 }
145}