From f448f751f855a6ba76fc0165b6d2225034a51ed1 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 07:40:52 -0700 Subject: [PATCH 1/7] Inline some hot functions --- src/deserializer/read.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/deserializer/read.rs b/src/deserializer/read.rs index 441e8d3..8328c96 100644 --- a/src/deserializer/read.rs +++ b/src/deserializer/read.rs @@ -22,6 +22,7 @@ use crate::{ /// /// assert_eq!(slice, &[0x01, 0x02]); /// ``` +#[inline(always)] pub fn read_exact_bytes(data: &[u8], n: usize) -> Result<&[u8]> { let range = data .get(0..n) @@ -45,6 +46,7 @@ pub fn read_exact_bytes(data: &[u8], n: usize) -> Result<&[u8]> { /// /// assert_eq!(*byte, 0xFF); /// ``` +#[inline(always)] pub fn read_byte_at(data: &[u8], idx: usize) -> Result<&u8> { data.get(idx) .ok_or(TypedStreamError::OutOfBounds(idx, data.len())) @@ -67,6 +69,7 @@ pub fn read_byte_at(data: &[u8], idx: usize) -> Result<&u8> { /// assert_eq!(consumed.value, 2); /// assert_eq!(consumed.bytes_consumed, 1); /// ``` +#[inline(always)] pub fn read_pointer(pointer: &u8) -> Result> { let result = u64::from(*pointer) .checked_sub(REFERENCE_TAG) From 97d6c1dc70cdbde0868ac2d2b492809dfcffe35d Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 07:42:55 -0700 Subject: [PATCH 2/7] Remove unnecessary alloc in --- src/deserializer/typedstream.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index 185640c..3afe4aa 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -153,6 +153,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> { let byte = read_byte_at(self.data, self.position)?; self.position += 1; @@ -316,8 +317,8 @@ impl<'a> TypedStreamDeserializer<'a> { } /// Reads numeric types (signed, unsigned, float, double) and returns the corresponding `OutputData` - fn read_number(&mut self, ty: &Type<'a>) -> Result> { - match ty { + fn read_number(&mut self, table_index: usize, type_index: usize) -> Result> { + match self.type_table[table_index][type_index] { Type::SignedInt => { let signed_int = read_signed_int(&self.data[self.position..])?; self.position += signed_int.bytes_consumed; @@ -343,12 +344,12 @@ impl<'a> TypedStreamDeserializer<'a> { } fn read_types(&mut self, types_index: usize) -> Result>>> { - // Clone types to avoid holding an immutable borrow on self during parsing - let types = self.type_table[types_index].clone(); - let mut out_v = Vec::with_capacity(types.len()); - - for ty in types { - match ty { + // 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); + for i in 0..len { + // Read the next type from the type table + match self.type_table[types_index][i] { Type::Utf8String => { let str_data = read_string(&self.data[self.position..])?; self.position += str_data.bytes_consumed; @@ -378,9 +379,9 @@ impl<'a> TypedStreamDeserializer<'a> { // Read a single byte for unknown data out_v.push(OutputData::Byte(byte)); } - // numeric types + // Handle all numeric types Type::SignedInt | Type::UnsignedInt | Type::Float | Type::Double => { - let val = self.read_number(&ty)?; + let val = self.read_number(types_index, i)?; out_v.push(val); } } From 7153510dbfb200ff70ead50d56a413ac099bd80a Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 07:59:31 -0700 Subject: [PATCH 3/7] Swap `seen_embedded_types` to `Vec` --- src/deserializer/typedstream.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index 3afe4aa..670afe5 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -4,8 +4,6 @@ A writeup about the reverse engineering of `typedstream` can be found [here](https://chrissardegna.com/blog/reverse-engineering-apples-typedstream-format/). */ -use std::collections::HashSet; - use crate::{ deserializer::{ constants::{EMPTY, END, START}, @@ -40,7 +38,7 @@ pub struct TypedStreamDeserializer<'a> { /// As we parse the `typedstream`, build a table of seen [`Archived`] data to reference in the future pub object_table: Vec>, /// We want to copy embedded types the first time they are seen, even if the types were resolved through references - pub(crate) seen_embedded_types: HashSet, + pub(crate) seen_embedded_types: Vec, } impl<'a> TypedStreamDeserializer<'a> { @@ -61,7 +59,7 @@ impl<'a> TypedStreamDeserializer<'a> { position: 0, type_table: Vec::with_capacity(16), object_table: Vec::with_capacity(32), - seen_embedded_types: HashSet::with_capacity(8), + seen_embedded_types: Vec::with_capacity(8), } } @@ -406,7 +404,7 @@ impl<'a> TypedStreamDeserializer<'a> { self.object_table.push(Archived::Type(new_type_index)); // We only want to include the first embedded reference tag, not subsequent references to the same embed self.seen_embedded_types - .insert(self.object_table.len().saturating_sub(1)); + .push(self.object_table.len().saturating_sub(1)); } self.type_table.push(new_types.value); @@ -429,7 +427,7 @@ impl<'a> TypedStreamDeserializer<'a> { && self.type_table.get(ref_tag as usize).is_some() { self.object_table.push(Archived::Type(ref_tag)); - self.seen_embedded_types.insert(ref_tag); + self.seen_embedded_types.push(ref_tag); } } From fff15c1b82e181d07ff6131f55a08e5a94f29b18 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 08:00:55 -0700 Subject: [PATCH 4/7] Add comment, inline func --- src/deserializer/typedstream.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index 670afe5..6d68cd7 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -158,6 +158,8 @@ impl<'a> TypedStreamDeserializer<'a> { Ok(byte) } + /// Reads a signed integer from the stream, advancing the position. + #[inline(always)] fn read_unsigned_int(&mut self) -> Result { let unsigned_int = read_unsigned_int(&self.data[self.position..])?; self.position += unsigned_int.bytes_consumed; From 97e144ffa2831a6f392a604813eff20ebeb93ab4 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 08:08:54 -0700 Subject: [PATCH 5/7] Fix comments --- src/deserializer/typedstream.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index 6d68cd7..7b7eed1 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -212,11 +212,11 @@ impl<'a> TypedStreamDeserializer<'a> { } fn read_class(&mut self) -> Result> { - // index of the first START we encounter (the bottom-most child) + // Index of the first START we encounter (the bottom-most child) let mut first_new: Option = None; - // index of the most recently pushed class (current “child”) + // Index of the most recently pushed class (current “child”) let mut prev_new: Option = None; - // parent for the outer-most new class (set by EMPTY or a pointer) + // Parent for the outer-most new class (set by EMPTY or a pointer) let final_parent: Option; loop { @@ -347,6 +347,7 @@ impl<'a> TypedStreamDeserializer<'a> { // 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); + for i in 0..len { // Read the next type from the type table match self.type_table[types_index][i] { @@ -413,8 +414,7 @@ impl<'a> TypedStreamDeserializer<'a> { self.position += new_types.bytes_consumed; Ok(Some(self.type_table.len() - 1)) } - EMPTY => Ok(None), - END => Ok(None), + END | EMPTY => Ok(None), ptr => { let pointer = read_pointer(&ptr)?; let ref_tag = pointer.value as usize; From 226d4189273ce38507c5251a28d03444cdc64b1e Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 08:33:48 -0700 Subject: [PATCH 6/7] fix comment --- src/deserializer/typedstream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index 7b7eed1..f634d46 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -402,7 +402,7 @@ impl<'a> TypedStreamDeserializer<'a> { // Get the type of the object let new_types = Type::read_new_type(&self.data[self.position..])?; let new_type_index = self.type_table.len(); - // Embedded data is stored as a String in the objects table + // Embedded data is stored as a Type in the objects table if is_embedded_type { self.object_table.push(Archived::Type(new_type_index)); // We only want to include the first embedded reference tag, not subsequent references to the same embed From f29267b14f617a8cb38f1ff6ab7cb9c5fd670883 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Wed, 25 Jun 2025 08:34:29 -0700 Subject: [PATCH 7/7] fix comment --- src/deserializer/typedstream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deserializer/typedstream.rs b/src/deserializer/typedstream.rs index f634d46..cc55ba7 100644 --- a/src/deserializer/typedstream.rs +++ b/src/deserializer/typedstream.rs @@ -158,7 +158,7 @@ impl<'a> TypedStreamDeserializer<'a> { Ok(byte) } - /// Reads a signed integer from the stream, advancing the position. + /// Reads an unsigned integer from the stream, advancing the position. #[inline(always)] fn read_unsigned_int(&mut self) -> Result { let unsigned_int = read_unsigned_int(&self.data[self.position..])?;