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}