use core::mem::size_of;
use core::str;
use crate::payload::{EncodeError, Malformed};
pub const OFFSET_SIZE: usize = 4;
pub const VOFFSET_SIZE: usize = 2;
pub const VTABLE_HEADER: usize = 4;
fn at(buf: &[u8], from: usize, len: usize) -> Result<&[u8], Malformed> {
buf.get(from..)
.and_then(|rest| rest.get(..len))
.ok_or(Malformed::OutOfBounds)
}
macro_rules! read_scalar {
($name:ident, $ty:ty, $what:literal) => {
#[doc = concat!("Reads the little-endian `", $what, "` at `from`.")]
pub fn $name(buf: &[u8], from: usize) -> Result<$ty, Malformed> {
let mut bytes = [0u8; size_of::<$ty>()];
bytes.copy_from_slice(at(buf, from, size_of::<$ty>())?);
Ok(<$ty>::from_le_bytes(bytes))
}
};
}
read_scalar!(read_u8, u8, "u8");
read_scalar!(read_i8, i8, "i8");
read_scalar!(read_u16, u16, "u16");
read_scalar!(read_i16, i16, "i16");
read_scalar!(read_u32, u32, "u32");
read_scalar!(read_i32, i32, "i32");
read_scalar!(read_u64, u64, "u64");
read_scalar!(read_i64, i64, "i64");
read_scalar!(read_f32, f32, "f32");
read_scalar!(read_f64, f64, "f64");
pub fn read_bool(buf: &[u8], from: usize) -> Result<bool, Malformed> {
Ok(read_u8(buf, from)? != 0)
}
pub fn follow(buf: &[u8], from: usize) -> Result<usize, Malformed> {
let relative = u64::from(read_u32(buf, from)?);
let target = from as u64 + relative;
if target >= buf.len() as u64 {
return Err(Malformed::OutOfBounds);
}
Ok(target as usize)
}
pub fn root(buf: &[u8]) -> Result<usize, Malformed> {
follow(buf, 0)
}
pub fn field(
buf: &[u8],
table: usize,
slot: u16,
width: usize,
) -> Result<Option<usize>, Malformed> {
let soffset = read_i32(buf, table)?;
let vtable = table as i64 - i64::from(soffset);
if vtable < 0 || vtable as u64 >= buf.len() as u64 {
return Err(Malformed::OutOfBounds);
}
let vtable = vtable as usize;
let vtable_bytes = usize::from(read_u16(buf, vtable)?);
if vtable_bytes < VTABLE_HEADER {
return Err(Malformed::OutOfBounds);
}
at(buf, vtable, vtable_bytes)?;
let table_bytes = usize::from(read_u16(buf, vtable + VOFFSET_SIZE)?);
let entry = vtable + VTABLE_HEADER + usize::from(slot) * VOFFSET_SIZE;
if entry + VOFFSET_SIZE > vtable + vtable_bytes {
return Ok(None);
}
let offset = usize::from(read_u16(buf, entry)?);
if offset == 0 {
return Ok(None);
}
if offset < OFFSET_SIZE || offset + width > table_bytes {
return Err(Malformed::OutOfBounds);
}
let position = table as u64 + offset as u64;
if position >= buf.len() as u64 {
return Err(Malformed::OutOfBounds);
}
Ok(Some(position as usize))
}
pub fn string(buf: &[u8], from: usize) -> Result<&str, Malformed> {
let start = follow(buf, from)?;
let len = read_u32(buf, start)? as usize;
let bytes = at(buf, start + OFFSET_SIZE, len)?;
if at(buf, start + OFFSET_SIZE + len, 1)?[0] != 0 {
return Err(Malformed::OutOfBounds);
}
str::from_utf8(bytes).map_err(|_| Malformed::Utf8)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Vector {
pub len: usize,
pub first: usize,
}
impl Vector {
pub fn element(&self, index: usize, stride: usize) -> usize {
self.first + index * stride
}
}
pub fn vector(buf: &[u8], from: usize, stride: usize) -> Result<Vector, Malformed> {
let start = follow(buf, from)?;
let len = read_u32(buf, start)? as usize;
let first = start + OFFSET_SIZE;
let span = len.checked_mul(stride).ok_or(Malformed::OutOfBounds)?;
at(buf, first, span)?;
Ok(Vector { len, first })
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Pos(usize);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Field {
Bool(bool),
U8(u8),
I8(i8),
U16(u16),
I16(i16),
U32(u32),
I32(i32),
U64(u64),
I64(i64),
F32(f32),
F64(f64),
Offset(Pos),
}
impl Field {
pub fn size(&self) -> usize {
match self {
Field::Bool(_) | Field::U8(_) | Field::I8(_) => 1,
Field::U16(_) | Field::I16(_) => 2,
Field::U32(_) | Field::I32(_) | Field::F32(_) | Field::Offset(_) => 4,
Field::U64(_) | Field::I64(_) | Field::F64(_) => 8,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TableField {
pub slot: u16,
pub offset: u16,
pub value: Field,
}
pub struct Builder<'a> {
out: &'a mut [u8],
used: usize,
}
impl<'a> Builder<'a> {
pub fn new(out: &'a mut [u8]) -> Self {
Builder { out, used: 0 }
}
pub fn used(&self) -> usize {
self.used
}
pub fn reserve(&mut self, size: usize, align: usize) -> Result<(Pos, &mut [u8]), EncodeError> {
self.reserve_skewed(size, align, 0)
}
fn reserve_skewed(
&mut self,
size: usize,
align: usize,
skew: usize,
) -> Result<(Pos, &mut [u8]), EncodeError> {
debug_assert!(align.is_power_of_two(), "an alignment is a power of two");
let capacity = self.out.len();
let unpadded = match self.used.checked_add(size) {
Some(n) => n,
None => {
return Err(EncodeError::Capacity {
needed: usize::MAX,
available: capacity,
})
}
};
let padding = (align + (skew % align) - (unpadded % align)) % align;
let needed = match unpadded.checked_add(padding) {
Some(n) => n,
None => {
return Err(EncodeError::Capacity {
needed: usize::MAX,
available: capacity,
})
}
};
if needed > capacity {
return Err(EncodeError::Capacity {
needed,
available: capacity,
});
}
self.used = needed;
let start = capacity - needed;
self.out[start..start + size + padding].fill(0);
Ok((Pos(needed), &mut self.out[start..start + size]))
}
fn delta(from: Pos, to: Pos) -> usize {
debug_assert!(
to < from,
"a uoffset points forward, so its target is written before it and by this builder"
);
from.0.saturating_sub(to.0)
}
}
macro_rules! push_scalar {
($name:ident, $ty:ty, $what:literal) => {
#[doc = concat!("Writes a little-endian `", $what, "`, aligned to its own size.")]
pub fn $name(&mut self, value: $ty) -> Result<Pos, EncodeError> {
let (position, bytes) = self.reserve(size_of::<$ty>(), size_of::<$ty>())?;
bytes.copy_from_slice(&value.to_le_bytes());
Ok(position)
}
};
}
impl<'a> Builder<'a> {
push_scalar!(push_u8, u8, "u8");
push_scalar!(push_i8, i8, "i8");
push_scalar!(push_u16, u16, "u16");
push_scalar!(push_i16, i16, "i16");
push_scalar!(push_u32, u32, "u32");
push_scalar!(push_i32, i32, "i32");
push_scalar!(push_u64, u64, "u64");
push_scalar!(push_i64, i64, "i64");
push_scalar!(push_f32, f32, "f32");
push_scalar!(push_f64, f64, "f64");
pub fn push_offset(&mut self, target: Pos) -> Result<Pos, EncodeError> {
let (position, bytes) = self.reserve(OFFSET_SIZE, OFFSET_SIZE)?;
let delta = Builder::delta(position, target) as u32;
bytes.copy_from_slice(&delta.to_le_bytes());
Ok(position)
}
pub fn push_string(&mut self, value: &str) -> Result<Pos, EncodeError> {
let len = value.len();
let size = OFFSET_SIZE + len + 1;
let (position, bytes) = self.reserve(size, OFFSET_SIZE)?;
bytes[..OFFSET_SIZE].copy_from_slice(&(len as u32).to_le_bytes());
bytes[OFFSET_SIZE..OFFSET_SIZE + len].copy_from_slice(value.as_bytes());
Ok(position)
}
pub fn push_vector(&mut self, elements: &[u8], stride: usize) -> Result<Pos, EncodeError> {
debug_assert!(stride > 0, "an element occupies at least one byte");
debug_assert!(
elements.len() % stride == 0,
"the element bytes are a whole number of elements"
);
let count = elements.len() / stride;
let size = OFFSET_SIZE + elements.len();
let (align, skew) = if stride > OFFSET_SIZE {
(stride, OFFSET_SIZE)
} else {
(OFFSET_SIZE, 0)
};
let (position, bytes) = self.reserve_skewed(size, align, skew)?;
bytes[..OFFSET_SIZE].copy_from_slice(&(count as u32).to_le_bytes());
bytes[OFFSET_SIZE..].copy_from_slice(elements);
Ok(position)
}
pub fn push_offset_vector(&mut self, targets: &[Pos]) -> Result<Pos, EncodeError> {
let capacity = self.out.len();
let count = targets.len();
let size = match count
.checked_add(1)
.and_then(|slots| slots.checked_mul(OFFSET_SIZE))
{
Some(size) => size,
None => {
return Err(EncodeError::Capacity {
needed: usize::MAX,
available: capacity,
})
}
};
let (position, bytes) = self.reserve(size, OFFSET_SIZE)?;
bytes[..OFFSET_SIZE].copy_from_slice(&(count as u32).to_le_bytes());
for (index, target) in targets.iter().enumerate() {
let at = OFFSET_SIZE * (index + 1);
let here = Pos(position.0 - at);
let delta = Builder::delta(here, *target) as u32;
bytes[at..at + OFFSET_SIZE].copy_from_slice(&delta.to_le_bytes());
}
Ok(position)
}
pub fn push_table(
&mut self,
size: usize,
align: usize,
slots: u16,
fields: &[TableField],
) -> Result<Pos, EncodeError> {
debug_assert!(
size >= OFFSET_SIZE,
"a table begins with the offset to its vtable"
);
debug_assert!(
size <= usize::from(u16::MAX),
"a table's size is stated in its vtable as a u16"
);
let table = {
let (table, bytes) = self.reserve(size, align)?;
for f in fields {
let from = usize::from(f.offset);
let width = f.value.size();
debug_assert!(
from >= OFFSET_SIZE && from + width <= size,
"a field lies inside the table and after the vtable offset"
);
let slot = &mut bytes[from..from + width];
match f.value {
Field::Bool(v) => slot.copy_from_slice(&u8::from(v).to_le_bytes()),
Field::U8(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::I8(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::U16(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::I16(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::U32(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::I32(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::U64(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::I64(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::F32(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::F64(v) => slot.copy_from_slice(&v.to_le_bytes()),
Field::Offset(target) => {
let here = Pos(table.0 - from);
let delta = Builder::delta(here, target) as u32;
slot.copy_from_slice(&delta.to_le_bytes());
}
}
}
table
};
let vtable_bytes = VTABLE_HEADER + usize::from(slots) * VOFFSET_SIZE;
debug_assert!(
vtable_bytes <= usize::from(u16::MAX),
"a vtable's size is stated in its first field as a u16"
);
let vtable = {
let (vtable, bytes) = self.reserve(vtable_bytes, VOFFSET_SIZE)?;
bytes[..VOFFSET_SIZE].copy_from_slice(&(vtable_bytes as u16).to_le_bytes());
bytes[VOFFSET_SIZE..VTABLE_HEADER].copy_from_slice(&(size as u16).to_le_bytes());
for f in fields {
debug_assert!(f.slot < slots, "a field's slot is one the vtable carries");
let entry = VTABLE_HEADER + usize::from(f.slot) * VOFFSET_SIZE;
bytes[entry..entry + VOFFSET_SIZE].copy_from_slice(&f.offset.to_le_bytes());
}
vtable
};
let soffset = (vtable.0 - table.0) as i32;
let start = self.out.len() - table.0;
self.out[start..start + OFFSET_SIZE].copy_from_slice(&soffset.to_le_bytes());
Ok(table)
}
pub fn finish(mut self, root: Pos, align: usize) -> Result<&'a [u8], EncodeError> {
let align = if align > OFFSET_SIZE {
align
} else {
OFFSET_SIZE
};
let position = {
let (position, bytes) = self.reserve(OFFSET_SIZE, align)?;
let delta = Builder::delta(position, root) as u32;
bytes.copy_from_slice(&delta.to_le_bytes());
position
};
let out: &'a [u8] = self.out;
Ok(&out[out.len() - position.0..])
}
}