Skip to main content

hermes_tdata/
qdatastream.rs

1//! QDataStream parser implementation
2//!
3//! Implements reading Qt's QDataStream binary format (version Qt_5_1 = 14).
4//! All integers are Big Endian. Strings are UTF-16 BE.
5
6use byteorder::{BigEndian, ReadBytesExt};
7use std::io::{Cursor, Read};
8
9use crate::{Error, Result};
10
11/// Qt DataStream version used by Telegram Desktop
12pub const QT_VERSION_5_1: u32 = 14;
13
14/// Marker for null QByteArray/QString
15const NULL_MARKER: u32 = 0xFFFFFFFF;
16
17/// Marker for extended 64-bit length (Qt 6.7+, not used in tdata)
18const EXTENDED_LENGTH_MARKER: u32 = 0xFFFFFFFE;
19
20/// QDataStream reader for parsing Qt binary serialization format
21pub struct QDataStream<'a> {
22    cursor: Cursor<&'a [u8]>,
23    version: u32,
24}
25
26impl<'a> QDataStream<'a> {
27    /// Create a new QDataStream reader with Qt 5.1 version
28    pub fn new(data: &'a [u8]) -> Self {
29        Self {
30            cursor: Cursor::new(data),
31            version: QT_VERSION_5_1,
32        }
33    }
34
35    /// Create a new QDataStream reader with specified version
36    pub fn with_version(data: &'a [u8], version: u32) -> Self {
37        Self {
38            cursor: Cursor::new(data),
39            version,
40        }
41    }
42
43    /// Get the Qt version
44    pub fn version(&self) -> u32 {
45        self.version
46    }
47
48    /// Get current position in the stream
49    pub fn position(&self) -> u64 {
50        self.cursor.position()
51    }
52
53    /// Check if we've reached the end of the stream
54    pub fn at_end(&self) -> bool {
55        self.remaining() == 0
56    }
57
58    /// Get remaining bytes count
59    pub fn remaining(&self) -> usize {
60        let len = self.cursor.get_ref().len();
61        usize::try_from(self.cursor.position()).map_or(0, |pos| len.saturating_sub(pos))
62    }
63
64    /// Skip n bytes
65    pub fn skip(&mut self, n: usize) -> Result<()> {
66        if self.remaining() < n {
67            return Err(Error::UnexpectedEof {
68                offset: self.position(),
69            });
70        }
71
72        let offset = u64::try_from(n).map_err(|_| Error::UnexpectedEof {
73            offset: self.position(),
74        })?;
75        let next = self
76            .position()
77            .checked_add(offset)
78            .ok_or(Error::UnexpectedEof {
79                offset: self.position(),
80            })?;
81        self.cursor.set_position(next);
82        Ok(())
83    }
84
85    /// Read a single byte (quint8)
86    pub fn read_u8(&mut self) -> Result<u8> {
87        self.cursor.read_u8().map_err(|_| Error::UnexpectedEof {
88            offset: self.position(),
89        })
90    }
91
92    /// Read a signed 8-bit integer (qint8)
93    pub fn read_i8(&mut self) -> Result<i8> {
94        self.cursor.read_i8().map_err(|_| Error::UnexpectedEof {
95            offset: self.position(),
96        })
97    }
98
99    /// Read an unsigned 16-bit integer (quint16) - Big Endian
100    pub fn read_u16(&mut self) -> Result<u16> {
101        self.cursor
102            .read_u16::<BigEndian>()
103            .map_err(|_| Error::UnexpectedEof {
104                offset: self.position(),
105            })
106    }
107
108    /// Read a signed 16-bit integer (qint16) - Big Endian
109    pub fn read_i16(&mut self) -> Result<i16> {
110        self.cursor
111            .read_i16::<BigEndian>()
112            .map_err(|_| Error::UnexpectedEof {
113                offset: self.position(),
114            })
115    }
116
117    /// Read an unsigned 32-bit integer (quint32) - Big Endian
118    pub fn read_u32(&mut self) -> Result<u32> {
119        self.cursor
120            .read_u32::<BigEndian>()
121            .map_err(|_| Error::UnexpectedEof {
122                offset: self.position(),
123            })
124    }
125
126    /// Read a signed 32-bit integer (qint32) - Big Endian
127    pub fn read_i32(&mut self) -> Result<i32> {
128        self.cursor
129            .read_i32::<BigEndian>()
130            .map_err(|_| Error::UnexpectedEof {
131                offset: self.position(),
132            })
133    }
134
135    /// Read an unsigned 64-bit integer (quint64) - Big Endian
136    pub fn read_u64(&mut self) -> Result<u64> {
137        self.cursor
138            .read_u64::<BigEndian>()
139            .map_err(|_| Error::UnexpectedEof {
140                offset: self.position(),
141            })
142    }
143
144    /// Read a signed 64-bit integer (qint64) - Big Endian
145    pub fn read_i64(&mut self) -> Result<i64> {
146        self.cursor
147            .read_i64::<BigEndian>()
148            .map_err(|_| Error::UnexpectedEof {
149                offset: self.position(),
150            })
151    }
152
153    /// Read a boolean value
154    pub fn read_bool(&mut self) -> Result<bool> {
155        Ok(self.read_u8()? != 0)
156    }
157
158    /// Read a 32-bit float - Big Endian
159    pub fn read_f32(&mut self) -> Result<f32> {
160        self.cursor
161            .read_f32::<BigEndian>()
162            .map_err(|_| Error::UnexpectedEof {
163                offset: self.position(),
164            })
165    }
166
167    /// Read a 64-bit double - Big Endian
168    pub fn read_f64(&mut self) -> Result<f64> {
169        self.cursor
170            .read_f64::<BigEndian>()
171            .map_err(|_| Error::UnexpectedEof {
172                offset: self.position(),
173            })
174    }
175
176    /// Read raw bytes of specified length
177    pub fn read_raw(&mut self, len: usize) -> Result<Vec<u8>> {
178        if self.remaining() < len {
179            return Err(Error::UnexpectedEof {
180                offset: self.position(),
181            });
182        }
183
184        let mut buf = vec![0u8; len];
185        self.cursor
186            .read_exact(&mut buf)
187            .map_err(|_| Error::UnexpectedEof {
188                offset: self.position(),
189            })?;
190        Ok(buf)
191    }
192
193    /// Read a QByteArray
194    ///
195    /// Wire format:
196    /// - 4 bytes: length (quint32 BE)
197    ///   - 0xFFFFFFFF = null QByteArray (returns empty vec)
198    ///   - 0xFFFFFFFE = extended 64-bit length (followed by quint64)
199    /// - N bytes: raw data
200    pub fn read_qbytearray(&mut self) -> Result<Vec<u8>> {
201        let len = self.read_u32()?;
202
203        match len {
204            NULL_MARKER => Ok(Vec::new()),
205            EXTENDED_LENGTH_MARKER => {
206                // Extended 64-bit length (Qt 6.7+)
207                let real_len = usize::try_from(self.read_u64()?)
208                    .map_err(|_| Error::qdatastream("QByteArray length is too large"))?;
209                self.read_raw(real_len)
210            }
211            _ => self.read_raw(
212                usize::try_from(len)
213                    .map_err(|_| Error::qdatastream("QByteArray length is too large"))?,
214            ),
215        }
216    }
217
218    /// Read a QString
219    ///
220    /// Wire format:
221    /// - 4 bytes: length in BYTES (not chars!) of UTF-16 data
222    ///   - 0xFFFFFFFF = null QString (returns empty string)
223    /// - N bytes: UTF-16 Big Endian encoded characters
224    pub fn read_qstring(&mut self) -> Result<String> {
225        let byte_len = self.read_u32()?;
226
227        if byte_len == NULL_MARKER {
228            return Ok(String::new());
229        }
230
231        if byte_len % 2 != 0 {
232            return Err(Error::qdatastream("QString byte length is not even"));
233        }
234
235        let char_count = usize::try_from(byte_len / 2)
236            .map_err(|_| Error::qdatastream("QString length is too large"))?;
237        let mut utf16: Vec<u16> = Vec::with_capacity(char_count);
238
239        for _ in 0..char_count {
240            utf16.push(self.read_u16()?);
241        }
242
243        String::from_utf16(&utf16).map_err(|_| Error::InvalidUtf16)
244    }
245
246    /// Read a length-prefixed C string (writeBytes format)
247    ///
248    /// Wire format:
249    /// - 4 bytes: length including null terminator
250    /// - N bytes: string data including null terminator
251    pub fn read_cstring(&mut self) -> Result<String> {
252        let data = self.read_qbytearray()?;
253
254        // Remove null terminator if present
255        let data = data.strip_suffix(&[0]).unwrap_or(&data);
256
257        String::from_utf8(data.to_vec())
258            .map_err(|_| Error::qdatastream("invalid UTF-8 in C string"))
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn test_read_u32() -> Result<()> {
268        let data = [0x12, 0x34, 0x56, 0x78];
269        let mut stream = QDataStream::new(&data);
270        assert_eq!(stream.read_u32()?, 0x12345678);
271        Ok(())
272    }
273
274    #[test]
275    fn test_read_i32() -> Result<()> {
276        let data = [0xFF, 0xFF, 0xFF, 0xFE]; // -2 in big endian
277        let mut stream = QDataStream::new(&data);
278        assert_eq!(stream.read_i32()?, -2);
279        Ok(())
280    }
281
282    #[test]
283    fn test_read_qbytearray() -> Result<()> {
284        // Length = 4, data = [0x01, 0x02, 0x03, 0x04]
285        let data = [0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04];
286        let mut stream = QDataStream::new(&data);
287        assert_eq!(stream.read_qbytearray()?, vec![0x01, 0x02, 0x03, 0x04]);
288        Ok(())
289    }
290
291    #[test]
292    fn test_read_null_qbytearray() -> Result<()> {
293        let data = [0xFF, 0xFF, 0xFF, 0xFF];
294        let mut stream = QDataStream::new(&data);
295        assert!(stream.read_qbytearray()?.is_empty());
296        Ok(())
297    }
298
299    #[test]
300    fn test_read_qstring() -> Result<()> {
301        // "Hi" in UTF-16 BE: length = 4 bytes, 'H' = 0x0048, 'i' = 0x0069
302        let data = [0x00, 0x00, 0x00, 0x04, 0x00, 0x48, 0x00, 0x69];
303        let mut stream = QDataStream::new(&data);
304        assert_eq!(stream.read_qstring()?, "Hi");
305        Ok(())
306    }
307
308    #[test]
309    fn test_read_null_qstring() -> Result<()> {
310        let data = [0xFF, 0xFF, 0xFF, 0xFF];
311        let mut stream = QDataStream::new(&data);
312        assert!(stream.read_qstring()?.is_empty());
313        Ok(())
314    }
315
316    #[test]
317    fn test_position_and_remaining() -> Result<()> {
318        let data = [0x01, 0x02, 0x03, 0x04, 0x05];
319        let mut stream = QDataStream::new(&data);
320
321        assert_eq!(stream.position(), 0);
322        assert_eq!(stream.remaining(), 5);
323
324        stream.read_u8()?;
325        assert_eq!(stream.position(), 1);
326        assert_eq!(stream.remaining(), 4);
327
328        stream.skip(2)?;
329        assert_eq!(stream.position(), 3);
330        assert_eq!(stream.remaining(), 2);
331        Ok(())
332    }
333
334    #[test]
335    fn test_at_end() -> Result<()> {
336        let data = [0x01, 0x02];
337        let mut stream = QDataStream::new(&data);
338
339        assert!(!stream.at_end());
340        stream.read_u16()?;
341        assert!(stream.at_end());
342        Ok(())
343    }
344}