forked from BigfootDev/flatbuffers
* flatbuffers Rust reflection: replace num with num-traits num crate is a wrapper over num-traits and a few other crates, that reexports the APIs from all of them. We only need num-traits. Signed-off-by: Marcin Radomski <dextero@google.com> * Rust reflection: drop dependency on stdint crate We only use it to get intmax_t for deriving alignment, which is an alias for `core::ffi::c_long` [1]. We can use that directly instead. [1] https://docs.rs/stdint/1.0.0/stdint/type.intmax_t.html Signed-off-by: Marcin Radomski <dextero@google.com> * Rust reflection: drop dependency on escape_string crate It's used to format a string used for debugging only, so we might as well use the builtin Debug representation of a string. Signed-off-by: Marcin Radomski <dextero@google.com> * Rust codegen: add derives on generated bitflags Otherwise it limits the use of structs generated for reflection.fbs in Rust reflection API. Signed-off-by: Marcin Radomski <dextero@google.com> * Rust flatbuffers: update bitflags dependency to 2.8 Signed-off-by: Marcin Radomski <dextero@google.com> * Rust codegen: use bitflags v2 API for converting from bits from_bits_unchecked was replaced with safe from_bits_retain. Signed-off-by: Marcin Radomski <dextero@google.com> * Regenerate Rust code after idl change Signed-off-by: Marcin Radomski <dextero@google.com> * Regenerate reflection_generated.rs With flatc --rust ../../../reflection/reflection.fbs Signed-off-by: Marcin Radomski <dextero@google.com> * ts/BUILD.bazel: add missing import Found by Buildifire presubmit: Function "sh_binary" is not global anymore and needs to be loaded from "@rules_shell//shell:sh_binary.bzl". Signed-off-by: Marcin Radomski <dextero@google.com> * Update expected value in generated_code_debug_prints_correctly test In bitflags v2, the debug string representation of enum values is different than it was in v1: Blue -> Color(Blue) (empty) -> LongEnum(0x0) This change adjusts the expected test value. Signed-off-by: Marcin Radomski <dextero@google.com> * Fix tests build on Swift 5.8 grpc-swift 1.4.1 depends on swift-nio-ssl 2.14.0+ [1]. swift-nio-ssl 2.29.1 published on 2025-01-30, introduced some code [2] that uses a "switch expression syntax" supported since Swift 5.9 [3]. Attempts to compile it with Swift 5.8 cause build errors. swift-nio-ssl project doesn't seem to support Swift 5.8. A commit from 2024-10-29 removes a "deprecated reference to a Swift 5.8 pipeline" [4]. swift-nio-ssl 2.29.0 is the last version that can be compiled with Swift 5.8. This commit pins it to that exact version. [1]66e27d7e84/Package.swift (L33)[2]3cb4d5ad12 (diff-bc1db1321ff689c2819245dcce1a3080554f0fc13f81b8d326c97e7d42717c8fR54)[3] https://github.com/swiftlang/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md [4]8a6b89d9a4--------- Signed-off-by: Marcin Radomski <dextero@google.com> Co-authored-by: Marcin Radomski <dextero@google.com>
1144 lines
40 KiB
Rust
1144 lines
40 KiB
Rust
/*
|
|
* 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.
|
|
*/
|
|
|
|
mod reflection_generated;
|
|
mod reflection_verifier;
|
|
mod safe_buffer;
|
|
mod r#struct;
|
|
pub use crate::r#struct::Struct;
|
|
pub use crate::reflection_generated::reflection;
|
|
pub use crate::safe_buffer::SafeBuffer;
|
|
|
|
use flatbuffers::{
|
|
emplace_scalar, read_scalar, EndianScalar, Follow, ForwardsUOffset, InvalidFlatbuffer,
|
|
SOffsetT, Table, UOffsetT, VOffsetT, Vector, SIZE_SOFFSET, SIZE_UOFFSET,
|
|
};
|
|
use reflection_generated::reflection::{BaseType, Field, Object, Schema};
|
|
|
|
use core::mem::size_of;
|
|
use num_traits::float::Float;
|
|
use num_traits::int::PrimInt;
|
|
use num_traits::FromPrimitive;
|
|
use thiserror::Error;
|
|
|
|
#[derive(Error, Debug, PartialEq)]
|
|
pub enum FlatbufferError {
|
|
#[error(transparent)]
|
|
VerificationError(#[from] flatbuffers::InvalidFlatbuffer),
|
|
#[error("Failed to convert between data type {0} and field type {1}")]
|
|
FieldTypeMismatch(String, String),
|
|
#[error("Set field value not supported for non-populated or non-scalar fields")]
|
|
SetValueNotSupported,
|
|
#[error(transparent)]
|
|
ParseFloatError(#[from] std::num::ParseFloatError),
|
|
#[error(transparent)]
|
|
TryFromIntError(#[from] std::num::TryFromIntError),
|
|
#[error("Couldn't set string because cache vector is polluted")]
|
|
SetStringPolluted,
|
|
#[error("Invalid schema: Polluted buffer or the schema doesn't match the buffer.")]
|
|
InvalidSchema,
|
|
#[error("Type not supported: {0}")]
|
|
TypeNotSupported(String),
|
|
#[error("No type or invalid type found in union enum")]
|
|
InvalidUnionEnum,
|
|
#[error("Table or Struct doesn't belong to the buffer")]
|
|
InvalidTableOrStruct,
|
|
#[error("Field not found in the table schema")]
|
|
FieldNotFound,
|
|
}
|
|
|
|
pub type FlatbufferResult<T, E = FlatbufferError> = core::result::Result<T, E>;
|
|
|
|
/// Gets the root table from a trusted Flatbuffer.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Flatbuffers accessors do not perform validation checks before accessing. Users
|
|
/// must trust [data] contains a valid flatbuffer. Reading unchecked buffers may cause panics or even UB.
|
|
pub unsafe fn get_any_root(data: &[u8]) -> Table {
|
|
<ForwardsUOffset<Table>>::follow(data, 0)
|
|
}
|
|
|
|
/// 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 type size doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type T
|
|
pub unsafe fn get_field_integer<T: for<'a> Follow<'a, Inner = T> + PrimInt + FromPrimitive>(
|
|
table: &Table,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Option<T>> {
|
|
if size_of::<T>() != get_type_size(field.type_().base_type()) {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
std::any::type_name::<T>().to_string(),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
let default = T::from_i64(field.default_integer());
|
|
Ok(table.get::<T>(field.offset(), default))
|
|
}
|
|
|
|
/// 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 type doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type T
|
|
pub unsafe fn get_field_float<T: for<'a> Follow<'a, Inner = T> + Float>(
|
|
table: &Table,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Option<T>> {
|
|
if size_of::<T>() != get_type_size(field.type_().base_type()) {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
std::any::type_name::<T>().to_string(),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
let default = T::from(field.default_real());
|
|
Ok(table.get::<T>(field.offset(), default))
|
|
}
|
|
|
|
/// 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 type size doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type String
|
|
pub unsafe fn get_field_string<'a>(
|
|
table: &Table<'a>,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Option<&'a str>> {
|
|
if field.type_().base_type() != BaseType::String {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("String"),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(table.get::<ForwardsUOffset<&'a str>>(field.offset(), Some("")))
|
|
}
|
|
|
|
/// Gets a [Struct] table field given its exact type. Returns [None] if the field is not set. Returns error if the type doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type Struct
|
|
pub unsafe fn get_field_struct<'a>(
|
|
table: &Table<'a>,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Option<Struct<'a>>> {
|
|
// TODO inherited from C++: This does NOT check if the field is a table or struct, but we'd need
|
|
// access to the schema to check the is_struct flag.
|
|
if field.type_().base_type() != BaseType::Obj {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("Obj"),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(table.get::<Struct>(field.offset(), None))
|
|
}
|
|
|
|
/// Gets a Vector table field given its exact type. Returns empty vector if the field is not set. Returns error if the type doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type Vector
|
|
pub unsafe fn get_field_vector<'a, T: Follow<'a, Inner = T>>(
|
|
table: &Table<'a>,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Option<Vector<'a, T>>> {
|
|
if field.type_().base_type() != BaseType::Vector
|
|
|| core::mem::size_of::<T>() != get_type_size(field.type_().element())
|
|
{
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
std::any::type_name::<T>().to_string(),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(table.get::<ForwardsUOffset<Vector<'a, T>>>(field.offset(), Some(Vector::<T>::default())))
|
|
}
|
|
|
|
/// Gets a Table table field given its exact type. Returns [None] if the field is not set. Returns error if the type doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type Table
|
|
pub unsafe fn get_field_table<'a>(
|
|
table: &Table<'a>,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Option<Table<'a>>> {
|
|
if field.type_().base_type() != BaseType::Obj {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("Obj"),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(table.get::<ForwardsUOffset<Table<'a>>>(field.offset(), None))
|
|
}
|
|
|
|
/// 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.
|
|
/// [num_traits](https://docs.rs/num-traits/latest/num_traits/cast/trait.NumCast.html) is used for number casting.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [table] must contain recursively valid offsets that match the [field].
|
|
pub unsafe fn get_any_field_integer(table: &Table, field: &Field) -> FlatbufferResult<i64> {
|
|
if let Some(field_loc) = get_field_loc(table, field) {
|
|
get_any_value_integer(field.type_().base_type(), table.buf(), field_loc)
|
|
} else {
|
|
Ok(field.default_integer())
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [table] must contain recursively valid offsets that match the [field].
|
|
pub unsafe fn get_any_field_float(table: &Table, field: &Field) -> FlatbufferResult<f64> {
|
|
if let Some(field_loc) = get_field_loc(table, field) {
|
|
get_any_value_float(field.type_().base_type(), table.buf(), field_loc)
|
|
} else {
|
|
Ok(field.default_real())
|
|
}
|
|
}
|
|
|
|
/// Returns the value of any table field as a string, regardless of what type it is. Returns empty string if the field is not set.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [table] must contain recursively valid offsets that match the [field].
|
|
pub unsafe fn get_any_field_string(table: &Table, field: &Field, schema: &Schema) -> String {
|
|
if let Some(field_loc) = get_field_loc(table, field) {
|
|
get_any_value_string(
|
|
field.type_().base_type(),
|
|
table.buf(),
|
|
field_loc,
|
|
schema,
|
|
field.type_().index() as usize,
|
|
)
|
|
} else {
|
|
String::from("")
|
|
}
|
|
}
|
|
|
|
/// Gets a [Struct] struct field given its exact type. Returns error if the type doesn't match.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type Struct.
|
|
pub unsafe fn get_field_struct_in_struct<'a>(
|
|
st: &Struct<'a>,
|
|
field: &Field,
|
|
) -> FlatbufferResult<Struct<'a>> {
|
|
// TODO inherited from C++: This does NOT check if the field is a table or struct, but we'd need
|
|
// access to the schema to check the is_struct flag.
|
|
if field.type_().base_type() != BaseType::Obj {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("Obj"),
|
|
field
|
|
.type_()
|
|
.base_type()
|
|
.variant_name()
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(st.get::<Struct>(field.offset() as usize))
|
|
}
|
|
|
|
/// Returns the value of any struct field as a 64-bit int, regardless of what type it is. Returns error if the value cannot be parsed as integer.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [st] must contain valid offsets that match the [field].
|
|
pub unsafe fn get_any_field_integer_in_struct(st: &Struct, field: &Field) -> FlatbufferResult<i64> {
|
|
let field_loc = st.loc() + field.offset() as usize;
|
|
|
|
get_any_value_integer(field.type_().base_type(), st.buf(), field_loc)
|
|
}
|
|
|
|
/// Returns the value of any struct field as a 64-bit floating point, regardless of what type it is. Returns error if the value cannot be parsed as float.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [st] must contain valid offsets that match the [field].
|
|
pub unsafe fn get_any_field_float_in_struct(st: &Struct, field: &Field) -> FlatbufferResult<f64> {
|
|
let field_loc = st.loc() + field.offset() as usize;
|
|
|
|
get_any_value_float(field.type_().base_type(), st.buf(), field_loc)
|
|
}
|
|
|
|
/// Returns the value of any struct field as a string, regardless of what type it is.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [st] must contain valid offsets that match the [field].
|
|
pub unsafe fn get_any_field_string_in_struct(
|
|
st: &Struct,
|
|
field: &Field,
|
|
schema: &Schema,
|
|
) -> String {
|
|
let field_loc = st.loc() + field.offset() as usize;
|
|
|
|
get_any_value_string(
|
|
field.type_().base_type(),
|
|
st.buf(),
|
|
field_loc,
|
|
schema,
|
|
field.type_().index() as usize,
|
|
)
|
|
}
|
|
|
|
/// Sets any table field with the value of a 64-bit integer. Returns error if the field is not originally set or is with non-scalar value or the provided value cannot be cast into the field type.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [buf] must contain a valid root table and valid offset to it.
|
|
pub unsafe fn set_any_field_integer(
|
|
buf: &mut [u8],
|
|
table_loc: usize,
|
|
field: &Field,
|
|
v: i64,
|
|
) -> FlatbufferResult<()> {
|
|
let field_type = field.type_().base_type();
|
|
let table = Table::follow(buf, table_loc);
|
|
|
|
let Some(field_loc) = get_field_loc(&table, field) else {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
};
|
|
|
|
if !is_scalar(field_type) {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
}
|
|
|
|
set_any_value_integer(field_type, buf, field_loc, v)
|
|
}
|
|
|
|
/// Sets any table field with the value of a 64-bit floating point. Returns error if the field is not originally set or is with non-scalar value or the provided value cannot be cast into the field type.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [buf] must contain a valid root table and valid offset to it.
|
|
pub unsafe fn set_any_field_float(
|
|
buf: &mut [u8],
|
|
table_loc: usize,
|
|
field: &Field,
|
|
v: f64,
|
|
) -> FlatbufferResult<()> {
|
|
let field_type = field.type_().base_type();
|
|
let table = Table::follow(buf, table_loc);
|
|
|
|
let Some(field_loc) = get_field_loc(&table, field) else {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
};
|
|
|
|
if !is_scalar(field_type) {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
}
|
|
|
|
set_any_value_float(field_type, buf, field_loc, v)
|
|
}
|
|
|
|
/// Sets any table field with the value of a string. Returns error if the field is not originally set or is with non-scalar value or the provided value cannot be parsed as the field type.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [buf] must contain a valid root table and valid offset to it.
|
|
pub unsafe fn set_any_field_string(
|
|
buf: &mut [u8],
|
|
table_loc: usize,
|
|
field: &Field,
|
|
v: &str,
|
|
) -> FlatbufferResult<()> {
|
|
let field_type = field.type_().base_type();
|
|
let table = Table::follow(buf, table_loc);
|
|
|
|
let Some(field_loc) = get_field_loc(&table, field) else {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
};
|
|
|
|
if !is_scalar(field_type) {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
}
|
|
|
|
set_any_value_float(field_type, buf, field_loc, v.parse::<f64>()?)
|
|
}
|
|
|
|
/// Sets any scalar field given its exact type. Returns error if the field is not originally set or is with non-scalar value.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [buf] must contain a valid root table and valid offset to it.
|
|
pub unsafe fn set_field<T: EndianScalar>(
|
|
buf: &mut [u8],
|
|
table_loc: usize,
|
|
field: &Field,
|
|
v: T,
|
|
) -> FlatbufferResult<()> {
|
|
let field_type = field.type_().base_type();
|
|
let table = Table::follow(buf, table_loc);
|
|
|
|
if !is_scalar(field_type) {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
}
|
|
|
|
if core::mem::size_of::<T>() != get_type_size(field_type) {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
std::any::type_name::<T>().to_string(),
|
|
field_type.variant_name().unwrap_or_default().to_string(),
|
|
));
|
|
}
|
|
|
|
let Some(field_loc) = get_field_loc(&table, field) else {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
};
|
|
|
|
if buf.len() < field_loc.saturating_add(get_type_size(field_type)) {
|
|
return Err(FlatbufferError::VerificationError(
|
|
InvalidFlatbuffer::RangeOutOfBounds {
|
|
range: core::ops::Range {
|
|
start: field_loc,
|
|
end: field_loc.saturating_add(get_type_size(field_type)),
|
|
},
|
|
error_trace: Default::default(),
|
|
},
|
|
));
|
|
}
|
|
|
|
// SAFETY: the buffer range was verified above.
|
|
unsafe { Ok(emplace_scalar::<T>(&mut buf[field_loc..], v)) }
|
|
}
|
|
|
|
/// Sets a string field to a new value. Returns error if the field is not originally set or is not of string type in which cases the [buf] stays intact. Returns error if the [buf] fails to be updated.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [buf] must contain a valid root table and valid offset to it and conform to the [schema].
|
|
pub unsafe fn set_string(
|
|
buf: &mut Vec<u8>,
|
|
table_loc: usize,
|
|
field: &Field,
|
|
v: &str,
|
|
schema: &Schema,
|
|
) -> FlatbufferResult<()> {
|
|
if v.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
let field_type = field.type_().base_type();
|
|
if field_type != BaseType::String {
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("String"),
|
|
field_type.variant_name().unwrap_or_default().to_string(),
|
|
));
|
|
}
|
|
|
|
let table = Table::follow(buf, table_loc);
|
|
|
|
let Some(field_loc) = get_field_loc(&table, field) else {
|
|
return Err(FlatbufferError::SetValueNotSupported);
|
|
};
|
|
|
|
if buf.len() < field_loc + get_type_size(field_type) {
|
|
return Err(FlatbufferError::VerificationError(
|
|
InvalidFlatbuffer::RangeOutOfBounds {
|
|
range: core::ops::Range {
|
|
start: field_loc,
|
|
end: field_loc.saturating_add(get_type_size(field_type)),
|
|
},
|
|
error_trace: Default::default(),
|
|
},
|
|
));
|
|
}
|
|
|
|
// SAFETY: the buffer range was verified above.
|
|
let string_loc = unsafe { deref_uoffset(buf, field_loc)? };
|
|
if buf.len() < string_loc.saturating_add(SIZE_UOFFSET) {
|
|
return Err(FlatbufferError::VerificationError(
|
|
InvalidFlatbuffer::RangeOutOfBounds {
|
|
range: core::ops::Range {
|
|
start: string_loc,
|
|
end: string_loc.saturating_add(SIZE_UOFFSET),
|
|
},
|
|
error_trace: Default::default(),
|
|
},
|
|
));
|
|
}
|
|
|
|
// SAFETY: the buffer range was verified above.
|
|
let len_old = unsafe { read_uoffset(buf, string_loc) };
|
|
if buf.len()
|
|
< string_loc
|
|
.saturating_add(SIZE_UOFFSET)
|
|
.saturating_add(len_old.try_into()?)
|
|
{
|
|
return Err(FlatbufferError::VerificationError(
|
|
InvalidFlatbuffer::RangeOutOfBounds {
|
|
range: core::ops::Range {
|
|
start: string_loc,
|
|
end: string_loc
|
|
.saturating_add(SIZE_UOFFSET)
|
|
.saturating_add(len_old.try_into()?),
|
|
},
|
|
error_trace: Default::default(),
|
|
},
|
|
));
|
|
}
|
|
|
|
let len_new = v.len();
|
|
let delta = len_new as isize - len_old as isize;
|
|
let mut bytes_to_insert = v.as_bytes().to_vec();
|
|
|
|
if delta != 0 {
|
|
// Rounds the delta up to the nearest multiple of the maximum int size to keep the types after the insersion point aligned.
|
|
// stdint crate defines intmax_t as an alias for c_long; use it directly to avoid extra
|
|
// dependency.
|
|
let mask = (size_of::<core::ffi::c_long>() - 1) as isize;
|
|
let offset = (delta + mask) & !mask;
|
|
let mut visited_vec = vec![false; buf.len()];
|
|
|
|
if offset != 0 {
|
|
update_offset(
|
|
buf,
|
|
table_loc,
|
|
&mut visited_vec,
|
|
&schema.root_table().unwrap(),
|
|
schema,
|
|
string_loc,
|
|
offset,
|
|
)?;
|
|
|
|
// Sets the new length.
|
|
emplace_scalar::<SOffsetT>(
|
|
&mut buf[string_loc..string_loc + SIZE_UOFFSET],
|
|
len_new.try_into()?,
|
|
);
|
|
}
|
|
|
|
// Pads the bytes vector with 0 if `offset` doesn't equal `delta`.
|
|
bytes_to_insert.resize(bytes_to_insert.len() + (offset - delta) as usize, 0);
|
|
}
|
|
|
|
// Replaces the data.
|
|
buf.splice(
|
|
string_loc + SIZE_SOFFSET..string_loc + SIZE_UOFFSET + usize::try_from(len_old)?,
|
|
bytes_to_insert,
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the size of a scalar type in the `BaseType` enum. In the case of structs, returns the size of their offset (`UOffsetT`) in the buffer.
|
|
fn get_type_size(base_type: BaseType) -> usize {
|
|
match base_type {
|
|
BaseType::UType | BaseType::Bool | BaseType::Byte | BaseType::UByte => 1,
|
|
BaseType::Short | BaseType::UShort => 2,
|
|
BaseType::Int
|
|
| BaseType::UInt
|
|
| BaseType::Float
|
|
| BaseType::String
|
|
| BaseType::Vector
|
|
| BaseType::Obj
|
|
| BaseType::Union => 4,
|
|
BaseType::Long | BaseType::ULong | BaseType::Double | BaseType::Vector64 => 8,
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
/// Returns the absolute field location in the buffer and [None] if the field is not populated.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// [table] must contain a valid vtable.
|
|
unsafe fn get_field_loc(table: &Table, field: &Field) -> Option<usize> {
|
|
let field_offset = table.vtable().get(field.offset()) as usize;
|
|
if field_offset == 0 {
|
|
return None;
|
|
}
|
|
|
|
Some(table.loc() + field_offset)
|
|
}
|
|
|
|
/// Reads value as a 64-bit int from the provided byte slice at the specified location. Returns error if the value cannot be parsed as integer.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Caller must ensure `buf.len() >= loc + size_of::<T>()` at all the access layers.
|
|
unsafe fn get_any_value_integer(
|
|
base_type: BaseType,
|
|
buf: &[u8],
|
|
loc: usize,
|
|
) -> FlatbufferResult<i64> {
|
|
match base_type {
|
|
BaseType::UType | BaseType::UByte => i64::from_u8(u8::follow(buf, loc)),
|
|
BaseType::Bool => bool::follow(buf, loc).try_into().ok(),
|
|
BaseType::Byte => i64::from_i8(i8::follow(buf, loc)),
|
|
BaseType::Short => i64::from_i16(i16::follow(buf, loc)),
|
|
BaseType::UShort => i64::from_u16(u16::follow(buf, loc)),
|
|
BaseType::Int => i64::from_i32(i32::follow(buf, loc)),
|
|
BaseType::UInt => i64::from_u32(u32::follow(buf, loc)),
|
|
BaseType::Long => Some(i64::follow(buf, loc)),
|
|
BaseType::ULong => i64::from_u64(u64::follow(buf, loc)),
|
|
BaseType::Float => i64::from_f32(f32::follow(buf, loc)),
|
|
BaseType::Double => i64::from_f64(f64::follow(buf, loc)),
|
|
BaseType::String => ForwardsUOffset::<&str>::follow(buf, loc)
|
|
.parse::<i64>()
|
|
.ok(),
|
|
_ => None, // Tables & vectors do not make sense.
|
|
}
|
|
.ok_or(FlatbufferError::FieldTypeMismatch(
|
|
String::from("i64"),
|
|
base_type.variant_name().unwrap_or_default().to_string(),
|
|
))
|
|
}
|
|
|
|
/// Reads value as a 64-bit floating point from the provided byte slice at the specified location. Returns error if the value cannot be parsed as float.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Caller must ensure `buf.len() >= loc + size_of::<T>()` at all the access layers.
|
|
unsafe fn get_any_value_float(
|
|
base_type: BaseType,
|
|
buf: &[u8],
|
|
loc: usize,
|
|
) -> FlatbufferResult<f64> {
|
|
match base_type {
|
|
BaseType::UType | BaseType::UByte => f64::from_u8(u8::follow(buf, loc)),
|
|
BaseType::Bool => bool::follow(buf, loc).try_into().ok(),
|
|
BaseType::Byte => f64::from_i8(i8::follow(buf, loc)),
|
|
BaseType::Short => f64::from_i16(i16::follow(buf, loc)),
|
|
BaseType::UShort => f64::from_u16(u16::follow(buf, loc)),
|
|
BaseType::Int => f64::from_i32(i32::follow(buf, loc)),
|
|
BaseType::UInt => f64::from_u32(u32::follow(buf, loc)),
|
|
BaseType::Long => f64::from_i64(i64::follow(buf, loc)),
|
|
BaseType::ULong => f64::from_u64(u64::follow(buf, loc)),
|
|
BaseType::Float => f64::from_f32(f32::follow(buf, loc)),
|
|
BaseType::Double => Some(f64::follow(buf, loc)),
|
|
BaseType::String => ForwardsUOffset::<&str>::follow(buf, loc)
|
|
.parse::<f64>()
|
|
.ok(),
|
|
_ => None,
|
|
}
|
|
.ok_or(FlatbufferError::FieldTypeMismatch(
|
|
String::from("f64"),
|
|
base_type.variant_name().unwrap_or_default().to_string(),
|
|
))
|
|
}
|
|
|
|
/// Reads value as a string from the provided byte slice at the specified location.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Caller must ensure `buf.len() >= loc + size_of::<T>()` at all the access layers.
|
|
unsafe fn get_any_value_string(
|
|
base_type: BaseType,
|
|
buf: &[u8],
|
|
loc: usize,
|
|
schema: &Schema,
|
|
type_index: usize,
|
|
) -> String {
|
|
match base_type {
|
|
BaseType::Float | BaseType::Double => get_any_value_float(base_type, buf, loc)
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
BaseType::String => {
|
|
String::from_utf8_lossy(ForwardsUOffset::<&[u8]>::follow(buf, loc)).to_string()
|
|
}
|
|
BaseType::Obj => {
|
|
// Converts the table to a string. This is mostly for debugging purposes,
|
|
// and does NOT promise to be JSON compliant.
|
|
// Also prefixes the type.
|
|
let object: Object = schema.objects().get(type_index);
|
|
let mut s = object.name().to_string();
|
|
s += " { ";
|
|
if object.is_struct() {
|
|
let st: Struct<'_> = Struct::follow(buf, loc);
|
|
for field in object.fields() {
|
|
let field_value = get_any_field_string_in_struct(&st, &field, schema);
|
|
s += field.name();
|
|
s += ": ";
|
|
s += field_value.as_str();
|
|
s += ", ";
|
|
}
|
|
} else {
|
|
let table = ForwardsUOffset::<Table>::follow(buf, loc);
|
|
for field in object.fields() {
|
|
if table.vtable().get(field.offset()) == 0 {
|
|
continue;
|
|
}
|
|
let mut field_value = get_any_field_string(&table, &field, schema);
|
|
if field.type_().base_type() == BaseType::String {
|
|
// Escape the string
|
|
field_value = format!("{:?}", field_value.as_str());
|
|
}
|
|
s += field.name();
|
|
s += ": ";
|
|
s += field_value.as_str();
|
|
s += ", ";
|
|
}
|
|
}
|
|
s + "}"
|
|
}
|
|
BaseType::Vector => String::from("[(elements)]"), // TODO inherited from C++: implement this as well.
|
|
BaseType::Union => String::from("(union)"), // TODO inherited from C++: implement this as well.
|
|
_ => get_any_value_integer(base_type, buf, loc)
|
|
.unwrap_or_default()
|
|
.to_string(),
|
|
}
|
|
}
|
|
|
|
/// Sets any scalar value with a 64-bit integer. Returns error if the value is not successfully replaced.
|
|
fn set_any_value_integer(
|
|
base_type: BaseType,
|
|
buf: &mut [u8],
|
|
field_loc: usize,
|
|
v: i64,
|
|
) -> FlatbufferResult<()> {
|
|
if buf.len() < get_type_size(base_type) {
|
|
return Err(FlatbufferError::VerificationError(
|
|
InvalidFlatbuffer::RangeOutOfBounds {
|
|
range: core::ops::Range {
|
|
start: field_loc,
|
|
end: field_loc.saturating_add(get_type_size(base_type)),
|
|
},
|
|
error_trace: Default::default(),
|
|
},
|
|
));
|
|
}
|
|
let buf = &mut buf[field_loc..];
|
|
let type_name = base_type.variant_name().unwrap_or_default().to_string();
|
|
|
|
macro_rules! try_emplace {
|
|
($ty:ty, $value:expr) => {
|
|
if let Ok(v) = TryInto::<$ty>::try_into($value) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe { Ok(emplace_scalar::<$ty>(buf, v)) }
|
|
} else {
|
|
Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("i64"),
|
|
type_name,
|
|
))
|
|
}
|
|
};
|
|
}
|
|
|
|
match base_type {
|
|
BaseType::UType | BaseType::UByte => {
|
|
try_emplace!(u8, v)
|
|
}
|
|
BaseType::Bool => {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe { Ok(emplace_scalar::<bool>(buf, v != 0)) }
|
|
}
|
|
BaseType::Byte => {
|
|
try_emplace!(i8, v)
|
|
}
|
|
BaseType::Short => {
|
|
try_emplace!(i16, v)
|
|
}
|
|
BaseType::UShort => {
|
|
try_emplace!(u16, v)
|
|
}
|
|
BaseType::Int => {
|
|
try_emplace!(i32, v)
|
|
}
|
|
BaseType::UInt => {
|
|
try_emplace!(u32, v)
|
|
}
|
|
BaseType::Long => {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe { Ok(emplace_scalar::<i64>(buf, v)) }
|
|
}
|
|
BaseType::ULong => {
|
|
try_emplace!(u64, v)
|
|
}
|
|
BaseType::Float => {
|
|
if let Some(value) = f32::from_i64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe { Ok(emplace_scalar::<f32>(buf, value)) }
|
|
} else {
|
|
Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("i64"),
|
|
type_name,
|
|
))
|
|
}
|
|
}
|
|
BaseType::Double => {
|
|
if let Some(value) = f64::from_i64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe { Ok(emplace_scalar::<f64>(buf, value)) }
|
|
} else {
|
|
Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("i64"),
|
|
type_name,
|
|
))
|
|
}
|
|
}
|
|
_ => Err(FlatbufferError::SetValueNotSupported),
|
|
}
|
|
}
|
|
|
|
/// Sets any scalar value with a 64-bit floating point. Returns error if the value is not successfully replaced.
|
|
fn set_any_value_float(
|
|
base_type: BaseType,
|
|
buf: &mut [u8],
|
|
field_loc: usize,
|
|
v: f64,
|
|
) -> FlatbufferResult<()> {
|
|
if buf.len() < get_type_size(base_type) {
|
|
return Err(FlatbufferError::VerificationError(
|
|
InvalidFlatbuffer::RangeOutOfBounds {
|
|
range: core::ops::Range {
|
|
start: field_loc,
|
|
end: field_loc.saturating_add(get_type_size(base_type)),
|
|
},
|
|
error_trace: Default::default(),
|
|
},
|
|
));
|
|
}
|
|
let buf = &mut buf[field_loc..];
|
|
let type_name = base_type.variant_name().unwrap_or_default().to_string();
|
|
|
|
match base_type {
|
|
BaseType::UType | BaseType::UByte => {
|
|
if let Some(value) = u8::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<u8>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::Bool => {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<bool>(buf, v != 0f64));
|
|
}
|
|
}
|
|
BaseType::Byte => {
|
|
if let Some(value) = i8::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<i8>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::Short => {
|
|
if let Some(value) = i16::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<i16>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::UShort => {
|
|
if let Some(value) = u16::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<u16>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::Int => {
|
|
if let Some(value) = i32::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<i32>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::UInt => {
|
|
if let Some(value) = u32::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<u32>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::Long => {
|
|
if let Some(value) = i64::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<i64>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::ULong => {
|
|
if let Some(value) = u64::from_f64(v) {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<u64>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
BaseType::Float => {
|
|
if let Some(value) = f32::from_f64(v) {
|
|
// Value converted to inf if overflow occurs
|
|
if value != f32::INFINITY {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<f32>(buf, value));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BaseType::Double => {
|
|
// SAFETY: buffer size is verified at the beginning of this function.
|
|
unsafe {
|
|
return Ok(emplace_scalar::<f64>(buf, v));
|
|
}
|
|
}
|
|
_ => return Err(FlatbufferError::SetValueNotSupported),
|
|
}
|
|
return Err(FlatbufferError::FieldTypeMismatch(
|
|
String::from("f64"),
|
|
type_name,
|
|
));
|
|
}
|
|
|
|
fn is_scalar(base_type: BaseType) -> bool {
|
|
return base_type <= BaseType::Double;
|
|
}
|
|
|
|
/// Iterates through the buffer and updates all the relative offsets affected by the insertion.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Caller must ensure [buf] contains valid data that conforms to [schema].
|
|
unsafe fn update_offset(
|
|
buf: &mut [u8],
|
|
table_loc: usize,
|
|
updated: &mut [bool],
|
|
object: &Object,
|
|
schema: &Schema,
|
|
insertion_loc: usize,
|
|
offset: isize,
|
|
) -> FlatbufferResult<()> {
|
|
if updated.len() != buf.len() {
|
|
return Err(FlatbufferError::SetStringPolluted);
|
|
}
|
|
|
|
if updated[table_loc] {
|
|
return Ok(());
|
|
}
|
|
|
|
let slice = &mut buf[table_loc..table_loc + SIZE_SOFFSET];
|
|
let vtable_offset = isize::try_from(read_scalar::<SOffsetT>(slice))?;
|
|
let vtable_loc = (isize::try_from(table_loc)? - vtable_offset).try_into()?;
|
|
|
|
if insertion_loc <= table_loc {
|
|
// Checks if insertion point is between the table and a vtable that
|
|
// precedes it.
|
|
if (vtable_loc..table_loc).contains(&insertion_loc) {
|
|
emplace_scalar::<SOffsetT>(slice, (vtable_offset + offset).try_into()?);
|
|
updated[table_loc] = true;
|
|
}
|
|
|
|
// Early out: since all fields inside the table must point forwards in
|
|
// memory, if the insertion point is before the table we can stop here.
|
|
return Ok(());
|
|
}
|
|
|
|
for field in object.fields() {
|
|
let field_type = field.type_().base_type();
|
|
if is_scalar(field_type) {
|
|
continue;
|
|
}
|
|
|
|
let field_offset = VOffsetT::follow(buf, vtable_loc.saturating_add(field.offset().into()));
|
|
if field_offset == 0 {
|
|
continue;
|
|
}
|
|
|
|
let field_loc = table_loc + usize::from(field_offset);
|
|
if updated[field_loc] {
|
|
continue;
|
|
}
|
|
|
|
if field_type == BaseType::Obj
|
|
&& schema
|
|
.objects()
|
|
.get(field.type_().index().try_into()?)
|
|
.is_struct()
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Updates the relative offset from table to actual data if needed
|
|
let slice = &mut buf[field_loc..field_loc + SIZE_UOFFSET];
|
|
let field_value_offset = read_scalar::<UOffsetT>(slice);
|
|
let field_value_loc = field_loc.saturating_add(field_value_offset.try_into()?);
|
|
if (field_loc..field_value_loc).contains(&insertion_loc) {
|
|
emplace_scalar::<UOffsetT>(
|
|
slice,
|
|
(isize::try_from(field_value_offset)? + offset).try_into()?,
|
|
);
|
|
updated[field_loc] = true;
|
|
}
|
|
|
|
match field_type {
|
|
BaseType::Obj => {
|
|
let field_obj = schema.objects().get(field.type_().index().try_into()?);
|
|
update_offset(
|
|
buf,
|
|
field_value_loc,
|
|
updated,
|
|
&field_obj,
|
|
schema,
|
|
insertion_loc,
|
|
offset,
|
|
)?;
|
|
}
|
|
BaseType::Vector => {
|
|
let elem_type = field.type_().element();
|
|
if elem_type != BaseType::Obj || elem_type != BaseType::String {
|
|
continue;
|
|
}
|
|
if elem_type == BaseType::Obj
|
|
&& schema
|
|
.objects()
|
|
.get(field.type_().index().try_into()?)
|
|
.is_struct()
|
|
{
|
|
continue;
|
|
}
|
|
let vec_size = usize::try_from(read_uoffset(buf, field_value_loc))?;
|
|
for index in 0..vec_size {
|
|
let elem_loc = field_value_loc + SIZE_UOFFSET + index * SIZE_UOFFSET;
|
|
if updated[elem_loc] {
|
|
continue;
|
|
}
|
|
let slice = &mut buf[elem_loc..elem_loc + SIZE_UOFFSET];
|
|
let elem_value_offset = read_scalar::<UOffsetT>(slice);
|
|
let elem_value_loc = elem_loc.saturating_add(elem_value_offset.try_into()?);
|
|
if (elem_loc..elem_value_loc).contains(&insertion_loc) {
|
|
emplace_scalar::<UOffsetT>(
|
|
slice,
|
|
(isize::try_from(elem_value_offset)? + offset).try_into()?,
|
|
);
|
|
updated[elem_loc] = true;
|
|
}
|
|
|
|
if elem_type == BaseType::Obj {
|
|
let elem_obj = schema.objects().get(field.type_().index().try_into()?);
|
|
update_offset(
|
|
buf,
|
|
elem_value_loc,
|
|
updated,
|
|
&elem_obj,
|
|
schema,
|
|
insertion_loc,
|
|
offset,
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
BaseType::Union => {
|
|
let union_enum = schema.enums().get(field.type_().index().try_into()?);
|
|
let union_type = object
|
|
.fields()
|
|
.lookup_by_key(field.name().to_string() + "_type", |field, key| {
|
|
field.key_compare_with_value(key)
|
|
})
|
|
.unwrap();
|
|
let union_type_loc = vtable_loc.saturating_add(union_type.offset().into());
|
|
let union_type_offset = VOffsetT::follow(buf, union_type_loc);
|
|
let union_type_value =
|
|
u8::follow(buf, table_loc.saturating_add(union_type_offset.into()));
|
|
let union_enum_value = union_enum
|
|
.values()
|
|
.lookup_by_key(union_type_value.into(), |value, key| {
|
|
value.key_compare_with_value(*key)
|
|
})
|
|
.unwrap();
|
|
let union_object = schema
|
|
.objects()
|
|
.get(union_enum_value.union_type().unwrap().index().try_into()?);
|
|
update_offset(
|
|
buf,
|
|
field_value_loc,
|
|
updated,
|
|
&union_object,
|
|
schema,
|
|
insertion_loc,
|
|
offset,
|
|
)?;
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
// Checks if the vtable offset points beyond the insertion point.
|
|
if (table_loc..vtable_loc).contains(&insertion_loc) {
|
|
let slice = &mut buf[table_loc..table_loc + SIZE_SOFFSET];
|
|
emplace_scalar::<SOffsetT>(slice, (vtable_offset - offset).try_into()?);
|
|
updated[table_loc] = true;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the absolute location of the data (e.g. string) in the buffer when the field contains relative offset (`UOffsetT`) to the data.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type `UOffsetT`.
|
|
unsafe fn deref_uoffset(buf: &[u8], field_loc: usize) -> FlatbufferResult<usize> {
|
|
Ok(field_loc.saturating_add(read_uoffset(buf, field_loc).try_into()?))
|
|
}
|
|
|
|
/// Reads the value of `UOffsetT` at the give location.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The value of the corresponding slot must have type `UOffsetT`.
|
|
unsafe fn read_uoffset(buf: &[u8], loc: usize) -> UOffsetT {
|
|
let slice = &buf[loc..loc + SIZE_UOFFSET];
|
|
read_scalar::<UOffsetT>(slice)
|
|
}
|