use crate::{BinTag4, RiffChunk, RiffChunkIter, RiffError, is, slice, unwrap, write_at};
#[doc = crate::_tags!(data codec)]
#[doc = crate::_doc_meta!{location("data/codec/pack")}]
#[derive(Debug)]
pub struct Riff;
impl Riff {
pub const CHUNK_HEADER_LEN: usize = 8;
pub const FORM_TYPE_LEN: usize = 4;
pub const ROOT_HEADER_LEN: usize = Self::CHUNK_HEADER_LEN + Self::FORM_TYPE_LEN;
#[must_use]
#[inline(always)]
pub const fn pad_len(len: usize) -> usize {
len & 1
}
#[must_use]
#[inline(always)]
pub const fn padded_len(len: usize) -> Option<usize> {
len.checked_add(Self::pad_len(len))
}
pub const fn chunk(bytes: &[u8]) -> Result<RiffChunk<'_>, RiffError> {
let (chunk, _) = unwrap![ok? Self::chunk_at(bytes, 0)];
Ok(chunk)
}
#[inline(always)]
pub const fn chunks<'a>(bytes: &'a [u8]) -> RiffChunkIter<'a> {
RiffChunkIter::new(bytes)
}
#[must_use]
pub const fn chunk_len(data_len: usize) -> Option<usize> {
let len = unwrap![some? Self::CHUNK_HEADER_LEN.checked_add(data_len)];
len.checked_add(Self::pad_len(data_len))
}
pub const fn root(bytes: &[u8]) -> Result<RiffChunk<'_>, RiffError> {
let chunk = unwrap![ok? Self::chunk(bytes)];
is! { chunk.id.eq(Self::RIFX), return Err(RiffError::UnsupportedRifx) }
is! { !chunk.id.eq(Self::RIFF), return Err(RiffError::NotRiff) }
is! { chunk.data.len() < BinTag4::LEN, return Err(RiffError::MissingContainerType) }
Ok(chunk)
}
#[must_use]
pub const fn form_len(subchunks_len: usize) -> Option<usize> {
Self::ROOT_HEADER_LEN.checked_add(subchunks_len)
}
#[must_use]
pub const fn fits_u32(data_len: usize) -> bool {
data_len <= u32::MAX as usize
}
pub const fn write_chunk_header(
dst: &mut [u8],
id: BinTag4,
data_len: usize,
) -> Result<usize, RiffError> {
is! { dst.len() < Self::CHUNK_HEADER_LEN, return Err(RiffError::NotEnoughSpace) }
is! { !Self::fits_u32(data_len), return Err(RiffError::Overflow) }
let size = (data_len as u32).to_le_bytes();
let id = id.bytes();
write_at![dst, 0, id[0], id[1], id[2], id[3], size[0], size[1], size[2], size[3]];
Ok(Self::CHUNK_HEADER_LEN)
}
pub const fn write_form_header(
dst: &mut [u8],
form: BinTag4,
subchunks_len: usize,
) -> Result<usize, RiffError> {
let Some(riff_data_len) = Self::FORM_TYPE_LEN.checked_add(subchunks_len) else {
return Err(RiffError::Overflow);
};
is! { dst.len() < Self::ROOT_HEADER_LEN, return Err(RiffError::NotEnoughSpace) }
let mut offset = unwrap![ok? Self::write_chunk_header(dst, Self::RIFF, riff_data_len)];
let form = form.bytes();
write_at![dst, +=offset, form[0], form[1], form[2], form[3]];
Ok(offset)
}
pub(super) const fn chunk_at<'a>(
bytes: &'a [u8],
offset: usize,
) -> Result<(RiffChunk<'a>, usize), RiffError> {
let header_end = unwrap![some_ok_or? offset.checked_add(8), RiffError::Overflow];
is! { header_end > bytes.len(), return Err(RiffError::TruncatedHeader) }
let id =
BinTag4::new([bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]]);
let size = u32::from_le_bytes([
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]);
let len = cfg_select! { any(target_pointer_width = "8", target_pointer_width = "16") => {
unwrap![ok_or? crate::cast![checked size => usize], RiffError::Overflow]
} _ => { size as usize }
};
let data_end = unwrap![some_ok_or? header_end.checked_add(len), RiffError::Overflow];
is! { data_end > bytes.len(), return Err(RiffError::TruncatedData) }
let next = unwrap![some_ok_or? data_end.checked_add(Self::pad_len(len)),
RiffError::Overflow];
is! { next > bytes.len(), return Err(RiffError::TruncatedPad) }
let data = slice![bytes, header_end, ..data_end];
Ok((RiffChunk { id, size, data, offset }, next))
}
}
impl Riff {
pub const RIFF: BinTag4 = BinTag4::new(*b"RIFF");
pub const RIFX: BinTag4 = BinTag4::new(*b"RIFX");
pub const LIST: BinTag4 = BinTag4::new(*b"LIST");
pub const WAVE: BinTag4 = BinTag4::new(*b"WAVE");
pub const FMT: BinTag4 = BinTag4::new(*b"fmt ");
pub const FACT: BinTag4 = BinTag4::new(*b"fact");
pub const DATA: BinTag4 = BinTag4::new(*b"data");
pub const JUNK: BinTag4 = BinTag4::new(*b"JUNK");
pub const INFO: BinTag4 = BinTag4::new(*b"INFO");
}