use std::io::{Result as IoResult, Write};
use crate::boxes::STYP;
pub fn build_styp(major_brand: [u8; 4], compatible_brands: &[[u8; 4]]) -> Vec<u8> {
build_styp_with_minor(major_brand, 0, compatible_brands)
}
pub fn build_styp_with_minor(
major_brand: [u8; 4],
minor_version: u32,
compatible_brands: &[[u8; 4]],
) -> Vec<u8> {
let body_len = 8 + 4 * compatible_brands.len();
let total = 8 + body_len;
debug_assert!(
total <= u32::MAX as usize,
"styp box exceeds u32 size limit"
);
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&(total as u32).to_be_bytes());
out.extend_from_slice(&STYP);
out.extend_from_slice(&major_brand);
out.extend_from_slice(&minor_version.to_be_bytes());
for b in compatible_brands {
out.extend_from_slice(b);
}
debug_assert_eq!(out.len(), total);
out
}
pub fn write_styp<W: Write>(
writer: &mut W,
major_brand: [u8; 4],
compatible_brands: &[[u8; 4]],
) -> IoResult<()> {
write_styp_with_minor(writer, major_brand, 0, compatible_brands)
}
pub fn write_styp_with_minor<W: Write>(
writer: &mut W,
major_brand: [u8; 4],
minor_version: u32,
compatible_brands: &[[u8; 4]],
) -> IoResult<()> {
let body_len = 8 + 4 * compatible_brands.len();
let total = (8 + body_len) as u32;
writer.write_all(&total.to_be_bytes())?;
writer.write_all(&STYP)?;
writer.write_all(&major_brand)?;
writer.write_all(&minor_version.to_be_bytes())?;
for b in compatible_brands {
writer.write_all(b)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_back(bytes: &[u8]) -> ([u8; 4], u32, Vec<[u8; 4]>) {
let size = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
assert_eq!(size as usize, bytes.len(), "box size header mismatch");
assert_eq!(&bytes[4..8], b"styp", "box type not styp");
let mut major = [0u8; 4];
major.copy_from_slice(&bytes[8..12]);
let minor = u32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
let rest = &bytes[16..];
assert_eq!(rest.len() % 4, 0, "compat-brand tail not 4-aligned");
let mut brands = Vec::with_capacity(rest.len() / 4);
for c in rest.chunks_exact(4) {
let mut b = [0u8; 4];
b.copy_from_slice(c);
brands.push(b);
}
(major, minor, brands)
}
#[test]
fn build_styp_byte_exact_for_dash_segment() {
let got = build_styp(*b"iso5", &[*b"iso5", *b"dash", *b"msdh"]);
let want: &[u8] = &[
0x00, 0x00, 0x00, 0x1c, b's', b't', b'y', b'p', b'i', b's', b'o', b'5', 0x00, 0x00, 0x00, 0x00, b'i', b's', b'o', b'5', b'd', b'a', b's', b'h', b'm', b's', b'd', b'h', ];
assert_eq!(got.as_slice(), want);
}
#[test]
fn build_styp_with_empty_compat_is_sixteen_bytes() {
let got = build_styp(*b"msdh", &[]);
assert_eq!(got.len(), 16);
let want: &[u8] = &[
0x00, 0x00, 0x00, 0x10, b's', b't', b'y', b'p', b'm', b's', b'd', b'h', 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(got.as_slice(), want);
}
#[test]
fn build_styp_with_minor_round_trips_field_set() {
let major = *b"avif";
let minor = 0x0001_0002;
let compat = vec![*b"mif1", *b"miaf"];
let bytes = build_styp_with_minor(major, minor, &compat);
let (got_major, got_minor, got_compat) = parse_back(&bytes);
assert_eq!(got_major, major);
assert_eq!(got_minor, minor);
assert_eq!(got_compat, compat);
}
#[test]
fn write_styp_matches_build_styp() {
let major = *b"iso6";
let compat = vec![*b"cmfs", *b"msdh"];
let v = build_styp(major, &compat);
let mut sink = Vec::new();
write_styp(&mut sink, major, &compat).unwrap();
assert_eq!(sink, v);
}
#[test]
fn write_styp_with_minor_matches_build_with_minor() {
let major = *b"heic";
let minor = 0xDEAD_BEEF;
let compat = vec![*b"mif1"];
let v = build_styp_with_minor(major, minor, &compat);
let mut sink = Vec::new();
write_styp_with_minor(&mut sink, major, minor, &compat).unwrap();
assert_eq!(sink, v);
}
#[test]
fn build_styp_preserves_compat_brand_order() {
let in_order = vec![*b"iso5", *b"msdh", *b"msix"];
let bytes = build_styp(*b"iso5", &in_order);
let (_major, _minor, got) = parse_back(&bytes);
assert_eq!(got, in_order);
}
#[test]
fn write_styp_with_empty_compat_writes_sixteen_bytes() {
let mut sink = Vec::new();
write_styp(&mut sink, *b"cmfs", &[]).unwrap();
assert_eq!(sink.len(), 16);
assert_eq!(&sink[4..8], b"styp");
assert_eq!(&sink[8..12], b"cmfs");
assert_eq!(&sink[12..16], &[0u8; 4]);
}
#[test]
fn build_styp_box_size_field_matches_total_length() {
for (major, compat) in [
(*b"iso5", &[][..]),
(*b"msdh", &[*b"msdh", *b"msix"][..]),
(*b"iso6", &[*b"iso6", *b"cmfs", *b"cmfc", *b"dash"][..]),
] {
let bytes = build_styp(major, compat);
let size = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
assert_eq!(size as usize, bytes.len());
}
}
#[test]
fn build_styp_with_one_compat_brand_is_twenty_bytes() {
let bytes = build_styp(*b"msdh", &[*b"msdh"]);
assert_eq!(bytes.len(), 20);
}
#[test]
fn write_styp_returns_io_error_when_writer_fails() {
struct FailingWriter;
impl Write for FailingWriter {
fn write(&mut self, _: &[u8]) -> IoResult<usize> {
Err(std::io::Error::other("boom"))
}
fn flush(&mut self) -> IoResult<()> {
Ok(())
}
}
let res = write_styp(&mut FailingWriter, *b"iso5", &[*b"msdh"]);
assert!(res.is_err(), "writer error should propagate");
}
}