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#[derive(Debug, Clone, PartialEq)]
180pub enum Type {
181    /// Encoded string data, usually embedded in an object. Denoted by:
182    ///
183    /// | Hex    | UTF-8 |
184    /// |--------|-------|
185    /// | `0x28` | [`+`](https://www.compart.com/en/unicode/U+002B) |
186    Utf8String,
187    /// Encoded bytes that can be parsed again as data. Denoted by:
188    ///
189    /// | Hex    | UTF-8 |
190    /// |--------|-------|
191    /// | `0x2A` | [`*`](https://www.compart.com/en/unicode/U+002A) |
192    EmbeddedData,
193    /// An instance of a class, usually with data. Denoted by:
194    ///
195    /// | Hex    | UTF-8 |
196    /// |--------|-------|
197    /// | `0x40` | [`@`](https://www.compart.com/en/unicode/U+0040) |
198    Object,
199    /// An [`i8`], [`i16`], or [`i32`]. Denoted by:
200    /// | Hex    | UTF-8 |
201    /// |--------|-------|
202    /// | `0x63` | [`c`](https://www.compart.com/en/unicode/U+0063) |
203    /// | `0x69` | [`i`](https://www.compart.com/en/unicode/U+0069) |
204    /// | `0x6c` | [`l`](https://www.compart.com/en/unicode/U+006c) |
205    /// | `0x71` | [`q`](https://www.compart.com/en/unicode/U+0071) |
206    /// | `0x73` | [`s`](https://www.compart.com/en/unicode/U+0073) |
207    ///
208    /// The width is determined by the prefix: [`i8`] has none, [`i16`] has `0x81`, and [`i32`] has `0x82`.
209    SignedInt,
210    /// A [`u8`], [`u16`], or [`u32`]. Denoted by:
211    /// | Hex    | UTF-8 |
212    /// |--------|-------|
213    /// | `0x43` | [`C`](https://www.compart.com/en/unicode/U+0043) |
214    /// | `0x49` | [`I`](https://www.compart.com/en/unicode/U+0049) |
215    /// | `0x4c` | [`L`](https://www.compart.com/en/unicode/U+004c) |
216    /// | `0x51` | [`Q`](https://www.compart.com/en/unicode/U+0051) |
217    /// | `0x53` | [`S`](https://www.compart.com/en/unicode/U+0053) |
218    ///
219    /// The width is determined by the prefix: [`u8`] has none, [`u16`] has `0x81`, and [`u32`] has `0x82`.
220    UnsignedInt,
221    /// An [`f32`]. Denoted by:
222    ///
223    /// | Hex    | UTF-8 |
224    /// |--------|-------|
225    /// | `0x66` | [`f`](https://www.compart.com/en/unicode/U+0066) |
226    Float,
227    /// An [`f64`]. Denoted by:
228    ///
229    /// | Hex    | UTF-8 |
230    /// |--------|-------|
231    /// | `0x64` | [`d`](https://www.compart.com/en/unicode/U+0064) |
232    Double,
233    /// Some text we can reuse later, i.e. a class name.
234    String(String),
235    /// An array containing some data of a given length. Denoted by braced digits: `[123]`.
236    Array(usize),
237    /// Data for which we do not know the type, likely for something this parser does not implement.
238    Unknown(u8),
239}
240
241impl Type {
242    /// Convert a byte to a Type enum variant
243    #[inline]
244    pub(crate) fn from_byte(byte: &u8) -> Self {
245        match *byte {
246            0x40 => Self::Object,
247            0x2B => Self::Utf8String,
248            0x2A => Self::EmbeddedData,
249            0x66 => Self::Float,
250            0x64 => Self::Double,
251            0x63 | 0x69 | 0x6c | 0x71 | 0x73 => Self::SignedInt,
252            0x43 | 0x49 | 0x4c | 0x51 | 0x53 => Self::UnsignedInt,
253            other => Self::Unknown(other),
254        }
255    }
256
257    #[inline]
258    pub(crate) fn new_string(string: String) -> Self {
259        Self::String(string)
260    }
261
262    pub(crate) fn get_array_length(types: &[u8]) -> Option<Vec<Type>> {
263        if types.first() == Some(&0x5b) {
264            let len =
265                types[1..]
266                    .iter()
267                    .take_while(|a| a.is_ascii_digit())
268                    .fold(None, |acc, ch| {
269                        char::from_u32(u32::from(*ch))?
270                            .to_digit(10)
271                            .map(|b| acc.unwrap_or(0) * 10 + b)
272                    })?;
273            return Some(vec![Type::Array(len as usize)]);
274        }
275        None
276    }
277}
278
279/// Represents data that results from attempting to parse a class from the `typedstream`
280#[derive(Debug)]
281pub(crate) enum ClassResult {
282    /// A reference to an already-seen class in the [`TypedStreamReader::object_table`](crate::util::typedstream::parser::TypedStreamReader::object_table)
283    Index(usize),
284    /// A new class heirarchy to be inserted into the [`TypedStreamReader::object_table`](crate::util::typedstream::parser::TypedStreamReader::object_table)
285    ClassHierarchy(Vec<Archivable>),
286}