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}