#[derive(Debug, Default)]
pub struct FlatBuilder {
buf: Vec<u8>,
pub(crate) vtables: Vec<usize>,
finished: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FlatError {
AlreadyFinished,
NotFinished,
InvalidOffset,
}
impl std::fmt::Display for FlatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AlreadyFinished => write!(f, "builder already finished"),
Self::NotFinished => write!(f, "builder not yet finished"),
Self::InvalidOffset => write!(f, "invalid FlatBuffers offset"),
}
}
}
impl std::error::Error for FlatError {}
impl FlatBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(cap: usize) -> Self {
FlatBuilder {
buf: Vec::with_capacity(cap),
vtables: vec![],
finished: false,
}
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn push_u8(&mut self, v: u8) -> Result<usize, FlatError> {
if self.finished {
return Err(FlatError::AlreadyFinished);
}
let offset = self.buf.len();
self.buf.push(v);
Ok(offset)
}
pub fn push_u32(&mut self, v: u32) -> Result<usize, FlatError> {
if self.finished {
return Err(FlatError::AlreadyFinished);
}
let offset = self.buf.len();
self.buf.extend_from_slice(&v.to_le_bytes());
Ok(offset)
}
pub fn push_bytes(&mut self, data: &[u8]) -> Result<usize, FlatError> {
if self.finished {
return Err(FlatError::AlreadyFinished);
}
let offset = self.buf.len();
self.buf.extend_from_slice(data);
Ok(offset)
}
pub fn start_table(&mut self, num_slots: usize) -> Result<TableBuilder<'_>, FlatError> {
if self.finished {
return Err(FlatError::AlreadyFinished);
}
let object_start = self.buf.len();
self.buf.extend_from_slice(&[0u8; 4]);
Ok(TableBuilder {
builder: self,
object_start,
slots: vec![None; num_slots],
})
}
pub fn finish(mut self) -> Result<Vec<u8>, FlatError> {
self.finished = true;
Ok(self.buf)
}
pub fn bytes(&self) -> &[u8] {
&self.buf
}
pub fn align(&mut self, alignment: usize) {
while !self.buf.len().is_multiple_of(alignment) {
self.buf.push(0);
}
}
}
#[derive(Debug)]
pub struct TableBuilder<'a> {
builder: &'a mut FlatBuilder,
object_start: usize,
slots: Vec<Option<usize>>,
}
impl<'a> TableBuilder<'a> {
pub fn add_slot_u32(&mut self, slot: usize, value: u32, default: u32) -> Result<(), FlatError> {
if value == default {
return Ok(()); }
let offset_in_object = self.builder.buf.len() - self.object_start;
self.builder.push_u32(value)?;
if slot < self.slots.len() {
self.slots[slot] = Some(offset_in_object);
}
Ok(())
}
pub fn add_slot_bytes(&mut self, slot: usize, bytes: &[u8]) -> Result<(), FlatError> {
let offset_in_object = self.builder.buf.len() - self.object_start;
self.builder.push_bytes(bytes)?;
if slot < self.slots.len() {
self.slots[slot] = Some(offset_in_object);
}
Ok(())
}
pub fn end_table(self) -> Result<usize, FlatError> {
let TableBuilder {
builder,
object_start,
slots,
} = self;
let object_size = builder.buf.len() - object_start;
let num_slots = slots.len();
let vtable_size_bytes: u16 = (2 + 2 + num_slots * 2) as u16;
let object_size_u16: u16 = object_size as u16;
let mut vtable_bytes: Vec<u8> = Vec::with_capacity(vtable_size_bytes as usize);
vtable_bytes.extend_from_slice(&vtable_size_bytes.to_le_bytes());
vtable_bytes.extend_from_slice(&object_size_u16.to_le_bytes());
for slot_opt in &slots {
let field_offset: u16 = slot_opt.map_or(0, |off| off as u16);
vtable_bytes.extend_from_slice(&field_offset.to_le_bytes());
}
let vtable_start = builder
.vtables
.iter()
.find(|&&off| {
let end = off.saturating_add(vtable_bytes.len());
end <= builder.buf.len() && builder.buf[off..end] == vtable_bytes[..]
})
.copied()
.unwrap_or_else(|| {
let start = builder.buf.len();
builder.vtables.push(start);
builder.buf.extend_from_slice(&vtable_bytes);
start
});
let soffset: i32 = vtable_start as i32 - object_start as i32;
let soffset_bytes = soffset.to_le_bytes();
builder.buf[object_start..object_start + 4].copy_from_slice(&soffset_bytes);
Ok(object_start)
}
}
pub fn read_u32(data: &[u8], offset: usize) -> Result<u32, FlatError> {
let bytes: [u8; 4] = data
.get(offset..offset + 4)
.and_then(|s| s.try_into().ok())
.ok_or(FlatError::InvalidOffset)?;
Ok(u32::from_le_bytes(bytes))
}
pub fn read_table_u32(buf: &[u8], table_off: usize, slot: usize, default: u32) -> u32 {
read_table_u32_inner(buf, table_off, slot).unwrap_or(default)
}
fn read_table_u32_inner(buf: &[u8], table_off: usize, slot: usize) -> Option<u32> {
let soffset_bytes: [u8; 4] = buf
.get(table_off..table_off + 4)
.and_then(|s| s.try_into().ok())?;
let soffset = i32::from_le_bytes(soffset_bytes);
let vtable_off = (table_off as i64 + soffset as i64) as usize;
let vtable_size_bytes: [u8; 2] = buf
.get(vtable_off..vtable_off + 2)
.and_then(|s| s.try_into().ok())?;
let vtable_size = u16::from_le_bytes(vtable_size_bytes) as usize;
let slot_entry_off = vtable_off + 4 + slot * 2;
if slot_entry_off + 2 > vtable_off + vtable_size {
return None;
}
let field_offset_bytes: [u8; 2] = buf
.get(slot_entry_off..slot_entry_off + 2)
.and_then(|s| s.try_into().ok())?;
let field_off = u16::from_le_bytes(field_offset_bytes) as usize;
if field_off == 0 {
return None; }
let abs_off = table_off + field_off;
let value_bytes: [u8; 4] = buf
.get(abs_off..abs_off + 4)
.and_then(|s| s.try_into().ok())?;
Some(u32::from_le_bytes(value_bytes))
}
pub fn padded_size(size: usize, alignment: usize) -> usize {
let rem = size % alignment;
if rem == 0 {
size
} else {
size + alignment - rem
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_builder_empty() {
let b = FlatBuilder::new();
assert!(b.is_empty());
}
#[test]
fn test_push_u8() {
let mut b = FlatBuilder::new();
b.push_u8(0xAB).expect("should succeed");
assert_eq!(b.len(), 1);
}
#[test]
fn test_push_u32() {
let mut b = FlatBuilder::new();
b.push_u32(0xDEAD_BEEF).expect("should succeed");
assert_eq!(b.len(), 4);
}
#[test]
fn test_push_after_finish_fails() {
let b = FlatBuilder::new();
b.finish().expect("should succeed");
let mut b2 = FlatBuilder::new();
b2.finished = true;
assert!(b2.push_u8(1).is_err());
}
#[test]
fn test_push_bytes() {
let mut b = FlatBuilder::new();
b.push_bytes(&[1, 2, 3]).expect("should succeed");
assert_eq!(b.len(), 3);
}
#[test]
fn test_finish_returns_bytes() {
let mut b = FlatBuilder::new();
b.push_u8(99).expect("should succeed");
let data = b.finish().expect("should succeed");
assert_eq!(data, &[99]);
}
#[test]
fn test_align_pads_to_boundary() {
let mut b = FlatBuilder::new();
b.push_u8(1).expect("should succeed");
b.align(4);
assert_eq!(b.len() % 4, 0);
}
#[test]
fn test_read_u32_ok() {
let data = [1u8, 0, 0, 0];
assert_eq!(read_u32(&data, 0).expect("should succeed"), 1);
}
#[test]
fn test_read_u32_overflow() {
let data = [0u8; 3];
assert!(read_u32(&data, 0).is_err());
}
#[test]
fn test_padded_size() {
assert_eq!(padded_size(5, 4), 8);
assert_eq!(padded_size(8, 4), 8);
}
#[test]
fn test_empty_table_round_trip() {
let mut builder = FlatBuilder::new();
let table = builder.start_table(0).expect("start_table");
let obj_off = table.end_table().expect("end_table");
let buf = builder.finish().expect("finish");
assert!(!buf.is_empty());
assert_eq!(obj_off, 0);
}
#[test]
fn test_single_u32_field() {
let mut builder = FlatBuilder::new();
let mut table = builder.start_table(1).expect("start_table");
table.add_slot_u32(0, 42, 0).expect("add_slot_u32");
let obj_off = table.end_table().expect("end_table");
let buf = builder.finish().expect("finish");
assert_eq!(read_table_u32(&buf, obj_off, 0, 0), 42);
}
#[test]
fn test_default_value_unset() {
let mut builder = FlatBuilder::new();
let table = builder.start_table(2).expect("start_table");
let obj_off = table.end_table().expect("end_table");
let buf = builder.finish().expect("finish");
assert_eq!(read_table_u32(&buf, obj_off, 0, 5), 5);
assert_eq!(read_table_u32(&buf, obj_off, 1, 99), 99);
}
#[test]
fn test_two_field_round_trip() {
let mut builder = FlatBuilder::new();
let mut table = builder.start_table(2).expect("start_table");
table.add_slot_u32(0, 100, 0).expect("slot 0");
table.add_slot_u32(1, 200, 0).expect("slot 1");
let obj_off = table.end_table().expect("end_table");
let buf = builder.finish().expect("finish");
assert_eq!(read_table_u32(&buf, obj_off, 0, 0), 100);
assert_eq!(read_table_u32(&buf, obj_off, 1, 0), 200);
}
#[test]
fn test_vtable_dedup() {
let mut builder = FlatBuilder::new();
let mut t1 = builder.start_table(1).expect("start t1");
t1.add_slot_u32(0, 10, 0).expect("t1 slot");
let off1 = t1.end_table().expect("t1 end");
let mut t2 = builder.start_table(1).expect("start t2");
t2.add_slot_u32(0, 20, 0).expect("t2 slot");
let off2 = t2.end_table().expect("t2 end");
assert_eq!(builder.vtables.len(), 1);
let buf = builder.finish().expect("finish");
assert_eq!(read_table_u32(&buf, off1, 0, 0), 10);
assert_eq!(read_table_u32(&buf, off2, 0, 0), 20);
}
#[test]
fn test_bytes_field_round_trip() {
let mut builder = FlatBuilder::new();
let mut table = builder.start_table(1).expect("start_table");
table
.add_slot_bytes(0, &[0x01, 0x00, 0x00, 0x00])
.expect("add_slot_bytes");
let obj_off = table.end_table().expect("end_table");
let buf = builder.finish().expect("finish");
assert_eq!(read_table_u32(&buf, obj_off, 0, 0), 1);
}
#[test]
fn test_start_table_after_finish_fails() {
let mut builder = FlatBuilder::new();
builder.finished = true;
assert_eq!(
builder.start_table(1).unwrap_err(),
FlatError::AlreadyFinished
);
}
}