1use crate::read::ReadAt;
3
4#[derive(Debug, Clone, PartialEq)]
7pub enum VerifyError {
8 OutOfBounds { context: &'static str, offset: usize, needed: usize, buf_len: usize },
9 BadOffset { at: usize },
10 VTableTooSmall { vtable_size: usize },
11 FieldOutOfBounds { field_idx: usize, voff: usize, field_size: usize, object_size: usize },
12 InvalidUtf8 { at: usize },
13 VectorOverflow { at: usize, len: usize, elem_size: usize },
14 DepthLimitExceeded,
15}
16
17pub type VerifyResult = Result<(), VerifyError>;
20
21pub const MAX_DEPTH: usize = 64;
22
23pub trait Verify {
26 const INLINE_SIZE: usize;
27
28 fn verify_at(buf: &[u8], offset: usize, depth: usize, out: &mut Vec<usize>) -> VerifyResult;
32}
33
34pub fn verify_root<T: Verify>(buf: &[u8]) -> Result<Vec<usize>, VerifyError> {
38 check_bounds(buf, 0, 4, "root offset")?;
39 let root_offset = u32::read_at(buf, 0) as usize;
40 let mut positions = Vec::new();
41 T::verify_at(buf, root_offset, MAX_DEPTH, &mut positions)?;
42 Ok(positions)
43}
44
45#[inline]
48pub fn verify_table_header(
49 buf: &[u8], t_pos: usize, depth: usize,
50) -> Result<(usize, usize, usize), VerifyError> {
51 if depth == 0 { return Err(VerifyError::DepthLimitExceeded); }
52
53 check_bounds(buf, t_pos, 4, "vtable pointer")?;
54 let jump = i32::read_at(buf, t_pos);
55 let v_pos = checked_apply_i32(t_pos, jump)
56 .filter(|&v| v < buf.len())
57 .ok_or(VerifyError::BadOffset { at: t_pos })?;
58
59 check_bounds(buf, v_pos, 4, "vtable header")?;
60 let vtable_size = u16::read_at(buf, v_pos) as usize;
61 let object_size = u16::read_at(buf, v_pos + 2) as usize;
62
63 if vtable_size < 4 { return Err(VerifyError::VTableTooSmall { vtable_size }); }
64 check_bounds(buf, v_pos, vtable_size, "vtable body")?;
65 check_bounds(buf, t_pos, object_size, "table object")?;
66
67 Ok((v_pos, vtable_size, object_size))
68}
69
70#[inline]
71pub fn verify_vtable_field(
72 buf: &[u8], v_pos: usize, vtable_size: usize,
73 t_pos: usize, object_size: usize,
74 field_idx: usize, field_size: usize,
75) -> Result<Option<usize>, VerifyError> {
76 let entry_off = 4 + field_idx * 2;
77 if entry_off + 2 > vtable_size { return Ok(None); }
78 let voff = u16::read_at(buf, v_pos + entry_off) as usize;
79 if voff == 0 { return Ok(None); }
80 if voff.saturating_add(field_size) > object_size {
81 return Err(VerifyError::FieldOutOfBounds { field_idx, voff, field_size, object_size });
82 }
83 Ok(Some(t_pos + voff))
84}
85
86#[inline]
87pub fn verify_string_field(buf: &[u8], field_pos: usize) -> VerifyResult {
88 check_bounds(buf, field_pos, 4, "string forward-offset")?;
89 let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
90 check_bounds(buf, hdr_pos, 4, "string length prefix")?;
91 let len = u32::read_at(buf, hdr_pos) as usize;
92 check_bounds(buf, hdr_pos + 4, len, "string bytes")?;
93 std::str::from_utf8(&buf[hdr_pos + 4..hdr_pos + 4 + len])
94 .map_err(|_| VerifyError::InvalidUtf8 { at: hdr_pos + 4 })?;
95 Ok(())
96}
97#[inline]
98pub fn verify_file_field(buf: &[u8], field_pos: usize) -> VerifyResult {
99 check_bounds(buf, field_pos, 4, "File forward-offset")?;
100 let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
101 check_bounds(buf, hdr_pos, 4, "File length prefix")?;
102 let len = u32::read_at(buf, hdr_pos) as usize;
103 check_bounds(buf, hdr_pos + 4, len, "File bytes")?;
104 std::str::from_utf8(&buf[hdr_pos + 4..hdr_pos + 4 + len])
105 .map_err(|_| VerifyError::InvalidUtf8 { at: hdr_pos + 4 })?;
106 Ok(())
107}
108#[inline]
109pub fn verify_scalar_array(buf: &[u8], field_pos: usize, elem_size: usize) -> VerifyResult {
110 check_bounds(buf, field_pos, 4, "array forward-offset")?;
111 let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
112 check_bounds(buf, hdr_pos, 4, "array length prefix")?;
113 let len = u32::read_at(buf, hdr_pos) as usize;
114 let total = len.checked_mul(elem_size)
115 .ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size })?;
116 check_bounds(buf, hdr_pos + 4, total, "array data")
117}
118
119#[inline]
120pub fn verify_string_array(buf: &[u8], field_pos: usize) -> VerifyResult {
121 check_bounds(buf, field_pos, 4, "string-array forward-offset")?;
122 let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
123 check_bounds(buf, hdr_pos, 4, "string-array length prefix")?;
124 let len = u32::read_at(buf, hdr_pos) as usize;
125 let total = len.checked_mul(4)
126 .ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size: 4 })?;
127 check_bounds(buf, hdr_pos + 4, total, "string-array offset table")?;
128 for i in 0..len {
129 verify_string_field(buf, hdr_pos + 4 + i * 4)?;
130 }
131 Ok(())
132}
133#[inline]
134pub fn verify_file_array(buf: &[u8], field_pos: usize) -> VerifyResult {
135 check_bounds(buf, field_pos, 4, "file-array forward-offset")?;
136 let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
137 check_bounds(buf, hdr_pos, 4, "file-array length prefix")?;
138 let len = u32::read_at(buf, hdr_pos) as usize;
139 let total = len.checked_mul(4)
140 .ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size: 4 })?;
141 check_bounds(buf, hdr_pos + 4, total, "file-array offset table")?;
142 for i in 0..len {
143 verify_file_field(buf, hdr_pos + 4 + i * 4)?;
144 }
145 Ok(())
146}
147#[inline]
150pub fn verify_table_array<T: Verify>(
151 buf: &[u8], field_pos: usize, depth: usize, out: &mut Vec<usize>,
152) -> VerifyResult {
153 check_bounds(buf, field_pos, 4, "table-array forward-offset")?;
154 let hdr_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
155 check_bounds(buf, hdr_pos, 4, "table-array length prefix")?;
156 let len = u32::read_at(buf, hdr_pos) as usize;
157 let total = len.checked_mul(4)
158 .ok_or(VerifyError::VectorOverflow { at: hdr_pos, len, elem_size: 4 })?;
159 check_bounds(buf, hdr_pos + 4, total, "table-array offset table")?;
160 for i in 0..len {
161 let ep = hdr_pos + 4 + i * 4;
162 let table_pos = ep.saturating_add(u32::read_at(buf, ep) as usize);
163 T::verify_at(buf, table_pos, depth, out)?;
164 }
165 Ok(())
166}
167
168#[inline]
169pub fn verify_table_field<T: Verify>(
170 buf: &[u8], field_pos: usize, depth: usize, out: &mut Vec<usize>,
171) -> VerifyResult {
172 check_bounds(buf, field_pos, 4, "table forward-offset")?;
173 let table_pos = field_pos.saturating_add(u32::read_at(buf, field_pos) as usize);
174 T::verify_at(buf, table_pos, depth, out)
175}
176
177macro_rules! impl_verify_scalar {
180 ($($t:ty),*) => {$(
181 impl Verify for $t {
182 const INLINE_SIZE: usize = ::std::mem::size_of::<Self>();
183 #[inline(always)]
184 fn verify_at(buf: &[u8], offset: usize, _depth: usize, _out: &mut Vec<usize>) -> VerifyResult {
185 check_bounds(buf, offset, ::std::mem::size_of::<Self>(), "scalar")
186 }
187 }
188 )*};
189}
190impl_verify_scalar!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool);
191
192impl Verify for &str {
195 const INLINE_SIZE: usize = 4;
196 #[inline]
197 fn verify_at(buf: &[u8], offset: usize, _depth: usize, _out: &mut Vec<usize>) -> VerifyResult {
198 check_bounds(buf, offset, 4, "string length prefix")?;
201 let len = u32::read_at(buf, offset) as usize;
202 check_bounds(buf, offset + 4, len, "string bytes")?;
203 std::str::from_utf8(&buf[offset + 4..offset + 4 + len])
204 .map_err(|_| VerifyError::InvalidUtf8 { at: offset + 4 })?;
205 Ok(())
206 }
207}
208
209impl Verify for String {
210 const INLINE_SIZE: usize = 4;
211 #[inline]
212 fn verify_at(buf: &[u8], offset: usize, _depth: usize, _out: &mut Vec<usize>) -> VerifyResult {
213 check_bounds(buf, offset, 4, "string length prefix")?;
214 let len = u32::read_at(buf, offset) as usize;
215 check_bounds(buf, offset + 4, len, "string bytes")?;
216 std::str::from_utf8(&buf[offset + 4..offset + 4 + len])
217 .map_err(|_| VerifyError::InvalidUtf8 { at: offset + 4 })?;
218 Ok(())
219 }
220}
221
222#[inline(always)]
225pub fn check_bounds(buf: &[u8], offset: usize, needed: usize, ctx: &'static str) -> VerifyResult {
226 if offset.saturating_add(needed) > buf.len() {
227 Err(VerifyError::OutOfBounds { context: ctx, offset, needed, buf_len: buf.len() })
228 } else {
229 Ok(())
230 }
231}
232
233#[inline(always)]
234fn checked_apply_i32(base: usize, delta: i32) -> Option<usize> {
235 if delta >= 0 { base.checked_sub(delta as usize) }
236 else { base.checked_add(delta.unsigned_abs() as usize) }
237}
238