imessage_database/util/typedstream/
models.rs

1/*!
2 Data structures and models used by the `typedstream` parser.
3*/
4
5/// Represents a class stored in the `typedstream`
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Class {
8    /// The name of the class
9    pub name: String,
10    /// The encoded version of the class
11    pub version: u64,
12}
13
14impl Class {
15    pub(crate) fn new(name: String, version: u64) -> Self {
16        Self { name, version }
17    }
18}
19
20/// Rust structures containing data stored in the `typedstream`
21#[derive(Debug, Clone, PartialEq)]
22pub enum OutputData {
23    /// Text data, denoted in the stream by [`Type::String`]
24    String(String),
25    /// Signed integer types are coerced into this container, denoted in the stream by [`Type::SignedInt`]
26    SignedInteger(i64),
27    /// Unsigned integer types are coerced into this container, denoted in the stream by [`Type::UnsignedInt`]
28    UnsignedInteger(u64),
29    /// Floating point numbers, denoted in the stream by [`Type::Float`]
30    Float(f32),
31    /// Double precision floats, denoted in the stream by [`Type::Double`]
32    Double(f64),
33    /// Bytes whose type is not known, denoted in the stream by [`Type::Unknown`]
34    Byte(u8),
35    /// Arbitrary collection of bytes in an array, denoted in the stream by [`Type::Array`]
36    Array(Vec<u8>),
37    /// A found class, in order of inheritance, used by [`Archivable::Class`]
38    Class(Class),
39}
40
41/// Types of data that can be archived into the `typedstream`
42#[derive(Debug, Clone, PartialEq)]
43pub enum Archivable {
44    /// An instance of a class that may contain some embedded data. `typedstream` data doesn't include property
45    /// names, so data is stored in order of appearance.
46    Object(Class, Vec<OutputData>),
47    /// Some data that is likely a property on the object described by the `typedstream` but not part of a class.
48    Data(Vec<OutputData>),
49    /// A class referenced in the `typedstream`, usually part of an inheritance heirarchy that does not contain any data itself.
50    Class(Class),
51    /// A placeholder, only used when reserving a spot in the objects table for a reference to be filled with read class information.
52    /// In a `typedstream`, the classes are stored in order of inheritance, so the top-level class described by the `typedstream`
53    /// comes before the ones it inherits from. To preserve the order, we reserve the first slot to store the actual object's data
54    /// and then later add it back to the right place.
55    Placeholder,
56    /// A type that made it through the parsing process without getting replaced by an object.
57    Type(Vec<Type>),
58}
59
60impl Archivable {
61    /// If `self` is an [`Object`](Archivable::Object) that contains a [`Class`] named `NSString` or `NSMutableString`,
62    /// extract a [`&str`](str) from the associated [`Data`](Archivable::Data).
63    ///
64    /// # Example
65    ///
66    /// ```
67    /// use imessage_database::util::typedstream::models::{Archivable, Class, OutputData};
68    ///
69    /// let nsstring = Archivable::Object(
70    ///     Class {
71    ///         name: "NSString".to_string(),
72    ///         version: 1
73    ///     },
74    ///     vec![OutputData::String("Hello world".to_string())]
75    /// );
76    /// println!("{:?}", nsstring.as_nsstring()); // Some("Hello world")
77    ///
78    /// let not_nsstring = Archivable::Object(
79    ///     Class {
80    ///         name: "NSNumber".to_string(),
81    ///         version: 1
82    ///     },
83    ///     vec![OutputData::SignedInteger(100)]
84    /// );
85    /// println!("{:?}", not_nsstring.as_nsstring()); // None
86    /// ```
87    pub fn as_nsstring(&self) -> Option<&str> {
88        if let Archivable::Object(Class { name, .. }, value) = self {
89            if name == "NSString" || name == "NSMutableString" {
90                if let Some(OutputData::String(text)) = value.first() {
91                    return Some(text);
92                }
93            }
94        }
95        None
96    }
97
98    /// If `self` is an [`Object`](Archivable::Object) that contains a [`Class`] named `NSNumber` pointing to a [`SignedInteger`](OutputData::SignedInteger),
99    /// extract an [`i64`] from the [`Data`](Archivable::Data).
100    ///
101    /// # Example
102    ///
103    /// ```
104    /// use imessage_database::util::typedstream::models::{Archivable, Class, OutputData};
105    ///
106    /// let nsnumber = Archivable::Object(
107    ///     Class {
108    ///         name: "NSNumber".to_string(),
109    ///         version: 1
110    ///     },
111    ///     vec![OutputData::SignedInteger(100)]
112    /// );
113    /// println!("{:?}", nsnumber.as_nsnumber_int()); // Some(100)
114    ///
115    /// let not_nsnumber = Archivable::Object(
116    ///     Class {
117    ///         name: "NSString".to_string(),
118    ///         version: 1
119    ///     },
120    ///     vec![OutputData::String("Hello world".to_string())]
121    /// );
122    /// println!("{:?}", not_nsnumber.as_nsnumber_int()); // None
123    /// ```
124    pub fn as_nsnumber_int(&self) -> Option<&i64> {
125        if let Archivable::Object(Class { name, .. }, value) = self {
126            if name == "NSNumber" {
127                if let Some(OutputData::SignedInteger(num)) = value.first() {
128                    return Some(num);
129                }
130            }
131        }
132        None
133    }
134
135    /// If `self` is an [`Object`](Archivable::Object) that contains a [`Class`] named `NSNumber` pointing to a [`Double`](OutputData::Double),
136    /// extract an [`f64`] from the [`Data`](Archivable::Data).
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// use imessage_database::util::typedstream::models::{Archivable, Class, OutputData};
142    ///
143    /// let nsnumber = Archivable::Object(
144    ///     Class {
145    ///         name: "NSNumber".to_string(),
146    ///         version: 1
147    ///     },
148    ///     vec![OutputData::Double(100.001)]
149    /// );
150    /// println!("{:?}", nsnumber.as_nsnumber_float()); // Some(100.001)
151    ///
152    /// let not_nsnumber = Archivable::Object(
153    ///     Class {
154    ///         name: "NSString".to_string(),
155    ///         version: 1
156    ///     },
157    ///     vec![OutputData::String("Hello world".to_string())]
158    /// );
159    /// println!("{:?}", not_nsnumber.as_nsnumber_float()); // None
160    /// ```
161    pub fn as_nsnumber_float(&self) -> Option<&f64> {
162        if let Archivable::Object(Class { name, .. }, value) = self {
163            if name == "NSNumber" {
164                if let Some(OutputData::Double(num)) = value.first() {
165                    return Some(num);
166                }
167            }
168        }
169        None
170    }
171}
172
173/// Represents primitive types of data that can be stored in a `typedstream`
174///
175/// These type encodings are partially documented [here](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1) by Apple.
176// TODO: Remove clone
177#[derive(Debug, Clone, PartialEq)]
178pub enum Type {
179    /// Encoded string data, usually embedded in an object. Denoted by:
180    ///
181    /// | Hex    | UTF-8 |
182    /// |--------|-------|
183    /// | `0x28` | [`+`](https://www.compart.com/en/unicode/U+002B) |
184    Utf8String,
185    /// Encoded bytes that can be parsed again as data. Denoted by:
186    ///
187    /// | Hex    | UTF-8 |
188    /// |--------|-------|
189    /// | `0x2A` | [`*`](https://www.compart.com/en/unicode/U+002A) |
190    EmbeddedData,
191    /// An instance of a class, usually with data. Denoted by:
192    ///
193    /// | Hex    | UTF-8 |
194    /// |--------|-------|
195    /// | `0x40` | [`@`](https://www.compart.com/en/unicode/U+0040) |
196    Object,
197    /// An [`i8`], [`i16`], or [`i32`]. Denoted by:
198    /// | Hex    | UTF-8 |
199    /// |--------|-------|
200    /// | `0x63` | [`c`](https://www.compart.com/en/unicode/U+0063) |
201    /// | `0x69` | [`i`](https://www.compart.com/en/unicode/U+0069) |
202    /// | `0x6c` | [`l`](https://www.compart.com/en/unicode/U+006c) |
203    /// | `0x71` | [`q`](https://www.compart.com/en/unicode/U+0071) |
204    /// | `0x73` | [`s`](https://www.compart.com/en/unicode/U+0073) |
205    ///
206    /// The width is determined by the prefix: [`i8`] has none, [`i16`] has `0x81`, and [`i32`] has `0x82`.
207    SignedInt,
208    /// A [`u8`], [`u16`], or [`u32`]. Denoted by:
209    /// | Hex    | UTF-8 |
210    /// |--------|-------|
211    /// | `0x43` | [`C`](https://www.compart.com/en/unicode/U+0043) |
212    /// | `0x49` | [`I`](https://www.compart.com/en/unicode/U+0049) |
213    /// | `0x4c` | [`L`](https://www.compart.com/en/unicode/U+004c) |
214    /// | `0x51` | [`Q`](https://www.compart.com/en/unicode/U+0051) |
215    /// | `0x53` | [`S`](https://www.compart.com/en/unicode/U+0053) |
216    ///
217    /// The width is determined by the prefix: [`u8`] has none, [`u16`] has `0x81`, and [`u32`] has `0x82`.
218    UnsignedInt,
219    /// An [`f32`]. Denoted by:
220    ///
221    /// | Hex    | UTF-8 |
222    /// |--------|-------|
223    /// | `0x66` | [`f`](https://www.compart.com/en/unicode/U+0066) |
224    Float,
225    /// An [`f64`]. Denoted by:
226    ///
227    /// | Hex    | UTF-8 |
228    /// |--------|-------|
229    /// | `0x64` | [`d`](https://www.compart.com/en/unicode/U+0064) |
230    Double,
231    /// Some text we can reuse later, i.e. a class name.
232    String(String),
233    /// An array containing some data of a given length. Denoted by braced digits: `[123]`.
234    Array(usize),
235    /// Data for which we do not know the type, likely for something this parser does not implement.
236    Unknown(u8),
237}
238
239impl Type {
240    pub(crate) fn from_byte(byte: &u8) -> Self {
241        match byte {
242            0x40 => Self::Object,
243            0x2B => Self::Utf8String,
244            0x2A => Self::EmbeddedData,
245            0x66 => Self::Float,
246            0x64 => Self::Double,
247            0x63 | 0x69 | 0x6c | 0x71 | 0x73 => Self::SignedInt,
248            0x43 | 0x49 | 0x4c | 0x51 | 0x53 => Self::UnsignedInt,
249            other => Self::Unknown(*other),
250        }
251    }
252
253    pub(crate) fn new_string(string: String) -> Self {
254        Self::String(string)
255    }
256
257    pub(crate) fn get_array_length(types: &[u8]) -> Option<Vec<Type>> {
258        if types.first() == Some(&0x5b) {
259            let len =
260                types[1..]
261                    .iter()
262                    .take_while(|a| a.is_ascii_digit())
263                    .fold(None, |acc, ch| {
264                        char::from_u32(*ch as u32)?
265                            .to_digit(10)
266                            .map(|b| acc.unwrap_or(0) * 10 + b)
267                    })?;
268            return Some(vec![Type::Array(len as usize)]);
269        }
270        None
271    }
272}
273
274/// Represents data that results from attempting to parse a class from the `typedstream`
275#[derive(Debug)]
276pub(crate) enum ClassResult {
277    /// A reference to an already-seen class in the [`TypedStreamReader::object_table`](crate::util::typedstream::parser::TypedStreamReader::object_table)
278    Index(usize),
279    /// A new class heirarchy to be inserted into the [`TypedStreamReader::object_table`](crate::util::typedstream::parser::TypedStreamReader::object_table)
280    ClassHierarchy(Vec<Archivable>),
281}