use crate::read::ReadAt;
#[derive(Debug, Clone, PartialEq)]
pub enum VerifyError {
OutOfBounds { context: &'static str, offset: usize, needed: usize, buf_len: usize },
BadOffset { at: usize },
VTableTooSmall { vtable_size: usize },
FieldOutOfBounds { field_idx: usize, voff: usize, field_size: usize, object_size: usize },
InvalidUtf8 { at: usize },
VectorOverflow { at: usize, len: usize, elem_size: usize },
DepthLimitExceeded,
}
pub type VerifyResult = Result<(), VerifyError>;
pub const MAX_DEPTH: usize = 64;
pub trait Verify {
const INLINE_SIZE: usize;
fn verify_at(buf: &[u8], offset: usize, depth: usize, out: &mut Vec<usize>) -> VerifyResult;
}
pub fn verify_root<T: Verify>(buf: &[u8]) -> Result<Vec<usize>, VerifyError> {
check_bounds(buf, 0, 4, "root offset")?;
let root_offset = u32::read_at(buf, 0) as usize;
let mut positions = Vec::new();
T::verify_at(buf, root_offset, MAX_DEPTH, &mut positions)?;
Ok(positions)
}
#[inline]
pub fn verify_table_header(
buf: &[u8], t_pos: usize, depth: usize,
) -> Result<(usize, usize, usize), VerifyError> {
if depth == 0 { return Err(VerifyError::DepthLimitExceeded); }
check_bounds(buf, t_pos, 4, "vtable pointer")?;
let jump = i32::read_at(buf, t_pos);
let v_pos = checked_apply_i32(t_pos, jump)
.filter(|&v| v < buf.len())
.ok_or(VerifyError::BadOffset { at: t_pos })?;
check_bounds(buf, v_pos, 4, "vtable header")?;
let vtable_size = u16::read_at(buf, v_pos) as usize;
let object_size = u16::read_at(buf, v_pos + 2) as usize;
if vtable_size < 4 { return Err(VerifyError::VTableTooSmall { vtable_size }); }
check_bounds(buf, v_pos, vtable_size, "vtable body")?;
check_bounds(buf, t_pos, object_size, "table object")?;
Ok((v_pos, vtable_size, object_size))
}
#[inline]
pub fn verify_vtable_field(
buf: &[u8], v_pos: usize, vtable_size: usize,
t_pos: usize, object_size: usize,
field_idx: usize, field_size: usize,
) -> Result<Option<usize>, VerifyError> {
let entry_off = 4 + field_idx * 2;
if entry_off + 2 > vtable_size { return Ok(None); }
let voff = u16::read_at(buf, v_pos + entry_off) as usize;
if voff == 0 { return Ok(None); }
if voff.saturating_add(field_size) > object_size {
return Err(VerifyError::FieldOutOfBounds { field_idx, voff, field_size, object_size });
}
Ok(Some(t_pos + voff))
}
#[inline]
pub fn verify_string_field(buf: &[u8], field_pos: usize) -> VerifyResult {
check_bounds(buf, field_pos, 4, "string forward-offset")?;
let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
check_bounds(buf, hdr_pos, 4, "string length prefix")?;
let len = u32::read_at(buf, hdr_pos) as usize;
check_bounds(buf, hdr_pos + 4, len, "string bytes")?;
std::str::from_utf8(&buf[hdr_pos + 4..hdr_pos + 4 + len])
.map_err(|_| VerifyError::InvalidUtf8 { at: hdr_pos + 4 })?;
Ok(())
}
#[inline]
pub fn verify_file_field(buf: &[u8], field_pos: usize) -> VerifyResult {
check_bounds(buf, field_pos, 4, "File forward-offset")?;
let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
check_bounds(buf, hdr_pos, 4, "File length prefix")?;
let len = u32::read_at(buf, hdr_pos) as usize;
check_bounds(buf, hdr_pos + 4, len, "File bytes")?;
std::str::from_utf8(&buf[hdr_pos + 4..hdr_pos + 4 + len])
.map_err(|_| VerifyError::InvalidUtf8 { at: hdr_pos + 4 })?;
Ok(())
}
#[inline]
pub fn verify_scalar_array(buf: &[u8], field_pos: usize, elem_size: usize) -> VerifyResult {
check_bounds(buf, field_pos, 4, "array forward-offset")?;
let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
check_bounds(buf, hdr_pos, 4, "array length prefix")?;
let len = u32::read_at(buf, hdr_pos) as usize;
let total = len.checked_mul(elem_size)
.ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size })?;
check_bounds(buf, hdr_pos + 4, total, "array data")
}
#[inline]
pub fn verify_string_array(buf: &[u8], field_pos: usize) -> VerifyResult {
check_bounds(buf, field_pos, 4, "string-array forward-offset")?;
let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
check_bounds(buf, hdr_pos, 4, "string-array length prefix")?;
let len = u32::read_at(buf, hdr_pos) as usize;
let total = len.checked_mul(4)
.ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size: 4 })?;
check_bounds(buf, hdr_pos + 4, total, "string-array offset table")?;
for i in 0..len {
verify_string_field(buf, hdr_pos + 4 + i * 4)?;
}
Ok(())
}
#[inline]
pub fn verify_file_array(buf: &[u8], field_pos: usize) -> VerifyResult {
check_bounds(buf, field_pos, 4, "file-array forward-offset")?;
let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
check_bounds(buf, hdr_pos, 4, "file-array length prefix")?;
let len = u32::read_at(buf, hdr_pos) as usize;
let total = len.checked_mul(4)
.ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size: 4 })?;
check_bounds(buf, hdr_pos + 4, total, "file-array offset table")?;
for i in 0..len {
verify_file_field(buf, hdr_pos + 4 + i * 4)?;
}
Ok(())
}
#[inline]
pub fn verify_table_array<T: Verify>(
buf: &[u8], field_pos: usize, depth: usize, out: &mut Vec<usize>,
) -> VerifyResult {
check_bounds(buf, field_pos, 4, "table-array forward-offset")?;
let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
check_bounds(buf, hdr_pos, 4, "table-array length prefix")?;
let len = u32::read_at(buf, hdr_pos) as usize;
let total = len.checked_mul(4)
.ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size: 4 })?;
check_bounds(buf, hdr_pos + 4, total, "table-array offset table")?;
for i in 0..len {
let ep = hdr_pos + 4 + i * 4;
let table_pos = ep.saturating_add(u32::read_at(buf, ep) as usize);
T::verify_at(buf, table_pos, depth, out)?;
}
Ok(())
}
#[inline]
pub fn verify_table_field<T: Verify>(
buf: &[u8], field_pos: usize, depth: usize, out: &mut Vec<usize>,
) -> VerifyResult {
check_bounds(buf, field_pos, 4, "table forward-offset")?;
let table_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
T::verify_at(buf, table_pos, depth, out)
}
macro_rules! impl_verify_scalar {
($($t:ty),*) => {$(
impl Verify for $t {
const INLINE_SIZE: usize = ::std::mem::size_of::<Self>();
#[inline(always)]
fn verify_at(buf: &[u8], offset: usize, _depth: usize, _out: &mut Vec<usize>) -> VerifyResult {
check_bounds(buf, offset, ::std::mem::size_of::<Self>(), "scalar")
}
}
)*};
}
impl_verify_scalar!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool);
impl Verify for &str {
const INLINE_SIZE: usize = 4;
#[inline]
fn verify_at(buf: &[u8], offset: usize, _depth: usize, _out: &mut Vec<usize>) -> VerifyResult {
check_bounds(buf, offset, 4, "string length prefix")?;
let len = u32::read_at(buf, offset) as usize;
check_bounds(buf, offset + 4, len, "string bytes")?;
std::str::from_utf8(&buf[offset + 4..offset + 4 + len])
.map_err(|_| VerifyError::InvalidUtf8 { at: offset + 4 })?;
Ok(())
}
}
impl Verify for String {
const INLINE_SIZE: usize = 4;
#[inline]
fn verify_at(buf: &[u8], offset: usize, _depth: usize, _out: &mut Vec<usize>) -> VerifyResult {
check_bounds(buf, offset, 4, "string length prefix")?;
let len = u32::read_at(buf, offset) as usize;
check_bounds(buf, offset + 4, len, "string bytes")?;
std::str::from_utf8(&buf[offset + 4..offset + 4 + len])
.map_err(|_| VerifyError::InvalidUtf8 { at: offset + 4 })?;
Ok(())
}
}
#[inline(always)]
pub fn check_bounds(buf: &[u8], offset: usize, needed: usize, ctx: &'static str) -> VerifyResult {
if offset.saturating_add(needed) > buf.len() {
Err(VerifyError::OutOfBounds { context: ctx, offset, needed, buf_len: buf.len() })
} else {
Ok(())
}
}
#[inline(always)]
fn checked_apply_i32(base: usize, delta: i32) -> Option<usize> {
if delta >= 0 { base.checked_sub(delta as usize) }
else { base.checked_add(delta.unsigned_abs() as usize) }
}