/* * 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 = core::result::Result; /// 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 { >::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 Follow<'a, Inner = T> + PrimInt + FromPrimitive>( table: &Table, field: &Field, ) -> FlatbufferResult> { if size_of::() != get_type_size(field.type_().base_type()) { return Err(FlatbufferError::FieldTypeMismatch( std::any::type_name::().to_string(), field .type_() .base_type() .variant_name() .unwrap_or_default() .to_string(), )); } let default = T::from_i64(field.default_integer()); Ok(table.get::(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 Follow<'a, Inner = T> + Float>( table: &Table, field: &Field, ) -> FlatbufferResult> { if size_of::() != get_type_size(field.type_().base_type()) { return Err(FlatbufferError::FieldTypeMismatch( std::any::type_name::().to_string(), field .type_() .base_type() .variant_name() .unwrap_or_default() .to_string(), )); } let default = T::from(field.default_real()); Ok(table.get::(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> { 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::>(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>> { // 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::(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>> { if field.type_().base_type() != BaseType::Vector || core::mem::size_of::() != get_type_size(field.type_().element()) { return Err(FlatbufferError::FieldTypeMismatch( std::any::type_name::().to_string(), field .type_() .base_type() .variant_name() .unwrap_or_default() .to_string(), )); } Ok(table.get::>>(field.offset(), Some(Vector::::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>> { 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::>>(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 { 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 { 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> { // 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::(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 { 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 { 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::()?) } /// 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( 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::() != get_type_size(field_type) { return Err(FlatbufferError::FieldTypeMismatch( std::any::type_name::().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::(&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, 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::() - 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::( &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 { 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::()` at all the access layers. unsafe fn get_any_value_integer( base_type: BaseType, buf: &[u8], loc: usize, ) -> FlatbufferResult { 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::() .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::()` at all the access layers. unsafe fn get_any_value_float( base_type: BaseType, buf: &[u8], loc: usize, ) -> FlatbufferResult { 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::() .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::()` 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::::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::(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::(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::(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::(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::(buf, value)); } } } BaseType::Bool => { // SAFETY: buffer size is verified at the beginning of this function. unsafe { return Ok(emplace_scalar::(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::(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::(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::(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::(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::(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::(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::(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::(buf, value)); } } } } BaseType::Double => { // SAFETY: buffer size is verified at the beginning of this function. unsafe { return Ok(emplace_scalar::(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::(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::(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::(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::( 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::(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::( 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::(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 { 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::(slice) }