use std::fmt::{Display, Formatter};
use std::io::Read;
use std::string::FromUtf8Error;
use itertools::Itertools;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DecodeError {
#[error("incomplete '{0}': {1}")]
IncompleteData(&'static str, std::io::Error),
#[error("invalid '{0}'")]
InvalidData(&'static str),
#[error("incorrect magic: expecting '{expected_magic}', found '{real_bytes}'")]
MagicMismatch {
real_bytes: Magic,
expected_magic: Magic,
},
#[error("invalid UTF-8: found '{invalid_bytes:?}, after successfully decoding '{valid_prefix}''")]
DecodeUtf8Error {
valid_prefix: String,
invalid_bytes: Box<[u8]>,
},
#[error("input stream not exhausted, remaining bytes: {0:?}")]
SuperfluousBytes(Box<[u8]>),
}
use DecodeError::*;
impl From<FromUtf8Error> for DecodeError {
fn from(err: FromUtf8Error) -> Self {
let utf8_error = err.utf8_error();
let valid_up_to = utf8_error.valid_up_to();
let invalid_to = valid_up_to + utf8_error.error_len().unwrap_or(0);
let mut buffer = err.into_bytes();
let invalid_bytes = buffer[valid_up_to..invalid_to].to_vec().into_boxed_slice();
buffer.truncate(valid_up_to);
let valid_prefix = String::from_utf8(buffer).unwrap();
DecodeUtf8Error { valid_prefix, invalid_bytes }
}
}
pub type Result<T, E = DecodeError> = std::result::Result<T, E>;
pub trait PlainData: Sized {
const SIZE_IN_BYTES: usize;
const TYPE_NAME: &'static str;
fn from_bytes(data: &[u8]) -> Option<Self>;
}
macro_rules! impl_plain_data {
($($type_name:ty),+) => {
$(
impl PlainData for $type_name {
const SIZE_IN_BYTES: usize = std::mem::size_of::<$type_name>();
const TYPE_NAME: &'static str = stringify!($type_name);
fn from_bytes(data: &[u8]) -> Option<$type_name> {
let data: &[u8; Self::SIZE_IN_BYTES] = data.try_into().unwrap();
Some(<$type_name>::from_le_bytes(*data))
}
}
)+
}
}
impl_plain_data!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64);
impl PlainData for Option<f32> {
const SIZE_IN_BYTES: usize = 4;
const TYPE_NAME: &'static str = "optional f32";
fn from_bytes(data: &[u8]) -> Option<Option<f32>> {
let n = f32::from_bytes(data)?;
Some(if n <= -10000.0 { None } else { Some(n) })
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct Magic([u8; 4]);
impl From<u32> for Magic {
fn from(n: u32) -> Self {
Magic(n.to_le_bytes())
}
}
impl Display for Magic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let [x, y, z, w] = self.0;
write!(f, "{x:02X} {y:02X} {z:02X} {w:02X}")
}
}
impl PlainData for Magic {
const SIZE_IN_BYTES: usize = 4;
const TYPE_NAME: &'static str = "Magic";
fn from_bytes(data: &[u8]) -> Option<Self> {
let data: &[u8; 4] = data.try_into().unwrap();
Some(Magic(*data))
}
}
pub trait Stream: Read {
fn read_data<T: PlainData>(&mut self) -> Result<T> {
tracing::trace!("reading plain data '{}'", T::TYPE_NAME);
let mut buffer = vec![0_u8; T::SIZE_IN_BYTES];
self.read_exact(&mut buffer).map_err(|err| IncompleteData(T::TYPE_NAME, err))?;
T::from_bytes(&buffer).ok_or(InvalidData(T::TYPE_NAME))
}
fn read_optional<T>(&mut self) -> Result<Option<T>>
where Option<T>: PlainData {
self.read_data::<Option<T>>()
}
fn read_n<T: Decode<()>>(&mut self, n: usize) -> Result<Vec<T>> {
tracing::trace!("reading {n} consecutive elements");
std::iter::repeat_with(|| T::decode(self)).take(n).collect()
}
fn read_array<T: Decode<()>>(&mut self) -> Result<Vec<T>> {
let length = self.read_data::<u32>()?;
self.read_n(length as usize)
}
fn read_string(&mut self) -> Result<String> {
let length = self.read_data::<u32>()?;
tracing::trace!("reading string of length {length}");
let mut buffer = vec![0_u8; length as usize];
self.read_exact(&mut buffer).map_err(|err| IncompleteData("String", err))?;
String::from_utf8(buffer).map_err(Into::into)
}
fn check_magic<M: Into<Magic>>(&mut self, magic: M) -> Result<()> {
let magic = magic.into();
tracing::trace!("checking magic {magic}");
let val = self.read_data::<Magic>()?;
if magic == val { Ok(()) } else {
Err(MagicMismatch {
real_bytes: val,
expected_magic: magic,
})
}
}
fn drop_padding(&mut self, hint: &str, n: usize) -> Result<()> {
let mut buffer = vec![0_u8; n];
self.read_exact(&mut buffer).map_err(|err| IncompleteData("padding", err))?;
if !buffer.iter().all(|x| *x == 0) {
let buffer = buffer.iter().format(" ");
tracing::info!("dropped {n} bytes of padding [{hint}]: {buffer:02X}");
} else {
tracing::trace!("dropped {n} bytes of zero padding [{hint}]");
}
Ok(())
}
}
impl<S: Read + ?Sized> Stream for S {}
pub trait NamedArgs {
type ArgsBuilder;
fn args_builder() -> Self::ArgsBuilder;
}
#[derive(Debug, Copy, Clone)]
pub struct NoArgs;
impl NoArgs {
pub fn finish(self) {}
}
macro_rules! declare_no_args {
($t:ty) => {
impl crate::stream::NamedArgs for $t {
type ArgsBuilder = crate::stream::NoArgs;
fn args_builder() -> Self::ArgsBuilder { crate::stream::NoArgs }
}
}
}
pub trait Decode<Args>: NamedArgs + Sized {
fn decode_with<S: Stream + ?Sized>(s: &mut S, args: Args) -> Result<Self>;
}
pub trait DecodeExt: Decode<()> {
fn decode<S: Stream + ?Sized>(s: &mut S) -> Result<Self> {
Self::decode_with(s, ())
}
}
impl<T: Decode<()>> DecodeExt for T {}
impl<T: PlainData> NamedArgs for T {
type ArgsBuilder = NoArgs;
fn args_builder() -> NoArgs { NoArgs }
}
impl<T: PlainData> Decode<()> for T {
fn decode_with<S: Stream + ?Sized>(s: &mut S, _args: ()) -> Result<Self> { s.read_data::<T>() }
}