//! Binary data conversion utilities. use crate::err::*; use std::{convert::{TryFrom, TryInto}, fmt, num::NonZeroU16}; #[doc(hidden)] #[macro_export] macro_rules! rd_impl { // worker, creates let statement for 16 bit numbers (W $b:expr, $pth:path, $nam:ident, $n:expr) => { let $nam = $pth([$b[$n], $b[$n + 1]]); }; // worker, creates let statement for 32 bit numbers (D $b:expr, $pth:path, $nam:ident, $n:expr) => { let $nam = $pth([$b[$n], $b[$n + 1], $b[$n + 2], $b[$n + 3]]); }; // big endian, u16 (BIG, $b:expr, $at:expr, $n:expr; $nam:ident, u16) => { $crate::rd_impl!(W $b, u16::from_be_bytes, $nam, $n + $at); }; // big endian, i16 (BIG, $b:expr, $at:expr, $n:expr; $nam:ident, i16) => { $crate::rd_impl!(W $b, i16::from_be_bytes, $nam, $n + $at); }; // big endian, u32 (BIG, $b:expr, $at:expr, $n:expr; $nam:ident, u32) => { $crate::rd_impl!(D $b, u32::from_be_bytes, $nam, $n + $at); }; // big endian, i32 (BIG, $b:expr, $at:expr, $n:expr; $nam:ident, i32) => { $crate::rd_impl!(D $b, i32::from_be_bytes, $nam, $n + $at); }; // little endian, u16 (LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, u16) => { $crate::rd_impl!(W $b, u16::from_le_bytes, $nam, $n + $at); }; // little endian, i16 (LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, i16) => { $crate::rd_impl!(W $b, i16::from_le_bytes, $nam, $n + $at); }; // little endian, u32 (LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, u32) => { $crate::rd_impl!(D $b, u32::from_le_bytes, $nam, $n + $at); }; // little endian, i32 (LITTLE, $b:expr, $at:expr, $n:expr; $nam:ident, i32) => { $crate::rd_impl!(D $b, i32::from_le_bytes, $nam, $n + $at); }; // either endianness, Angle ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Angle) => { $crate::rd_impl!($e, $b, $at, $n; $nam, u16); let $nam = $crate::fixed::Angle::from_bits($nam); }; // either endianness, Fixed ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Fixed) => { $crate::rd_impl!($e, $b, $at, $n; $nam, u32); let $nam = $crate::fixed::Fixed::from_bits($nam); }; // either endianness, Unit ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Unit) => { $crate::rd_impl!($e, $b, $at, $n; $nam, u16); let $nam = $crate::fixed::Unit::from_bits($nam); }; // either endianness, OptU16 ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, OptU16) => { $crate::rd_impl!($e, $b, $at, $n; $nam, u16); let $nam = $crate::bin::OptU16::from($nam); }; // either endianness, u16 -> usize ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, usize, u16) => { $crate::rd_impl!($e, $b, $at, $n; $nam, u16); let $nam = usize::from($nam); }; // either endianness, u32 -> usize ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, usize, u32) => { $crate::rd_impl!($e, $b, $at, $n; $nam, u32); let $nam = $crate::bin::usize_from_u32($nam); }; // either endianness, enum type with TryFrom ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, enum, $et:ident$(::$etc:ident)*, $t:ident) => { $crate::rd_impl!($e, $b, $at, $n; $nam, $t); let $nam: $et$(::$etc)* = std::convert::TryFrom::try_from($nam)?; }; // either endianness, bitflag type ($e:ident, $b:expr, $at:expr, $n:expr; $nam:ident, flag, $ft:ident$(::$ftc:ident)*, $t:ident) => { $crate::rd_impl!($e, $b, $at, $n; $nam, $t); let $nam = flag_ok!($ft$(::$ftc)*, $nam)?; }; // no endianness, u8 ($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, u8) => { let $nam = $b[$n + $at]; }; // no endianness, i8 ($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, i8) => { let $nam = $b[$n + $at] as i8; }; // no endianness, [u8] ($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, u8) => { let $nam = &$b[$n + $at..$n + $at + $rn]; }; // no endianness, Ident ($_:ident, $b:expr, $at:expr, $n:expr; $nam:ident, Ident) => { $crate::rd_impl!(D $b, Ident, $nam, $n + $at); }; // no endianness, fn([u8]) -> T ($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, no_try, $f:expr) => { let $nam = $f(&$b[$n + $at..$n + $at + $rn]); }; // no endianness, fn([u8]) -> Result ($_:ident, $b:expr, $at:expr, $n:expr; $rn:expr; $nam:ident, $f:expr) => { let $nam = $f(&$b[$n + $at..$n + $at + $rn])?; }; } /// Reads structured data from a byte slice. /// /// # Syntax /// /// First start by specifying the basic information, using the syntax: /// `endian: ENDIAN, buf: BUFFER, size: SIZE, start: START,` where: /// /// - `ENDIAN` is `BIG` or `LITTLE` for big- or little-endian respectively. /// - `BUFFER` is a `u8` slice to read data from. This expression will be /// evaluated many times, so be careful when specifying it. /// - `SIZE` is an expression specifying the last index that should be used by /// this macro in `BUFFER`. /// - `START` is an expression specifying the index to start at in `BUFFER`. All /// indices and sizes will have this added to them. /// /// Following that is a block with the syntax `data { ... }`. All lines within /// this block have the syntax `let NAME = TYPE[INDEX] 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 `INDEX`. If `INDEX` is a range, /// this will be a slice into `BUFFER` instead. /// - `u16` or `i16`: two bytes will be read at `INDEX` with `from_*_bytes`. /// If `OPTS` is `usize`, this converts the resulting number to `usize` by /// using `usize::from`. /// - `u32` or `i32`: four bytes will be read at `INDEX` with `from_*_bytes`. /// If `OPTS` is `usize`, this converts the resulting number to `usize` by /// using `usize_from_u32`. /// - `Ident`: four bytes will be read at `INDEX` 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`, /// resulting in an `OptU16` object. /// - The name of a function, which is passed the index range as its only /// argument. The function's result has the `?` operator applied to it, /// unless `OPTS` is `no_try`. /// - `OPT`, if not one of the things listed above, may be `enum TYPE` to apply /// `TryFrom`, or `flag TYPE` to apply a bitfield made by `c_bitfield!`. /// - `INDEX` is either an integer literal which must be representable as /// `usize`, or a range with the syntax `INDEX; SIZE` denoting the beginning /// and size of the range. /// /// # Panics /// /// This macro will not panic unless any index expression used exceeds or /// equals `SIZE + START`, or a function passed to it panics. /// /// # Examples /// /// ``` /// # #[macro_use] extern crate maraiah; /// # use maraiah::err::*; /// # /// # fn main() -> ResultS<()> /// # { /// let buffer = &[4, 0, 2, 0, 0, 0, 6]; /// /// read_data! { /// endian: LITTLE, buf: buffer, size: 7, start: 0, data { /// let four = u16[0]; /// let two = u32[2]; /// let six = u8[6]; /// let byte = u8[2; 4]; /// } /// } /// /// assert_eq!(four, 4_u16); /// assert_eq!(two, 2_u32); /// assert_eq!(six, 6_u8); /// assert_eq!(byte, &[2, 0, 0, 0]); /// # Ok(()) /// # } /// ``` #[macro_export] macro_rules! read_data { ( endian: $e:ident, buf: $b:expr, size: $sz:expr, start: $at:expr, data { $(let $nam:ident = $t:ident$(::$tc:ident)*[$n:expr $(; $rn:expr)?] $($ex:ident$(::$exc:ident)*)*;)* } ) => { $crate::bin::check_data($b, $at + $sz)?; $($crate::rd_impl!($e, $b, $at, $n; $($rn;)? $nam, $($ex$(::$exc)*,)* $t$(::$tc)*);)* }; } /// Checks if there is enough data in `b`. /// /// # Errors /// /// Returns `Err` if `b.len()` is less than `sz`. #[inline] pub fn check_data(b: &[T], sz: usize) -> ResultS<()> { if b.len() < sz { Err(err_msg("not enough data")) } else { Ok(()) } } /// Casts a `u32` to a `usize`. /// /// # Panics /// /// Will panic if the platform does not have a 32-bit `usize`. In this case, /// the application is not supported, but will still run and panic at runtime. /// /// # Examples /// /// ``` /// use maraiah::bin::usize_from_u32; /// /// assert_eq!(usize_from_u32(777u32), 777usize); /// ``` #[inline] pub fn usize_from_u32(n: u32) -> usize { usize::try_from(n).expect("platform is 16-bit") } /// Creates an `Ident` from a slice. /// /// # Panics /// /// A panic will occur if `b.len()` is less than 4. /// /// # Examples /// /// ``` /// use maraiah::bin::{ident, Ident}; /// /// assert_eq!(ident(b"POLY"), Ident([b'P', b'O', b'L', b'Y'])); /// ``` #[inline] pub fn ident(b: &[u8]) -> Ident { Ident(b[0..4].try_into().expect("not enough data")) } /// Applies `u32::from_be_bytes` to a slice. /// /// # Panics /// /// A panic will occur if `b.len()` is less than 4. /// /// # Examples /// /// ``` /// use maraiah::bin::u32b; /// /// assert_eq!(u32b(&[0x00, 0x0B, 0xDE, 0x31]), 777_777u32); /// ``` #[inline] pub fn u32b(b: &[u8]) -> u32 { u32::from_be_bytes(b[0..4].try_into().expect("not enough data")) } /// Applies `u16::from_be_bytes` to a slice. /// /// # Panics /// /// A panic will occur if `b.len()` is less than 2. /// /// # Examples /// /// ``` /// use maraiah::bin::u16b; /// /// assert_eq!(u16b(&[0x1E, 0x61]), 7_777u16); /// ``` #[inline] pub fn u16b(b: &[u8]) -> u16 { u16::from_be_bytes(b[0..2].try_into().expect("not enough data")) } /// Applies `i32::from_be_bytes` to a slice. /// /// # Panics /// /// A panic will occur if `b.len()` is less than 4. /// /// # Examples /// /// ``` /// use maraiah::bin::i32b; /// /// assert_eq!(i32b(&[0xFF, 0x89, 0x52, 0x0F]), -7_777_777i32); /// ``` #[inline] pub fn i32b(b: &[u8]) -> i32 { i32::from_be_bytes(b[0..4].try_into().expect("not enough data")) } /// Applies `i16::from_be_bytes` to a slice. /// /// # Panics /// /// A panic will occur if `b.len()` is less than 2. /// /// # Examples /// /// ``` /// use maraiah::bin::i16b; /// /// assert_eq!(i16b(&[0xE1, 0x9F]), -7_777i16); /// ``` #[inline] pub fn i16b(b: &[u8]) -> i16 { i16::from_be_bytes(b[0..2].try_into().expect("not enough data")) } /// 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. /// /// # Panics /// /// A panic will occur if the `read` function returns a disjoint index or /// otherwise panics (by an out of bounds index to `b` or otherwise.) /// /// # Errors /// /// Execution will return the result of `read` if `read` returns an error. /// /// # Examples /// /// ``` /// use maraiah::{bin::{rd_array, u16b}, err::*}; /// /// fn read_a_u16(b: &[u8]) -> ResultS<(u16, usize)> {Ok((u16b(b), 2))} /// /// let inp = &[0x1E, 0x61, 0x03, 0x09]; /// assert_eq!(rd_array(inp, read_a_u16).unwrap(), vec![7_777u16, 777u16]); /// ``` 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 a number of times 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 `n` elements /// have been read, the function returns. /// /// # Panics /// /// A panic will occur if the `read` function returns a disjoint index or /// otherwise panics (by an out of bounds index to `b` or otherwise.) /// /// # Errors /// /// Execution will return the result of `read` if `read` returns an error. pub fn rd_array_num(b: &[u8], n: usize, read: F) -> ResultS<(Vec, usize)> where T: Sized, F: Fn(&[u8]) -> ResultS<(T, usize)> { let mut v = Vec::with_capacity(n); let mut p = 0; for _ in 0..n { let (r, s) = read(&b[p..])?; v.push(r); p += s; } Ok((v, p)) } /// 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. /// /// # Panics /// /// A panic will occur if the `read` function panics. /// /// # Errors /// /// Execution will return the result of `read` if `read` returns an error. 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 = usize_from_u32(u32b(&b[p..p + 4])); check_data(b, ofs)?; v.push(read(&b[ofs..])?); p += 4; } Ok(v) } impl From for OptU16 { #[inline] fn from(n: u16) -> Self { if n == u16::max_value() { Self(None) } else { Self(NonZeroU16::new(n + 1)) } } } impl Into for OptU16 { /// Returns the `u16` representation. /// /// # Examples /// /// ``` /// use maraiah::bin::OptU16; /// /// let u16_max = u16::max_value(); /// /// // These type annotations are necessary. /// /// assert_eq!(>::into(OptU16::from(500u16)), 500u16); /// assert_eq!(>::into(OptU16::from(u16_max)), u16_max); /// assert_eq!(>::into(OptU16::from(0u16)), 0u16); /// ``` #[inline] fn into(self) -> u16 { match self.0 { None => u16::max_value(), Some(n) => n.get() - 1, } } } impl OptU16 { /// Creates an `OptU16` representing `None`. /// /// # Examples /// /// ``` /// use maraiah::bin::OptU16; /// /// assert_eq!(OptU16::none(), OptU16::from(u16::max_value())); /// ``` #[inline] pub const fn none() -> Self {Self(None)} /// Returns the `Option` representation. /// /// # Examples /// /// ``` /// use maraiah::bin::OptU16; /// /// assert_eq!(OptU16::from(500u16).get(), Some(500u16)); /// assert_eq!(OptU16::from(u16::max_value()).get(), None); /// assert_eq!(OptU16::from(0u16).get(), Some(0u16)); /// ``` #[inline] pub fn get(self) -> Option { match self.0 { None => None, Some(n) => Some(n.get() - 1), } } /// Return the memory representation of this integer as a byte array /// in big-endian (network) byte order. #[inline] pub fn to_be_bytes(self) -> [u8; 2] { >::into(self).to_be_bytes() } /// Return the memory representation of this integer as a byte array /// in little-endian byte order. #[inline] pub fn to_le_bytes(self) -> [u8; 2] { >::into(self).to_le_bytes() } } impl fmt::Display for OptU16 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.get() { None => write!(f, "None"), Some(n) => write!(f, "Some({})", n), } } } impl fmt::Debug for OptU16 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.get() { None => write!(f, "OptU16::none()"), Some(n) => write!(f, "OptU16::from({})", n), } } } impl PartialEq<[u8; 4]> for Ident { #[inline] fn eq(&self, o: &[u8; 4]) -> bool {self.0 == *o} } impl<'a> PartialEq<[u8; 4]> for &'a Ident { #[inline] fn eq(&self, o: &[u8; 4]) -> bool {PartialEq::eq(*self, o)} } impl<'a> PartialEq<&'a [u8; 4]> for Ident { #[inline] fn eq(&self, o: &&'a [u8; 4]) -> bool {PartialEq::eq(self, *o)} } /// A four-character-code identifier. /// /// # Examples /// /// ``` /// use maraiah::bin::Ident; /// /// assert_eq!(Ident(*b"POLY").0, *b"POLY"); /// assert_eq!(Ident(*b"POLY"), *b"POLY"); /// assert_eq!(Ident(*b"POLY"), b"POLY"); /// assert_eq!(&Ident(*b"POLY"), *b"POLY"); /// assert_eq!(&Ident(*b"POLY"), b"POLY"); /// ``` #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))] pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]); /// An object identified by a `u16` which may be `u16::max_value()` to /// represent a nulled value. #[derive(Clone, Copy, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))] pub struct OptU16(Option); // EOF