use super::{AlacError, AlacResult};
pub const DEFAULT_PB: u8 = 40;
pub const DEFAULT_MB: u8 = 10;
pub const DEFAULT_KB: u8 = 14;
pub const COOKIE_SIZE: usize = 24;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AlacSpecificConfig {
pub frame_length: u32,
pub compatible_version: u8,
pub bit_depth: u8,
pub pb: u8,
pub mb: u8,
pub kb: u8,
pub num_channels: u8,
pub max_run: u16,
pub max_frame_bytes: u32,
pub avg_bit_rate: u32,
pub sample_rate: u32,
}
impl AlacSpecificConfig {
#[must_use]
pub fn new(frame_length: u32, sample_rate: u32, num_channels: u8, bit_depth: u8) -> Self {
Self {
frame_length,
compatible_version: 0,
bit_depth,
pb: DEFAULT_PB,
mb: DEFAULT_MB,
kb: DEFAULT_KB,
num_channels,
max_run: 255,
max_frame_bytes: 0,
avg_bit_rate: 0,
sample_rate,
}
}
pub fn validate(&self) -> AlacResult<()> {
if self.frame_length == 0 {
return Err(AlacError::InvalidConfig("frame_length is zero".into()));
}
if self.num_channels == 0 {
return Err(AlacError::InvalidConfig("num_channels is zero".into()));
}
match self.bit_depth {
16 | 20 | 24 | 32 => {}
other => {
return Err(AlacError::InvalidConfig(format!(
"unsupported bit_depth {other} (expected 16/20/24/32)"
)));
}
}
if self.sample_rate == 0 {
return Err(AlacError::InvalidConfig("sample_rate is zero".into()));
}
Ok(())
}
pub fn parse(bytes: &[u8]) -> AlacResult<Self> {
let body = locate_config(bytes)?;
if body.len() < COOKIE_SIZE {
return Err(AlacError::InvalidCookie(format!(
"need {COOKIE_SIZE} bytes, have {}",
body.len()
)));
}
let frame_length = read_u32(body, 0);
let compatible_version = body[4];
let bit_depth = body[5];
let pb = body[6];
let mb = body[7];
let kb = body[8];
let num_channels = body[9];
let max_run = read_u16(body, 10);
let max_frame_bytes = read_u32(body, 12);
let avg_bit_rate = read_u32(body, 16);
let sample_rate = read_u32(body, 20);
let config = Self {
frame_length,
compatible_version,
bit_depth,
pb,
mb,
kb,
num_channels,
max_run,
max_frame_bytes,
avg_bit_rate,
sample_rate,
};
config.validate()?;
Ok(config)
}
#[must_use]
pub fn serialize(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(COOKIE_SIZE);
out.extend_from_slice(&self.frame_length.to_be_bytes());
out.push(self.compatible_version);
out.push(self.bit_depth);
out.push(self.pb);
out.push(self.mb);
out.push(self.kb);
out.push(self.num_channels);
out.extend_from_slice(&self.max_run.to_be_bytes());
out.extend_from_slice(&self.max_frame_bytes.to_be_bytes());
out.extend_from_slice(&self.avg_bit_rate.to_be_bytes());
out.extend_from_slice(&self.sample_rate.to_be_bytes());
out
}
}
fn locate_config(bytes: &[u8]) -> AlacResult<&[u8]> {
if bytes.len() >= 12 && &bytes[4..8] == b"alac" {
Ok(&bytes[12..])
} else if bytes.is_empty() {
Err(AlacError::InvalidCookie("empty cookie".into()))
} else {
Ok(bytes)
}
}
#[inline]
fn read_u16(b: &[u8], off: usize) -> u16 {
u16::from_be_bytes([b[off], b[off + 1]])
}
#[inline]
fn read_u32(b: &[u8], off: usize) -> u32 {
u32::from_be_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip_serialize_parse() {
let cfg = AlacSpecificConfig::new(4096, 44_100, 2, 16);
let bytes = cfg.serialize();
assert_eq!(bytes.len(), COOKIE_SIZE);
let parsed = AlacSpecificConfig::parse(&bytes).expect("parse");
assert_eq!(parsed, cfg);
}
#[test]
fn test_defaults() {
let cfg = AlacSpecificConfig::new(4096, 48_000, 1, 24);
assert_eq!(cfg.pb, DEFAULT_PB);
assert_eq!(cfg.mb, DEFAULT_MB);
assert_eq!(cfg.kb, DEFAULT_KB);
assert_eq!(cfg.compatible_version, 0);
}
#[test]
fn test_parse_with_box_header() {
let cfg = AlacSpecificConfig::new(4096, 44_100, 2, 16);
let mut boxed = Vec::new();
boxed.extend_from_slice(&(36u32).to_be_bytes());
boxed.extend_from_slice(b"alac");
boxed.extend_from_slice(&0u32.to_be_bytes()); boxed.extend_from_slice(&cfg.serialize());
let parsed = AlacSpecificConfig::parse(&boxed).expect("parse boxed");
assert_eq!(parsed, cfg);
}
#[test]
fn test_parse_too_short() {
assert!(AlacSpecificConfig::parse(&[0u8; 10]).is_err());
assert!(AlacSpecificConfig::parse(&[]).is_err());
}
#[test]
fn test_validate_rejects_bad_depth() {
let mut cfg = AlacSpecificConfig::new(4096, 44_100, 2, 16);
cfg.bit_depth = 17;
assert!(cfg.validate().is_err());
}
#[test]
fn test_validate_rejects_zero_channels() {
let mut cfg = AlacSpecificConfig::new(4096, 44_100, 2, 16);
cfg.num_channels = 0;
assert!(cfg.validate().is_err());
}
}