From a6cd4871a27c52080d92224e5d1d051e98718cb7 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 09:55:26 -0700 Subject: [PATCH 1/9] Rist 1.89 syntax --- src/deserializer/iter.rs | 37 ++++++++++++++++----------------- src/deserializer/typedstream.rs | 27 ++++++++++++------------ src/models/types.rs | 4 ++-- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/deserializer/iter.rs b/src/deserializer/iter.rs index 628aaa6..558b64d 100644 --- a/src/deserializer/iter.rs +++ b/src/deserializer/iter.rs @@ -142,26 +142,25 @@ impl<'a, 'b: 'a> Iterator for PropertyIterator<'a, 'b> { class: cls, data: _, }) = self.object_table.get(*idx) + && let Some(Archived::Class(cls)) = self.object_table.get(*cls) { - if let Some(Archived::Class(cls)) = self.object_table.get(*cls) { - let class_name = self - .type_table - .get(cls.name_index) - .and_then(|types| types.first()) - .and_then(|t| match t { - Type::String(name) => Some(*name), - _ => None, - }) - .unwrap_or("Unknown Class"); - // recurse into that object’s own data - let sub_iter = - PropertyIterator::new(self.object_table, self.type_table, *idx)?; - resolved.push(Property::Object { - class: cls, - name: class_name, - data: sub_iter, - }); - } + let class_name = self + .type_table + .get(cls.name_index) + .and_then(|types| types.first()) + .and_then(|t| match t { + Type::String(name) => Some(*name), + _ => None, + }) + .unwrap_or("Unknown Class"); + // recurse into that object’s own data + let sub_iter = + PropertyIterator::new(self.object_table, self.type_table, *idx)?; + resolved.push(Property::Object { + class: cls, + name: class_name, + data: sub_iter, + }); } } prim => resolved.push(Property::Primitive(prim)), diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index cc55ba7..c6c604a 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -232,10 +232,10 @@ impl<'a> TypedStreamDeserializer<'a> { // The class we just appended (*idx*) is the **parent** of the // class we appended in the previous iteration (*prev_new*) - if let Some(child_idx) = prev_new { - if let Archived::Class(ref mut child_cls) = self.object_table[child_idx] { - child_cls.parent_index = Some(idx); - } + if let Some(child_idx) = prev_new + && let Archived::Class(ref mut child_cls) = self.object_table[child_idx] + { + child_cls.parent_index = Some(idx); } // remember the first class we ever pushed @@ -264,10 +264,10 @@ impl<'a> TypedStreamDeserializer<'a> { // Patch the outer-most newly created class so that it points to the // already-existing parent (or to `None` if EMPTY terminated the list). - if let Some(outer_idx) = prev_new { - if let Archived::Class(ref mut outer_cls) = self.object_table[outer_idx] { - outer_cls.parent_index = final_parent; - } + if let Some(outer_idx) = prev_new + && let Archived::Class(ref mut outer_cls) = self.object_table[outer_idx] + { + outer_cls.parent_index = final_parent; } // Return the index of the bottom-most child we created first. @@ -294,15 +294,14 @@ impl<'a> TypedStreamDeserializer<'a> { // Read the next type, which should be an object if let Some(next_index) = self.read_type(false)? { // Recursively read the types for this object - if let Some(data) = self.read_types(next_index)? { - if let Some(Archived::Object { + if let Some(data) = self.read_types(next_index)? + && let Some(Archived::Object { class: _, data: data_vec, }) = self.object_table.get_mut(placeholder_index) - { - // Add the data to the object - data_vec.push(data); - } + { + // Add the data to the object + data_vec.push(data); } } } diff --git a/src/models/types.rs b/src/models/types.rs index fe8259c..622ecaf 100644 --- a/src/models/types.rs +++ b/src/models/types.rs @@ -93,7 +93,7 @@ impl<'a> Type<'a> { Self::String(str) } - pub(crate) fn get_array_length(types: &[u8]) -> Option> { + pub(crate) fn get_array_length(types: &'_ [u8]) -> Option>> { if types.first() == Some(&0x5b) { let len = types[1..] @@ -109,7 +109,7 @@ impl<'a> Type<'a> { None } - pub(crate) fn read_new_type(data: &[u8]) -> Result>> { + pub(crate) fn read_new_type(data: &'_ [u8]) -> Result>>> { // Get the type of the object let type_length = read_unsigned_int(data)?; From 3ce7b703d055ac976327be6d810f2ecf79381895 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:37:15 -0700 Subject: [PATCH 2/9] Support null type for #5 --- src/models/output_data.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/models/output_data.rs b/src/models/output_data.rs index 90a46de..565dcb9 100644 --- a/src/models/output_data.rs +++ b/src/models/output_data.rs @@ -19,6 +19,8 @@ pub enum OutputData<'a> { Array(&'a [u8]), /// Reference to another object by index in the [`object_table`](crate::deserializer::typedstream::TypedStreamDeserializer::object_table). Object(usize), + /// Represents a null value. + Null, } impl<'a> OutputData<'a> { @@ -187,6 +189,7 @@ impl std::fmt::Display for OutputData<'_> { OutputData::Byte(b) => write!(f, "0x{b:02x}"), OutputData::Array(arr) => write!(f, "[{arr:02x?}]"), OutputData::Object(idx) => write!(f, "Object({idx})"), + OutputData::Null => write!(f, "Null"), } } } From fb2140158e6dc242db7691c3009575673d522474 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:37:38 -0700 Subject: [PATCH 3/9] Support null type for #5 --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 4bbdcb7..b84e930 100644 --- a/src/error.rs +++ b/src/error.rs @@ -56,7 +56,7 @@ impl Display for TypedStreamError { } TypedStreamError::InvalidHeader => write!(f, "Invalid header in typedstream!"), TypedStreamError::InvalidPointer(pointer) => { - write!(f, "Invalid pointer: {pointer}") + write!(f, "Invalid pointer: {pointer:x}") } TypedStreamError::InvalidArray(offset) => { write!(f, "Invalid array at index: {offset:x}") From fee44b78f20ded5c8aeea6fe1e5f2286bb9bfa95 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:40:56 -0700 Subject: [PATCH 4/9] Add test for #5 --- src/lib.rs | 22 ++++++++++++++++++++++ src/test_data/HugeWithRefs | Bin 0 -> 67462 bytes 2 files changed, 22 insertions(+) create mode 100644 src/test_data/HugeWithRefs diff --git a/src/lib.rs b/src/lib.rs index 167593f..c1c1ba2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6866,4 +6866,26 @@ mod test_typedstream_deserializer { assert_eq!(typedstream.type_table, expected_types); assert_eq!(typedstream.object_table, expected_objects); } + + #[test] + fn test_parse_large_with_null() { + let typedstream_path = current_dir() + .unwrap() + .as_path() + .join("src/test_data/HugeWithRefs"); + let mut file = File::open(typedstream_path).unwrap(); + let mut bytes = vec![]; + file.read_to_end(&mut bytes).unwrap(); + + let mut typedstream = TypedStreamDeserializer::new(&bytes); + + // Unwrapping here means we resolved the object + let root = typedstream.oxidize().unwrap(); + println!("\nResults: {root:?}"); + // Unwrapping here means we resolved the properties + let root_obj = typedstream.resolve_properties(root).unwrap(); + println!("\nResults: {root_obj:?}"); + + print_resolved(root_obj, 4); + } } diff --git a/src/test_data/HugeWithRefs b/src/test_data/HugeWithRefs new file mode 100644 index 0000000000000000000000000000000000000000..a4718d9628fe94a11b02eb582c53d4c2bc546184 GIT binary patch literal 67462 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 M@BDfH_q*Tyzp{Q`6#xJL literal 0 HcmV?d00001 From 1eae957ce9405edfcebd0df05b459e4817137bf1 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:41:46 -0700 Subject: [PATCH 5/9] Resolve #5 --- src/deserializer/typedstream.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index c6c604a..aa0ea32 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -274,7 +274,7 @@ impl<'a> TypedStreamDeserializer<'a> { Ok(Some(first_idx)) } - fn read_object(&mut self) -> Result { + fn read_object(&mut self) -> Result> { match *read_byte_at(self.data, self.position)? { START => { let placeholder_index = self.object_table.len(); @@ -284,9 +284,12 @@ impl<'a> TypedStreamDeserializer<'a> { self.position += 1; if let Some(cls) = self.read_class()? { + // Estimate initial capacity for object data to reduce reallocations + let estimated_data_capacity = + ((self.data.len() - self.position) / 64).clamp(8, 64); self.object_table[placeholder_index] = Archived::Object { class: cls, - data: Vec::with_capacity(8), + data: Vec::with_capacity(estimated_data_capacity), }; while self.position < self.data.len() && *read_byte_at(self.data, self.position)? != END @@ -306,11 +309,15 @@ impl<'a> TypedStreamDeserializer<'a> { } } } - Ok(placeholder_index) + Ok(Some(placeholder_index)) + } + EMPTY => { + self.position += 1; + Ok(None) } ptr => { let pointer = read_pointer(&ptr)?; - Ok(pointer.value as usize) + Ok(Some(pointer.value as usize)) } } } @@ -365,7 +372,11 @@ impl<'a> TypedStreamDeserializer<'a> { Type::Object => { let obj_idx = self.read_object()?; self.position += 1; - out_v.push(OutputData::Object(obj_idx)); + if let Some(obj_idx) = obj_idx { + out_v.push(OutputData::Object(obj_idx)); + } else { + out_v.push(OutputData::Null); + } } Type::String(s) => { out_v.push(OutputData::String(s)); From 90dd160ce2fe72b63ea81ce497fafc130ee25f22 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:42:08 -0700 Subject: [PATCH 6/9] Perf++ --- src/deserializer/typedstream.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index aa0ea32..b73daa1 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -54,12 +54,18 @@ impl<'a> TypedStreamDeserializer<'a> { /// ``` #[must_use] pub fn new(data: &'a [u8]) -> Self { + // Estimate initial capacities based on data size to reduce reallocations + let estimated_size = data.len(); + let type_capacity = (estimated_size / 64).clamp(16, 256); + let object_capacity = (estimated_size / 32).clamp(32, 512); + let embedded_capacity = (estimated_size / 128).clamp(8, 64); + Self { data, position: 0, - type_table: Vec::with_capacity(16), - object_table: Vec::with_capacity(32), - seen_embedded_types: Vec::with_capacity(8), + type_table: Vec::with_capacity(type_capacity), + object_table: Vec::with_capacity(object_capacity), + seen_embedded_types: Vec::with_capacity(embedded_capacity), } } @@ -153,7 +159,13 @@ impl<'a> TypedStreamDeserializer<'a> { /// Reads the next byte from the stream, advancing the position. #[inline(always)] fn consume_current_byte(&mut self) -> Result<&u8> { - let byte = read_byte_at(self.data, self.position)?; + if self.position >= self.data.len() { + return Err(crate::error::TypedStreamError::OutOfBounds( + 1, + self.data.len(), + )); + } + let byte = &self.data[self.position]; self.position += 1; Ok(byte) } @@ -351,6 +363,7 @@ impl<'a> TypedStreamDeserializer<'a> { fn read_types(&mut self, types_index: usize) -> Result>>> { // Start reading types from the specified index in the type table + let len = self.type_table[types_index].len(); let mut out_v = Vec::with_capacity(len); @@ -429,21 +442,20 @@ impl<'a> TypedStreamDeserializer<'a> { let pointer = read_pointer(&ptr)?; let ref_tag = pointer.value as usize; - if ref_tag as usize >= self.type_table.len() { + // Optimize bounds checking + if ref_tag >= self.type_table.len() { return Ok(None); } if is_embedded_type { // We only want to include the first embedded reference tag, not subsequent references to the same embed - if !self.seen_embedded_types.contains(&ref_tag) - && self.type_table.get(ref_tag as usize).is_some() - { + if !self.seen_embedded_types.contains(&ref_tag) { self.object_table.push(Archived::Type(ref_tag)); self.seen_embedded_types.push(ref_tag); } } - Ok(Some(ref_tag as usize)) + Ok(Some(ref_tag)) } } } From fce1d07254e44922c608d83cea6694ef36c31ce5 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:49:34 -0700 Subject: [PATCH 7/9] Stack + depth limit --- src/deserializer/iter.rs | 142 +++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 34 deletions(-) diff --git a/src/deserializer/iter.rs b/src/deserializer/iter.rs index 558b64d..f51c4c0 100644 --- a/src/deserializer/iter.rs +++ b/src/deserializer/iter.rs @@ -80,6 +80,8 @@ impl<'a, 'b> PropertyIterator<'a, 'b> { impl<'a, 'b: 'a> PropertyIterator<'a, 'b> { /// Collects only primitive data values from a `typedstream` using a depth-first-search over the deserialized object graph. /// + /// Note: There is a max depth of 100 and a max item limit of 1,000,000. + /// /// # Examples /// /// ```no_run @@ -101,23 +103,53 @@ impl<'a, 'b: 'a> PropertyIterator<'a, 'b> { /// ``` #[must_use] pub fn primitives(self) -> Vec<&'b OutputData<'a>> { + self.primitives_with_limits(100, 1000000) + } + + /// Collects primitive data values with safety limits to prevent infinite loops. + /// + /// # Arguments + /// + /// * `max_depth` - Maximum depth to traverse (prevents infinite recursion on cycles) + /// * `max_items` - Maximum total items to process (prevents runaway expansion) + #[must_use] + pub fn primitives_with_limits( + self, + max_depth: usize, + max_items: usize, + ) -> Vec<&'b OutputData<'a>> { let mut primitives = Vec::new(); - // Use an explicit stack for depth-first traversal - let mut stack: Vec> = self.collect(); - while let Some(prop) = stack.pop() { + let mut processed_items = 0; + + // Use an explicit stack for depth-first traversal with depth tracking + let initial_props: Vec> = self.collect(); + let mut stack: Vec<(Property<'a, 'b>, usize)> = + initial_props.into_iter().map(|p| (p, 0)).collect(); + + while let Some((prop, depth)) = stack.pop() { + // Safety checks to prevent infinite expansion + if processed_items >= max_items { + break; + } + if depth >= max_depth { + continue; + } + + processed_items += 1; + match prop { Property::Primitive(p) => primitives.push(p), Property::Group(mut group) => { // push children in reverse to preserve order while let Some(child) = group.pop() { - stack.push(child); + stack.push((child, depth + 1)); } } Property::Object { data, .. } => { // data is a PropertyIterator; collect its items let mut nested: Vec<_> = data.collect(); while let Some(child) = nested.pop() { - stack.push(child); + stack.push((child, depth + 1)); } } } @@ -172,8 +204,10 @@ impl<'a, 'b: 'a> Iterator for PropertyIterator<'a, 'b> { /// Print a resolved [`PropertyIterator`] in a human-readable tree format for debugging. /// -/// This function recursively prints all properties with proper indentation to show the nested structure -/// of the deserialized object graph. +/// This function iteratively prints all properties with proper indentation to show the nested structure +/// of the deserialized object graph. Uses an explicit stack to avoid stack overflow for large structures. +/// +/// Note: There is a max depth of 100 and a max item limit of 1,000,000. /// /// # Arguments /// @@ -217,40 +251,80 @@ impl<'a, 'b: 'a> Iterator for PropertyIterator<'a, 'b> { /// Primitive: SignedInteger(0) /// ``` pub fn print_resolved(iter: PropertyIterator<'_, '_>, indent: usize) { - for prop in iter { - print_property(prop, indent); - } + print_resolved_with_limits(iter, indent, 100, 1000000); } -/// Print a single `Property` with indentation, recursing for nested data. +/// Print a resolved [`PropertyIterator`] with depth and item limits to prevent infinite expansion. /// /// # Arguments /// -/// * `prop` - The property to print. -/// * `indent` - Number of spaces to indent each level. -/// -/// This function is intended for debugging purposes. -pub(crate) fn print_property<'a, 'b: 'a>(prop: Property<'a, 'b>, indent: usize) { - match prop { - Property::Object { - class: _, - name, - data, - } => { - // Print the object itself - println!("{:indent$}Object: {:?}", "", name, indent = indent); - // Recurse into its children with increased indent - print_resolved(data, indent + 2); +/// * `iter` - The property iterator to print +/// * `indent` - Number of spaces to indent each level +/// * `max_depth` - Maximum depth to traverse (prevents infinite recursion on cycles) +/// * `max_items` - Maximum total items to print (prevents runaway output) +fn print_resolved_with_limits( + iter: PropertyIterator<'_, '_>, + indent: usize, + max_depth: usize, + max_items: usize, +) { + // Use an explicit stack to avoid recursion and potential stack overflow + let mut stack: Vec<(Property<'_, '_>, usize)> = Vec::new(); + let mut items_printed = 0; + + // Push all properties from the iterator onto the stack with their indent level + for prop in iter { + stack.push((prop, indent)); + } + + // Process the stack + while let Some((prop, current_indent)) = stack.pop() { + // Safety checks to prevent infinite expansion + if items_printed >= max_items { + println!( + "{:indent$}... (truncated after {max_items} items)", + "", + indent = current_indent + ); + break; } - Property::Group(slice) => { - println!("{:indent$}Group:", "", indent = indent); - // Drill into every slot in the group - for slot in slice { - print_property(slot, indent + 2); - } + + let depth = (current_indent - indent) / 2; + if depth >= max_depth { + println!( + "{:indent$}... (max depth {max_depth} reached)", + "", + indent = current_indent + ); + continue; } - Property::Primitive(p) => { - println!("{:indent$}Primitive: {:?}", "", p, indent = indent); + + items_printed += 1; + + match prop { + Property::Object { + class: _, + name, + data, + } => { + // Print the object itself + println!("{:indent$}Object: {:?}", "", name, indent = current_indent); + // Push its children onto the stack with increased indent (in reverse order) + let children: Vec<_> = data.collect(); + for child in children.into_iter().rev() { + stack.push((child, current_indent + 2)); + } + } + Property::Group(group) => { + println!("{:indent$}Group:", "", indent = current_indent); + // Push every slot in the group onto the stack with increased indent (in reverse order) + for slot in group.into_iter().rev() { + stack.push((slot, current_indent + 2)); + } + } + Property::Primitive(p) => { + println!("{:indent$}Primitive: {:?}", "", p, indent = current_indent); + } } } } From a0e8b2249b6f3c60c17f24e61acfce9dd051386a Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 13:49:41 -0700 Subject: [PATCH 8/9] Update test --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c1c1ba2..b98bb51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6886,6 +6886,8 @@ mod test_typedstream_deserializer { let root_obj = typedstream.resolve_properties(root).unwrap(); println!("\nResults: {root_obj:?}"); - print_resolved(root_obj, 4); + // This will only complete if the circular references are handled + let primitives = root_obj.primitives(); + println!("\nPrimitive Values: {primitives:?}"); } } From 9cf0316712b72deea2433966ab1487428f323aaf Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Thu, 28 Aug 2025 16:09:17 -0700 Subject: [PATCH 9/9] Simplify logic --- src/deserializer/typedstream.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index b73daa1..d29d974 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -159,13 +159,7 @@ impl<'a> TypedStreamDeserializer<'a> { /// Reads the next byte from the stream, advancing the position. #[inline(always)] fn consume_current_byte(&mut self) -> Result<&u8> { - if self.position >= self.data.len() { - return Err(crate::error::TypedStreamError::OutOfBounds( - 1, - self.data.len(), - )); - } - let byte = &self.data[self.position]; + let byte = read_byte_at(self.data, self.position)?; self.position += 1; Ok(byte) }