use std::io::Read;
use crate::read_bytes::ReadBytesExt;
type Error = crate::NcwError;
pub const HEADER_SIZE: usize = 120;
const FILE_MAGICS: [u64; 2] = [0x01A89ED631010000, 0x01A89ED630010000];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NcwHeader {
pub channels: u16,
pub bits_per_sample: u16,
pub sample_rate: u32,
pub num_samples: u32,
pub blocks_offset: u32,
pub data_offset: u32,
pub data_size: u32,
}
impl NcwHeader {
pub fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let buf = reader.read_bytes(HEADER_SIZE)?;
let mut reader = buf.as_slice();
if !FILE_MAGICS.contains(&reader.read_u64_be()?) {
return Err(Error::InvalidFileSignature);
}
Ok(Self {
channels: reader.read_u16_le()?,
bits_per_sample: reader.read_u16_le()?,
sample_rate: reader.read_u32_le()?,
num_samples: reader.read_u32_le()?,
blocks_offset: reader.read_u32_le()?,
data_offset: reader.read_u32_le()?,
data_size: reader.read_u32_le()?,
})
}
pub(crate) fn validate(&self) -> Result<(), Error> {
if self.channels == 0 {
return Err(Error::InvalidHeader("channel count is zero"));
}
if !matches!(self.bits_per_sample, 8 | 16 | 24 | 32) {
return Err(Error::InvalidHeader("unsupported bits per sample"));
}
if self.sample_rate == 0 {
return Err(Error::InvalidHeader("sample rate is zero"));
}
if self.blocks_offset < HEADER_SIZE as u32 {
return Err(Error::InvalidHeader("block table overlaps file header"));
}
if self.data_offset < self.blocks_offset {
return Err(Error::InvalidHeader(
"data offset precedes block offset table",
));
}
let table_bytes = self.data_offset - self.blocks_offset;
let expected_entries = self.num_samples.div_ceil(512) + 1;
if table_bytes != expected_entries * 4 {
return Err(Error::InvalidHeader(
"block table length does not match frame count",
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bad_magic_is_an_error() {
let bytes = [0u8; HEADER_SIZE];
assert!(matches!(
NcwHeader::read(&mut &bytes[..]),
Err(Error::InvalidFileSignature)
));
}
#[test]
fn short_header_is_an_io_error() {
let bytes = [0u8; HEADER_SIZE - 1];
assert!(matches!(
NcwHeader::read(&mut &bytes[..]),
Err(Error::IoError(_))
));
}
}