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    #[must_use]
88    pub fn as_nsstring(&self) -> Option<&str> {
89        if let Archivable::Object(Class { name, .. }, value) = self {
90            if name == "NSString" || name == "NSMutableString" {
91                if let Some(OutputData::String(text)) = value.first() {
92                    return Some(text);
93                }
94            }
95        }
96        None
97    }
98
99    /// If `self` is an [`Object`](Archivable::Object) that contains a [`Class`] named `NSNumber` pointing to a [`SignedInteger`](OutputData::SignedInteger),
100    /// extract an [`i64`] from the [`Data`](Archivable::Data).
101    ///
102    /// # Example
103    ///
104    /// ```
105    /// use imessage_database::util::typedstream::models::{Archivable, Class, OutputData};
106    ///
107    /// let nsnumber = Archivable::Object(
108    ///     Class {
109    ///         name: "NSNumber".to_string(),
110    ///         version: 1
111    ///     },
112    ///     vec![OutputData::SignedInteger(100)]
113    /// );
114    /// println!("{:?}", nsnumber.as_nsnumber_int()); // Some(100)
115    ///
116    /// let not_nsnumber = Archivable::Object(
117    ///     Class {
118    ///         name: "NSString".to_string(),
119    ///         version: 1
120    ///     },
121    ///     vec![OutputData::String("Hello world".to_string())]
122    /// );
123    /// println!("{:?}", not_nsnumber.as_nsnumber_int()); // None
124    /// ```
125    #[must_use]
126    pub fn as_nsnumber_int(&self) -> Option<&i64> {
127        if let Archivable::Object(Class { name, .. }, value) = self {
128            if name == "NSNumber" {
129                if let Some(OutputData::SignedInteger(num)) = value.first() {
130                    return Some(num);
131                }
132            }
133        }
134        None
135    }
136
137    /// If `self` is an [`Object`](Archivable::Object) that contains a [`Class`] named `NSNumber` pointing to a [`Double`](OutputData::Double),
138    /// extract an [`f64`] from the [`Data`](Archivable::Data).
139    ///
140    /// # Example
141    ///
142    /// ```
143    /// use imessage_database::util::typedstream::models::{Archivable, Class, OutputData};
144    ///
145    /// let nsnumber = Archivable::Object(
146    ///     Class {
147    ///         name: "NSNumber".to_string(),
148    ///         version: 1
149    ///     },
150    ///     vec![OutputData::Double(100.001)]
151    /// );
152    /// println!("{:?}", nsnumber.as_nsnumber_float()); // Some(100.001)
153    ///
154    /// let not_nsnumber = Archivable::Object(
155    ///     Class {
156    ///         name: "NSString".to_string(),
157    ///         version: 1
158    ///     },
159    ///     vec![OutputData::String("Hello world".to_string())]
160    /// );
161    /// println!("{:?}", not_nsnumber.as_nsnumber_float()); // None
162    /// ```
163    #[must_use]
164    pub fn as_nsnumber_float(&self) -> Option<&f64> {
165        if let Archivable::Object(Class { name, .. }, value) = self {
166            if name == "NSNumber" {
167                if let Some(OutputData::Double(num)) = value.first() {
168                    return Some(num);
169                }
170            }
171        }
172        None
173    }
174}
175
176/// Represents primitive types of data that can be stored in a `typedstream`
177///
178/// 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.
179// TODO: Remove clone
180#[derive(Debug, Clone, PartialEq)]
181pub enum Type {
182    /// Encoded string data, usually embedded in an object. Denoted by:
183    ///
184    /// | Hex    | UTF-8 |
185    /// |--------|-------|
186    /// | `0x28` | [`+`](https://www.compart.com/en/unicode/U+002B) |
187    Utf8String,
188    /// Encoded bytes that can be parsed again as data. Denoted by:
189    ///
190    /// | Hex    | UTF-8 |
191    /// |--------|-------|
192    /// | `0x2A` | [`*`](https://www.compart.com/en/unicode/U+002A) |
193    EmbeddedData,
194    /// An instance of a class, usually with data. Denoted by:
195    ///
196    /// | Hex    | UTF-8 |
197    /// |--------|-------|
198    /// | `0x40` | [`@`](https://www.compart.com/en/unicode/U+0040) |
199    Object,
200    /// An [`i8`], [`i16`], or [`i32`]. Denoted by:
201    /// | Hex    | UTF-8 |
202    /// |--------|-------|
203    /// | `0x63` | [`c`](https://www.compart.com/en/unicode/U+0063) |
204    /// | `0x69` | [`i`](https://www.compart.com/en/unicode/U+0069) |
205    /// | `0x6c` | [`l`](https://www.compart.com/en/unicode/U+006c) |
206    /// | `0x71` | [`q`](https://www.compart.com/en/unicode/U+0071) |
207    /// | `0x73` | [`s`](https://www.compart.com/en/unicode/U+0073) |
208    ///
209    /// The width is determined by the prefix: [`i8`] has none, [`i16`] has `0x81`, and [`i32`] has `0x82`.
210    SignedInt,
211    /// A [`u8`], [`u16`], or [`u32`]. Denoted by:
212    /// | Hex    | UTF-8 |
213    /// |--------|-------|
214    /// | `0x43` | [`C`](https://www.compart.com/en/unicode/U+0043) |
215    /// | `0x49` | [`I`](https://www.compart.com/en/unicode/U+0049) |
216    /// | `0x4c` | [`L`](https://www.compart.com/en/unicode/U+004c) |
217    /// | `0x51` | [`Q`](https://www.compart.com/en/unicode/U+0051) |
218    /// | `0x53` | [`S`](https://www.compart.com/en/unicode/U+0053) |
219    ///
220    /// The width is determined by the prefix: [`u8`] has none, [`u16`] has `0x81`, and [`u32`] has `0x82`.
221    UnsignedInt,
222    /// An [`f32`]. Denoted by:
223    ///
224    /// | Hex    | UTF-8 |
225    /// |--------|-------|
226    /// | `0x66` | [`f`](https://www.compart.com/en/unicode/U+0066) |
227    Float,
228    /// An [`f64`]. Denoted by:
229    ///
230    /// | Hex    | UTF-8 |
231    /// |--------|-------|
232    /// | `0x64` | [`d`](https://www.compart.com/en/unicode/U+0064) |
233    Double,
234    /// Some text we can reuse later, i.e. a class name.
235    String(String),
236    /// An array containing some data of a given length. Denoted by braced digits: `[123]`.
237    Array(usize),
238    /// Data for which we do not know the type, likely for something this parser does not implement.
239    Unknown(u8),
240}
241
242impl Type {
243    pub(crate) fn from_byte(byte: &u8) -> Self {
244        match byte {
245            0x40 => Self::Object,
246            0x2B => Self::Utf8String,
247            0x2A => Self::EmbeddedData,
248            0x66 => Self::Float,
249            0x64 => Self::Double,
250            0x63 | 0x69 | 0x6c | 0x71 | 0x73 => Self::SignedInt,
251            0x43 | 0x49 | 0x4c | 0x51 | 0x53 => Self::UnsignedInt,
252            other => Self::Unknown(*other),
253        }
254    }
255
256    pub(crate) fn new_string(string: String) -> Self {
257        Self::String(string)
258    }
259
260    pub(crate) fn get_array_length(types: &[u8]) -> Option<Vec<Type>> {
261        if types.first() == Some(&0x5b) {
262            let len =
263                types[1..]
264                    .iter()
265                    .take_while(|a| a.is_ascii_digit())
266                    .fold(None, |acc, ch| {
267                        char::from_u32(u32::from(*ch))?
268                            .to_digit(10)
269                            .map(|b| acc.unwrap_or(0) * 10 + b)
270                    })?;
271            return Some(vec![Type::Array(len as usize)]);
272        }
273        None
274    }
275}
276
277/// Represents data that results from attempting to parse a class from the `typedstream`
278#[derive(Debug)]
279pub(crate) enum ClassResult {
280    /// A reference to an already-seen class in the [`TypedStreamReader::object_table`](crate::util::typedstream::parser::TypedStreamReader::object_table)
281    Index(usize),
282    /// A new class heirarchy to be inserted into the [`TypedStreamReader::object_table`](crate::util::typedstream::parser::TypedStreamReader::object_table)
283    ClassHierarchy(Vec<Archivable>),
284}