#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FbxByteOrder {
Little,
Big,
}
macro_rules! decode_scalar {
($name:ident, $ty:ty, $width:expr) => {
#[inline]
pub fn $name(self, bytes: [u8; $width]) -> $ty {
match self {
FbxByteOrder::Little => <$ty>::from_le_bytes(bytes),
FbxByteOrder::Big => <$ty>::from_be_bytes(bytes),
}
}
};
}
impl FbxByteOrder {
decode_scalar!(u16, u16, 2);
decode_scalar!(i16, i16, 2);
decode_scalar!(u32, u32, 4);
decode_scalar!(i32, i32, 4);
decode_scalar!(u64, u64, 8);
decode_scalar!(i64, i64, 8);
decode_scalar!(f32, f32, 4);
decode_scalar!(f64, f64, 8);
#[inline]
pub(crate) fn swap_elements_in_place(self, data: &mut [u8], element_size: usize) {
if self == FbxByteOrder::Little || element_size < 2 {
return;
}
for element in data.chunks_exact_mut(element_size) {
element.reverse();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct FbxDecodeLimits {
pub max_file_bytes: u64,
pub max_depth: u32,
pub max_nodes: u64,
pub max_properties_per_node: u64,
pub max_string_bytes: u64,
pub max_blob_bytes: u64,
pub max_array_elements: u64,
pub max_array_raw_bytes: u64,
pub max_total_array_raw_bytes: u64,
}
impl Default for FbxDecodeLimits {
fn default() -> Self {
Self {
max_file_bytes: 1 << 30, max_depth: 64, max_nodes: 8_000_000,
max_properties_per_node: 1 << 16,
max_string_bytes: 16 << 20, max_blob_bytes: 128 << 20, max_array_elements: 1 << 28,
max_array_raw_bytes: 256 << 20, max_total_array_raw_bytes: 1 << 30, }
}
}
impl FbxDecodeLimits {
pub fn permissive() -> Self {
Self {
max_file_bytes: 16 << 30,
max_depth: 256,
max_nodes: 64_000_000,
max_properties_per_node: 1 << 20,
max_string_bytes: 256 << 20,
max_blob_bytes: 2 << 30,
max_array_elements: 1 << 32,
max_array_raw_bytes: 4 << 30,
max_total_array_raw_bytes: 8 << 30,
}
}
pub fn fuzzing() -> Self {
Self {
max_file_bytes: 1 << 20,
max_depth: 16,
max_nodes: 64_000,
max_properties_per_node: 4096,
max_string_bytes: 1 << 20,
max_blob_bytes: 1 << 20,
max_array_elements: 1 << 20,
max_array_raw_bytes: 4 << 20,
max_total_array_raw_bytes: 4 << 20,
}
}
}
macro_rules! limit_setter {
($name:ident, $field:ident, $ty:ty, $doc:expr) => {
#[doc = $doc]
#[must_use]
pub fn $name(mut self, value: $ty) -> Self {
self.$field = value;
self
}
};
}
impl FbxDecodeLimits {
limit_setter!(
with_max_file_bytes,
max_file_bytes,
u64,
"Sets [`Self::max_file_bytes`]."
);
limit_setter!(with_max_depth, max_depth, u32, "Sets [`Self::max_depth`].");
limit_setter!(with_max_nodes, max_nodes, u64, "Sets [`Self::max_nodes`].");
limit_setter!(
with_max_properties_per_node,
max_properties_per_node,
u64,
"Sets [`Self::max_properties_per_node`]."
);
limit_setter!(
with_max_string_bytes,
max_string_bytes,
u64,
"Sets [`Self::max_string_bytes`]."
);
limit_setter!(
with_max_blob_bytes,
max_blob_bytes,
u64,
"Sets [`Self::max_blob_bytes`]."
);
limit_setter!(
with_max_array_elements,
max_array_elements,
u64,
"Sets [`Self::max_array_elements`]."
);
limit_setter!(
with_max_array_raw_bytes,
max_array_raw_bytes,
u64,
"Sets [`Self::max_array_raw_bytes`]."
);
limit_setter!(
with_max_total_array_raw_bytes,
max_total_array_raw_bytes,
u64,
"Sets [`Self::max_total_array_raw_bytes`]."
);
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct FbxReadOptions {
pub limits: FbxDecodeLimits,
pub strict: bool,
}
impl FbxReadOptions {
pub fn strict() -> Self {
Self {
strict: true,
..Self::default()
}
}
#[must_use]
pub fn with_limits(mut self, limits: FbxDecodeLimits) -> Self {
self.limits = limits;
self
}
#[must_use]
pub fn with_strict(mut self, strict: bool) -> Self {
self.strict = strict;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn byte_order_decodes_both_layouts() {
let bytes = [0x00, 0x00, 0x1d, 0x4c];
assert_eq!(FbxByteOrder::Big.u32(bytes), 7500);
assert_eq!(FbxByteOrder::Little.u32([0x4c, 0x1d, 0x00, 0x00]), 7500);
}
#[test]
fn little_endian_swap_is_a_no_op() {
let mut data = [1u8, 2, 3, 4, 5, 6, 7, 8];
FbxByteOrder::Little.swap_elements_in_place(&mut data, 4);
assert_eq!(data, [1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn big_endian_swap_reverses_each_element() {
let mut data = [1u8, 2, 3, 4, 5, 6, 7, 8];
FbxByteOrder::Big.swap_elements_in_place(&mut data, 4);
assert_eq!(data, [4, 3, 2, 1, 8, 7, 6, 5]);
}
#[test]
fn single_byte_elements_are_never_swapped() {
let mut data = [1u8, 2, 3];
FbxByteOrder::Big.swap_elements_in_place(&mut data, 1);
assert_eq!(data, [1, 2, 3]);
}
#[test]
fn setters_work_through_non_exhaustive() {
let limits = FbxDecodeLimits::default()
.with_max_depth(8)
.with_max_nodes(9);
assert_eq!(limits.max_depth, 8);
assert_eq!(limits.max_nodes, 9);
}
#[test]
fn defaults_accept_the_measured_corpus_maxima() {
let limits = FbxDecodeLimits::default();
assert!(limits.max_file_bytes >= 7_045_584);
assert!(limits.max_nodes >= 15_039);
assert!(limits.max_depth >= 8);
assert!(limits.max_properties_per_node >= 4_224);
assert!(limits.max_blob_bytes >= 328_086);
assert!(limits.max_string_bytes >= 87_207);
assert!(limits.max_array_raw_bytes >= 1_661_184);
assert!(limits.max_array_elements >= 207_648);
assert!(limits.max_total_array_raw_bytes >= 7_352_416);
}
}