use std::io::{self, Read, Seek, SeekFrom};
use crate::IsoError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectorMode {
Iso2048,
Raw2352,
Raw2352Mode2,
Raw2448,
Raw2448Mode2,
Mode2_2336,
}
impl SectorMode {
pub fn detect<R: Read + Seek>(reader: &mut R) -> Result<Self, IsoError> {
if probe_cd001(reader, 16 * 2048 + 1)? {
return Ok(Self::Iso2048);
}
let synced = [
(Self::Raw2352, 2352u64, 16u64),
(Self::Raw2352Mode2, 2352, 24),
(Self::Raw2448, 2448, 16),
(Self::Raw2448Mode2, 2448, 24),
];
for (mode, phys, off) in synced {
if probe_cd001(reader, 16 * phys + off + 1)? && has_sync_pattern(reader, 0)? {
return Ok(mode);
}
}
if probe_cd001(reader, 16 * 2336 + 8 + 1)? {
return Ok(Self::Mode2_2336);
}
Err(IsoError::NotAnIso)
}
pub const fn physical_sector_size(self) -> u64 {
match self {
Self::Iso2048 => 2048,
Self::Raw2352 | Self::Raw2352Mode2 => 2352,
Self::Raw2448 | Self::Raw2448Mode2 => 2448,
Self::Mode2_2336 => 2336,
}
}
pub const fn data_offset(self) -> u64 {
match self {
Self::Iso2048 => 0,
Self::Raw2352 | Self::Raw2448 => 16,
Self::Raw2352Mode2 | Self::Raw2448Mode2 => 24,
Self::Mode2_2336 => 8,
}
}
pub fn user_data_pos(self, lba: u64) -> u64 {
lba * self.physical_sector_size() + self.data_offset()
}
}
pub fn read_sector_data<R: Read + Seek>(
reader: &mut R,
mode: SectorMode,
lba: u64,
buf: &mut [u8],
) -> io::Result<()> {
debug_assert!(buf.len() <= 2048, "cannot read more than one sector at a time");
reader.seek(SeekFrom::Start(mode.user_data_pos(lba)))?;
reader.read_exact(buf)
}
fn probe_cd001<R: Read + Seek>(reader: &mut R, pos: u64) -> io::Result<bool> {
let mut sig = [0u8; 5];
reader.seek(SeekFrom::Start(pos))?;
match reader.read_exact(&mut sig) {
Ok(()) => Ok(&sig == b"CD001"),
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(false),
Err(e) => Err(e),
}
}
pub fn cd_edc(data: &[u8]) -> u32 {
use std::sync::OnceLock;
static TABLE: OnceLock<[u32; 256]> = OnceLock::new();
let table = TABLE.get_or_init(|| {
let mut t = [0u32; 256];
let mut i = 0usize;
while i < 256 {
let mut e = i as u32;
let mut bit = 0;
while bit < 8 {
e = if e & 1 != 0 { (e >> 1) ^ 0xD801_8001 } else { e >> 1 };
bit += 1;
}
t[i] = e;
i += 1;
}
t
});
let mut edc = 0u32;
for &b in data {
edc = table[((edc ^ u32::from(b)) & 0xFF) as usize] ^ (edc >> 8);
}
edc
}
fn ecc_luts() -> ([u8; 256], [u8; 256]) {
let mut f = [0u8; 256];
let mut b = [0u8; 256];
let mut i = 0usize;
while i < 256 {
let j = (i << 1) ^ (if i & 0x80 != 0 { 0x11D } else { 0 });
f[i] = j as u8;
b[(i ^ j) & 0xFF] = i as u8;
i += 1;
}
(f, b)
}
fn ecc_block(
sector: &[u8],
major_count: usize,
minor_count: usize,
major_mult: usize,
minor_inc: usize,
f: &[u8; 256],
b: &[u8; 256],
) -> Vec<u8> {
let size = major_count * minor_count;
let mut dest = vec![0u8; major_count * 2];
for major in 0..major_count {
let mut index = (major >> 1) * major_mult + (major & 1);
let mut ecc_a = 0u8;
let mut ecc_b = 0u8;
for _ in 0..minor_count {
let temp = sector[12 + index];
index += minor_inc;
if index >= size {
index -= size;
}
ecc_a ^= temp;
ecc_b ^= temp;
ecc_a = f[ecc_a as usize];
}
ecc_a = b[(f[ecc_a as usize] ^ ecc_b) as usize];
dest[major] = ecc_a;
dest[major + major_count] = ecc_a ^ ecc_b;
}
dest
}
pub fn cd_ecc_stamp(sector: &mut [u8]) {
let (f, b) = ecc_luts();
let p = ecc_block(sector, 86, 24, 2, 86, &f, &b);
sector[2076..2248].copy_from_slice(&p);
let q = ecc_block(sector, 52, 43, 86, 88, &f, &b);
sector[2248..2352].copy_from_slice(&q);
}
pub fn mode1_ecc_valid(sector: &[u8]) -> bool {
if sector.len() < 2352 {
return false;
}
let (f, b) = ecc_luts();
let p = ecc_block(sector, 86, 24, 2, 86, &f, &b);
if sector[2076..2248] != p[..] {
return false;
}
let q = ecc_block(sector, 52, 43, 86, 88, &f, &b);
sector[2248..2352] == q[..]
}
fn has_sync_pattern<R: Read + Seek>(reader: &mut R, sector_start: u64) -> io::Result<bool> {
const SYNC: [u8; 12] = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
let mut buf = [0u8; 12];
reader.seek(SeekFrom::Start(sector_start))?;
match reader.read_exact(&mut buf) {
Ok(()) => Ok(buf == SYNC),
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(false),
Err(e) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cd_ecc_validates_and_detects_tamper() {
let mut sector = vec![0u8; 2352];
sector[1..11].fill(0xFF);
sector[15] = 1;
for (i, b) in sector[16..2064].iter_mut().enumerate() {
*b = (i % 251) as u8;
}
let edc = cd_edc(§or[0..2064]);
sector[2064..2068].copy_from_slice(&edc.to_le_bytes());
cd_ecc_stamp(&mut sector);
assert!(mode1_ecc_valid(§or), "stamped ECC must validate");
sector[100] ^= 0xFF;
assert!(!mode1_ecc_valid(§or), "tamper must invalidate ECC");
}
#[test]
fn cd_ecc_all_zero_sector_is_valid() {
assert!(mode1_ecc_valid(&[0u8; 2352]));
}
#[test]
fn cd_edc_validates_and_detects_tamper() {
let mut sector = vec![0u8; 2352];
sector[1..11].fill(0xFF); sector[15] = 1; for (i, b) in sector[16..2064].iter_mut().enumerate() {
*b = (i % 251) as u8;
}
let edc = cd_edc(§or[0..2064]);
sector[2064..2068].copy_from_slice(&edc.to_le_bytes());
let stored = u32::from_le_bytes(sector[2064..2068].try_into().unwrap());
assert_eq!(cd_edc(§or[0..2064]), stored, "valid EDC must match");
assert_eq!(cd_edc(§or[0..2068]), 0, "EDC over data+EDC must be zero");
sector[100] ^= 0xFF;
assert_ne!(cd_edc(§or[0..2064]), stored, "tamper must invalidate EDC");
}
}