use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Cursor, Read};
use crate::{Error, Result};
pub const QT_VERSION_5_1: u32 = 14;
const NULL_MARKER: u32 = 0xFFFFFFFF;
const EXTENDED_LENGTH_MARKER: u32 = 0xFFFFFFFE;
pub struct QDataStream<'a> {
cursor: Cursor<&'a [u8]>,
version: u32,
}
impl<'a> QDataStream<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self {
cursor: Cursor::new(data),
version: QT_VERSION_5_1,
}
}
pub fn with_version(data: &'a [u8], version: u32) -> Self {
Self {
cursor: Cursor::new(data),
version,
}
}
pub fn version(&self) -> u32 {
self.version
}
pub fn position(&self) -> u64 {
self.cursor.position()
}
pub fn at_end(&self) -> bool {
self.remaining() == 0
}
pub fn remaining(&self) -> usize {
let len = self.cursor.get_ref().len();
usize::try_from(self.cursor.position()).map_or(0, |pos| len.saturating_sub(pos))
}
pub fn skip(&mut self, n: usize) -> Result<()> {
if self.remaining() < n {
return Err(Error::UnexpectedEof {
offset: self.position(),
});
}
let offset = u64::try_from(n).map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})?;
let next = self
.position()
.checked_add(offset)
.ok_or(Error::UnexpectedEof {
offset: self.position(),
})?;
self.cursor.set_position(next);
Ok(())
}
pub fn read_u8(&mut self) -> Result<u8> {
self.cursor.read_u8().map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_i8(&mut self) -> Result<i8> {
self.cursor.read_i8().map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_u16(&mut self) -> Result<u16> {
self.cursor
.read_u16::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_i16(&mut self) -> Result<i16> {
self.cursor
.read_i16::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_u32(&mut self) -> Result<u32> {
self.cursor
.read_u32::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_i32(&mut self) -> Result<i32> {
self.cursor
.read_i32::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_u64(&mut self) -> Result<u64> {
self.cursor
.read_u64::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_i64(&mut self) -> Result<i64> {
self.cursor
.read_i64::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_bool(&mut self) -> Result<bool> {
Ok(self.read_u8()? != 0)
}
pub fn read_f32(&mut self) -> Result<f32> {
self.cursor
.read_f32::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_f64(&mut self) -> Result<f64> {
self.cursor
.read_f64::<BigEndian>()
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})
}
pub fn read_raw(&mut self, len: usize) -> Result<Vec<u8>> {
if self.remaining() < len {
return Err(Error::UnexpectedEof {
offset: self.position(),
});
}
let mut buf = vec![0u8; len];
self.cursor
.read_exact(&mut buf)
.map_err(|_| Error::UnexpectedEof {
offset: self.position(),
})?;
Ok(buf)
}
pub fn read_qbytearray(&mut self) -> Result<Vec<u8>> {
let len = self.read_u32()?;
match len {
NULL_MARKER => Ok(Vec::new()),
EXTENDED_LENGTH_MARKER => {
let real_len = usize::try_from(self.read_u64()?)
.map_err(|_| Error::qdatastream("QByteArray length is too large"))?;
self.read_raw(real_len)
}
_ => self.read_raw(
usize::try_from(len)
.map_err(|_| Error::qdatastream("QByteArray length is too large"))?,
),
}
}
pub fn read_qstring(&mut self) -> Result<String> {
let byte_len = self.read_u32()?;
if byte_len == NULL_MARKER {
return Ok(String::new());
}
if byte_len % 2 != 0 {
return Err(Error::qdatastream("QString byte length is not even"));
}
let char_count = usize::try_from(byte_len / 2)
.map_err(|_| Error::qdatastream("QString length is too large"))?;
let mut utf16: Vec<u16> = Vec::with_capacity(char_count);
for _ in 0..char_count {
utf16.push(self.read_u16()?);
}
String::from_utf16(&utf16).map_err(|_| Error::InvalidUtf16)
}
pub fn read_cstring(&mut self) -> Result<String> {
let data = self.read_qbytearray()?;
let data = data.strip_suffix(&[0]).unwrap_or(&data);
String::from_utf8(data.to_vec())
.map_err(|_| Error::qdatastream("invalid UTF-8 in C string"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_u32() -> Result<()> {
let data = [0x12, 0x34, 0x56, 0x78];
let mut stream = QDataStream::new(&data);
assert_eq!(stream.read_u32()?, 0x12345678);
Ok(())
}
#[test]
fn test_read_i32() -> Result<()> {
let data = [0xFF, 0xFF, 0xFF, 0xFE]; let mut stream = QDataStream::new(&data);
assert_eq!(stream.read_i32()?, -2);
Ok(())
}
#[test]
fn test_read_qbytearray() -> Result<()> {
let data = [0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04];
let mut stream = QDataStream::new(&data);
assert_eq!(stream.read_qbytearray()?, vec![0x01, 0x02, 0x03, 0x04]);
Ok(())
}
#[test]
fn test_read_null_qbytearray() -> Result<()> {
let data = [0xFF, 0xFF, 0xFF, 0xFF];
let mut stream = QDataStream::new(&data);
assert!(stream.read_qbytearray()?.is_empty());
Ok(())
}
#[test]
fn test_read_qstring() -> Result<()> {
let data = [0x00, 0x00, 0x00, 0x04, 0x00, 0x48, 0x00, 0x69];
let mut stream = QDataStream::new(&data);
assert_eq!(stream.read_qstring()?, "Hi");
Ok(())
}
#[test]
fn test_read_null_qstring() -> Result<()> {
let data = [0xFF, 0xFF, 0xFF, 0xFF];
let mut stream = QDataStream::new(&data);
assert!(stream.read_qstring()?.is_empty());
Ok(())
}
#[test]
fn test_position_and_remaining() -> Result<()> {
let data = [0x01, 0x02, 0x03, 0x04, 0x05];
let mut stream = QDataStream::new(&data);
assert_eq!(stream.position(), 0);
assert_eq!(stream.remaining(), 5);
stream.read_u8()?;
assert_eq!(stream.position(), 1);
assert_eq!(stream.remaining(), 4);
stream.skip(2)?;
assert_eq!(stream.position(), 3);
assert_eq!(stream.remaining(), 2);
Ok(())
}
#[test]
fn test_at_end() -> Result<()> {
let data = [0x01, 0x02];
let mut stream = QDataStream::new(&data);
assert!(!stream.at_end());
stream.read_u16()?;
assert!(stream.at_end());
Ok(())
}
}