Rust full reflection (#8102)

* #Rust Create a crate for reflection

* #Rust Add a crate for reflection tests and helper to access schema

* #Rust Get root table of a buffer and access field with schema

* #Rust Add 'Struct' struct and corresponding getter

* #Rust Add functions of getting any table/struct field value as integer/float/string

* #Rust Add setters for scalar fields

* #Rust Add setter for string fields

* #Rust Add getter for Table/Vector fields

* #Rust Add buffer verification

* Add a 'SafeBuffer' struct which provides safe methods for reflection

It verifies buffer against schema during construction and provides all the unsafe getters in lib.rs in a safe way

---------

Co-authored-by: Derek Bailey <derekbailey@google.com>
This commit is contained in:
Chan Wang
2025-01-15 19:03:10 +01:00
committed by GitHub
parent 5414e04b45
commit 733e432bfd
13 changed files with 7181 additions and 29 deletions

View File

@@ -56,7 +56,7 @@ pub use crate::push::{Push, PushAlignment};
pub use crate::table::{buffer_has_identifier, Table};
pub use crate::vector::{follow_cast_ref, Vector, VectorIter};
pub use crate::verifier::{
ErrorTraceDetail, InvalidFlatbuffer, SimpleToVerifyInSlice, Verifiable, Verifier,
ErrorTraceDetail, InvalidFlatbuffer, SimpleToVerifyInSlice, TableVerifier, Verifiable, Verifier,
VerifierOptions,
};
pub use crate::vtable::field_index_to_field_offset;

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
use core::cmp::Ordering;
use core::fmt::{Debug, Formatter, Result};
use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator};
use core::marker::PhantomData;
@@ -102,6 +103,37 @@ impl<'a, T: Follow<'a> + 'a> Vector<'a, T> {
unsafe { T::follow(self.0, self.1 as usize + SIZE_UOFFSET + sz * idx) }
}
#[inline(always)]
pub fn lookup_by_key<K: Ord>(
&self,
key: K,
f: fn(&<T as Follow<'a>>::Inner, &K) -> Ordering,
) -> Option<T::Inner> {
if self.is_empty() {
return None;
}
let mut left: usize = 0;
let mut right = self.len() - 1;
while left <= right {
let mid = (left + right) / 2;
let value = self.get(mid);
match f(&value, &key) {
Ordering::Equal => return Some(value),
Ordering::Less => left = mid + 1,
Ordering::Greater => {
if mid == 0 {
return None;
}
right = mid - 1;
},
}
}
None
}
#[inline(always)]
pub fn iter(&self) -> VectorIter<'a, T> {
VectorIter::from_vector(*self)

View File

@@ -5,6 +5,11 @@ use alloc::vec::Vec;
use core::ops::Range;
use core::option::Option;
#[cfg(not(feature = "std"))]
use alloc::borrow::Cow;
#[cfg(feature = "std")]
use std::borrow::Cow;
#[cfg(all(nightly, not(feature = "std")))]
use core::error::Error;
#[cfg(feature = "std")]
@@ -20,11 +25,11 @@ pub enum ErrorTraceDetail {
position: usize,
},
TableField {
field_name: &'static str,
field_name: Cow<'static, str>,
position: usize,
},
UnionVariant {
variant: &'static str,
variant: Cow<'static, str>,
position: usize,
},
}
@@ -44,12 +49,12 @@ impl core::convert::AsRef<[ErrorTraceDetail]> for ErrorTrace {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InvalidFlatbuffer {
MissingRequiredField {
required: &'static str,
required: Cow<'static, str>,
error_trace: ErrorTrace,
},
InconsistentUnion {
field: &'static str,
field_type: &'static str,
field: Cow<'static, str>,
field_type: Cow<'static, str>,
error_trace: ErrorTrace,
},
Utf8Error {
@@ -63,7 +68,7 @@ pub enum InvalidFlatbuffer {
},
Unaligned {
position: usize,
unaligned_type: &'static str,
unaligned_type: Cow<'static, str>,
error_trace: ErrorTrace,
},
RangeOutOfBounds {
@@ -217,16 +222,19 @@ impl InvalidFlatbuffer {
error_trace: Default::default(),
})
}
fn new_inconsistent_union<T>(field: &'static str, field_type: &'static str) -> Result<T> {
pub fn new_inconsistent_union<T>(
field: impl Into<Cow<'static, str>>,
field_type: impl Into<Cow<'static, str>>,
) -> Result<T> {
Err(Self::InconsistentUnion {
field,
field_type,
field: field.into(),
field_type: field_type.into(),
error_trace: Default::default(),
})
}
fn new_missing_required<T>(required: &'static str) -> Result<T> {
pub fn new_missing_required<T>(required: impl Into<Cow<'static, str>>) -> Result<T> {
Err(Self::MissingRequiredField {
required,
required: required.into(),
error_trace: Default::default(),
})
}
@@ -251,7 +259,7 @@ fn append_trace<T>(mut res: Result<T>, d: ErrorTraceDetail) -> Result<T> {
}
/// Adds a TableField trace detail if `res` is a data error.
fn trace_field<T>(res: Result<T>, field_name: &'static str, position: usize) -> Result<T> {
fn trace_field<T>(res: Result<T>, field_name: Cow<'static, str>, position: usize) -> Result<T> {
append_trace(
res,
ErrorTraceDetail::TableField {
@@ -333,19 +341,19 @@ impl<'opts, 'buf> Verifier<'opts, 'buf> {
///
/// Note this does not impact soundness as this crate does not assume alignment of structs
#[inline]
fn is_aligned<T>(&self, pos: usize) -> Result<()> {
pub fn is_aligned<T>(&self, pos: usize) -> Result<()> {
if pos % core::mem::align_of::<T>() == 0 {
Ok(())
} else {
Err(InvalidFlatbuffer::Unaligned {
unaligned_type: core::any::type_name::<T>(),
unaligned_type: Cow::Borrowed(core::any::type_name::<T>()),
position: pos,
error_trace: Default::default(),
})
}
}
#[inline]
fn range_in_buffer(&mut self, pos: usize, size: usize) -> Result<()> {
pub fn range_in_buffer(&mut self, pos: usize, size: usize) -> Result<()> {
let end = pos.saturating_add(size);
if end > self.buffer.len() {
return InvalidFlatbuffer::new_range_oob(pos, end);
@@ -363,12 +371,17 @@ impl<'opts, 'buf> Verifier<'opts, 'buf> {
self.range_in_buffer(pos, core::mem::size_of::<T>())
}
#[inline]
pub fn get_u8(&mut self, pos: usize) -> Result<u8> {
self.in_buffer::<u8>(pos)?;
Ok(u8::from_le_bytes([self.buffer[pos]]))
}
#[inline]
fn get_u16(&mut self, pos: usize) -> Result<u16> {
self.in_buffer::<u16>(pos)?;
Ok(u16::from_le_bytes([self.buffer[pos], self.buffer[pos + 1]]))
}
#[inline]
fn get_uoffset(&mut self, pos: usize) -> Result<UOffsetT> {
pub fn get_uoffset(&mut self, pos: usize) -> Result<UOffsetT> {
self.in_buffer::<u32>(pos)?;
Ok(u32::from_le_bytes([
self.buffer[pos],
@@ -434,11 +447,17 @@ impl<'opts, 'buf> Verifier<'opts, 'buf> {
/// tracing the error.
pub fn verify_union_variant<T: Verifiable>(
&mut self,
variant: &'static str,
variant: impl Into<Cow<'static, str>>,
position: usize,
) -> Result<()> {
let res = T::run_verifier(self, position);
append_trace(res, ErrorTraceDetail::UnionVariant { variant, position })
append_trace(
res,
ErrorTraceDetail::UnionVariant {
variant: variant.into(),
position,
},
)
}
}
@@ -456,7 +475,7 @@ pub struct TableVerifier<'ver, 'opts, 'buf> {
}
impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
fn deref(&mut self, field: VOffsetT) -> Result<Option<usize>> {
pub fn deref(&mut self, field: VOffsetT) -> Result<Option<usize>> {
let field = field as usize;
if field < self.vtable_len {
let field_offset = self.verifier.get_u16(self.vtable.saturating_add(field))?;
@@ -469,23 +488,28 @@ impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
Ok(None)
}
#[inline]
pub fn verifier(&mut self) -> &mut Verifier<'opts, 'buf> {
self.verifier
}
#[inline]
pub fn visit_field<T: Verifiable>(
mut self,
field_name: &'static str,
field_name: impl Into<Cow<'static, str>>,
field: VOffsetT,
required: bool,
) -> Result<Self> {
if let Some(field_pos) = self.deref(field)? {
trace_field(
T::run_verifier(self.verifier, field_pos),
field_name,
field_name.into(),
field_pos,
)?;
return Ok(self);
}
if required {
InvalidFlatbuffer::new_missing_required(field_name)
InvalidFlatbuffer::new_missing_required(field_name.into())
} else {
Ok(self)
}
@@ -496,9 +520,9 @@ impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
/// reads the key, then invokes the callback to perform data-dependent verification.
pub fn visit_union<Key, UnionVerifier>(
mut self,
key_field_name: &'static str,
key_field_name: impl Into<Cow<'static, str>>,
key_field_voff: VOffsetT,
val_field_name: &'static str,
val_field_name: impl Into<Cow<'static, str>>,
val_field_voff: VOffsetT,
required: bool,
verify_union: UnionVerifier,
@@ -515,24 +539,28 @@ impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
match (key_pos, val_pos) {
(None, None) => {
if required {
InvalidFlatbuffer::new_missing_required(val_field_name)
InvalidFlatbuffer::new_missing_required(val_field_name.into())
} else {
Ok(self)
}
}
(Some(k), Some(v)) => {
trace_field(Key::run_verifier(self.verifier, k), key_field_name, k)?;
trace_field(
Key::run_verifier(self.verifier, k),
key_field_name.into(),
k,
)?;
// Safety:
// Run verifier on `k` above
let discriminant = unsafe { Key::follow(self.verifier.buffer, k) };
trace_field(
verify_union(discriminant, self.verifier, v),
val_field_name,
val_field_name.into(),
v,
)?;
Ok(self)
}
_ => InvalidFlatbuffer::new_inconsistent_union(key_field_name, val_field_name),
_ => InvalidFlatbuffer::new_inconsistent_union(key_field_name.into(), val_field_name.into()),
}
}
pub fn finish(self) -> &'ver mut Verifier<'opts, 'buf> {

2
rust/reflection/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

View File

@@ -0,0 +1,12 @@
[package]
name = "flatbuffers-reflection"
version = "0.1.0"
edition = "2021"
[dependencies]
flatbuffers = { path = "../flatbuffers"}
escape_string = "0.1.2"
stdint = "0.2.0"
num = "0.4.1"
anyhow = "1.0.75"
thiserror = "1.0"

1142
rust/reflection/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,421 @@
/*
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::reflection_generated::reflection::{BaseType, Field, Object, Schema};
use crate::{FlatbufferError, FlatbufferResult};
use flatbuffers::{
ForwardsUOffset, InvalidFlatbuffer, TableVerifier, UOffsetT, Vector, Verifiable, Verifier,
VerifierOptions, SIZE_UOFFSET, SIZE_VOFFSET,
};
use std::collections::HashMap;
/// Verifies a buffer against its schema with custom verification options.
pub fn verify_with_options(
buffer: &[u8],
schema: &Schema,
opts: &VerifierOptions,
buf_loc_to_obj_idx: &mut HashMap<usize, i32>,
) -> FlatbufferResult<()> {
let mut verifier = Verifier::new(opts, buffer);
if let Some(table_object) = schema.root_table() {
if let core::result::Result::Ok(table_pos) = verifier.get_uoffset(0) {
// Inserts -1 as object index for root table
buf_loc_to_obj_idx.insert(table_pos.try_into()?, -1);
let mut verified = vec![false; buffer.len()];
return verify_table(
&mut verifier,
&table_object,
table_pos.try_into()?,
schema,
&mut verified,
buf_loc_to_obj_idx,
);
}
}
Err(FlatbufferError::InvalidSchema)
}
fn verify_table(
verifier: &mut Verifier,
table_object: &Object,
table_pos: usize,
schema: &Schema,
verified: &mut [bool],
buf_loc_to_obj_idx: &mut HashMap<usize, i32>,
) -> FlatbufferResult<()> {
if table_pos < verified.len() && verified[table_pos] {
return Ok(());
}
let mut table_verifier = verifier.visit_table(table_pos)?;
for field in &table_object.fields() {
let field_name = field.name().to_owned();
table_verifier = match field.type_().base_type() {
BaseType::UType | BaseType::UByte => {
table_verifier.visit_field::<u8>(field_name, field.offset(), field.required())?
}
BaseType::Bool => {
table_verifier.visit_field::<bool>(field_name, field.offset(), field.required())?
}
BaseType::Byte => {
table_verifier.visit_field::<i8>(field_name, field.offset(), field.required())?
}
BaseType::Short => {
table_verifier.visit_field::<i16>(field_name, field.offset(), field.required())?
}
BaseType::UShort => {
table_verifier.visit_field::<u16>(field_name, field.offset(), field.required())?
}
BaseType::Int => {
table_verifier.visit_field::<i32>(field_name, field.offset(), field.required())?
}
BaseType::UInt => {
table_verifier.visit_field::<u32>(field_name, field.offset(), field.required())?
}
BaseType::Long => {
table_verifier.visit_field::<i64>(field_name, field.offset(), field.required())?
}
BaseType::ULong => {
table_verifier.visit_field::<u64>(field_name, field.offset(), field.required())?
}
BaseType::Float => {
table_verifier.visit_field::<f32>(field_name, field.offset(), field.required())?
}
BaseType::Double => {
table_verifier.visit_field::<f64>(field_name, field.offset(), field.required())?
}
BaseType::String => table_verifier.visit_field::<ForwardsUOffset<&str>>(
field_name,
field.offset(),
field.required(),
)?,
BaseType::Vector => {
verify_vector(table_verifier, &field, schema, verified, buf_loc_to_obj_idx)?
}
BaseType::Obj => {
if let Some(field_pos) = table_verifier.deref(field.offset())? {
let object_index = field.type_().index();
let child_obj = schema.objects().get(object_index.try_into()?);
if child_obj.is_struct() {
buf_loc_to_obj_idx.insert(field_pos, object_index);
verify_struct(
table_verifier.verifier(),
&child_obj,
field_pos,
schema,
buf_loc_to_obj_idx,
)?
} else {
let field_value = table_verifier.verifier().get_uoffset(field_pos)?;
let table_pos = field_pos.saturating_add(field_value.try_into()?);
buf_loc_to_obj_idx.insert(table_pos, object_index);
verify_table(
table_verifier.verifier(),
&child_obj,
table_pos,
schema,
verified,
buf_loc_to_obj_idx,
)?;
}
} else if field.required() {
return InvalidFlatbuffer::new_missing_required(field.name().to_string())?;
}
table_verifier
}
BaseType::Union => {
if let Some(field_pos) = table_verifier.deref(field.offset())? {
let field_value = table_verifier.verifier().get_uoffset(field_pos)?;
verify_union(
table_verifier,
&field,
field_pos.saturating_add(field_value.try_into()?),
schema,
verified,
buf_loc_to_obj_idx,
)?
} else if field.required() {
return InvalidFlatbuffer::new_missing_required(field.name().to_string())?;
} else {
table_verifier
}
}
_ => {
return Err(FlatbufferError::TypeNotSupported(
field
.type_()
.base_type()
.variant_name()
.unwrap_or_default()
.to_string(),
));
}
};
}
table_verifier.finish();
verified[table_pos] = true;
Ok(())
}
fn verify_struct(
verifier: &mut Verifier,
struct_object: &Object,
struct_pos: usize,
schema: &Schema,
buf_loc_to_obj_idx: &mut HashMap<usize, i32>,
) -> FlatbufferResult<()> {
verifier.range_in_buffer(struct_pos, struct_object.bytesize().try_into()?)?;
for field in &struct_object.fields() {
if field.type_().base_type() == BaseType::Obj {
let obj_idx = field.type_().index();
let child_obj = schema.objects().get(obj_idx.try_into()?);
if child_obj.is_struct() {
let field_pos = struct_pos.saturating_add(field.offset().into());
buf_loc_to_obj_idx.insert(field_pos, obj_idx);
verify_struct(verifier, &child_obj, field_pos, schema, buf_loc_to_obj_idx)?;
}
}
}
Ok(())
}
fn verify_vector<'a, 'b, 'c>(
mut table_verifier: TableVerifier<'a, 'b, 'c>,
field: &Field,
schema: &Schema,
verified: &mut [bool],
buf_loc_to_obj_idx: &mut HashMap<usize, i32>,
) -> FlatbufferResult<TableVerifier<'a, 'b, 'c>> {
let field_name = field.name().to_owned();
match field.type_().element() {
BaseType::UType | BaseType::UByte => table_verifier
.visit_field::<ForwardsUOffset<Vector<u8>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Bool => table_verifier
.visit_field::<ForwardsUOffset<Vector<bool>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Byte => table_verifier
.visit_field::<ForwardsUOffset<Vector<i8>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Short => table_verifier
.visit_field::<ForwardsUOffset<Vector<i16>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::UShort => table_verifier
.visit_field::<ForwardsUOffset<Vector<u16>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Int => table_verifier
.visit_field::<ForwardsUOffset<Vector<i32>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::UInt => table_verifier
.visit_field::<ForwardsUOffset<Vector<u32>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Long => table_verifier
.visit_field::<ForwardsUOffset<Vector<i64>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::ULong => table_verifier
.visit_field::<ForwardsUOffset<Vector<u64>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Float => table_verifier
.visit_field::<ForwardsUOffset<Vector<f32>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Double => table_verifier
.visit_field::<ForwardsUOffset<Vector<f64>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::String => table_verifier
.visit_field::<ForwardsUOffset<Vector<ForwardsUOffset<&str>>>>(
field_name,
field.offset(),
field.required(),
)
.map_err(FlatbufferError::VerificationError),
BaseType::Obj => {
if let Some(field_pos) = table_verifier.deref(field.offset())? {
let verifier = table_verifier.verifier();
let vector_offset = verifier.get_uoffset(field_pos)?;
let vector_pos = field_pos.saturating_add(vector_offset.try_into()?);
let vector_len = verifier.get_uoffset(vector_pos)?;
let vector_start = vector_pos.saturating_add(SIZE_UOFFSET);
let child_obj_idx = field.type_().index();
let child_obj = schema.objects().get(child_obj_idx.try_into()?);
if child_obj.is_struct() {
let vector_size = vector_len.saturating_mul(child_obj.bytesize().try_into()?);
verifier.range_in_buffer(vector_start, vector_size.try_into()?)?;
let vector_range = core::ops::Range {
start: vector_start,
end: vector_start.saturating_add(vector_size.try_into()?),
};
for struct_pos in vector_range.step_by(child_obj.bytesize().try_into()?) {
buf_loc_to_obj_idx.insert(struct_pos, child_obj_idx);
verify_struct(
verifier,
&child_obj,
struct_pos,
schema,
buf_loc_to_obj_idx,
)?;
}
} else {
verifier.is_aligned::<UOffsetT>(vector_start)?;
let vector_size = vector_len.saturating_mul(SIZE_UOFFSET.try_into()?);
verifier.range_in_buffer(vector_start, vector_size.try_into()?)?;
let vector_range = core::ops::Range {
start: vector_start,
end: vector_start.saturating_add(vector_size.try_into()?),
};
for element_pos in vector_range.step_by(SIZE_UOFFSET) {
let table_pos = element_pos
.saturating_add(verifier.get_uoffset(element_pos)?.try_into()?);
buf_loc_to_obj_idx.insert(table_pos, child_obj_idx);
verify_table(
verifier,
&child_obj,
table_pos,
schema,
verified,
buf_loc_to_obj_idx,
)?;
}
}
} else if field.required() {
return InvalidFlatbuffer::new_missing_required(field.name().to_string())?;
}
Ok(table_verifier)
}
_ => {
return Err(FlatbufferError::TypeNotSupported(
field
.type_()
.base_type()
.variant_name()
.unwrap_or_default()
.to_string(),
))
}
}
}
fn verify_union<'a, 'b, 'c>(
mut table_verifier: TableVerifier<'a, 'b, 'c>,
field: &Field,
union_pos: usize,
schema: &Schema,
verified: &mut [bool],
buf_loc_to_obj_idx: &mut HashMap<usize, i32>,
) -> FlatbufferResult<TableVerifier<'a, 'b, 'c>> {
let union_enum = schema.enums().get(field.type_().index().try_into()?);
if union_enum.values().is_empty() {
return Err(FlatbufferError::InvalidUnionEnum);
}
let enum_offset = field.offset() - u16::try_from(SIZE_VOFFSET)?;
if let Some(enum_pos) = table_verifier.deref(enum_offset)? {
let enum_value = table_verifier.verifier().get_u8(enum_pos)?;
let enum_type = union_enum
.values()
.get(enum_value.into())
.union_type()
.ok_or(FlatbufferError::InvalidUnionEnum)?;
match enum_type.base_type() {
BaseType::String => <&str>::run_verifier(table_verifier.verifier(), union_pos)?,
BaseType::Obj => {
let child_obj = schema.objects().get(enum_type.index().try_into()?);
buf_loc_to_obj_idx.insert(union_pos, enum_type.index());
if child_obj.is_struct() {
verify_struct(
table_verifier.verifier(),
&child_obj,
union_pos,
schema,
buf_loc_to_obj_idx,
)?
} else {
verify_table(
table_verifier.verifier(),
&child_obj,
union_pos,
schema,
verified,
buf_loc_to_obj_idx,
)?;
}
}
_ => {
return Err(FlatbufferError::TypeNotSupported(
enum_type
.base_type()
.variant_name()
.unwrap_or_default()
.to_string(),
))
}
}
} else {
return InvalidFlatbuffer::new_inconsistent_union(
format!("{}_type", field.name()),
field.name().to_string(),
)?;
}
verified[union_pos] = true;
Ok(table_verifier)
}

View File

@@ -0,0 +1,319 @@
/*
* Copyright 2025 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::r#struct::Struct;
use crate::reflection_generated::reflection::{Field, Schema};
use crate::reflection_verifier::verify_with_options;
use crate::{
get_any_field_float, get_any_field_float_in_struct, get_any_field_integer,
get_any_field_integer_in_struct, get_any_field_string, get_any_field_string_in_struct,
get_any_root, get_field_float, get_field_integer, get_field_string, get_field_struct,
get_field_struct_in_struct, get_field_table, get_field_vector, FlatbufferError,
FlatbufferResult, ForwardsUOffset,
};
use flatbuffers::{Follow, Table, Vector, VerifierOptions};
use num::traits::float::Float;
use num::traits::int::PrimInt;
use num::traits::FromPrimitive;
use std::collections::HashMap;
#[derive(Debug)]
pub struct SafeBuffer<'a> {
buf: &'a [u8],
schema: &'a Schema<'a>,
buf_loc_to_obj_idx: HashMap<usize, i32>,
}
impl<'a> SafeBuffer<'a> {
pub fn new(buf: &'a [u8], schema: &'a Schema) -> FlatbufferResult<Self> {
Self::new_with_options(buf, schema, &VerifierOptions::default())
}
pub fn new_with_options(
buf: &'a [u8],
schema: &'a Schema,
opts: &VerifierOptions,
) -> FlatbufferResult<Self> {
let mut buf_loc_to_obj_idx = HashMap::new();
verify_with_options(&buf, schema, opts, &mut buf_loc_to_obj_idx)?;
Ok(SafeBuffer {
buf,
schema,
buf_loc_to_obj_idx,
})
}
/// Gets the root table in the buffer.
pub fn get_root(&self) -> SafeTable {
// SAFETY: the buffer was verified during construction.
let table = unsafe { get_any_root(self.buf) };
SafeTable {
safe_buf: self,
loc: table.loc(),
}
}
fn find_field_by_name(
&self,
buf_loc: usize,
field_name: &str,
) -> FlatbufferResult<Option<Field>> {
Ok(self
.get_all_fields(buf_loc)?
.lookup_by_key(field_name, |field: &Field<'_>, key| {
field.key_compare_with_value(key)
}))
}
fn get_all_fields(&self, buf_loc: usize) -> FlatbufferResult<Vector<ForwardsUOffset<Field>>> {
if let Some(&obj_idx) = self.buf_loc_to_obj_idx.get(&buf_loc) {
let obj = if obj_idx == -1 {
self.schema.root_table().unwrap()
} else {
self.schema.objects().get(obj_idx.try_into()?)
};
Ok(obj.fields())
} else {
Err(FlatbufferError::InvalidTableOrStruct)
}
}
}
#[derive(Debug)]
pub struct SafeTable<'a> {
safe_buf: &'a SafeBuffer<'a>,
loc: usize,
}
impl<'a> SafeTable<'a> {
/// Gets an integer table field given its exact type. Returns default integer value if the field is not set. Returns [None] if no default value is found. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table or
/// the field type doesn't match.
pub fn get_field_integer<T: for<'b> Follow<'b, Inner = T> + PrimInt + FromPrimitive>(
&self,
field_name: &str,
) -> FlatbufferResult<Option<T>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe { get_field_integer::<T>(&Table::new(&self.safe_buf.buf, self.loc), &field) }
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Gets a floating point table field given its exact type. Returns default float value if the field is not set. Returns [None] if no default value is found. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table or
/// the field type doesn't match.
pub fn get_field_float<T: for<'b> Follow<'b, Inner = T> + Float>(
&self,
field_name: &str,
) -> FlatbufferResult<Option<T>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe { get_field_float::<T>(&Table::new(&self.safe_buf.buf, self.loc), &field) }
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Gets a String table field given its exact type. Returns empty string if the field is not set. Returns [None] if no default value is found. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table or
/// the field type doesn't match.
pub fn get_field_string(&self, field_name: &str) -> FlatbufferResult<Option<&str>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe { get_field_string(&Table::new(&self.safe_buf.buf, self.loc), &field) }
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Gets a [SafeStruct] table field given its exact type. Returns [None] if the field is not set. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table or
/// the field type doesn't match.
pub fn get_field_struct(&self, field_name: &str) -> FlatbufferResult<Option<SafeStruct<'a>>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
let optional_st =
unsafe { get_field_struct(&Table::new(&self.safe_buf.buf, self.loc), &field)? };
Ok(optional_st.map(|st| SafeStruct {
safe_buf: self.safe_buf,
loc: st.loc(),
}))
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Gets a Vector table field given its exact type. Returns empty vector if the field is not set. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table or
/// the field type doesn't match.
pub fn get_field_vector<T: Follow<'a, Inner = T>>(
&self,
field_name: &str,
) -> FlatbufferResult<Option<Vector<'a, T>>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe { get_field_vector(&Table::new(&self.safe_buf.buf, self.loc), &field) }
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Gets a [SafeTable] table field given its exact type. Returns [None] if the field is not set. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table or
/// the field type doesn't match.
pub fn get_field_table(&self, field_name: &str) -> FlatbufferResult<Option<SafeTable<'a>>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
let optional_table =
unsafe { get_field_table(&Table::new(&self.safe_buf.buf, self.loc), &field)? };
Ok(optional_table.map(|t| SafeTable {
safe_buf: self.safe_buf,
loc: t.loc(),
}))
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Returns the value of any table field as a 64-bit int, regardless of what type it is. Returns default integer if the field is not set or error if
/// the value cannot be parsed as integer or
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table.
/// [num_traits](https://docs.rs/num-traits/latest/num_traits/cast/trait.NumCast.html) is used for number casting.
pub fn get_any_field_integer(&self, field_name: &str) -> FlatbufferResult<i64> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe { get_any_field_integer(&Table::new(&self.safe_buf.buf, self.loc), &field) }
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Returns the value of any table field as a 64-bit floating point, regardless of what type it is. Returns default float if the field is not set or error if
/// the value cannot be parsed as float or
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table.
pub fn get_any_field_float(&self, field_name: &str) -> FlatbufferResult<f64> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe { get_any_field_float(&Table::new(&self.safe_buf.buf, self.loc), &field) }
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Returns the string representation of any table field value (e.g. integer 123 is returned as "123"), regardless of what type it is. Returns empty string if the field is not set. Returns error if
/// the table doesn't match the buffer or
/// the [field_name] doesn't match the table.
pub fn get_any_field_string(&self, field_name: &str) -> FlatbufferResult<String> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe {
Ok(get_any_field_string(
&Table::new(&self.safe_buf.buf, self.loc),
&field,
self.safe_buf.schema,
))
}
} else {
Err(FlatbufferError::FieldNotFound)
}
}
}
#[derive(Debug)]
pub struct SafeStruct<'a> {
safe_buf: &'a SafeBuffer<'a>,
loc: usize,
}
impl<'a> SafeStruct<'a> {
/// Gets a [SafeStruct] struct field given its exact type. Returns error if
/// the struct doesn't match the buffer or
/// the [field_name] doesn't match the struct or
/// the field type doesn't match.
pub fn get_field_struct(&self, field_name: &str) -> FlatbufferResult<SafeStruct<'a>> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
let st = unsafe {
get_field_struct_in_struct(&Struct::new(&self.safe_buf.buf, self.loc), &field)?
};
Ok(SafeStruct {
safe_buf: self.safe_buf,
loc: st.loc(),
})
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Returns the value of any struct field as a 64-bit int, regardless of what type it is. Returns error if
/// the struct doesn't match the buffer or
/// the [field_name] doesn't match the struct or
/// the value cannot be parsed as integer.
pub fn get_any_field_integer(&self, field_name: &str) -> FlatbufferResult<i64> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe {
get_any_field_integer_in_struct(&Struct::new(&self.safe_buf.buf, self.loc), &field)
}
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Returns the value of any struct field as a 64-bit floating point, regardless of what type it is. Returns error if
/// the struct doesn't match the buffer or
/// the [field_name] doesn't match the struct or
/// the value cannot be parsed as float.
pub fn get_any_field_float(&self, field_name: &str) -> FlatbufferResult<f64> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe {
get_any_field_float_in_struct(&Struct::new(&self.safe_buf.buf, self.loc), &field)
}
} else {
Err(FlatbufferError::FieldNotFound)
}
}
/// Returns the string representation of any struct field value (e.g. integer 123 is returned as "123"), regardless of what type it is. Returns error if
/// the struct doesn't match the buffer or
/// the [field_name] doesn't match the struct.
pub fn get_any_field_string(&self, field_name: &str) -> FlatbufferResult<String> {
if let Some(field) = self.safe_buf.find_field_by_name(self.loc, field_name)? {
// SAFETY: the buffer was verified during construction.
unsafe {
Ok(get_any_field_string_in_struct(
&Struct::new(&self.safe_buf.buf, self.loc),
&field,
self.safe_buf.schema,
))
}
} else {
Err(FlatbufferError::FieldNotFound)
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use flatbuffers::Follow;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Struct<'a> {
buf: &'a [u8],
loc: usize,
}
impl<'a> Struct<'a> {
#[inline]
pub fn buf(&self) -> &'a [u8] {
self.buf
}
#[inline]
pub fn loc(&self) -> usize {
self.loc
}
/// # Safety
///
/// [buf] must contain a valid struct at [loc]
#[inline]
pub unsafe fn new(buf: &'a [u8], loc: usize) -> Self {
Struct { buf, loc }
}
/// Retrieves the value at the provided [byte_loc]. No field in [Struct] is optional.
///
/// # Safety
///
/// The value of the corresponding slot must have type T
#[inline]
pub unsafe fn get<T: Follow<'a> + 'a>(&self, byte_loc: usize) -> T::Inner {
<T>::follow(self.buf, self.loc + byte_loc)
}
}
impl<'a> Follow<'a> for Struct<'a> {
type Inner = Struct<'a>;
#[inline]
unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {
Struct { buf, loc }
}
}

2
tests/rust_reflection_test/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

View File

@@ -0,0 +1,9 @@
[package]
name = "rust_reflection_test"
version = "0.1.0"
edition = "2018"
[dependencies]
flatbuffers-reflection = { path = "../../rust/reflection" }
flatbuffers = { path = "../../rust/flatbuffers" }
assert_approx_eq = "1.1.0"

File diff suppressed because it is too large Load Diff