raad crate for eating and pushing bytes
init
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.toml | 16 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 50 | ||||
| -rw-r--r-- | src/lib.rs | 341 |
5 files changed, 429 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffa3bbd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Cargo.lock
\ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4081473 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "raad" +version = "0.1.0" +authors = ["bend-n <[email protected]>"] +license = "MIT" +edition = "2021" +description = "raad library for reading and writing bytes" +repository = "https://github.com/bend-n/raad" +exclude = ["tdata", "benches/", ".gitignore"] +categories = ["encoding", "parsing"] +rust-version = "1.79" +keywords = ["data", "read", "write", "io"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 bendn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7723183 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# rad crate for r/w + +This crate provides neat ways to eat bytes out of your favorite [readers](https://doc.rust-lang.org/std/io/trait.Read.html) and push bytes into cute [writers](https://doc.rust-lang.org/std/io/trait.Write.html). + +This crate has three modules, one for each kind of [endianness](https://en.wikipedia.org/wiki/Endianness): [`be`](https://docs.rs/raad/latest/raad/be/index.html) (big endian), [`le`](https://docs.rs/raad/latest/raad/le/index.html) (little endian), and [`ne`](https://docs.rs/raad/latest/raad/ne/index.html) (native endian-- whatever your system is on) + +# Examples + +Read unsigned 16 bit big-endian integers from a [`Reader`](https://doc.rust-lang.org/std/io/trait.Read.html): + +```rust +use raad::be::*; // < note how we specify we want big endian when we import the trait +let mut rdr = &mut &[02, 05, 03, 00][..]; +assert_eq!([0x0205, 0x0300], rdr.r::<[u16; 2]>().unwrap()); +``` + +Write unsigned 16 bit little-endian integers to a [`Writer`](https://doc.rust-lang.org/std/io/trait.Write.html): + +```rust +use raad::le::*; // and here we specify little endian +let mut wtr = vec![]; +wtr.w([0x0205u16, 0x0300]).unwrap(); +assert_eq!(wtr, vec![05, 02, 00, 03]); +``` + +# Why + +These helpers can greatly increase the ease of reading numbers and other things from a file/… + +See, to read 3 u64s from a reader, you would have to go through all this trouble: + +```rust +use std::io::Read; +fn read3(t: &mut impl Read) -> std::io::Result<[u64; 3]> { + let mut out = [0; 3]; + let mut tmp = [0; 8]; + for elem in &mut out { + t.read_exact(&mut tmp)?; + *elem = u64::from_ne_bytes(tmp); + } + Ok(out) +} +``` + +wheras, with this crate, its as simple as + +```rust,ignore +use raad::ne::*; +t.read::<[u64; 3]>(); +``` diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1a8375b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,341 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn( + clippy::undocumented_unsafe_blocks, + clippy::missing_const_for_fn, + clippy::missing_safety_doc, + clippy::suboptimal_flops, + unsafe_op_in_unsafe_fn, + clippy::dbg_macro, + clippy::use_self, + missing_docs +)] +#![allow( + private_bounds, + clippy::zero_prefixed_literal, + mixed_script_confusables, + confusable_idents +)] +use std::mem::ManuallyDrop as MD; +/// # Safety +/// +/// this is a transmute. what do you want me to say. the types should, yknow, work? idk. +const unsafe fn transmute_unchecked<T, U>(value: T) -> U { + // SAFETY: transmutation + unsafe { + #[repr(C)] + union Transmute<T, U> { + t: MD<T>, + u: MD<U>, + } + MD::into_inner(Transmute { t: MD::new(value) }.u) + } +} + +macro_rules! trt { + (r $(#[doc = $x: expr])+ f $(#[doc = $z: expr])+ w $(#[doc = $y: expr])+) => { + use std::io::prelude::*; + use std::io::Result; + use std::mem::MaybeUninit as MU; + + $(#[doc = $x])+ + pub trait R: Read { + $(#[doc = $z])+ + fn r<T: Readable>(&mut self) -> Result<T>; + /// Reads one byte out of a [`Reader`](Read). + /// ``` + /// use raad::ne::*; + /// let mut d = &mut &[1u8, 2][..]; + /// assert_eq!(d.b().unwrap(), 1); + /// assert_eq!(d.b().unwrap(), 2); + /// assert!(d.b().is_err()); + /// ``` + fn b(&mut self) -> Result<u8> { + self.r::<u8>() + } + } + + #[doc(hidden)] + impl<D: Read> R for D { + fn r<T: Readable>(&mut self) -> Result<T> { + T::r(self) + } + } + trait Readable + where + Self: Sized, + { + fn r(from: &mut impl Read) -> Result<Self>; + } + + impl<const N: usize> Readable for [u8; N] { + fn r(from: &mut impl Read) -> Result<[u8; N]> { + let mut buf = [0; N]; + from.read_exact(&mut buf).map(|()| buf) + } + } + + $(#[doc = $y])+ + pub trait W: Write { + /// Writes a type to a [`Writer`](Write) + fn w<T: Writable>(&mut self, data: T) -> Result<()>; + } + + #[doc(hidden)] + impl<D: Write> W for D { + fn w<T: Writable>(&mut self, data: T) -> Result<()> { + data._w(self) + } + } + trait Writable { + fn _w(self, to: &mut impl Write) -> Result<()>; + } + + impl Writable for &[u8] { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.write_all(self) + } + } + }; +} + +macro_rules! n { + (writes $bytes:ident $($n:ident)+) => { + $( + impl Writable for &[$n] { + fn _w(self, to: &mut impl Write) -> Result<()> { + if (cfg!(target_endian = "little") && stringify!($bytes) == "le") || (cfg!(target_endian = "big") && stringify!($bytes) == "be") { + // SAFETY: len correct + to.w(unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * ($n::BITS / 8) as usize) }) + } else { + self.iter().try_for_each(|x| to.w(x)) + } + } + } + impl<const N: usize> Readable for [$n; N] { + fn r(from: &mut impl Read) -> Result<[$n; N]> { + if (cfg!(target_endian = "little") && stringify!($bytes) == "le") || (cfg!(target_endian = "big") && stringify!($bytes) == "be") { + let mut buf = [0; N]; + // SAFETY: len matches + let mut u8s = unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, N * ($n::BITS / 8) as usize) }; + from.read_exact(&mut u8s).map(|()| buf) + } else { + let mut buf = [MU::<$n>::uninit(); N]; + for elem in &mut buf{ + elem.write(from.r::<$n>()?); + } + // SAFETY: array init + Ok(unsafe { crate::transmute_unchecked(buf) }) + } + } + } + )+ + }; + (float $bytes:ident $([$n:ident <=> $int:ident])+) => { + $( + impl Readable for $n { + fn r(from: &mut impl Read) -> Result<$n> { + from.r::<$int>().map($n::from_bits) + } + } + )+ + + $( + impl Writable for $n { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(self.to_bits()) + } + } + impl Writable for &$n { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(self.to_bits()) + } + } + macro_rules! bytes { + ($t:ty) => { + impl Writable for $t { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(&*self) + } + } + } + } + bytes![Vec<$n>]; + bytes![Box<[$n]>]; + impl<const N: usize> Writable for [$n; N] { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(&self[..]) + } + } + + impl Writable for &[$n] { + fn _w(self, to: &mut impl Write) -> Result<()> { + if (cfg!(target_endian = "little") && stringify!($bytes) == "le") || (cfg!(target_endian = "big") && stringify!($bytes) == "be") { + // SAFETY: len correct + to.w(unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * ($int::BITS / 8) as usize) }) + } else { + self.iter().try_for_each(|x| to.w(x)) + } + } + } + impl<const N: usize> Readable for [$n; N] { + fn r(from: &mut impl Read) -> Result<[$n; N]> { + if (cfg!(target_endian = "little") && stringify!($bytes) == "le") || (cfg!(target_endian = "big") && stringify!($bytes) == "be") { + let mut buf = [0.; N]; + // SAFETY: len matches + let mut u8s = unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, N * ($int::BITS / 8) as usize) }; + from.read_exact(&mut u8s).map(|()| buf) + } else { + let mut buf = [MU::<$n>::uninit(); N]; + for elem in &mut buf{ + elem.write(from.r::<$n>()?); + } + // SAFETY: array init + Ok(unsafe { crate::transmute_unchecked(buf) }) + } + } + } + )+ + }; + ($bytes:ident $($n:ident)+) => { + $( + impl Readable for $n { + fn r(from: &mut impl Read) -> Result<$n> { + from.r::<[u8; { std::mem::size_of::<$n>() }]>().map($n::from_ne_bytes).map($n::$bytes) + } + } + )+ + + $( + impl Writable for $n { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(self.$bytes().to_ne_bytes()) + } + } + impl Writable for &$n { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(self.$bytes().to_ne_bytes()) + } + } + macro_rules! bytes { + ($t:ty) => { + impl Writable for $t { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(&*self) + } + } + } + } + bytes![Vec<$n>]; + bytes![Box<[$n]>]; + impl<const N: usize> Writable for [$n; N] { + fn _w(self, to: &mut impl Write) -> Result<()> { + to.w(&self[..]) + } + } + )+ + }; +} + +macro_rules! test { + () => { + #[test] + fn x() { + let data = &mut &[0x12u8, 0x15][..]; + let mut out = vec![]; + out.w(data.r::<[u16; 1]>().unwrap()).unwrap(); + assert_eq!(out, [0x12, 0x15]); + + let mut out = vec![]; + out.w([12.0_f32, 13.]).unwrap(); + assert_eq!((&mut &*out).r::<[f32; 2]>().unwrap(), [12., 13.]); + } + }; +} +pub mod le { + //! little endian readers and writers + trt!( + r /// Read little endian (commonly native) data. + /// This trait provides a [`r`](R::r) method for easy reading. + /// + /// Without this crate, you would have to do things such as: + /// ``` + /// use std::io::Read; + /// let mut data = &mut &[0xff, 0xf1][..]; + /// let mut two_bytes = [0; 2]; + /// data.read(&mut two_bytes).unwrap(); + /// assert_eq!(u16::from_le_bytes(two_bytes), 0xf1ff) + /// ``` + /// Now, you can simply: + /// ``` + /// use raad::le::*; + /// let mut data = &mut &[0xff, 0xf1][..]; + /// assert_eq!(data.r::<u16>().unwrap(), 0xf1ff); + /// ``` + f /// Read a little endian type. + /// ``` + /// # #![allow(overflowing_literals)] + /// use raad::le::*; + /// let mut data = &mut &[0xc1, 0x00, 0x7c, 0xff][..]; + /// assert_eq!(data.r::<[i16; 2]>().unwrap(), [0x00c1, 0xff7c]); + /// ``` + w /// Write little endian (commonly native) data. + /// ``` + /// # use raad::le::*; + /// let mut wtr = Vec::new(); + /// wtr.w::<[u32; 2]>([267, 1205419366]).unwrap(); + /// assert_eq!(wtr, [11, 1, 0, 0, 102, 61, 217, 71]); + /// ``` + ); + n![writes le u16 u32 u64 u128 i8 i16 i32 i64 i128]; + n![float le [f32 <=> u32] [f64 <=> u64]]; + n![to_le u8 u16 u32 u64 u128 i8 i16 i32 i64 i128]; + test![]; +} + +#[doc(alias = "network")] +pub mod be { + //! big endian readers and writers + trt!( + r /// Read big endian (network) data. + /// ``` + /// use raad::be::*; + /// // this example doesnt actually care about endianness-- u8's dont have any. + /// let mut data = &mut &[2u8, 5, 1, 4, 3][..]; + /// assert_eq!(data.r::<[u8; 5]>().unwrap(), [2, 5, 1, 4, 3]); + /// ``` + f /// Read a big endian (network) type. + /// ``` + /// use raad::be::*; + /// let mut data: &mut &[u8] = &mut &[ + /// 0x00, 0x03, 0x43, 0x95, 0x4d, 0x60, 0x86, 0x83, + /// 0x00, 0x03, 0x43, 0x95, 0x4d, 0x60, 0x86, 0x83 + /// ][..]; + /// assert_eq!( + /// data.r::<u128>().unwrap(), + /// 16947640962301618749969007319746179 + /// ); + /// ``` + w /// Write a big endian (network) type. + /// ``` + /// use raad::be::*; + /// let mut wtr = Vec::new(); + /// wtr.w::<[u16; 2]>([517, 768]).unwrap(); + /// assert_eq!(wtr, b"\x02\x05\x03\x00"); + /// ``` + ); + n![writes be u16 u32 u64 u128 i8 i16 i32 i64 i128]; + n![float be [f32 <=> u32] [f64 <=> u64]]; + n![to_be u8 u16 u32 u64 u128 i8 i16 i32 i64 i128]; + test![]; +} + +pub mod ne { + //! native endian readers and writers + #[cfg(target_endian = "big")] + #[doc(inline)] + pub use super::be::{R, W}; + #[cfg(target_endian = "little")] + #[doc(inline)] + pub use super::le::{R, W}; +} |