//! Binary data conversion utilities. use crate::durandal::err::*; use std::{fmt, num::NonZeroU16}; #[doc(hidden)] #[macro_export] macro_rules! _durandal_read_impl { // big endian (BE $b:ident $nam:ident u16 $n:expr) => { _durandal_read_impl!($b u16::from_be_bytes, $nam 2 $n); }; (BE $b:ident $nam:ident i16 $n:expr) => { _durandal_read_impl!($b i16::from_be_bytes, $nam 2 $n); }; (BE $b:ident $nam:ident u32 $n:expr) => { _durandal_read_impl!($b u32::from_be_bytes, $nam 4 $n); }; (BE $b:ident $nam:ident i32 $n:expr) => { _durandal_read_impl!($b i32::from_be_bytes, $nam 4 $n); }; (BE $b:ident $nam:ident as usize u16 $n:expr) => { _durandal_read_impl!($b u16::from_be_bytes, $nam 2 $n); let $nam = $nam as usize; }; (BE $b:ident $nam:ident as usize u32 $n:expr) => { _durandal_read_impl!($b u32::from_be_bytes, $nam 4 $n); let $nam = $nam as usize; }; // little endian (LE $b:ident $nam:ident u16 $n:expr) => { _durandal_read_impl!($b u16::from_le_bytes $nam 2 $n); }; (LE $b:ident $nam:ident i16 $n:expr) => { _durandal_read_impl!($b i16::from_le_bytes $nam 2 $n); }; (LE $b:ident $nam:ident u32 $n:expr) => { _durandal_read_impl!($b u32::from_le_bytes $nam 4 $n); }; (LE $b:ident $nam:ident i32 $n:expr) => { _durandal_read_impl!($b i32::from_le_bytes $nam 4 $n); }; (LE $b:ident $nam:ident as usize u16 $n:expr) => { _durandal_read_impl!($b u16::from_le_bytes $nam 2 $n); let $nam = $nam as usize; }; (LE $b:ident $nam:ident as usize u32 $n:expr) => { _durandal_read_impl!($b u32::from_le_bytes $nam 4 $n); let $nam = $nam as usize; }; // either endianness ($e:ident $b:ident $nam:ident Angle $n:expr) => { _durandal_read_impl!($e $b $nam u16 $n); let $nam = Angle::from_bits($nam); }; ($e:ident $b:ident $nam:ident Fixed $n:expr) => { _durandal_read_impl!($e $b $nam u32 $n); let $nam = Fixed::from_bits($nam); }; ($e:ident $b:ident $nam:ident Unit $n:expr) => { _durandal_read_impl!($e $b $nam u16 $n); let $nam = Unit::from_bits($nam); }; ($e:ident $b:ident $nam:ident OptU16 $n:expr) => { _durandal_read_impl!($e $b $nam u16 $n); let $nam = OptU16::from_repr($nam); }; // generic endianness ($_:ident $b:ident $nam:ident u8 $n:expr) => { let $nam = $b[$n]; }; ($_:ident $b:ident $nam:ident array u8 $n:expr) => { let $nam = &$b[$n]; }; ($_:ident $b:ident $nam:ident i8 $n:expr) => { let $nam = $b[$n] as i8; }; ($_:ident $b:ident $nam:ident Ident $n:expr) => { let $nam = [$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]; }; ($_:ident $b:ident $nam:ident $f:ident $n:expr) => { let $nam = $f(&$b[$n])?; }; ($_:ident $b:ident $nam:ident nt $f:ident $n:expr) => { let $nam = $f(&$b[$n]); }; // worker - creates let statement ($b:ident $pth:path , $nam:ident 2 $n:expr) => { let $nam = $pth([$b[$n], $b[$n + 1]]); }; ($b:ident $pth:path , $nam:ident 4 $n:expr) => { let $nam = $pth([$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]); }; } /// Reads structured data from a byte array. /// /// First start by specifying the endianness, size and source using the syntax /// `endian, size in source =>` where: /// /// - `endian` is `BE` or `LE` for big- or little-endian respectively. /// - `size` is an expression specifying the last index that should be used by /// this macro in `source`. /// - `source` is a `u8` slice to read data from. /// /// After the initializer line, all lines have the syntax /// `name = type[place] opts;` where: /// /// - `name` is the binding to put the resulting data in. /// - `type` is one of: /// - `u8` or `i8`: one byte will be read at `place`. /// - `u16` or `i16`: two bytes will be read at `place` with `from_*_bytes`. /// - `u32` or `i32`: four bytes will be read at `place` with `from_*_bytes`. /// - `Ident`: four bytes will be read at `place` into an array, disregarding /// endianness, creating an `Ident` object. /// - `Angle`: same as `u16`, but the result is passed to /// `fixed::Angle::from_bits`, resulting in a `fixed::Angle` object. /// - `Fixed`: same as `u32`, but the result is passed to /// `fixed::Fixed::from_bits`, resulting in a `fixed::Fixed` object. /// - `Unit`: same as `u16`, but the result is passed to /// `fixed::Unit::from_bits`, resulting in a `fixed::Unit` object. /// - `OptU16`: same as `u16`, but the result is passed to /// `OptU16::from_repr`, resulting in an `OptU16` object. /// - The name of a function, which is passed `&source[place]` as its only /// argument. The function's result has the `?` operator applied to it. /// - `opts` may be one of: /// - `array` when `type` is `u8`: `place` is a range specifying a `u8` slice /// to be taken from `source`. /// - `as usize` when `type` is `u16` or `u32`: converts the resulting /// integer to `usize` by primitive cast. /// - `nt` when `type` is a function name: does not use the `?` operator on /// the resulting function call /// - Nothing at all. /// - `place` is either an integer literal which must be representable as /// `usize`, or a range, which may only be used when `type` is a function /// name. #[macro_export] macro_rules! read_data { ( $sz:expr , $ty:ident in $b:ident => $( $nam:ident = $t:ident [ $n:expr ] $( $ex:ident )* ; )* ) => { if $b.len() < $sz { bail!("not enough data"); } $($crate::_durandal_read_impl!($ty $b $nam $($ex)* $t $n);)* }; } /// Creates an `Ident` from a slice. /// /// `b` must be at least 4 bytes, or a panic will occur. pub fn ident(b: &[u8]) -> Ident {[b[0], b[1], b[2], b[3]]} /// Applies `u32::from_be_bytes` to a slice. /// /// `b` must be at least 4 bytes, or a panic will occur. pub fn u32b(b: &[u8]) -> u32 {u32::from_be_bytes([b[0], b[1], b[2], b[3]])} /// Applies `u16::from_be_bytes` to a slice. /// /// `b` must be at least 2 bytes, or a panic will occur. pub fn u16b(b: &[u8]) -> u16 {u16::from_be_bytes([b[0], b[1]])} /// Applies `i32::from_be_bytes` to a slice. /// /// `b` must be at least 4 bytes, or a panic will occur. pub fn i32b(b: &[u8]) -> i32 {i32::from_be_bytes([b[0], b[1], b[2], b[3]])} /// Applies `i16::from_be_bytes` to a slice. /// /// `b` must be at least 2 bytes, or a panic will occur. pub fn i16b(b: &[u8]) -> i16 {i16::from_be_bytes([b[0], b[1]])} /// Applies a read function over a slice. /// /// Applies `read` over `b`, resulting in a vector of its return values. Each /// iteration will pass a slice of `b` to `read` for it to read from, and then /// increments the slice index by the second return value. When there is no /// data left in `b`, the function returns. pub fn rd_array(b: &[u8], read: F) -> ResultS> where T: Sized, F: Fn(&[u8]) -> ResultS<(T, usize)> { let mut v = Vec::new(); let mut p = 0; while p < b.len() { let (r, s) = read(&b[p..])?; v.push(r); p += s; } Ok(v) } /// Applies a read function over a slice with an offset table. /// /// Applies `read` over each offset in `b`, of which there are `num` amount of /// starting at `p`, resulting in a vector of its return values. Each iteration /// reads a 32-bit big endian offset from `b`, and then passes a slice of `b` /// to `read` starting at that offset. When all offsets have been read, the /// function returns. pub fn rd_ofstable(b: &[u8], mut p: usize, num: usize, read: F) -> ResultS> where T: Sized, F: Fn(&[u8]) -> ResultS { let mut v = Vec::with_capacity(num); for _ in 0..num { let ofs = u32b(&b[p..p + 4]) as usize; if ofs >= b.len() { bail!("not enough data"); } v.push(read(&b[ofs..])?); p += 4; } Ok(v) } impl OptU16 { /// Creates an `OptU16` from a `u16`. pub fn from_repr(n: u16) -> Self { if n == u16::max_value() { Self(None) } else { Self(NonZeroU16::new(n + 1)) } } /// Returns the `u16` representation. pub fn get_repr(&self) -> u16 { match self.0 { None => u16::max_value(), Some(n) => n.get() - 1, } } /// Returns the `Option` representation. pub fn get(&self) -> Option { match self.0 { None => None, Some(n) => Some(n.get() - 1), } } } impl fmt::Debug for OptU16 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.get() { None => write!(f, "None"), Some(n) => write!(f, "Some({})", n), } } } /// A four-character-code identifier. pub type Ident = [u8; 4]; /// An object identified by a `u16` which may be `u16::max_value()` to /// represent a nulled value. #[derive(serde::Serialize, serde::Deserialize)] pub struct OptU16(Option); // EOF