use crate::c2pa_formats::{AssetFormat, FormatError};
#[inline]
pub(crate) fn be_u16(data: &[u8], off: usize) -> Option<u16> {
data.get(off..off + 2)
.map(|b| u16::from_be_bytes([b[0], b[1]]))
}
#[inline]
pub(crate) fn be_u24(data: &[u8], off: usize) -> Option<u32> {
data.get(off..off + 3)
.map(|b| u32::from_be_bytes([0, b[0], b[1], b[2]]))
}
#[inline]
pub(crate) fn be_u32(data: &[u8], off: usize) -> Option<u32> {
data.get(off..off + 4)
.map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
}
#[inline]
pub(crate) fn be_u64(data: &[u8], off: usize) -> Option<u64> {
data.get(off..off + 8)
.map(|b| u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]))
}
#[inline]
pub(crate) fn le_u32(data: &[u8], off: usize) -> Option<u32> {
data.get(off..off + 4)
.map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn u64_to_usize_with_max(value: u64, max: usize) -> Option<usize> {
let converted = usize::try_from(value).ok()?;
(converted <= max).then_some(converted)
}
#[inline]
pub(crate) fn u64_to_usize(value: u64) -> Option<usize> {
u64_to_usize_with_max(value, usize::MAX)
}
pub(crate) struct IsoBox {
pub box_type: [u8; 4],
pub start: usize,
pub payload_start: usize,
pub end: usize,
}
pub(crate) fn walk_iso_boxes(
data: &[u8],
format: AssetFormat,
mut f: impl FnMut(&IsoBox),
) -> Result<(), FormatError> {
let mut pos = 0usize;
while pos + 8 <= data.len() {
let size32 = be_u32(data, pos).ok_or(FormatError::Truncated(format))?;
let mut box_type = [0u8; 4];
box_type.copy_from_slice(&data[pos + 4..pos + 8]);
let (payload_start, end) = if size32 == 1 {
let large = be_u64(data, pos + 8).ok_or(FormatError::Truncated(format))?;
let large = u64_to_usize(large).ok_or(FormatError::Truncated(format))?;
let end = pos
.checked_add(large)
.filter(|&e| large >= 16 && e <= data.len())
.ok_or(FormatError::Truncated(format))?;
(pos + 16, end)
} else if size32 == 0 {
(pos + 8, data.len())
} else {
let size = usize::try_from(size32).map_err(|_| FormatError::Truncated(format))?;
let end = pos
.checked_add(size)
.filter(|&e| size >= 8 && e <= data.len())
.ok_or(FormatError::Truncated(format))?;
(pos + 8, end)
};
f(&IsoBox {
box_type,
start: pos,
payload_start,
end,
});
if end <= pos {
break;
}
pos = end;
}
Ok(())
}
#[cfg(test)]
pub(crate) fn iso_box_header(box_type: &[u8; 4], payload_len: usize) -> Vec<u8> {
let total = 8 + payload_len;
if (total as u64) < (1u64 << 32) {
let mut h = Vec::with_capacity(8);
h.extend_from_slice(&(total as u32).to_be_bytes());
h.extend_from_slice(box_type);
h
} else {
let mut h = Vec::with_capacity(16);
h.extend_from_slice(&1u32.to_be_bytes());
h.extend_from_slice(box_type);
h.extend_from_slice(&((total + 8) as u64).to_be_bytes());
h
}
}
#[cfg(test)]
pub(crate) struct Crc32 {
value: u32,
}
#[cfg(test)]
impl Crc32 {
pub(crate) fn new() -> Self {
Crc32 { value: 0xFFFF_FFFF }
}
pub(crate) fn update(&mut self, bytes: &[u8]) {
for &b in bytes {
self.value ^= b as u32;
for _ in 0..8 {
let mask = (self.value & 1).wrapping_neg();
self.value = (self.value >> 1) ^ (0xEDB8_8320 & mask);
}
}
}
pub(crate) fn finalize(self) -> u32 {
self.value ^ 0xFFFF_FFFF
}
}
#[cfg(test)]
pub(crate) fn crc32(bytes: &[u8]) -> u32 {
let mut c = Crc32::new();
c.update(bytes);
c.finalize()
}
#[cfg(test)]
const B64_ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[cfg(test)]
pub(crate) fn base64_encode(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(B64_ALPHABET[(n >> 18) as usize & 0x3F] as char);
out.push(B64_ALPHABET[(n >> 12) as usize & 0x3F] as char);
out.push(if chunk.len() > 1 {
B64_ALPHABET[(n >> 6) as usize & 0x3F] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
B64_ALPHABET[n as usize & 0x3F] as char
} else {
'='
});
}
out
}
pub(crate) fn base64_decode(s: &str) -> Option<Vec<u8>> {
fn val(c: u8) -> Option<u32> {
match c {
b'A'..=b'Z' => Some((c - b'A') as u32),
b'a'..=b'z' => Some((c - b'a' + 26) as u32),
b'0'..=b'9' => Some((c - b'0' + 52) as u32),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
let bytes: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
for chunk in bytes.chunks(4) {
if chunk.len() < 4 {
return None;
}
let pads = chunk.iter().filter(|&&c| c == b'=').count();
let mut n = 0u32;
for (i, &c) in chunk.iter().enumerate() {
let v = if c == b'=' { 0 } else { val(c)? };
n |= v << (18 - 6 * i);
}
out.push((n >> 16) as u8);
if pads < 2 {
out.push((n >> 8) as u8);
}
if pads < 1 {
out.push(n as u8);
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn u64_length_conversion_respects_target_width() {
let wasm32_max = usize::try_from(u32::MAX).unwrap();
assert_eq!(
u64_to_usize_with_max(u64::from(u32::MAX), wasm32_max),
Some(wasm32_max)
);
assert_eq!(u64_to_usize_with_max(1u64 << 32, wasm32_max), None);
}
#[test]
fn iso_box_largesize_above_wasm32_range_fails_closed() {
let mut box_bytes = Vec::from(1u32.to_be_bytes());
box_bytes.extend_from_slice(b"free");
box_bytes.extend_from_slice(&((1u64 << 32) + 16).to_be_bytes());
assert_eq!(
walk_iso_boxes(&box_bytes, AssetFormat::Bmff, |_| {}),
Err(FormatError::Truncated(AssetFormat::Bmff))
);
}
#[test]
fn iso_box_minimum_largesize_is_accepted() {
let mut box_bytes = Vec::from(1u32.to_be_bytes());
box_bytes.extend_from_slice(b"free");
box_bytes.extend_from_slice(&16u64.to_be_bytes());
let mut span = None;
walk_iso_boxes(&box_bytes, AssetFormat::Bmff, |b| {
span = Some((b.payload_start, b.end));
})
.unwrap();
assert_eq!(span, Some((16, 16)));
}
}