use crate::error::{AvifError as Error, Result};
pub use oxideav_heif::boxes::BoxHeader;
pub type BoxType = oxideav_heif::boxes::FourCc;
pub const fn b(s: &[u8; 4]) -> BoxType {
*s
}
pub fn type_str(t: &BoxType) -> String {
String::from_utf8_lossy(t).into_owned()
}
pub fn iter_boxes(buf: &[u8]) -> BoxIter<'_> {
BoxIter {
inner: oxideav_heif::boxes::iter_boxes(buf),
}
}
pub struct BoxIter<'a> {
inner: oxideav_heif::boxes::BoxIter<'a>,
}
impl Iterator for BoxIter<'_> {
type Item = Result<BoxHeader>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|r| r.map_err(Error::from))
}
}
pub fn parse_box_header(buf: &[u8], start: usize) -> Result<BoxHeader> {
oxideav_heif::boxes::parse_box_header(buf, start).map_err(Error::from)
}
pub fn parse_full_box(payload: &[u8]) -> Result<(u8, u32, &[u8])> {
oxideav_heif::boxes::parse_full_box(payload).map_err(Error::from)
}
pub fn find_box<'a>(buf: &'a [u8], target: &BoxType) -> Result<Option<(&'a [u8], BoxHeader)>> {
Ok(oxideav_heif::boxes::find_box(buf, target)?.map(|(h, p)| (p, h)))
}
fn reader_at<'a>(
buf: &'a [u8],
at: usize,
width: usize,
what: &str,
) -> Result<oxideav_heif::boxes::Reader<'a>> {
let end = at
.checked_add(width)
.ok_or_else(|| Error::invalid(format!("avif: {what} offset overflow")))?;
if end > buf.len() {
return Err(Error::invalid(format!("avif: truncated {what} read")));
}
Ok(oxideav_heif::boxes::Reader::new(&buf[at..]))
}
#[doc(hidden)]
pub fn read_u16(buf: &[u8], at: usize) -> Result<u16> {
Ok(reader_at(buf, at, 2, "u16")?.u16("u16")?)
}
#[doc(hidden)]
pub fn read_u32(buf: &[u8], at: usize) -> Result<u32> {
Ok(reader_at(buf, at, 4, "u32")?.u32("u32")?)
}
#[doc(hidden)]
pub fn read_u64(buf: &[u8], at: usize) -> Result<u64> {
Ok(reader_at(buf, at, 8, "u64")?.u64("u64")?)
}
#[doc(hidden)]
pub fn read_var_uint(buf: &[u8], at: usize, width_bytes: usize) -> Result<u64> {
match width_bytes {
0 => Ok(0),
4 | 8 => {
Ok(reader_at(buf, at, width_bytes, "iloc field")?.uint(width_bytes, "iloc field")?)
}
_ => Err(Error::invalid(format!(
"avif: unsupported iloc field width {width_bytes}"
))),
}
}
#[doc(hidden)]
pub fn read_cstr(buf: &[u8], at: usize) -> Result<(String, usize)> {
let tail = buf
.get(at..)
.ok_or_else(|| Error::invalid("avif: unterminated C string"))?;
let nul = tail
.iter()
.position(|&c| c == 0)
.ok_or_else(|| Error::invalid("avif: unterminated C string"))?;
let mut r = oxideav_heif::boxes::Reader::new(&tail[..=nul]);
let s = r.cstr("string")?;
Ok((s, at + nul + 1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn walks_ftyp_then_meta() {
let mut buf = Vec::new();
buf.extend_from_slice(&0x20u32.to_be_bytes());
buf.extend_from_slice(b"ftyp");
buf.extend_from_slice(&[0u8; 0x18]);
buf.extend_from_slice(&0x08u32.to_be_bytes());
buf.extend_from_slice(b"meta");
let headers: Vec<_> = iter_boxes(&buf).collect::<Result<_>>().unwrap();
assert_eq!(headers.len(), 2);
assert_eq!(&headers[0].box_type, b"ftyp");
assert_eq!(headers[0].total_len(), 0x20);
assert_eq!(&headers[1].box_type, b"meta");
assert_eq!(headers[1].payload_len, 0);
}
#[test]
fn rejects_truncated() {
let buf = [0, 0, 0, 0x20, b'f', b't', b'y', b'p', 0, 0, 0]; let err = parse_box_header(&buf, 0).unwrap_err();
assert!(format!("{err}").contains("out of range"));
}
#[test]
fn rejects_offset_overflow() {
let buf = [0u8; 16];
let err = parse_box_header(&buf, usize::MAX - 3).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("overflow") || msg.contains("truncated"),
"expected overflow / truncated, got: {msg}"
);
}
#[test]
fn read_u32_rejects_overflow_offset() {
let buf = [0u8; 4];
let err = read_u32(&buf, usize::MAX - 1).unwrap_err();
assert!(format!("{err}").contains("overflow"));
}
}