blob: aec61d8691003a27d75eec13c60e69983450ed02 [file] [log] [blame]
//! Utilities for working with FlatBuffers types.
//!
//! These address similar use cases as the C++ library with the same name, but unlike most of these
//! wrappers the APIs look very different in most cases to be ergnomic in Rust.
use std::marker::PhantomData;
use flatbuffers::{
FlatBufferBuilder, Follow, FollowWith, ForwardsUOffset, InvalidFlatbuffer, SkipSizePrefix,
Verifiable, Verifier, WIPOffset,
};
/// Represents the various forms of buffers we can store tables in.
///
/// We need this trait to mark types which won't change the data in a flatbuffer after we verify
/// it. Rust's type system cannot express this constraint directly, so we use an unsafe trait to
/// call attention to what implementing types are promising.
///
/// # Safety
///
/// Implementors of this trait must return a slice with the same contents every time
/// [`AsRef.as_ref`] is called without any intervening mutable access to the object. In other words,
/// types implementing this trait must have exclusive ownership of the underlying storage, and not
/// do anything unusal like returning different pieces of storage each time.
///
/// Note that the slice is not required to have the same address each time [`AsRef.as_ref`] is
/// called, just the same contents. This means types using inline storage can implement this trait
/// without needing pinning.
pub unsafe trait FlatbufferStorage: AsRef<[u8]> {}
// SAFETY: Vec has exclusive ownership of its single storage.
unsafe impl FlatbufferStorage for Vec<u8> {}
// SAFETY: We have a shared reference to the slice and u8 does not have interior mutability, so
// nobody can change the memory as long as the shared reference exists. We're only using the one
// slice too.
unsafe impl FlatbufferStorage for &[u8] {}
// SAFETY: The array is its own storage.
unsafe impl<const N: usize> FlatbufferStorage for [u8; N] {}
/// A type to wrap buffers returned from [`FlatBufferBuilder.collapse`].
///
/// See [`Flatbuffer.finish_minimal_and_collapse`] for an easy way to make use of this.
#[derive(Clone)]
pub struct CollapsedFlatBufferBuilder(pub Vec<u8>, pub usize);
impl AsRef<[u8]> for CollapsedFlatBufferBuilder {
fn as_ref(&self) -> &[u8] {
&self.0[self.1..]
}
}
// SAFETY: The Vec has exclusive ownership of its single storage, and we always access the same
// part of it.
unsafe impl FlatbufferStorage for CollapsedFlatBufferBuilder {}
/// An internal sealed trait to manage the size-prefixed versions vs non-size-prefixed variants.
pub trait PrefixedOrNot: private_size_prefixed::Sealed {
/// [`FlatBufferBuilder::finish`] or [`FlatBufferBuilder::finish_size_prefixed`].
fn finish_builder<T>(
fbb: &mut FlatBufferBuilder,
root: WIPOffset<T>,
file_identifier: Option<&str>,
);
/// [`flatbuffers::root`] or [`flatbuffers::size_prefixed_root`].
fn root<'buf, T: 'buf + Follow<'buf> + Verifiable>(
data: &'buf [u8],
) -> Result<T::Inner, InvalidFlatbuffer>;
/// [`flatbuffers::root_unchecked`] or [`flatbuffers::size_prefixed_root_unchecked`].
unsafe fn root_unchecked<'buf, T: 'buf + Follow<'buf>>(data: &'buf [u8]) -> T::Inner;
/// [`ForwardsUOffset::run_verifier`], after skipping the size if present.
fn run_verifier<T: Verifiable>(
v: &mut Verifier<'_, '_>,
pos: usize,
) -> Result<(), InvalidFlatbuffer>;
}
/// Marker type to implement [`PrefixedOrNot`] on.
pub struct NotSizePrefixed;
impl PrefixedOrNot for NotSizePrefixed {
fn finish_builder<T>(
fbb: &mut FlatBufferBuilder,
root: WIPOffset<T>,
file_identifier: Option<&str>,
) {
fbb.finish::<T>(root, file_identifier);
}
fn root<'buf, T: 'buf + Follow<'buf> + Verifiable>(
data: &'buf [u8],
) -> Result<T::Inner, InvalidFlatbuffer> {
flatbuffers::root::<'buf, T>(data)
}
unsafe fn root_unchecked<'buf, T: 'buf + Follow<'buf>>(data: &'buf [u8]) -> T::Inner {
flatbuffers::root_unchecked::<'buf, T>(data)
}
fn run_verifier<T: Verifiable>(
v: &mut Verifier<'_, '_>,
pos: usize,
) -> Result<(), InvalidFlatbuffer> {
<ForwardsUOffset<T>>::run_verifier(v, pos)
}
}
/// Marker type to implement [`PrefixedOrNot`] on.
pub struct SizePrefixed;
impl PrefixedOrNot for SizePrefixed {
fn finish_builder<T>(
fbb: &mut FlatBufferBuilder,
root: WIPOffset<T>,
file_identifier: Option<&str>,
) {
fbb.finish_size_prefixed::<T>(root, file_identifier);
}
fn root<'buf, T: 'buf + Follow<'buf> + Verifiable>(
data: &'buf [u8],
) -> Result<T::Inner, InvalidFlatbuffer> {
flatbuffers::size_prefixed_root::<'buf, T>(data)
}
unsafe fn root_unchecked<'buf, T: 'buf + Follow<'buf>>(data: &'buf [u8]) -> T::Inner {
flatbuffers::size_prefixed_root_unchecked::<'buf, T>(data)
}
fn run_verifier<T: Verifiable>(
v: &mut Verifier<'_, '_>,
pos: usize,
) -> Result<(), InvalidFlatbuffer> {
<SkipSizePrefix<ForwardsUOffset<T>>>::run_verifier(v, pos)
}
}
mod private_size_prefixed {
pub trait Sealed {}
impl Sealed for super::NotSizePrefixed {}
impl Sealed for super::SizePrefixed {}
}
/// A flatbuffer along with its backing storage.
///
/// `T` should be a generated flatbuffer table type. Its lifetime argument does not matter, using
/// `'static` is recommended for simplicity. This type uses the [`FollowWith`] trait to access the
/// equivalents of `T` with appropriate lifetimes as needed.
///
/// This is a bit tricky in Rust because we want to avoid internal pointers (requiring the buffer
/// to be pinned before using it would be hard to use). Instead of actually storing the flatbuffer
/// with its pointer to the storage, we (optionally) verify it once and then create the flatbuffer
/// type on demand.
pub trait Flatbuffer<T>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
{
/// Provides access to the message.
///
/// Note that we can't implement `Deref` because we're not returning a reference. We're
/// effectively returning a fat pointer which is semantically equivalent to a Rust reference,
/// but it's not the same thing to the language. Also implementing `Deref` would allow wrapping
/// this with `Pin`, which doesn't do anything because the actual type being dereferenced is
/// still `Unpin`, even though other languages commonly store pointers into the flatbuffer.
fn message<'this>(&'this self) -> <<T as FollowWith<'this>>::Inner as Follow<'this>>::Inner;
/// Revalidates the data. If this returns `Err`, then something has violated the safety
/// requirements and code responsible is likely unsound.
///
/// You should probably not be using this. It's intended for debug assertions when working with
/// cross-language boundaries and otherwise doing tricky things with buffers. Once again, you
/// probably have unsoundness if you're using this, verify your flatbuffers before wrapping them
/// in a [`Flatbuffer`], not after.
fn revalidate<'this>(&'this self) -> Result<(), InvalidFlatbuffer>
where
<T as FollowWith<'this>>::Inner: Verifiable;
}
/// A generic implementation of [`Flatbuffer`]. `Storage` is the type used to store the buffer.
/// `MaybePrefixed` controls whether we're using size-prefixed flatbuffers or not.
///
/// Flatbuffers provides a parallel set of APIs for handling size-prefixed buffers. This just means
/// the size is placed at the beginning, so the root table's offset isn't at the very beginning of
/// the buffer.
///
/// [`NonSizePrefixedFlatbuffer`] and [`SizePrefixedFlatbuffer`] provide more convenient names.
pub struct MaybePrefixedFlatbuffer<T, Storage: FlatbufferStorage, MaybePrefixed: PrefixedOrNot> {
// SAFETY: We have an invariant that this is always a valid flatbuffer.
//
// All of our constructors verify that, and we never hand out mutable references so the
// requirements for implementors of [`FlatbufferStorage`] ensure it stays valid.
storage: Storage,
/// Pretend we store one of these. The compiler needs this information to clarify that two
/// instances with different `T` and/or `MaybePrefixed` are not the same type.
_marker: PhantomData<(T, MaybePrefixed)>,
}
impl<T, Storage: FlatbufferStorage, MaybePrefixed: PrefixedOrNot> Flatbuffer<T>
for MaybePrefixedFlatbuffer<T, Storage, MaybePrefixed>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
{
fn message<'this>(&'this self) -> <<T as FollowWith<'this>>::Inner as Follow<'this>>::Inner {
// SAFETY: This being a valid flatbuffer is an invariant.
unsafe {
MaybePrefixed::root_unchecked::<<T as FollowWith<'this>>::Inner>(self.storage.as_ref())
}
}
fn revalidate<'this>(&'this self) -> Result<(), InvalidFlatbuffer>
where
<T as FollowWith<'this>>::Inner: Verifiable,
{
MaybePrefixed::root::<<T as FollowWith<'this>>::Inner>(self.storage.as_ref())?;
Ok(())
}
}
impl<T, Storage: FlatbufferStorage, MaybePrefixed: PrefixedOrNot>
MaybePrefixedFlatbuffer<T, Storage, MaybePrefixed>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a> + Verifiable,
{
/// Verifies the flatbuffer and then creates an instance.
///
/// Note that `storage` must only refer to the flatbuffer. `FlatBufferBuilder::collapse`
/// returns a buffer with unused space at the beginning, for example, you will have to either
/// use a slice here or remove the unused space first.
pub fn new(storage: Storage) -> Result<Self, InvalidFlatbuffer> {
MaybePrefixed::root::<<T as FollowWith<'_>>::Inner>(storage.as_ref())?;
Ok(Self {
storage,
_marker: PhantomData,
})
}
}
impl<T, Storage: FlatbufferStorage, MaybePrefixed: PrefixedOrNot>
MaybePrefixedFlatbuffer<T, Storage, MaybePrefixed>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
{
/// Wraps a flatbuffer without verifying it.
///
/// Note that `storage` must only refer to the flatbuffer. `FlatBufferBuilder::collapse`
/// returns a buffer with unused space at the beginning, for example, so you can not pass that
/// here directly. See [`CollapsedFlatBufferBuilder`] for that particular case.
///
/// # Safety
///
/// `storage` must refer to a valid flatbuffer.
pub unsafe fn new_unchecked(storage: Storage) -> Self {
Self {
storage,
_marker: PhantomData,
}
}
/// Returns a reference to the underlying storage.
pub fn storage_ref(&self) -> &[u8] {
self.storage.as_ref()
}
/// Converts back into the underlying storage.
pub fn into_storage(self) -> Storage {
self.storage
}
}
impl<T, Storage: FlatbufferStorage + Clone, MaybePrefixed: PrefixedOrNot> Clone
for MaybePrefixedFlatbuffer<T, Storage, MaybePrefixed>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
{
fn clone(&self) -> Self {
// SAFETY: We already know it's valid because it was used by `self`.
unsafe { Self::new_unchecked(self.storage.clone()) }
}
}
impl<T, MaybePrefixed: PrefixedOrNot>
MaybePrefixedFlatbuffer<T, CollapsedFlatBufferBuilder, MaybePrefixed>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
{
/// Finishes a [`FlatBufferBuilder`] and then grabs its buffer. This does not require
/// verification to be safe.
///
/// We have to encapsulate the `finish` call to enforce matching types.
pub fn finish_minimal_and_collapse(mut fbb: FlatBufferBuilder<'_>, root: WIPOffset<T>) -> Self {
MaybePrefixed::finish_builder(&mut fbb, root, None);
let (vec, offset) = fbb.collapse();
// SAFETY: We just finished the builder with an offset of the correct type. The builder
// maintained the invariant that it was building a valid flatbuffer, which we are directly
// using.
unsafe { Self::new_unchecked(CollapsedFlatBufferBuilder(vec, offset)) }
}
}
impl<T, F: Flatbuffer<T> + ?Sized> Flatbuffer<T> for Box<F>
where
for<'a> T: FollowWith<'a>,
for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
{
fn message<'this>(&'this self) -> <<T as FollowWith<'this>>::Inner as Follow<'this>>::Inner {
self.as_ref().message()
}
fn revalidate<'this>(&'this self) -> Result<(), InvalidFlatbuffer>
where
<T as FollowWith<'this>>::Inner: Verifiable,
{
self.as_ref().revalidate()
}
}
pub type NonSizePrefixedFlatbuffer<T, Storage> =
MaybePrefixedFlatbuffer<T, Storage, NotSizePrefixed>;
pub type SizePrefixedFlatbuffer<T, Storage> = MaybePrefixedFlatbuffer<T, Storage, SizePrefixed>;
/// This is the alias for a generic "some kind of buffer that contains a flatbuffer". Use this if
/// you want your code to work with any type of buffer without being generic.
///
/// `'buf` is the lifetime of the underlying storage.
///
/// If you only want to support flatbuffers that own their underlying buffer, and don't want to
/// even make your functions lifetime-generic, use `DynFlatbuffer<'static, T>`. However, note
/// that being lifetime-generic has no cost in code size or vtable lookups, and if your code isn't
/// storing references to the flatbuffer anywhere it's a strict increase in flexibility.
pub type DynFlatbuffer<'buf, T> = Box<dyn Flatbuffer<T> + 'buf>;
/// Transmutes the in-memory flatbuffer data to a type. This is only useful if `T` is a C++-style
/// flatbuffer generated class, it doesn't match the layout of any type in any language.
///
/// It is strongly recommended to explicitly specify `T` using the turbofish operator when using
/// this function. Mismatches of the type are very bad and leaving it implicit is error prone.
///
/// # Safety
///
/// This is extremely unsafe. `T` has to be something expecting a flatbuffer table of the correct
/// type as its address.
pub unsafe fn transmute_table_to<'a, T>(table: &flatbuffers::Table<'a>) -> &'a T {
let address = &table.buf[table.loc];
std::mem::transmute(address)
}
#[cfg(test)]
mod tests {
use super::*;
use aos_json_to_flatbuffer_fbs::aos::testing::{Location, LocationBuilder};
/// Tests various methods that take flatbuffers as arguments.
#[test]
fn test_take() {
fn take_box_dyn_ref(flatbuffer: &DynFlatbuffer<'_, Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_box_dyn_ref_static(flatbuffer: &DynFlatbuffer<'static, Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_bare_dyn_ref(flatbuffer: &dyn Flatbuffer<Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_impl_ref(flatbuffer: &impl Flatbuffer<Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_impl_ref_static(flatbuffer: &(impl Flatbuffer<Location<'static>> + 'static)) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_box_dyn(flatbuffer: DynFlatbuffer<'_, Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_box_dyn_static(flatbuffer: DynFlatbuffer<'static, Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_impl(flatbuffer: impl Flatbuffer<Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_impl_static(flatbuffer: impl Flatbuffer<Location<'static>> + 'static) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_nondyn_ref(
flatbuffer: &NonSizePrefixedFlatbuffer<Location<'static>, CollapsedFlatBufferBuilder>,
) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
fn take_nondyn(
flatbuffer: NonSizePrefixedFlatbuffer<Location<'static>, CollapsedFlatBufferBuilder>,
) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
let flatbuffer = {
let mut fbb = FlatBufferBuilder::new();
let xyz = fbb.create_string("xyz");
let mut location = LocationBuilder::new(&mut fbb);
location.add_name(xyz);
location.add_frequency(971);
let location = location.finish();
NonSizePrefixedFlatbuffer::finish_minimal_and_collapse(fbb, location)
};
let make_dyn_flatbuffer = || Box::new(flatbuffer.clone());
let dyn_flatbuffer: DynFlatbuffer<_> = make_dyn_flatbuffer();
take_box_dyn_ref(&dyn_flatbuffer);
take_box_dyn_ref_static(&dyn_flatbuffer);
take_bare_dyn_ref(&dyn_flatbuffer);
take_bare_dyn_ref(&flatbuffer);
take_impl_ref(&dyn_flatbuffer);
take_impl_ref(&flatbuffer);
take_impl_ref_static(&dyn_flatbuffer);
take_impl_ref_static(&flatbuffer);
take_box_dyn(make_dyn_flatbuffer());
take_box_dyn_static(make_dyn_flatbuffer());
take_impl(make_dyn_flatbuffer());
take_impl(flatbuffer.clone());
take_impl_static(make_dyn_flatbuffer());
take_impl_static(flatbuffer.clone());
take_nondyn_ref(&flatbuffer);
take_nondyn(flatbuffer.clone());
}
/// Tests creating a DynFlatbuffer from various types.
#[test]
fn test_make_dyn() {
fn take_ref(flatbuffer: &impl Flatbuffer<Location<'static>>) {
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
}
let flatbuffer = {
let mut fbb = FlatBufferBuilder::new();
let xyz = fbb.create_string("xyz");
let mut location = LocationBuilder::new(&mut fbb);
location.add_name(xyz);
location.add_frequency(971);
let location = location.finish();
NonSizePrefixedFlatbuffer::finish_minimal_and_collapse(fbb, location)
};
take_ref(&flatbuffer);
let slice_flatbuffer =
NonSizePrefixedFlatbuffer::<Location<'static>, &[u8]>::new(flatbuffer.storage_ref())
.unwrap();
take_ref(&slice_flatbuffer);
{
let dyn_flatbuffer: DynFlatbuffer<_> = Box::new(flatbuffer.clone());
take_ref(&dyn_flatbuffer);
}
{
let dyn_flatbuffer: DynFlatbuffer<_> = Box::new(slice_flatbuffer.clone());
take_ref(&dyn_flatbuffer);
}
}
macro_rules! test_variant {
($maybe_prefixed:ident, $modname:ident, $finish:ident) => {
mod $modname {
use super::*;
use aos_json_to_flatbuffer_fbs::aos::testing::{Location, LocationBuilder};
#[test]
fn test_basic() {
let fbb = {
let mut fbb = FlatBufferBuilder::new();
let xyz = fbb.create_string("xyz");
let mut location = LocationBuilder::new(&mut fbb);
location.add_name(xyz);
location.add_frequency(971);
let location = location.finish();
fbb.$finish(location, None);
fbb
};
{
let flatbuffer = $maybe_prefixed::<Location, _>::new(
fbb.finished_data().iter().copied().collect::<Vec<u8>>(),
)
.unwrap();
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
flatbuffer.revalidate().unwrap();
}
{
let flatbuffer =
$maybe_prefixed::<Location, _>::new(fbb.finished_data()).unwrap();
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
flatbuffer.revalidate().unwrap();
}
{
let mut array_data = [0; 64];
let finished_data = fbb.finished_data();
array_data[..finished_data.len()].copy_from_slice(finished_data);
let flatbuffer = $maybe_prefixed::<Location, _>::new(array_data).unwrap();
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
flatbuffer.revalidate().unwrap();
}
}
#[test]
fn test_collapse() {
let mut fbb = FlatBufferBuilder::new();
let xyz = fbb.create_string("xyz");
let mut location = LocationBuilder::new(&mut fbb);
location.add_name(xyz);
location.add_frequency(971);
let location = location.finish();
let flatbuffer = $maybe_prefixed::finish_minimal_and_collapse(fbb, location);
assert_eq!(Some("xyz"), flatbuffer.message().name());
assert_eq!(971, flatbuffer.message().frequency());
flatbuffer.revalidate().unwrap();
}
#[test]
fn test_verification_failed() {
// Something arbitrary that is not a valid flatbuffer. The test will fail if this does end
// up being valid.
let data = [5u8; 1];
assert!($maybe_prefixed::<Location, _>::new(data).is_err());
}
mod bad {
use super::*;
use std::cell::Cell;
struct Bad {
a: Vec<u8>,
b: [u8; 1],
first: Cell<bool>,
}
impl AsRef<[u8]> for Bad {
fn as_ref(&self) -> &[u8] {
if self.first.replace(false) {
self.a.as_ref()
} else {
self.b.as_ref()
}
}
}
unsafe impl FlatbufferStorage for Bad {}
/// A demonstration of how using an incorrect `FlatbufferStorage` implementation can go
/// wrong. Note that this test is careful to not actually do anything unsound by never
/// using the invalid flatbuffer, it only verifies validation fails on it.
#[test]
fn test_bad() {
let fbb = {
let mut fbb = FlatBufferBuilder::new();
let mut location = LocationBuilder::new(&mut fbb);
location.add_frequency(971);
let location = location.finish();
fbb.$finish(location, None);
fbb
};
let data = Bad {
a: fbb.finished_data().iter().copied().collect(),
// Something arbitrary that is not a valid flatbuffer. The test will fail if this
// does end up being valid.
b: [5; 1],
first: Cell::new(true),
};
let flatbuffer = $maybe_prefixed::<Location, _>::new(data).unwrap();
assert!(flatbuffer.revalidate().is_err());
}
}
}
};
}
test_variant!(NonSizePrefixedFlatbuffer, non_prefixed, finish);
test_variant!(SizePrefixedFlatbuffer, prefixed, finish_size_prefixed);
}