use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Cursor, Read, Seek, SeekFrom};
use crate::{Error, Result};
use super::constants::{EXTENDED_LENGTH_MARKER, NULL_MARKER, QT_VERSION_5_1};
pub struct QDataStream<'a> {
pub(super) cursor: Cursor<&'a [u8]>,
pub(super) version: u32,
}
impl std::fmt::Debug for QDataStream<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QDataStream")
.field("position", &self.cursor.position())
.field("version", &self.version)
.finish()
}
}
impl<'a> QDataStream<'a> {
#[must_use]
pub const fn new(data: &'a [u8]) -> Self {
Self { cursor: Cursor::new(data), version: QT_VERSION_5_1 }
}
#[must_use]
pub const fn with_version(data: &'a [u8], version: u32) -> Self {
Self { cursor: Cursor::new(data), version }
}
#[must_use]
pub const fn version(&self) -> u32 {
self.version
}
#[must_use]
pub const fn position(&self) -> u64 {
self.cursor.position()
}
#[must_use]
pub const fn at_end(&self) -> bool {
self.cursor.position() >= self.cursor.get_ref().len() as u64
}
#[must_use]
pub const fn remaining(&self) -> usize {
let pos = self.cursor.position() as usize;
let len = self.cursor.get_ref().len();
len.saturating_sub(pos)
}
pub fn skip(&mut self, n: usize) -> Result<()> {
let _ = self
.cursor
.seek(SeekFrom::Current(n as i64))
.map_err(|_| Error::UnexpectedEof { offset: self.position() })?;
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 = self.read_u64()? as usize;
self.read_raw(real_len)
},
_ => self.read_raw(len as usize),
}
}
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 = (byte_len / 2) as usize;
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 = if data.last() == Some(&0) { &data[..data.len() - 1] } else { &*data };
String::from_utf8(data.to_vec())
.map_err(|_| Error::qdatastream("invalid UTF-8 in C string"))
}
}