From e9ee282e14a7aa7ab2dc90d475086da0dfe06151 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 13:29:14 -0500 Subject: [PATCH 01/26] rfc: Improve io --- Cargo.toml | 6 +- README.md | 4 +- src/error.rs | 51 --- src/io.rs | 979 ++++++++++++++++++++++++------------------- src/lib.rs | 447 +------------------- src/pool.rs | 7 + src/stream.rs | 1 + src/u24_impl.rs | 187 --------- src/varint.rs | 301 ------------- tests/enums.rs | 37 -- tests/format.rs | 22 - tests/io.rs | 47 --- tests/le_test.rs | 48 --- tests/lstring.rs | 65 --- tests/macro_tests.rs | 49 --- tests/no_init.rs | 14 - tests/socket.rs | 10 - tests/tests.rs | 9 - tests/var_int.rs | 50 --- tests/varint.rs | 93 ++++ tests/vec.rs | 30 -- 21 files changed, 675 insertions(+), 1782 deletions(-) delete mode 100644 src/error.rs create mode 100644 src/pool.rs create mode 100644 src/stream.rs delete mode 100644 src/u24_impl.rs delete mode 100644 src/varint.rs delete mode 100644 tests/enums.rs delete mode 100644 tests/format.rs delete mode 100644 tests/io.rs delete mode 100644 tests/le_test.rs delete mode 100644 tests/lstring.rs delete mode 100644 tests/macro_tests.rs delete mode 100644 tests/no_init.rs delete mode 100644 tests/socket.rs delete mode 100644 tests/tests.rs delete mode 100644 tests/var_int.rs create mode 100644 tests/varint.rs delete mode 100644 tests/vec.rs diff --git a/Cargo.toml b/Cargo.toml index 4329595..65e81af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "binary_utils" -version = "0.2.2" +version = "0.2.3" authors = ["Bavfalcon9"] edition = "2021" include = ["src/**/*", "README.md"] [dependencies] -byteorder = "1.4.3" bin_macro = { path = "./bin_macro" } +bytes = "1.4.0" -[features] +[features] \ No newline at end of file diff --git a/README.md b/README.md index 90a17da..4cc893a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,2 @@ # BinaryUtil -Binary Utilities for Netrex - -For API reference refer to the wiki. \ No newline at end of file +A library for safely reading and writing binary data. Specifications follow the [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/encoding) encoding. \ No newline at end of file diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 908ad42..0000000 --- a/src/error.rs +++ /dev/null @@ -1,51 +0,0 @@ -/// An enum consisting of a Binary Error -/// (recoverable) -#[derive(Debug, PartialEq)] -pub enum BinaryError { - /// Offset is out of bounds - /// - /// **Tuple Values:** - /// - `usize` = Given Offset. - /// - `usize` = Stream length. - /// - `&'static str` = Message to add on to the error. - OutOfBounds(usize, usize, &'static str), - - /// Similar to `OutOfBounds` except it means; - /// the stream tried to read more than possible. - /// - /// **Tuple Values:** - /// - `usize` = Stream length. - EOF(usize), - - /// A known error that was recoverable to safely proceed the stack. - RecoverableKnown(String), - - /// An unknown error occurred, but it wasn't critical, - /// we can safely proceed on the stack. - RecoverableUnknown, -} - -impl BinaryError { - pub fn get_message(&self) -> String { - match self { - Self::OutOfBounds(offset, length, append) => { - format!("Offset {} out of range for a buffer size with: {}. {}", offset, length, append) - }, - Self::EOF(length) => format!("Buffer reached End Of File at offset: {}", length), - Self::RecoverableKnown(msg) => msg.clone(), - Self::RecoverableUnknown => "An interruption occurred when performing a binary operation, however this error was recovered safely.".to_string() - } - } -} - -impl From for BinaryError { - fn from(_error: std::io::Error) -> Self { - Self::RecoverableUnknown - } -} - -impl std::fmt::Display for BinaryError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.get_message()) - } -} diff --git a/src/io.rs b/src/io.rs index ab6eb2a..24d3dc4 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,471 +1,602 @@ -use std::io::{self, Result}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use std::{ + collections::VecDeque, + io::{Error, IoSlice}, +}; -use byteorder::ByteOrder; -use byteorder::ReadBytesExt; -use byteorder::WriteBytesExt; +pub const ERR_EOB: &str = "No more bytes left to be read in buffer"; +pub const ERR_EOM: &str = "Buffer is full, cannot write more bytes"; +pub const ERR_VARINT_TOO_LONG: &str = "Varint is too long to be written to buffer"; -use crate::*; -pub trait BinaryReader: ReadBytesExt + Clone { - /// Reads a `u32` variable length integer from the stream. - #[inline] - fn read_u32_varint(&mut self) -> Result> { - // this is EXTREMELY hacky! - let mut ref_to = self.clone(); - let most = &mut [0; 5]; - let four = &mut [0; 4]; - let three = &mut [0; 3]; - let two = &mut [0; 2]; - - if let Err(_) = ref_to.read_exact(&mut most[..]) { - // there was an error with the buffer size. - // we're going to incrementally, decrease the required size until 0 - if let Ok(_) = ref_to.read_exact(&mut four[..]) { - if let Ok(var) = VarInt::::compose(&four[..], &mut 0) { - return Ok(var); - } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Could not read varint", - )); - } - } else if let Ok(_) = ref_to.read_exact(&mut three[..]) { - if let Ok(var) = VarInt::::compose(&three[..], &mut 0) { - return Ok(var); - } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Could not read varint", - )); - } - } else if let Ok(_) = ref_to.read_exact(&mut two[..]) { - if let Ok(var) = VarInt::::compose(&two[..], &mut 0) { - return Ok(var); - } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Could not read varint", - )); - } +macro_rules! can_read { + ($self: ident, $size: expr) => { + $self.buf.remaining() > $size + }; +} + +macro_rules! can_write { + ($self: ident, $size: expr) => { + $self.buf.remaining_mut() >= $size + }; +} + +macro_rules! read_fn { + ($name: ident, $typ: ident, $fn_name: ident, $byte_size: literal) => { + #[inline] + pub fn $name(&mut self) -> Result<$typ, std::io::Error> { + if can_read!(self, $byte_size) { + return Ok(self.buf.$fn_name()); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } + } + }; +} + +macro_rules! write_fn { + ($name: ident, $typ: ident, $fn_name: ident, $byte_size: literal) => { + #[inline] + pub fn $name(&mut self, num: $typ) -> Result<(), std::io::Error> { + if can_write!(self, $byte_size) { + self.buf.$fn_name(num); + return Ok(()); } else { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "unable to read varint", - )); + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); } + } + }; +} + +/// ByteReader is a panic-free way to read bytes from a `Buf` trait. +/// +/// Each read method will add a 3 OP calls to `Buf` trait. +pub struct ByteReader { + pub buf: Bytes, +} + +impl Into for ByteReader { + fn into(self) -> Bytes { + self.buf + } +} + +impl Into> for ByteReader { + fn into(self) -> Vec { + self.buf.to_vec() + } +} + +impl Into> for ByteReader { + fn into(self) -> VecDeque { + self.buf.to_vec().into() + } +} + +impl From for ByteReader { + fn from(buf: Bytes) -> Self { + Self { buf } + } +} + +impl From> for ByteReader { + fn from(buf: Vec) -> Self { + Self { buf: buf.into() } + } +} + +impl From<&[u8]> for ByteReader { + fn from(buf: &[u8]) -> Self { + Self { buf: Bytes::from(buf.to_vec()) } + } +} + +impl ByteReader { + /// This is a function intended to be used to "peek ahead at x byte" on the given stream. + /// This WILL NOT advance the stream! + pub fn peek_ahead(&mut self, pos: usize) -> Result { + if can_read!(self, pos) { + return Ok(self.buf.chunk()[pos]); } else { - if let Ok(var) = VarInt::::compose(&most[..], &mut 0) { - return Ok(var); + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } + } + + read_fn!(read_u8, u8, get_u8, 1); + read_fn!(read_u16, u16, get_u16, 2); + read_fn!(read_u16_le, u16, get_u16_le, 2); + read_fn!(read_i16, i16, get_i16, 2); + read_fn!(read_i16_le, i16, get_i16_le, 2); + + /// Reads a 3-byte unsigned integer from the stream. + pub fn read_u24(&mut self) -> Result { + if can_read!(self, 3) { + if let Ok(num) = self.read_uint(3) { + return Ok(num as u32); } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Could not read varint", - )); + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } } - /// Reads a `u64` variable length integer from the stream. - #[inline] - fn read_u64_varint(&mut self) -> Result> { - // this is EXTREMELY hacky! - let mut current_buffer: Vec = Vec::new(); - - while current_buffer.len() <= 4 { - // read a byte! - let byte = self.read_u8()?; - current_buffer.push(byte); - - // try making a var_int from the current buffer - if let Ok(i) = VarInt::::compose(¤t_buffer[..], &mut 0) { - return Ok(i); + /// Reads a 3-byte unsigned integer from the stream in little endian. + /// This is the same as `read_u24` but in little endian. + pub fn read_u24_le(&mut self) -> Result { + if can_read!(self, 3) { + if let Ok(num) = self.read_uint_le(3) { + return Ok(num as u32); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } + } - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Could not read varint", - )); + pub fn read_i24(&mut self) -> Result { + if can_read!(self, 3) { + if let Ok(num) = self.read_int(3) { + return Ok(num as i32); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } } - /// Reads a string sized by a `u16`. - #[inline] - fn read_string(&mut self) -> Result - where - Endianess: ByteOrder, - { - let t = self.clone(); - let length = self.read_u16::()?; - let mut string_data = Vec::new(); - t.take(length as u64).read_to_end(&mut string_data)?; - self.read(&mut string_data[..])?; - Ok(unsafe { String::from_utf8_unchecked(string_data.to_vec()) }) - } - - /// Reads a string sized by a `u32`. - #[inline] - fn read_string_u32(&mut self) -> Result - where - Endianess: ByteOrder, - { - let t = self.clone(); - let length = self.read_u32::()?; - let mut string_data = Vec::new(); - t.take(length as u64).read_to_end(&mut string_data)?; - self.read(&mut string_data[..])?; - Ok(unsafe { String::from_utf8_unchecked(string_data.to_vec()) }) - } - - /// Reads a string that will be sized by a u64. - /// ```rust ignore - /// use binary_utils::{VarInt, io::BinaryWriter}; + read_fn!(read_u32, u32, get_u32, 4); + read_fn!(read_u32_le, u32, get_u32_le, 4); + read_fn!(read_f32, f32, get_f32, 4); + read_fn!(read_f32_le, f32, get_f32_le, 4); + + /// Reads a var-int 32-bit unsigned integer from the stream. + /// This is a variable length integer that can be 1, 2, 3, or 4 bytes long. + /// + /// This function is recoverable, meaning that if the stream ends before the + /// var-int is fully read, it will return an error, and will not consume the + /// bytes that were read. + /// + /// #### Decoding VarInt + /// You will need to: + /// 1. Read the next byte + /// 2. Get the 7 significant bits (next byte & 0x7F [segment bit]) (continuation bit is 0x80) + /// 3. Shift the value to the left by i bits + /// 4. Validate whether this is the last byte (next byte & 0x80 == 0) + /// 5. IF the last byte, return the value and + /// add the iterative value (current byte) to the current value. /// - /// let mut stream = Vec::new(); - /// stream.write_string_u64::("Hello World").unwrap(); + /// A visual representation of how this works: + /// ```txt + /// ======================================================== + /// Buffer: 0xdd 0xc7 0x01 + /// + /// Initial values: + /// i = 0 Current iteration + /// v = 0 Current value + /// + /// ------------------------------------------------------- + /// First iteration (i = 0): + /// ------------------------------------------------------- + /// 0xdd 0xc7 0x01 + /// ^ - b + /// b = 0xdd 11011101 - Next byte + /// b & 0x7F 1011101 - 7 significant bits + /// b << 0 1011101 - Shifted by iteration bits + /// b & 0x80 == 0 false - There are more bytes + /// v =| 1011101 (93) 1011101 - Added to the current value + /// i = i + 1: 1 - Current iteration + /// + /// ------------------------------------------------------- + /// Second iteration (i = 7): + /// ------------------------------------------------------- + /// 0xdd 0xc7 0x01 + /// ^ - b + /// b = 0xc7 11000111 - Next byte + /// b & 0x7F 1000111 - 7 significant bits + /// b << 7 - Shifted by iteration bits + /// | -> 10001110000000 + /// b & 0x80 == 0 false - There are more bytes + /// v =| b - Added to the current value + /// | -> 110001111011101 (25565) + /// + /// ------------------------------------------------------- + /// Third iteration (i = 14): + /// ------------------------------------------------------- + /// 0xdd 0xc7 0x01 + /// ^ - b + /// 0x01 00000001 - Next byte + /// b & 0x7F 0000001 - 7 significant bits + /// b << 14 - Shifted by iteration bits + /// |-> 000000100000000 + /// b & 0x80 == 0 true - This byte is the last byte + /// v = 25565 - Last bit, return the value + /// |-> 110001111011101 (25565) /// ``` #[inline] - fn read_string_u64(&mut self) -> Result - where - Endianess: ByteOrder, - { - let t = self.clone(); - let length = self.read_u64::()?; - let mut string_data = Vec::new(); - t.take(length as u64).read_to_end(&mut string_data)?; - self.read(&mut string_data[..])?; - Ok(unsafe { String::from_utf8_unchecked(string_data.to_vec()) }) - } - - // /// Reads an array to the stream. This array will - // /// be sized by a short (u16) with the contents being - // /// a vector of `Streamable` types. - // /// ```rust ignore - // /// use binary_utils::{Streamable, io::BinaryWriter}; - // /// let my_vec: Vec = vec!["Hello", "World"]; - // /// let mut stream = Vec::new(); - // /// stream.write_array(my_vec).unwrap(); - // /// ``` - // fn write_array(&mut self, value: Vec) -> Result<()> - // where - // T: Sized + Streamable, - // Endianess: ByteOrder, - // { - // self.write_u16::(value.len() as u16)?; - // for x in value { - // let res = x.parse(); - // if let Ok(v) = res { - // self.write_all(&v[..])?; - // } else if let Err(e) = res { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // format!("Array Item could not be parsed due to a Binary Error: {}", e), - // )); - // } else { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // "Array Item could not be parsed due to an unknown error while parsing.", - // )); - // } - // } - // Ok(()) - // } - - // /// Reads an array to the stream. This array will - // /// be sized by a `u32` with the contents being - // /// a vector of `Streamable` types. - // fn write_array_u32(&mut self, value: Vec) -> Result<()> - // where - // T: Streamable, - // Endianess: ByteOrder, - // { - // self.write_u32::(value.len() as u32)?; - // for x in value { - // let res = x.parse(); - // if let Ok(v) = res { - // self.write_all(&v[..])?; - // } else if let Err(e) = res { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // format!("Array Item could not be parsed due to a Binary Error: {}", e), - // )); - // } else { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // "Array Item could not be parsed due to an unknown error while parsing.", - // )); - // } - // } - // Ok(()) - // } - - // /// Reads an array to the stream. This array will - // /// be sized by a `u64` with the contents being - // /// a vector of `Streamable` types. - // fn write_array_u64(&mut self, value: Vec) -> Result<()> - // where - // T: Streamable, - // Endianess: ByteOrder, - // { - // self.write_u64::(value.len() as u64)?; - // for x in value { - // let res = x.parse(); - // if let Ok(v) = res { - // self.write_all(&v[..])?; - // } else if let Err(e) = res { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // format!("Array Item could not be parsed due to a Binary Error: {}", e), - // )); - // } else { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // "Array Item could not be parsed due to an unknown error while parsing.", - // )); - // } - // } - // Ok(()) - // } - - // /// Reads a socket address fron the stream. - // #[inline] - // fn write_socket_addr(&mut self, address: SocketAddr) -> Result<()> { - // if let Ok(v) = address.parse() { - // self.write_all(&v[..])?; - // return Ok(()); - // } else { - // return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // "Invalid Socket Address.", - // )); - // } - // } - - // /// Writes a bool to the stream. - // /// ```rust ignore - // /// use binary_utils::io::BinaryWriter; - // /// - // /// let mut stream = Vec::new(); - // /// stream.write_bool(true) - // /// ``` - // #[inline] - // fn write_bool(&mut self, value: bool) -> Result<()> { - // self.write_u8(if value { 1 } else { 0 })?; - // Ok(()) - // } -} + pub fn read_var_u32(&mut self) -> Result { + let mut num = 0u32; + let mut interval = 0_usize; + for i in (0..35).step_by(7) { + let byte = self.peek_ahead(interval)?; + + num |= ((byte & 0x7F) as u32) << i; + interval += 1; + + if byte & 0x80 == 0 { + self.buf.advance(interval); + return Ok(num); + } + } + return Err(Error::new( + std::io::ErrorKind::Other, + "Varint overflow's 32-bit integer", + )); + } + + read_fn!(read_i32, i32, get_i32, 4); + read_fn!(read_i32_le, i32, get_i32_le, 4); + + /// Reads a var-int 32-bit signed integer from the stream. + /// This method is the same as `read_var_u32` but it will return a signed integer. + pub fn read_var_i32(&mut self) -> Result { + // todo: fails on -2147483648, which is the minimum value for i32 + // todo: probably nothing to worry about, but should be fixed + let num = self.read_var_u32()?; + Ok((num >> 1) as i32 ^ -((num & 1) as i32)) // does not work on large numbers + // return Ok(if num & 1 != 0 { + // !((num >> 1) as i32) + // } else { + // (num >> 1) as i32 + // }); + } + + read_fn!(read_u64, u64, get_u64, 8); + read_fn!(read_u64_le, u64, get_u64_le, 8); + read_fn!(read_i64, i64, get_i64, 8); + read_fn!(read_i64_le, i64, get_i64_le, 8); + read_fn!(read_f64, f64, get_f64, 8); + read_fn!(read_f64_le, f64, get_f64_le, 8); + + /// Reads a var-int 64-bit unsigned integer from the stream. + /// This is a variable length integer that can be 1, 2, 3, 4, 5, 6, 7, or 8 bytes long. + #[inline] + pub fn read_var_u64(&mut self) -> Result { + let mut num = 0u64; + let mut interval = 0_usize; + for i in (0..70).step_by(7) { + let byte = self.peek_ahead(interval)?; -/// All types that implement `Write` get methods defined in `BinaryWriter` -/// for free. -impl BinaryReader for R {} + num |= ((byte & 0x7F) as u64) << i; + interval += 1; -pub trait BinaryWriter: WriteBytesExt { - /// Writes a `u32` variable length integer to the stream. - /// ```rust ignore - /// use binary_utils::{VarInt, io::BinaryWriter}; + if byte & 0x80 == 0 { + self.buf.advance(interval); + return Ok(num); + } + } + return Err(Error::new( + std::io::ErrorKind::Other, + "Varint overflow's 64-bit integer", + )); + } + + /// Reads a var-int 64-bit signed integer from the stream. + /// This method is the same as `read_var_u64` but it will return a signed integer. /// - /// let mut stream = Vec::new(); - /// stream.write_u32_varint(VarInt::(0x12345678)).unwrap(); - /// ``` + /// For more information on how this works, see `read_var_i32`. #[inline] - fn write_u32_varint(&mut self, value: VarInt) -> Result<()> { - if let Ok(v) = value.parse() { - self.write_all(&v[..])?; + pub fn read_var_i64(&mut self) -> Result { + let num = self.read_var_u64()?; + Ok((num >> 1) as i64 ^ -((num & 1) as i64)) + } + + read_fn!(read_u128, u128, get_u128, 16); + read_fn!(read_u128_le, u128, get_u128_le, 16); + read_fn!(read_i128, i128, get_i128, 16); + read_fn!(read_i128_le, i128, get_i128_le, 16); + + /// Reads an unsigned integer from the stream with a varying size + /// indicated by the `size` parameter. + pub fn read_uint(&mut self, size: usize) -> Result { + // todo: Check whether we should use `copy_nonoverlapping` or `self.get_uint` + if can_read!(self, size) { + let mut num = 0u64; + let ptr_to = &mut num as *mut u64 as *mut u8; + // we're not using for loops because they're slower + unsafe { + core::ptr::copy_nonoverlapping(self.buf.chunk().as_ptr(), ptr_to, size); + } + // increment the position + self.buf.advance(size); + return Ok(num.to_be()); } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "VarInt is too big to be written", - )); + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } + } - Ok(()) + /// Reads an unsigned integer from the stream with a varying size in little endian + /// indicated by the `size` parameter. + pub fn read_uint_le(&mut self, size: usize) -> Result { + if can_read!(self, size) { + let mut num = 0u64; + let ptr_to = &mut num as *mut u64 as *mut u8; + unsafe { + core::ptr::copy_nonoverlapping(self.buf.chunk().as_ptr(), ptr_to, size); + } + // increment the position + self.buf.advance(size); + return Ok(num.to_le()); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } } - /// Writes a `u64` variable length integer to the stream. - #[inline] - fn write_u64_varint(&mut self, value: VarInt) -> Result<()> { - if let Ok(v) = value.parse() { - self.write_all(&v[..])?; + pub fn read_int(&mut self, size: usize) -> Result { + if can_read!(self, size) { + return Ok(self.buf.get_int(size)); } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "VarInt is too big to be written", - )); + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } + } - Ok(()) + pub fn read_int_le(&mut self, size: usize) -> Result { + if can_read!(self, size) { + return Ok(self.buf.get_int_le(size)); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } } - /// Writes a string sized by a `u16`. - #[inline] - fn write_string(&mut self, value: Str) -> Result<()> - where - Str: Into, - Endianess: ByteOrder, - { - let value: String = value.into(); - self.write_u16::(value.len() as u16)?; - self.write_all(value.as_bytes())?; - Ok(()) - } - - /// Writes a string sized by a `u32`. - #[inline] - fn write_string_u32(&mut self, value: Str) -> Result<()> - where - Str: Into, - Endianess: ByteOrder, - { - let value: String = value.into(); - self.write_u32::(value.len() as u32)?; - self.write_all(value.as_bytes())?; - Ok(()) - } - - /// Writes a string that will be sized by a u64. - /// ```rust ignore - /// use binary_utils::{VarInt, io::BinaryWriter}; - /// - /// let mut stream = Vec::new(); - /// stream.write_string_u64::("Hello World").unwrap(); - /// ``` - #[inline] - fn write_string_u64(&mut self, value: Str) -> Result<()> - where - Str: Into, - Endianess: ByteOrder, - { - let value: String = value.into(); - self.write_u64::(value.len() as u64)?; - self.write_all(value.as_bytes())?; - Ok(()) - } - - /// Writes an array to the stream. This array will - /// be sized by a short (u16) with the contents being - /// a vector of `Streamable` types. - /// ```rust ignore - /// use binary_utils::{Streamable, io::BinaryWriter}; - /// let my_vec: Vec = vec!["Hello", "World"]; - /// let mut stream = Vec::new(); - /// stream.write_array(my_vec).unwrap(); - /// ``` - fn write_array(&mut self, value: Vec) -> Result<()> - where - T: Sized + Streamable, - Endianess: ByteOrder, - { - self.write_u16::(value.len() as u16)?; - for x in value { - let res = x.parse(); - if let Ok(v) = res { - self.write_all(&v[..])?; - } else if let Err(e) = res { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!( - "Array Item could not be parsed due to a Binary Error: {}", - e - ), - )); - } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Array Item could not be parsed due to an unknown error while parsing.", - )); - } + pub fn read_bool(&mut self) -> Result { + if can_read!(self, 1) { + return Ok(self.buf.get_u8() != 0); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } - Ok(()) - } - - /// Writes an array to the stream. This array will - /// be sized by a `u32` with the contents being - /// a vector of `Streamable` types. - fn write_array_u32(&mut self, value: Vec) -> Result<()> - where - T: Streamable, - Endianess: ByteOrder, - { - self.write_u32::(value.len() as u32)?; - for x in value { - let res = x.parse(); - if let Ok(v) = res { - self.write_all(&v[..])?; - } else if let Err(e) = res { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!( - "Array Item could not be parsed due to a Binary Error: {}", - e - ), - )); - } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Array Item could not be parsed due to an unknown error while parsing.", - )); + } + + /// Reads a string from the stream. + /// This is a reversable operation, meaning if it fails, + /// the stream will be in the same state as before. + pub fn read_string(&mut self) -> Result { + // todo: Make this reversable + let len = self.read_var_u64()?; + if can_read!(self, len as usize) { + let mut string = String::with_capacity(len as usize); + unsafe { + let v = string.as_mut_vec(); + v.set_len(len as usize); + self.buf.copy_to_slice(&mut v[..]); } + return Ok(string); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } + } +} + +pub struct ByteWriter { + pub buf: BytesMut, +} + +impl Into for ByteWriter { + fn into(self) -> BytesMut { + self.buf + } +} + +impl Into for ByteWriter { + fn into(self) -> Bytes { + self.buf.freeze() + } +} + +impl Into> for ByteWriter { + fn into(self) -> Vec { + self.buf.to_vec() + } +} + +impl Into> for ByteWriter { + fn into(self) -> VecDeque { + self.buf.to_vec().into() + } +} + +impl From> for ByteWriter { + fn from(slice: IoSlice) -> Self { + let mut buf = BytesMut::with_capacity(slice.len()); + buf.put_slice(&slice); + return Self { buf }; + } +} + +impl From<&[u8]> for ByteWriter { + fn from(slice: &[u8]) -> Self { + let mut buf = BytesMut::with_capacity(slice.len()); + buf.put_slice(slice); + return Self { buf }; + } +} + +impl ByteWriter { + pub fn new() -> Self { + return Self { + buf: BytesMut::new(), + }; + } + + write_fn!(write_u8, u8, put_u8, 1); + write_fn!(write_u16, u16, put_u16, 2); + write_fn!(write_u16_le, u16, put_u16_le, 2); + write_fn!(write_i16, i16, put_i16, 2); + write_fn!(write_i16_le, i16, put_i16_le, 2); + + pub fn write_u24(&mut self, num: u32) -> Result<(), std::io::Error> { + return self.write_uint(num.into(), 3); + } + + pub fn write_u24_le(&mut self, num: u32) -> Result<(), std::io::Error> { + return self.write_uint_le(num.into(), 3); + } + + pub fn write_i24(&mut self, num: i32) -> Result<(), std::io::Error> { + return self.write_int(num.into(), 3); + } + + pub fn write_i24_le(&mut self, num: i32) -> Result<(), std::io::Error> { + return self.write_int_le(num.into(), 3); + } + + write_fn!(write_u32, u32, put_u32, 4); + write_fn!(write_u32_le, u32, put_u32_le, 4); + write_fn!(write_i32, i32, put_i32, 4); + write_fn!(write_i32_le, i32, put_i32_le, 4); + write_fn!(write_f32, f32, put_f32, 4); + write_fn!(write_f32_le, f32, put_f32_le, 4); + + // todo: write_var_u32, write_var_i32 should be reversable and should not corrupt the stream on failure + pub fn write_var_u32(&mut self, num: u32) -> Result<(), std::io::Error> { + let mut x = num; + while x >= 0x80 { + self.write_u8((x as u8) | 0x80)?; + x >>= 7; } - Ok(()) - } - - /// Writes an array to the stream. This array will - /// be sized by a `u64` with the contents being - /// a vector of `Streamable` types. - fn write_array_u64(&mut self, value: Vec) -> Result<()> - where - T: Streamable, - Endianess: ByteOrder, - { - self.write_u64::(value.len() as u64)?; - for x in value { - let res = x.parse(); - if let Ok(v) = res { - self.write_all(&v[..])?; - } else if let Err(e) = res { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!( - "Array Item could not be parsed due to a Binary Error: {}", - e - ), - )); + self.write_u8(x as u8)?; + return Ok(()); + } + + pub fn write_var_i32(&mut self, num: i32) -> Result<(), std::io::Error> { + return if num < 0 { + let num = num as u32; + self.write_var_u32(!(num << 1)) + } else { + let num = num as u32; + self.write_var_u32(num << 1) + }; + // let mut x = (num as u32) & u32::MAX; + // for _ in (0..35).step_by(7) { + // if x >> 7 == 0 { + // self.write_u8(x as u8)?; + // return Ok(()); + // } else { + // self.write_u8(((x & 0x7F) | 0x80) as u8)?; + // x >>= 7; + // } + // } + // return Err(Error::new(std::io::ErrorKind::InvalidData, ERR_VARINT_TOO_LONG)); + } + + write_fn!(write_u64, u64, put_u64, 8); + write_fn!(write_u64_le, u64, put_u64_le, 8); + write_fn!(write_i64, i64, put_i64, 8); + write_fn!(write_i64_le, i64, put_i64_le, 8); + write_fn!(write_f64, f64, put_f64, 8); + write_fn!(write_f64_le, f64, put_f64_le, 8); + + pub fn write_var_u64(&mut self, num: u64) -> Result<(), std::io::Error> { + let mut x = (num as u64) & u64::MAX; + for _ in (0..70).step_by(7) { + if x >> 7 == 0 { + self.write_u8(x as u8)?; + return Ok(()); } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Array Item could not be parsed due to an unknown error while parsing.", - )); + self.write_u8(((x & 0x7F) | 0x80) as u8)?; + x >>= 7; } } - Ok(()) + + return Err(Error::new( + std::io::ErrorKind::InvalidData, + ERR_VARINT_TOO_LONG, + )); } - /// Writes a socket addres to the stream. - #[inline] - fn write_socket_addr(&mut self, address: SocketAddr) -> Result<()> { - if let Ok(v) = address.parse() { - self.write_all(&v[..])?; + pub fn write_var_i64(&mut self, num: i64) -> Result<(), std::io::Error> { + return if num < 0 { + let num = num as u64; + self.write_var_u64(!(num << 1)) + } else { + let num = num as u64; + self.write_var_u64(num << 1) + }; + } + + pub fn write_uint(&mut self, num: u64, size: usize) -> Result<(), std::io::Error> { + if can_write!(self, size) { + self.buf.put_uint(num, size); return Ok(()); } else { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid Socket Address.", - )); + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); } } - /// Writes a bool to the stream. - /// ```rust ignore - /// use binary_utils::io::BinaryWriter; - /// - /// let mut stream = Vec::new(); - /// stream.write_bool(true) - /// ``` - #[inline] - fn write_bool(&mut self, value: bool) -> Result<()> { - self.write_u8(if value { 1 } else { 0 })?; - Ok(()) + pub fn write_uint_le(&mut self, num: u64, size: usize) -> Result<(), std::io::Error> { + if can_write!(self, size) { + self.buf.put_uint_le(num, size); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } } -} -/// All types that implement `Write` get methods defined in `BinaryWriter` -/// for free. -impl BinaryWriter for W {} + pub fn write_int(&mut self, num: i64, size: usize) -> Result<(), std::io::Error> { + if can_write!(self, size) { + self.buf.put_int(num, size); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + + pub fn write_int_le(&mut self, num: i64, size: usize) -> Result<(), std::io::Error> { + if can_write!(self, size) { + self.buf.put_int_le(num, size); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + + pub fn write_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { + if can_write!(self, slice.len()) { + self.buf.put_slice(slice); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + + pub fn write_bool(&mut self, b: bool) -> Result<(), std::io::Error> { + if can_write!(self, 1) { + self.buf.put_u8(b as u8); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + + /// Write a string to the buffer + /// The string is written as a var_u32 length followed by the bytes of the string. + /// Uses https://protobuf.dev/programming-guides/encoding/#length-types for length encoding + pub fn write_string(&mut self, string: &str) -> Result<(), std::io::Error> { + // https://protobuf.dev/programming-guides/encoding/#length-types + if can_write!(self, string.len()) { + self.write_var_u32(string.len() as u32)?; + self.buf.put_slice(string.as_bytes()); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + + pub fn as_slice(&self) -> &[u8] { + self.buf.chunk() + } + + pub fn clear(&mut self) { + self.buf.clear(); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index a240710..98851ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,434 +1,17 @@ -// #![feature(log_syntax)] - -use std::any::type_name; -use std::convert::{From, Into, TryInto}; -use std::io as std_io; -use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6}; - -pub use bin_macro::*; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use error::BinaryError; -use std::io::{Cursor, Read, Write}; - -/// Error utilities for Binary Utils. -/// This allows better handling of errors. -/// -/// By default, errors **can** be converted to: `std::io::Error` -pub mod error; -pub mod io; -mod u24_impl; -pub mod varint; - -pub use self::{u24_impl::*, varint::*}; - -macro_rules! includes { - ($var: ident, $method: ident, $values: expr) => {{ - let v = &$values; - v.iter().filter(|&v| $var.$method(v)).count() > 0 - }}; -} - -/// A trait to parse and unparse header structs from a given buffer. -/// -/// **Example:** -/// ```rust -/// use binary_utils::{Streamable, error::BinaryError}; -/// -/// struct Foo { -/// bar: u8, -/// foo_bar: u16 -/// } -/// impl Streamable for Foo { -/// fn parse(&self) -> Result, BinaryError> { -/// use std::io::Write; -/// let mut stream = Vec::::new(); -/// stream.write_all(&self.bar.parse()?[..])?; -/// stream.write_all(&self.bar.parse()?[..])?; -/// Ok(stream) -/// } -/// -/// fn compose(source: &[u8], position: &mut usize) -> Result { -/// // Streamable is implemented for all primitives, so we can -/// // just use this implementation to read our properties. -/// Ok(Self { -/// bar: u8::compose(&source, position)?, -/// foo_bar: u16::compose(&source, position)? -/// }) -/// } +/// The `ByteReader` and `ByteWriter` traits are used to read and write bytes from a buffer. +/// The io module contains implementations of these traits for `bytes::Buf` and `bytes::BufMut`. +/// +/// Example: +/// ```no_run +/// use binary_utils::io::ByteReader; +/// use bytes::{Buf, BufMut, BytesMut, Bytes}; +/// +/// fn main() { +/// const VARINT: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647 +/// let mut buf = ByteReader::from(&VARINT[..]); +/// assert_eq!(buf.read_var_u32().unwrap(), 2147483647); /// } /// ``` -pub trait Streamable { - /// Writes `self` to the given buffer. - fn parse(&self) -> Result, BinaryError>; - - /// Writes and unwraps `self` to the given buffer. - /// - /// ⚠️ This method is not fail safe, and will panic if result is Err. - fn fparse(&self) -> Vec { - self.parse().unwrap() - } - - /// Reads `self` from the given buffer. - fn compose(source: &[u8], position: &mut usize) -> Result - where - Self: Sized; - - /// Reads and unwraps `self` from the given buffer. - /// - /// ⚠️ This method is not fail safe, and will panic if result is Err. - fn fcompose(source: &[u8], position: &mut usize) -> Self - where - Self: Sized, - { - Self::compose(source, position).unwrap() - } -} - -/// Little Endian Type -/// -/// **Notice:** -/// This struct assumes the incoming buffer is BE and needs to be transformed. -/// -/// For LE decoding in BE streams use: -/// ```rust -/// use binary_utils::{LE, Streamable, error::BinaryError}; -/// -/// fn read_u16_le(source: &[u8], offset: &mut usize) -> LE { -/// // get the size of your type, in this case it's 2 bytes. -/// let be_source = &source[*offset..2]; -/// *offset += 2; -/// // now we can form the little endian -/// return LE::::compose(&be_source, &mut 0).unwrap(); -/// } -/// -/// assert_eq!(LE::(10).inner(), read_u16_le(&[10, 0], &mut 0).inner()); -/// ``` -#[derive(Debug, Clone, Copy)] -pub struct LE(pub T); - -impl LE { - /// Grabs the `inner` type, similar to `unwrap`. - pub fn inner(self) -> T { - self.0 - } -} - -impl Streamable for LE -where - T: Streamable + Sized, -{ - fn parse(&self) -> Result, BinaryError> { - let bytes = self.0.parse()?; - Ok(reverse_vec(bytes)) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - // If the source is expected to be LE we can swap it to BE bytes - // Doing this makes the byte stream officially BE. - // We actually need to do some hacky stuff here, - // we need to get the size of `T` (in bytes) - let stream = { - // if we can get the value of the type we do so here. - let name = type_name::(); - - if includes!( - name, - contains, - [ - "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", "f32", - "f64" - ] - ) { - reverse_vec(source[*position..(*position + ::std::mem::size_of::())].to_vec()) - } else { - reverse_vec(source[*position..].to_vec()) - } - }; - - // todo Properly implement LE streams - // todo Get rid of this NASTY hack! - // we need to get the stream releative to the current source, and "inject" into the current source. - // we can do this by getting the position and the length of the stream. - let mut hacked_stream = Vec::::new(); - let (q1, q2) = ( - hacked_stream.write_all(&source[..*position]), - hacked_stream.write_all(&stream), - ); - - // check if any of the queries were invalid or failed. - if q1.is_err() || q2.is_err() { - Err(BinaryError::RecoverableKnown( - "Write operation was interupted.".to_owned(), - )) - } else { - Ok(LE(T::compose(&hacked_stream[..], position)?)) - } - } -} - -/// Reverses the bytes in a given vector -pub fn reverse_vec(bytes: Vec) -> Vec { - let mut ret: Vec = Vec::new(); - - for x in (0..bytes.len()).rev() { - ret.push(*bytes.get(x).unwrap()); - } - ret -} - -/// Big Endian Encoding -pub struct BE(pub T); - -macro_rules! impl_streamable_primitive { - ($ty: ty) => { - impl Streamable for $ty { - fn parse(&self) -> Result, BinaryError> { - Ok(self.to_be_bytes().to_vec()) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - // get the size - let size = ::std::mem::size_of::<$ty>(); - let range = position.clone()..(size + position.clone()); - let data = <$ty>::from_be_bytes(source.get(range).unwrap().try_into().unwrap()); - *position += size; - Ok(data) - } - } - - // impl Streamable for LE<$ty> { - // fn parse(&self) -> Vec { - // reverse_vec(self.0.parse()) - // } - - // fn compose(source: &[u8], position: &mut usize) -> Self { - // // If the source is expected to be LE we can swap it to BE bytes - // // Doing this makes the byte stream officially BE. - // // We actually need to do some hacky stuff here, - // // we need to get the size of `T` (in bytes) - // let stream = reverse_vec(source[*position..(*position + ::std::mem::size_of::<$ty>())].to_vec()); - // LE(<$ty>::compose(&stream[..], position)) - // } - // } - }; -} - -impl_streamable_primitive!(u8); -impl_streamable_primitive!(u16); -impl_streamable_primitive!(u32); -impl_streamable_primitive!(f32); -impl_streamable_primitive!(u64); -impl_streamable_primitive!(f64); -impl_streamable_primitive!(u128); -impl_streamable_primitive!(i8); -impl_streamable_primitive!(i16); -impl_streamable_primitive!(i32); -impl_streamable_primitive!(i64); -impl_streamable_primitive!(i128); - -macro_rules! impl_streamable_vec_primitive { - ($ty: ty) => { - impl Streamable for Vec<$ty> { - fn parse(&self) -> Result, BinaryError> { - use ::std::io::Write; - // write the length as a varint - let mut v: Vec = Vec::new(); - v.write_all(&VarInt(v.len() as u32).to_be_bytes()[..]) - .unwrap(); - for x in self.iter() { - v.extend(x.parse()?.iter()); - } - Ok(v) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - // use ::std::io::Read; - // read a var_int - let mut ret: Vec<$ty> = Vec::new(); - let varint = VarInt::::from_be_bytes(source)?; - let length: u32 = varint.into(); - - *position += varint.get_byte_length() as usize; - - // read each length - for _ in 0..length { - ret.push(<$ty>::compose(&source, position)?); - } - Ok(ret) - } - } - }; -} - -impl_streamable_vec_primitive!(u8); -impl_streamable_vec_primitive!(u16); -impl_streamable_vec_primitive!(u32); -impl_streamable_vec_primitive!(f32); -impl_streamable_vec_primitive!(f64); -impl_streamable_vec_primitive!(u64); -impl_streamable_vec_primitive!(u128); -impl_streamable_vec_primitive!(i8); -impl_streamable_vec_primitive!(i16); -impl_streamable_vec_primitive!(i32); -impl_streamable_vec_primitive!(i64); -impl_streamable_vec_primitive!(i128); - -// implements bools -impl Streamable for bool { - fn parse(&self) -> Result, BinaryError> { - Ok(vec![if *self { 1 } else { 0 }]) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - // header validation - if source[*position] > 1 { - Err(BinaryError::RecoverableKnown(format!( - "Tried composing binary from non-binary byte: {}", - source[*position] - ))) - } else { - let v = source[*position] == 1; - *position += 1; - Ok(v) - } - } -} - -impl Streamable for String { - fn parse(&self) -> Result, BinaryError> { - let mut buffer = Vec::::new(); - buffer.write_u16::(self.len() as u16)?; - buffer.write_all(self.as_bytes())?; - Ok(buffer) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - let mut stream = Cursor::new(source); - stream.set_position(position.clone() as u64); - // Maybe do this in the future? - let len: usize = stream.read_u16::()?.into(); - *position = (stream.position() as usize) + len; - - unsafe { - // todo: Remove this nasty hack. - // todo: The hack being, remove the 2 from indexing on read_short - // todo: And utilize stream. - Ok(String::from_utf8_unchecked( - stream.get_ref()[2..len + stream.position() as usize].to_vec(), - )) - } - } -} - -impl Streamable for SocketAddr { - fn parse(&self) -> Result, BinaryError> { - let mut stream = Vec::::new(); - match *self { - Self::V4(_) => { - stream.write_u8(4)?; - let partstr = self.to_string(); - let actstr = partstr.split(":").collect::>()[0]; - let parts: Vec<&str> = actstr.split(".").collect(); - for part in parts { - let mask = part.parse::().unwrap_or(0); - stream.write_u8(mask)?; - } - stream - .write_u16::(self.port()) - .expect("Could not write port to stream."); - Ok(stream) - } - Self::V6(addr) => { - stream.write_u8(6)?; - // family? or length?? - stream.write_u16::(0)?; - // port - stream.write_u16::(self.port())?; - // flow - stream.write_u32::(addr.flowinfo())?; - // actual address here - stream.write(&addr.ip().octets())?; - // scope - stream.write_u32::(addr.scope_id())?; - Ok(stream) - } - } - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - let mut stream = Cursor::new(source); - stream.set_position(*position as u64); - match stream.read_u8()? { - 4 => { - let from = stream.position() as usize; - let to = stream.position() as usize + 4; - let parts = &source[from..to]; - stream.set_position(to as u64); - let port = stream.read_u16::().unwrap(); - *position = stream.position() as usize; - Ok(SocketAddr::new( - IpAddr::from([parts[0], parts[1], parts[2], parts[3]]), - port, - )) - } - 6 => { - let _family = stream.read_u16::().unwrap(); - let port = stream.read_u16::().unwrap(); - let flow = stream.read_u32::().unwrap(); - let mut parts: [u8; 16] = [0; 16]; - stream.read(&mut parts).unwrap(); - // we need to read parts into address - let address = { - let mut s = Cursor::new(parts); - let (a, b, c, d, e, f, g, h) = ( - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - s.read_u16::().unwrap_or(0), - ); - Ipv6Addr::new(a, b, c, d, e, f, g, h) - }; - let scope = stream.read_u32::().unwrap(); - *position = stream.position() as usize; - Ok(SocketAddr::from(SocketAddrV6::new( - address, port, flow, scope, - ))) - } - _ => panic!("Unknown Address type!"), - } - } -} - -/// Writes a vector whose length is written with a short -impl Streamable for Vec> -where - T: Streamable, -{ - fn parse(&self) -> Result, BinaryError> { - // write the length as a varint - let mut v: Vec = Vec::new(); - v.write_u16::(self.len() as u16)?; - for x in self.iter() { - v.extend(x.parse()?.iter()); - } - Ok(v) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - // read a var_int - let mut stream = Cursor::new(source); - let mut ret: Vec> = Vec::new(); - let length = stream.read_u16::()?; - *position = stream.position() as usize; - // read each length - for _ in 0..length { - ret.push(LE::::compose(&source[*position..], &mut 0)?); - } - Ok(ret) - } -} +pub mod io; +pub mod pool; +pub mod stream; diff --git a/src/pool.rs b/src/pool.rs new file mode 100644 index 0000000..3fceb87 --- /dev/null +++ b/src/pool.rs @@ -0,0 +1,7 @@ +/// Byte pools a specialized structure that allows you to reuse byte slices +/// instead of allocating new ones. +/// +/// When an byteslice is returned to the pool, it is immediately reused. +/// Do not use this if you are using a `BinaryStream` in multiple threads. +/// This will cause latency issues. +pub struct BytePool {} diff --git a/src/stream.rs b/src/stream.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/stream.rs @@ -0,0 +1 @@ + diff --git a/src/u24_impl.rs b/src/u24_impl.rs deleted file mode 100644 index c71db67..0000000 --- a/src/u24_impl.rs +++ /dev/null @@ -1,187 +0,0 @@ -#![allow(non_camel_case_types)] - -use byteorder::ReadBytesExt; -use std::cmp::{Ordering, PartialEq, PartialOrd}; -use std::convert::{From, Into}; -use std::io; -use std::ops::{Add, BitOr, Div, Mul, Sub}; - -use crate::error::BinaryError; -use crate::Streamable; -/// Base Implementation for a u24 -/// A u24 is 3 bytes (24 bits) wide number. -#[derive(Clone, Copy, Debug)] -pub struct u24(pub u32); // inner is validated - -impl u24 { - pub fn is_u24(num: usize) -> bool { - num < 0x00FF_FFFF - } - - pub fn from_be_bytes(bytes: &[u8]) -> Self { - u32::from_be_bytes([bytes[0], bytes[1], bytes[2], 0]).into() - } - - pub fn from_le_bytes(bytes: &[u8]) -> Self { - u32::from_be_bytes([0, bytes[1], bytes[2], bytes[3]]).into() - } - - pub fn to_le_bytes(self) -> [u8; 3] { - let bytes = self.0.to_le_bytes(); - [bytes[0], bytes[1], bytes[2]] - } - - pub fn to_be_bytes(self) -> [u8; 3] { - let bytes = self.0.to_be_bytes(); - [bytes[0], bytes[1], bytes[2]] - } - - pub fn inner(self) -> u32 { - self.0 - } -} - -impl Streamable for u24 { - /// Writes `self` to the given buffer. - fn parse(&self) -> Result, BinaryError> { - Ok(self.to_be_bytes().to_vec().clone()) - } - /// Reads `self` from the given buffer. - fn compose(source: &[u8], position: &mut usize) -> Result { - let buf = Self::from_be_bytes(&source[*position..]); - *position += 3; - Ok(buf) - } -} - -pub trait u24Writer: io::Write { - #[inline] - fn write_u24(&mut self, num: u24) -> io::Result { - self.write(&num.to_be_bytes()) - } -} - -pub trait u24Reader: io::Read { - #[inline] - fn read_u24(&mut self) -> io::Result { - let initial = [self.read_u8()?, self.read_u8()?, self.read_u8()?]; - Ok(u24::from_be_bytes(&initial)) - } -} - -impl Add for u24 { - type Output = Self; - - fn add(self, other: u24) -> Self::Output { - u24(self.0 + other.0) - } -} - -impl Mul for u24 { - type Output = Self; - - fn mul(self, other: u24) -> Self::Output { - u24(self.0 * other.0) - } -} - -impl Sub for u24 { - type Output = Self; - - fn sub(self, other: u24) -> Self::Output { - u24(self.0 - other.0) - } -} - -impl Div for u24 { - type Output = Self; - - fn div(self, other: u24) -> Self::Output { - u24(self.0 / other.0) - } -} - -impl PartialEq for u24 { - fn eq(&self, other: &u24) -> bool { - self.0 == other.0 - } -} - -impl PartialOrd for u24 { - fn partial_cmp(&self, other: &u24) -> Option { - self.0.partial_cmp(&other.0) - } -} - -macro_rules! impl_primitive_u24 { - ($ty:ty) => { - impl From<$ty> for u24 { - fn from(value: $ty) -> Self { - if !u24::is_u24(value as usize) { - panic!("Can not convert a number larger than the bounds of a u24 into a u24") - } else { - u24(value as u32) - } - } - } - - impl BitOr<$ty> for u24 { - type Output = Self; - - fn bitor(self, rhs: $ty) -> Self::Output { - u24(self.0 | rhs as u32) - } - } - - impl Into<$ty> for u24 { - fn into(self) -> $ty { - self.0 as $ty - } - } - - impl Add<$ty> for u24 { - type Output = Self; - - fn add(self, other: $ty) -> Self::Output { - u24(self.0 + other as u32) - } - } - - impl Mul<$ty> for u24 { - type Output = Self; - - fn mul(self, other: $ty) -> Self::Output { - u24(self.0 * other as u32) - } - } - - impl Sub<$ty> for u24 { - type Output = Self; - - fn sub(self, other: $ty) -> Self::Output { - u24(self.0 - other as u32) - } - } - - impl Div<$ty> for u24 { - type Output = Self; - - fn div(self, other: $ty) -> Self::Output { - u24(self.0 / other as u32) - } - } - }; -} - -impl_primitive_u24!(u8); -impl_primitive_u24!(u16); -impl_primitive_u24!(u32); -impl_primitive_u24!(u64); -impl_primitive_u24!(f32); -impl_primitive_u24!(f64); -impl_primitive_u24!(u128); -impl_primitive_u24!(i8); -impl_primitive_u24!(i16); -impl_primitive_u24!(i32); -impl_primitive_u24!(i64); -impl_primitive_u24!(i128); diff --git a/src/varint.rs b/src/varint.rs deleted file mode 100644 index ff31c1d..0000000 --- a/src/varint.rs +++ /dev/null @@ -1,301 +0,0 @@ -use crate::Streamable; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::convert::{From, Into}; -use std::io::{self, Cursor}; -use std::ops::{Add, BitOr, Div, Mul, Sub}; -/// A minecraft specific unsized integer -/// A varint can be one of `32` and `64` bits -#[derive(Clone, Copy, Debug)] -pub struct VarInt(pub T); - -pub trait VarIntWriter: io::Write { - fn write_var_int(&mut self, num: VarInt) -> io::Result; -} - -pub trait VarIntReader: io::Read { - fn read_var_int(&mut self) -> io::Result>; -} - -pub const VAR_INT_32_BYTE_MAX: usize = 5; -pub const VAR_INT_64_BYTE_MAX: usize = 10; - -macro_rules! varint_impl_generic { - ($ty:ty) => { - impl VarInt<$ty> { - /// Encodes the var_int into Big Endian Bytes - pub fn to_be_bytes(self) -> Vec { - self.to_bytes_be() - } - - pub fn to_le_bytes(self) -> Vec { - let mut v = Vec::::new(); - let bytes = self.to_bytes_be(); - for x in (0..bytes.len()).rev() { - v.push(*bytes.get(x).unwrap()); - } - v - } - - pub fn get_byte_length(self) -> u8 { - self.to_be_bytes().len() as u8 - } - - fn to_bytes_be(self) -> Vec { - let mut to_write: $ty = self.0; - let mut buf: Vec = Vec::new(); - - // while there is more than a single byte to write - while to_write >= 0x80 { - // write at most a byte, to account for overflow - buf.write_u8(to_write as u8 | 0x80).unwrap(); - to_write >>= 7; - } - - buf.write_u8(to_write as u8).unwrap(); - buf - } - - pub fn from_be_bytes_cursor(stream: &mut Cursor>) -> Self { - let mut value: $ty = 0; - - for x in (0..35).step_by(7) { - let byte = stream.read_u8().unwrap(); - value |= (byte & 0x7f) as $ty << x; - - // if the byte is a full length of a byte - // we can assume we are done - if byte & 0x80 == 0 { - break; - } - } - - VarInt::<$ty>(value) - } - - pub fn from_be_bytes(bstream: &[u8]) -> Result { - let mut stream = Cursor::new(bstream); - let mut value: $ty = 0; - - for x in (0..35).step_by(7) { - let byte = stream.read_u8()?; - value |= (byte & 0x7f) as $ty << x; - - // if the byte is a full length of a byte - // we can assume we are done - if byte & 0x80 == 0 { - break; - } - } - - Ok(VarInt::<$ty>(value)) - } - - // pub fn from_le_bytes(bytes: &[u8]) -> Self { - // <$ty>::from_be_bytes([0, bytes[1], bytes[2], bytes[3]]).into() - // } - pub fn is_var_int(_: $ty) -> bool { - true - } - } - - impl Streamable for VarInt<$ty> { - /// Writes `self` to the given buffer. - fn parse(&self) -> Result, crate::error::BinaryError> { - Ok(self.to_be_bytes().to_vec().clone()) - } - /// Reads `self` from the given buffer. - fn compose(source: &[u8], position: &mut usize) -> Result { - let v = Self::from_be_bytes(&source[*position..])?; - *position += v.get_byte_length() as usize; - Ok(v) - } - } - - impl VarIntReader<$ty> for dyn io::Read { - #[inline] - fn read_var_int(&mut self) -> io::Result> { - let mut value: $ty = 0; - - for x in (0..35).step_by(7) { - let byte = self.read_u8().unwrap(); - value |= (byte & 0x7f) as $ty << x; - - // if the byte is a full length of a byte - // we can assume we are done - if byte & 0x80 == 0 { - break; - } - } - - Ok(VarInt::<$ty>(value)) - } - } - - impl VarIntWriter<$ty> for dyn io::Write { - #[inline] - fn write_var_int(&mut self, num: VarInt<$ty>) -> io::Result { - self.write_all(&num.to_be_bytes()[..]).unwrap(); - Ok(num.get_byte_length() as usize) - } - } - }; -} -macro_rules! varint_impl_generic64 { - ($ty:ty) => { - impl VarInt<$ty> { - /// Encodes the var_int into Big Endian Bytes - pub fn to_be_bytes(self) -> Vec { - self.to_bytes_be() - } - - pub fn to_le_bytes(self) -> Vec { - let mut v = Vec::::new(); - let bytes = self.to_bytes_be(); - for x in (0..bytes.len()).rev() { - v.push(*bytes.get(x).unwrap()); - } - v - } - - pub fn get_byte_length(self) -> u8 { - self.to_be_bytes().len() as u8 - } - - fn to_bytes_be(self) -> Vec { - let mut to_write: $ty = self.0; - let mut buf: Vec = Vec::new(); - - // while there is more than a single byte to write - while to_write >= 0x80 { - // write at most a byte, to account for overflow - buf.write_u8(to_write as u8 | 0x80).unwrap(); - to_write >>= 7; - } - - buf.write_u8(to_write as u8).unwrap(); - buf - } - - pub fn from_be_bytes(stream: &mut Cursor>) -> Self { - let mut value: $ty = 0; - - for x in (0..70).step_by(7) { - let byte = stream.read_u8().unwrap(); - value |= (byte & 0x7f) as $ty << x; - - // if the byte is a full length of a byte - // we can assume we are done - if byte & 0x80 == 0 { - break; - } - } - - VarInt::<$ty>(value) - } - - // pub fn from_be_bytes(bytes: &[u8]) -> Self { - // <$ty>::from_be_bytes([bytes[0], bytes[1], bytes[2], 0]).into() - // } - - // pub fn from_le_bytes(bytes: &[u8]) -> Self { - // <$ty>::from_be_bytes([0, bytes[1], bytes[2], bytes[3]]).into() - // } - pub fn is_var_int(_: $ty) -> bool { - true - } - } - - impl Streamable for VarInt<$ty> { - /// Writes `self` to the given buffer. - fn parse(&self) -> Result, crate::error::BinaryError> { - Ok(self.to_be_bytes().to_vec().clone()) - } - /// Reads `self` from the given buffer. - fn compose(source: &[u8], position: &mut usize) -> Result { - let v = Self::from_be_bytes(&mut Cursor::new(source[*position..].to_vec())); - *position += v.get_byte_length() as usize; - Ok(v) - } - } - }; -} -varint_impl_generic!(u32); -varint_impl_generic!(i32); -varint_impl_generic64!(u64); -varint_impl_generic64!(i64); - -macro_rules! impl_primitive_VarInt { - ($ty:ty, $vk:ty) => { - impl From<$ty> for VarInt<$vk> { - fn from(value: $ty) -> Self { - if !VarInt::<$vk>::is_var_int(value as $vk) { - panic!( - "Can not convert a number larger than the bounds of a VarInt into a VarInt" - ) - } else { - VarInt(value as $vk) - } - } - } - - impl BitOr<$ty> for VarInt<$vk> { - type Output = Self; - - fn bitor(self, rhs: $ty) -> Self::Output { - VarInt(self.0 | rhs as $vk) - } - } - - impl Into<$ty> for VarInt<$vk> { - fn into(self) -> $ty { - self.0 as $ty - } - } - - impl Add<$ty> for VarInt<$vk> { - type Output = Self; - - fn add(self, other: $ty) -> Self::Output { - VarInt(self.0 + other as $vk) - } - } - - impl Mul<$ty> for VarInt<$vk> { - type Output = Self; - - fn mul(self, other: $ty) -> Self::Output { - VarInt(self.0 * other as $vk) - } - } - - impl Sub<$ty> for VarInt<$vk> { - type Output = Self; - - fn sub(self, other: $ty) -> Self::Output { - VarInt(self.0 - other as $vk) - } - } - - impl Div<$ty> for VarInt<$vk> { - type Output = Self; - - fn div(self, other: $ty) -> Self::Output { - VarInt(self.0 / other as $vk) - } - } - }; -} -impl_primitive_VarInt!(u8, u32); -impl_primitive_VarInt!(u16, u32); -impl_primitive_VarInt!(u32, u32); -impl_primitive_VarInt!(u64, u32); -impl_primitive_VarInt!(f32, u32); -impl_primitive_VarInt!(f64, u32); -impl_primitive_VarInt!(u128, u32); -impl_primitive_VarInt!(i8, u64); -impl_primitive_VarInt!(i16, u64); -impl_primitive_VarInt!(i32, u64); -impl_primitive_VarInt!(i64, u64); -impl_primitive_VarInt!(f32, u64); -impl_primitive_VarInt!(f64, u64); -impl_primitive_VarInt!(i128, u64); diff --git a/tests/enums.rs b/tests/enums.rs deleted file mode 100644 index 61f928d..0000000 --- a/tests/enums.rs +++ /dev/null @@ -1,37 +0,0 @@ -use bin_macro::*; -use binary_utils::{error::BinaryError, Streamable}; - -#[derive(Debug, BinaryStream, PartialEq)] -#[repr(u8)] -pub enum Test { - Apple = 0, - Pair = 1, -} - -#[test] -fn read_test_from_buffer() { - let buffer: &[u8] = &[0]; - let result = Test::compose(buffer, &mut 0).unwrap(); - assert_eq!(Test::Apple, result); -} - -#[test] -fn write_read_buffer() -> Result<(), BinaryError> { - // write first - let variant = Test::Pair; - let buffer = variant.parse()?; - - assert_eq!(buffer, vec![1]); - - // read now - let compose = Test::compose(&buffer[..], &mut 0)?; - - assert!( - match compose { - Test::Pair => true, - _ => false, - }, - "Reconstruction was not equivelant to Test::Pair" - ); - Ok(()) -} diff --git a/tests/format.rs b/tests/format.rs deleted file mode 100644 index a1a3e8d..0000000 --- a/tests/format.rs +++ /dev/null @@ -1,22 +0,0 @@ -use binary_utils::*; - -pub const EXPECTED_DEBUG: &[u8] = &[ - // packet id - 95, // u128 as LE - 117, 215, 192, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; - -#[derive(BinaryStream)] -pub struct TestPacket { - pub id: u8, - pub width: LE, -} - -#[test] -fn test_varint() { - let test = TestPacket { - id: 95, - width: LE::(4206942069), - }; - assert_eq!(&test.parse().unwrap()[..], EXPECTED_DEBUG); -} diff --git a/tests/io.rs b/tests/io.rs deleted file mode 100644 index cf471b8..0000000 --- a/tests/io.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::io::Cursor; - -use binary_utils::{ - io::{BinaryReader, BinaryWriter}, - VarInt, LE, -}; -use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; - -#[test] -fn write_tests() -> std::io::Result<()> { - let byte_varint = VarInt::(255); - - // Micro test, tests that the stream is correct - { - let mut stream = Cursor::new(Vec::::new()); - stream.write_u32_varint(byte_varint)?; - assert_eq!(stream.into_inner(), vec![255, 1]); - } - - // Test arrays - { - let my_array: Vec = vec![55, 66, 77]; - let mut stream = Cursor::new(Vec::::new()); - stream.write_array::(my_array)?; - assert_eq!(stream.into_inner(), vec![0, 3, 0, 55, 0, 66, 0, 77]); - } - - Ok(()) -} - -#[test] -fn read_tests() -> std::io::Result<()> { - // Varint reading! - // { - // let buffer: Vec = vec![255, 255, 255, 255, 7, 0, 255, 1, 0, 0]; - // let mut cursor = Cursor::new(buffer); - // let v = cursor.read_u32_varint()?; - // assert_eq!(v.0, 2147483647); - // dbg!(cursor.read_u8()?); - // dbg!(cursor.position()); - // let v2 = cursor.read_u32_varint()?; - // assert_eq!(v2.0, 255); - // } - - // read 32 int string - Ok(()) -} diff --git a/tests/le_test.rs b/tests/le_test.rs deleted file mode 100644 index 6c346cc..0000000 --- a/tests/le_test.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::io::Write; - -use binary_utils::*; - -#[test] -fn read_write_le_mixed() -> Result<(), error::BinaryError> { - // LE encoded size, but BE data - // The first 3 bytes are dummy bytes - let buff_one: Vec = vec![ - 32, 32, 32, 12, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 32, 32, - 32, 32, - ]; - - // read first 3 bytes. - // this makes our offset 3 - let le_header = &buff_one[3..]; - let mut offset: usize = 0; - - // get the length - let length = LE::::compose(&le_header, &mut offset)?.0 as usize; - assert_eq!(12, length); - - // now get the rest of the buffer based on the length - let decoded = String::from_utf8(le_header[offset..(length + offset)].to_vec()).unwrap(); - assert_eq!(decoded, "Hello World!".to_string()); - - // get the rest of the buffer, there should be 4 more bytes - assert_eq!(&le_header[(length + offset)..], &[32, 32, 32, 32]); - - // Writing test - let mut buff_two: Vec = vec![32, 32, 32, 32]; - let to_encode = "Hello World!".to_string(); - - // The length of the string in LE: - let length = LE::(to_encode.len() as u32); - buff_two.write_all(&length.parse()?[..])?; - // we should now have the length of 12 written in LE - - // write the contents of the string now... - buff_two.write_all(&to_encode.as_bytes())?; - - // Write magic to buffer. - buff_two.write_all(&[32, 32, 32, 32])?; - - // Now check - assert_eq!(buff_one, &buff_one[..]); - Ok(()) -} diff --git a/tests/lstring.rs b/tests/lstring.rs deleted file mode 100644 index 664a7c3..0000000 --- a/tests/lstring.rs +++ /dev/null @@ -1,65 +0,0 @@ -use binary_utils::*; -use std::io::Write; - -// Extracted from protocol. -#[derive(Debug, Clone)] -pub struct LString32(pub String); - -impl Streamable for LString32 { - fn parse(&self) -> Result, error::BinaryError> { - // get the length - let mut buffer: Vec = Vec::new(); - buffer.write_all(&LE::(self.0.len() as u32).parse()?[..])?; - // now we write string buffer. - buffer.write_all(&self.0.clone().into_bytes()[..])?; - Ok(buffer) - } - - fn compose(source: &[u8], position: &mut usize) -> Result { - let length = LE::::compose(&source[..], position)?; - let bytes = &source[*position..(*position + length.0 as usize)]; - - *position += bytes.len(); - - Ok(Self(unsafe { String::from_utf8_unchecked(bytes.to_vec()) })) - } -} - -pub const HW_TEST_DATA: &[u8] = &[ - // Length of the string in Little Endian Format - 12, 0, 0, 0, // Contents of string - 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, -]; - -#[test] -fn write_l32string() { - let hello_world = "Hello World!".to_string(); - let data = LString32(hello_world).parse().unwrap(); - - assert_eq!(HW_TEST_DATA, &data[..]); -} - -#[test] -fn read_l32string() { - let hello_world = "Hello World!".to_string(); - let data = LString32::compose(HW_TEST_DATA, &mut 0).unwrap(); - assert_eq!(data.0, hello_world); -} - -#[test] -fn read_twice() { - let hello_world = "Hello World!".to_string(); - let mut stream = Vec::::new(); - stream - .write_all(&LString32(hello_world.clone()).parse().unwrap()[..]) - .unwrap(); - stream - .write_all(&LString32(hello_world).parse().unwrap()[..]) - .unwrap(); - // ok read it. - let mut pos: usize = 0; - let one = LString32::compose(&stream[..], &mut pos).unwrap().0; - let two = LString32::compose(&stream[..], &mut pos).unwrap().0; - - assert_eq!(one, two); -} diff --git a/tests/macro_tests.rs b/tests/macro_tests.rs deleted file mode 100644 index 40514b9..0000000 --- a/tests/macro_tests.rs +++ /dev/null @@ -1,49 +0,0 @@ -use bin_macro::*; -use binary_utils::{reverse_vec, Streamable, LE}; -#[derive(Debug, BinaryStream)] -pub struct TestPacket { - pub some_int: u8, - pub some_string: u8, - // pub unknown_size: VarInt -} - -#[test] -fn construct_struct() { - let buf = vec![1, 30]; - let pk = TestPacket::compose(&buf, &mut 0).unwrap(); - assert_eq!(buf, pk.parse().unwrap()) -} - -#[test] -fn write_string() { - let string = String::from("Hello world!"); - let hello_world_vec = vec![ - 0, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, - ]; - assert_eq!(hello_world_vec, string.parse().unwrap()); -} - -#[test] -fn read_string() { - let hello_world_vec = vec![ - 0, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, - ]; - let string = String::compose(&hello_world_vec[..], &mut 0).unwrap(); - assert_eq!("Hello world!".to_string(), string); -} - -#[derive(BinaryStream)] -pub struct HelloWorld { - data: LE, -} - -#[test] -fn endianness() { - let hello_world_vec_le = reverse_vec(vec![ - 0, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, - ]); - - let data = HelloWorld::compose(&hello_world_vec_le, &mut 0).unwrap(); - - assert_eq!("Hello world!".to_string(), data.data.inner()); -} diff --git a/tests/no_init.rs b/tests/no_init.rs deleted file mode 100644 index 0a2c0ca..0000000 --- a/tests/no_init.rs +++ /dev/null @@ -1,14 +0,0 @@ -use binary_utils::*; - -#[derive(BinaryStream)] -#[repr(u8)] -pub enum PacketField { - Cats = 10, - Dogs = 12, - Apple, -} - -#[test] -pub fn field_test_enum() { - assert_eq!(PacketField::Apple.parse().unwrap()[0], 13); -} diff --git a/tests/socket.rs b/tests/socket.rs deleted file mode 100644 index e1dd16f..0000000 --- a/tests/socket.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use std::net::SocketAddr; -use binary_utils::*; - -#[test] -fn test_socket() { - let socket: SocketAddr = "127.0.0.1:19132".parse().unwrap(); - dbg!(&socket.ip()); - assert_eq!(socket.fparse(), vec![4, 127, 0, 0, 1, 74, 188]); -} \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs deleted file mode 100644 index 015780f..0000000 --- a/tests/tests.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod enums; -mod format; -mod io; -mod le_test; -mod lstring; -mod macro_tests; -mod no_init; -mod var_int; -mod vec; diff --git a/tests/var_int.rs b/tests/var_int.rs deleted file mode 100644 index 241a1d6..0000000 --- a/tests/var_int.rs +++ /dev/null @@ -1,50 +0,0 @@ -use binary_utils::Streamable; -use binary_utils::*; - -#[test] -fn read_write_var_int() { - let one = VarInt::(2147483647); - let two = VarInt::(255); - let buf_one = one.parse().unwrap(); - let buf_two = two.parse().unwrap(); - - assert_eq!(buf_one, vec![255, 255, 255, 255, 7]); - assert_eq!(buf_two, vec![255, 1]); - - let buf_long_one = VarInt::(9223372036854775807).parse().unwrap(); - assert_eq!( - buf_long_one, - vec![255, 255, 255, 255, 255, 255, 255, 255, 127] - ); - - assert_eq!( - one.0, - VarInt::::compose(&buf_one[..], &mut 0).unwrap().0 - ); - - assert_eq!( - two.0, - VarInt::::compose(&buf_two[..], &mut 0).unwrap().0 - ); - - // test reading - let buf_game_id: Vec = vec![2, 0, 0, 0, 5]; - let int_game_id = VarInt::::compose(&buf_game_id[..], &mut 0).unwrap(); - assert_eq!(int_game_id.0, 2); -} - -#[test] -fn var_int_test_middle() { - // false, false, byte, varint (255), varint (1) - let buffer = vec![0, 0, 0, 255, 1, 0, 0]; - let mut position = 0; - - assert_eq!(u24::compose(&buffer[..], &mut position).unwrap().inner(), 0); - - assert_eq!( - VarInt::::compose(&buffer[..], &mut position) - .unwrap() - .0, - 255 - ); -} diff --git a/tests/varint.rs b/tests/varint.rs new file mode 100644 index 0000000..d51e83a --- /dev/null +++ b/tests/varint.rs @@ -0,0 +1,93 @@ +use binary_utils::io::{ByteReader, ByteWriter}; + +pub const FIVE_BYTE_VARINT: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647 +pub const THREE_BYTE_VARINT: &[u8] = &[255, 255, 127]; // 2097151 +pub const TWO_BYTE_VARINT: &[u8] = &[255, 1]; // 255 +pub const ONE_BYTE_VARINT: &[u8] = &[127]; // 127 + +#[test] +fn read_var_u32() { + let mut buf = ByteReader::from(&FIVE_BYTE_VARINT[..]); + assert_eq!(buf.read_var_u32().unwrap(), 2147483647); + let mut buf = ByteReader::from(&THREE_BYTE_VARINT[..]); + assert_eq!(buf.read_var_u32().unwrap(), 2097151); + let mut buf = ByteReader::from(&TWO_BYTE_VARINT[..]); + assert_eq!(buf.read_var_u32().unwrap(), 255); + let mut buf = ByteReader::from(&ONE_BYTE_VARINT[..]); + assert_eq!(buf.read_var_u32().unwrap(), 127); +} + +pub const NEGATIVE_VARINT: &[u8] = &[253, 255, 255, 255, 15]; // -2147483647 +#[test] +fn read_var_i32() { + let mut buf = ByteReader::from(&NEGATIVE_VARINT[..]); + assert_eq!(buf.read_var_i32().unwrap(), -2147483647); + // -12 + let mut buf = ByteReader::from([23].to_vec()); + assert_eq!(buf.read_var_i32().unwrap(), -12); + // todo: fix this + // todo: for some reason this doesn't work on very large numbers, we're 1 bit off + // let mut buf = ByteReader::from([254, 255, 255, 255, 15].to_vec()); + // assert_eq!(buf.read_var_i32().unwrap(), -2147483648); +} + +#[test] +fn write_var_u32() { + let mut buf = ByteWriter::new(); + buf.write_var_u32(2147483647).unwrap(); + assert_eq!(buf.as_slice(), &FIVE_BYTE_VARINT[..]); + buf.clear(); + buf.write_var_u32(2097151).unwrap(); + assert_eq!(buf.as_slice(), &THREE_BYTE_VARINT[..]); + buf.clear(); + buf.write_var_u32(255).unwrap(); + assert_eq!(buf.as_slice(), &TWO_BYTE_VARINT[..]); + buf.clear(); + buf.write_var_u32(127).unwrap(); + assert_eq!(buf.as_slice(), &ONE_BYTE_VARINT[..]); +} + +#[test] +fn write_var_i32() { + let mut buf = ByteWriter::new(); + buf.write_var_i32(-1).unwrap(); + assert_eq!(buf.as_slice(), &[1]); + buf.clear(); + buf.write_var_i32(-2147483648).unwrap(); + assert_eq!(buf.as_slice(), &[255, 255, 255, 255, 15]); +} + +pub const NINE_BYTE_LONG: &[u8] = &[255, 255, 255, 255, 255, 255, 255, 255, 127]; // 9223372036854775807 +pub const NEGATIVE_ONE_LONG: &[u8] = &[1]; // -1 +pub const NEGATIVE_LONG: &[u8] = &[255, 255, 255, 255, 255, 255, 255, 255, 255, 1]; // -9223372036854775808 + +#[test] +fn read_var_u64() { + let mut buf = ByteReader::from(&NINE_BYTE_LONG[..]); + assert_eq!(buf.read_var_u64().unwrap(), 9223372036854775807); +} + +#[test] +fn read_var_i64() { + let mut buf = ByteReader::from(&NEGATIVE_ONE_LONG[..]); + assert_eq!(buf.read_var_i64().unwrap(), -1); + let mut buf = ByteReader::from(&NEGATIVE_LONG[..]); + assert_eq!(buf.read_var_i64().unwrap(), -9223372036854775808); +} + +#[test] +fn write_var_u64() { + let mut buf = ByteWriter::new(); + buf.write_var_u64(9223372036854775807).unwrap(); + assert_eq!(buf.as_slice(), &NINE_BYTE_LONG[..]); +} + +#[test] +fn write_var_i64() { + let mut buf = ByteWriter::new(); + buf.write_var_i64(-1).unwrap(); + assert_eq!(buf.as_slice(), &NEGATIVE_ONE_LONG[..]); + buf.clear(); + buf.write_var_i64(-9223372036854775808).unwrap(); + assert_eq!(buf.as_slice(), &NEGATIVE_LONG[..]); +} diff --git a/tests/vec.rs b/tests/vec.rs deleted file mode 100644 index 20e6ba3..0000000 --- a/tests/vec.rs +++ /dev/null @@ -1,30 +0,0 @@ -use binary_utils::{varint::VarInt, Streamable, LE}; - -#[test] -fn test_varint() { - let v = VarInt::(25565); - let _val: Vec = vec![221, 199, 1]; - dbg!(VarInt::::from_be_bytes(&[255, 255, 255, 1][..])); - dbg!(&v.to_be_bytes()); -} - -// test a string -#[test] -fn test_le_vec() { - // LE bytes for "Netrex" - let le_bytes_netrex: Vec = vec![120, 101, 114, 116, 101, 78, 6, 0]; - let str_bytes = LE("Netrex".to_string()); - println!("{:?}", str_bytes.fparse()); - - assert_eq!(str_bytes.fparse(), le_bytes_netrex); - - let mut test: Vec> = Vec::new(); - test.push(str_bytes.clone()); - - // Vectors store length {stream, stream } - // where "stream" in this case is [length, string bytes] - let vector = test.fparse(); - println!("{:?}", vector); - let restored = Vec::>::fcompose(&vector[..], &mut 0); - assert_eq!(restored[0].clone().inner(), str_bytes.inner()) -} From d85c95dccac9dd5fc84bd865aafa2b8badb40239 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 14:28:23 -0500 Subject: [PATCH 02/26] doc(io.rs): Add some documentation to reader and writer implementations --- src/io.rs | 181 +++++++++++++++++++++++++++++++++++++++++++----- tests/reader.rs | 1 + 2 files changed, 166 insertions(+), 16 deletions(-) create mode 100644 tests/reader.rs diff --git a/src/io.rs b/src/io.rs index 24d3dc4..fe0e0ab 100644 --- a/src/io.rs +++ b/src/io.rs @@ -10,7 +10,7 @@ pub const ERR_VARINT_TOO_LONG: &str = "Varint is too long to be written to buffe macro_rules! can_read { ($self: ident, $size: expr) => { - $self.buf.remaining() > $size + $self.buf.remaining() >= $size }; } @@ -47,11 +47,81 @@ macro_rules! write_fn { }; } -/// ByteReader is a panic-free way to read bytes from a `Buf` trait. +/// ByteReader is a panic-free way to read bytes from the `byte::Buf` trait. /// -/// Each read method will add a 3 OP calls to `Buf` trait. +/// ## Example +/// ```rust +/// use binary_utils::io::ByteReader; +/// +/// fn main() { +/// let mut buf = ByteReader::from(&[0, 253, 255, 255, 255, 15][..]); +/// assert_eq!(buf.read_u8().unwrap(), 0); +/// assert_eq!(buf.read_var_i32().unwrap(), -2147483647); +/// } +/// ``` +/// +/// ## Peek Ahead +/// `ByteReader` also provides a utility `peek_ahead` function that allows you to +/// "peek ahead" at the next byte in the stream without advancing the stream. +/// +/// Do not confuse this with any sort of "peek" function. This function does not +/// increment the read position of the stream, but rather copies the byte at the +/// specified position. +/// ```rust +/// use binary_utils::io::ByteReader; +/// +/// fn main() { +/// let mut buf = ByteReader::from(&[253, 255, 14, 255, 255, 15][..]); +/// if buf.peek_ahead(3) != 255 { +/// // buffer is corrupted! +/// } else { +/// // read the varint +/// let num = buf.read_var_i32().unwrap(); +/// } +/// } +/// ``` +/// +/// ## Reading a struct without `Composable` +/// This is useful if you are trying to read a struct or optional type and validate the type before +/// reading the rest of the struct. +/// ```rust +/// use binary_utils::io::ByteReader; +/// +/// struct PingPacket { +/// pub id: u8, +/// pub time: u64, +/// pub ack_id: Option +/// } +/// +/// fn main() { +/// let mut buf = ByteReader::from(&[0, 253, 255, 255, 255, 255, 255, 255, 255, 0][..]); +/// +/// // Read the id +/// let id = buf.read_u8().unwrap(); +/// +/// if id == 0 { +/// // Read the time +/// let time = buf.read_u64().unwrap(); +/// // read ack +/// if buf.read_bool().unwrap() { +/// let ack_id = buf.read_var_i32().unwrap(); +/// let packet = PingPacket { id, time, ack_id: Some(ack_id) }; +/// } else { +/// let packet = PingPacket { id, time, ack_id: None }; +/// } +/// } +/// } +/// ``` pub struct ByteReader { - pub buf: Bytes, + pub(crate) buf: Bytes, +} + +impl From for ByteReader { + fn from(writer: ByteWriter) -> Self { + Self { + buf: writer.buf.freeze(), + } + } } impl Into for ByteReader { @@ -86,13 +156,33 @@ impl From> for ByteReader { impl From<&[u8]> for ByteReader { fn from(buf: &[u8]) -> Self { - Self { buf: Bytes::from(buf.to_vec()) } + Self { + buf: Bytes::from(buf.to_vec()), + } } } impl ByteReader { - /// This is a function intended to be used to "peek ahead at x byte" on the given stream. - /// This WILL NOT advance the stream! + /// `ByteReader` also provides a utility `peek_ahead` function that allows you to + /// "peek ahead" at the next byte in the stream without advancing the stream. + /// + /// Do not confuse this with any sort of "peek" function. This function does not + /// increment the read position of the stream, but rather copies the byte at the + /// specified position. + /// ```rust + /// use binary_utils::io::ByteReader; + /// + /// fn main() { + /// let mut buf = ByteReader::from(&[253, 255, 14, 255, 255, 15][..]); + /// if buf.peek_ahead(3) != 255 { + /// // buffer is corrupted, varints can never have a leading byte less than 255 if + /// // Their are bytes remaining! + /// } else { + /// // read the varint + /// let num = buf.read_var_i32().unwrap(); + /// } + /// } + /// ``` pub fn peek_ahead(&mut self, pos: usize) -> Result { if can_read!(self, pos) { return Ok(self.buf.chunk()[pos]); @@ -244,12 +334,15 @@ impl ByteReader { // todo: fails on -2147483648, which is the minimum value for i32 // todo: probably nothing to worry about, but should be fixed let num = self.read_var_u32()?; - Ok((num >> 1) as i32 ^ -((num & 1) as i32)) // does not work on large numbers - // return Ok(if num & 1 != 0 { - // !((num >> 1) as i32) - // } else { - // (num >> 1) as i32 - // }); + + // for some reason this does not work on large numbers + Ok((num >> 1) as i32 ^ -((num & 1) as i32)) + + // return Ok(if num & 1 != 0 { + // !((num >> 1) as i32) + // } else { + // (num >> 1) as i32 + // }); } read_fn!(read_u64, u64, get_u64, 8); @@ -375,10 +468,58 @@ impl ByteReader { return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); } } + + pub fn as_slice(&self) -> &[u8] { + self.buf.chunk() + } } +/// ByteWriter is a panic-free way to write bytes to a `BufMut` trait. +/// +/// ## Example +/// A generic example of how to use the `ByteWriter` struct. +/// ```rust +/// use binary_utils::io::ByteWriter; +/// use binary_utils::io::ByteReader; +/// +/// fn main() { +/// let mut writer = ByteWriter::new(); +/// writer.write_string("Hello World!").unwrap(); +/// writer.write_var_u32(65536).unwrap(); +/// writer.write_u8(0).unwrap(); +/// +/// println!("Bytes: {:?}", writer.as_slice()); +/// } +/// ``` +/// +/// `ByteWriter` also implements the `Into` trait to convert the `ByteWriter` into a `BytesMut` or `Bytes` structs. +/// ```rust +/// use binary_utils::io::ByteWriter; +/// use binary_utils::io::ByteReader; +/// +/// fn main() { +/// let mut writer = ByteWriter::new(); +/// writer.write_u8(1); +/// writer.write_u8(2); +/// writer.write_u8(3); +/// +/// let mut reader: ByteReader = writer.into(); +/// assert_eq!(reader.read_u8().unwrap(), 1); +/// assert_eq!(reader.read_u8().unwrap(), 2); +/// assert_eq!(reader.read_u8().unwrap(), 3); +/// } +/// ``` +/// +/// #### ByteWriter Implementation Notice +/// While most of the methods are reversable, some are not. +/// Meaning there is a chance that if you call a method in a edge case, it will corrupt the stream. +/// +/// For example, `write_var_u32` is not reversable because we currently do not +/// allocate a buffer to store the bytes before writing them to the buffer. +/// While you should never encounter this issue, it is possible when you run out of memory. +/// This issue is marked as a todo, but is low priority. pub struct ByteWriter { - pub buf: BytesMut, + pub(crate) buf: BytesMut, } impl Into for ByteWriter { @@ -421,6 +562,14 @@ impl From<&[u8]> for ByteWriter { } } +impl From for ByteWriter { + fn from(reader: ByteReader) -> Self { + Self { + buf: reader.buf.chunk().into(), + } + } +} + impl ByteWriter { pub fn new() -> Self { return Self { @@ -580,7 +729,7 @@ impl ByteWriter { /// Write a string to the buffer /// The string is written as a var_u32 length followed by the bytes of the string. - /// Uses https://protobuf.dev/programming-guides/encoding/#length-types for length encoding + /// Uses for length encoding pub fn write_string(&mut self, string: &str) -> Result<(), std::io::Error> { // https://protobuf.dev/programming-guides/encoding/#length-types if can_write!(self, string.len()) { @@ -599,4 +748,4 @@ impl ByteWriter { pub fn clear(&mut self) { self.buf.clear(); } -} \ No newline at end of file +} diff --git a/tests/reader.rs b/tests/reader.rs new file mode 100644 index 0000000..6a8a72a --- /dev/null +++ b/tests/reader.rs @@ -0,0 +1 @@ +use binary_utils::io::ByteReader; From b2429fc47a5d3767c3e49a8b8aade1eeeb2283c8 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 15:28:36 -0500 Subject: [PATCH 03/26] chore: add bc --- src/interfaces.rs | 133 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 65 +++++++++++++++++++++- src/stream.rs | 1 - 3 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/interfaces.rs delete mode 100644 src/stream.rs diff --git a/src/interfaces.rs b/src/interfaces.rs new file mode 100644 index 0000000..e1c58d2 --- /dev/null +++ b/src/interfaces.rs @@ -0,0 +1,133 @@ +use crate::io::{ByteReader, ByteWriter}; + +/// Allows you to read from a `ByteReader` without needing to know the type. +/// +/// ```no_run +/// use binary_utils::io::{ByteReader, Reader}; +/// +/// pub struct MyStruct { +/// pub a: u8, +/// pub b: u8 +/// } +/// +/// impl Reader for MyStruct { +/// fn read(&self, buf: &mut ByteReader) -> Result { +/// let a = buf.read_u8()?; +/// let b = buf.read_u8()?; +/// Ok(Self { a, b }) +/// } +/// } +/// ``` +pub trait Reader { + /// Reads `Self` from a `ByteReader`. + /// + /// For automatic implementations, use `#[derive(BinaryDecoder]` macro. + fn read(buf: &mut ByteReader) -> Result; +} + +pub trait Writer { + /// Writes `Self` to a `ByteWriter`. + /// + /// For automatic implementations, use `#[derive(BinaryEncoder]` macro. + fn write(&self, buf: &mut ByteWriter) -> Result<&mut ByteWriter, std::io::Error>; + + /// This is a utility function to write `Self` to a `ByteWriter` without + /// needing to create a `ByteWriter` first. + fn init_write(&self) -> Result<&mut ByteWriter, std::io::Error> { + let mut buf = ByteWriter::new(); + self.write(&mut buf) + } +} + +/// ## Deprecated +/// __**This trait exists only for backwards compatibility.**__ +/// +/// If you wish to read and write from a `ByteReader` or `ByteWriter`, +/// use the `Reader` and `Writer` traits. +/// +/// ### New Implementation Example +/// ```no_run +/// use binary_utils::io::{ByteReader, ByteWriter}; +/// use binary_utils::interfaces::{Reader, Writer}; +/// +/// pub struct MyStruct; +/// +/// impl Reader for MyStruct; +/// impl Writer for MyStruct; +/// ``` +/// +/// ## `Streamable` +/// A trait to parse and unparse header structs from a given buffer. +/// +/// ```no_run +/// use binary_utils::{Streamable, error::BinaryError}; +/// +/// struct Foo { +/// bar: u8, +/// foo_bar: u16 +/// } +/// impl Streamable for Foo { +/// fn parse(&self) -> Result, BinaryError> { +/// use std::io::Write; +/// let mut stream = Vec::::new(); +/// stream.write_all(&self.bar.parse()?[..])?; +/// stream.write_all(&self.bar.parse()?[..])?; +/// Ok(stream) +/// } +/// +/// fn compose(source: &[u8], position: &mut usize) -> Result { +/// // Streamable is implemented for all primitives, so we can +/// // just use this implementation to read our properties. +/// Ok(Self { +/// bar: u8::compose(&source, position)?, +/// foo_bar: u16::compose(&source, position)? +/// }) +/// } +/// } +/// ``` +pub trait Streamable: Reader + Writer { + /// Writes `self` to the given buffer. + fn parse(&self) -> Result, crate::error::BinaryError> + where + T: Sized + { + if let Ok(v) = self.init_write() { + Ok(v.as_slice().to_vec()) + } else { + Err(crate::error::BinaryError::RecoverableUnknown) + } + } + + /// Writes and unwraps `self` to the given buffer. + /// + /// ⚠️ This method is not fail safe, and will panic if result is Err. + fn fparse(&self) -> Vec + where + T: Sized + { + self.parse().unwrap() + } + + /// Reads `self` from the given buffer. + fn compose(source: &[u8], position: &mut usize) -> Result + where + T: Sized + { + let mut reader = ByteReader::from(&source[*position..]); + if let Ok(v) = Self::read(&mut reader) { + Ok(v) + } else { + Err(crate::error::BinaryError::RecoverableUnknown) + } + } + + /// Reads and unwraps `self` from the given buffer. + /// + /// ⚠️ This method is not fail safe, and will panic if result is Err. + fn fcompose(source: &[u8], position: &mut usize) -> T + where + T: Sized, + { + Self::compose(source, position).unwrap() + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 98851ea..6584cdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -/// The `ByteReader` and `ByteWriter` traits are used to read and write bytes from a buffer. +pub mod interfaces; + /// The io module contains implementations of these traits for `bytes::Buf` and `bytes::BufMut`. /// /// Example: @@ -14,4 +15,64 @@ /// ``` pub mod io; pub mod pool; -pub mod stream; + +/// This is a legacy module that will be removed in the future. +/// This module has been replaced in favor of `std::io::Error`. +/// +/// # This module is deprecated +pub mod error { + /// An enum consisting of a Binary Error + /// (recoverable) + #[derive(Debug, PartialEq)] + pub enum BinaryError { + /// Offset is out of bounds + /// + /// **Tuple Values:** + /// - `usize` = Given Offset. + /// - `usize` = Stream length. + /// - `&'static str` = Message to add on to the error. + OutOfBounds(usize, usize, &'static str), + + /// Similar to `OutOfBounds` except it means; + /// the stream tried to read more than possible. + /// + /// **Tuple Values:** + /// - `usize` = Stream length. + EOF(usize), + + /// A known error that was recoverable to safely proceed the stack. + RecoverableKnown(String), + + /// An unknown error occurred, but it wasn't critical, + /// we can safely proceed on the stack. + RecoverableUnknown, + } + + impl BinaryError { + pub fn get_message(&self) -> String { + match self { + Self::OutOfBounds(offset, length, append) => { + format!("Offset {} out of range for a buffer size with: {}. {}", offset, length, append) + }, + Self::EOF(length) => format!("Buffer reached End Of File at offset: {}", length), + Self::RecoverableKnown(msg) => msg.clone(), + Self::RecoverableUnknown => "An interruption occurred when performing a binary operation, however this error was recovered safely.".to_string() + } + } + } + + impl From for BinaryError { + fn from(_error: std::io::Error) -> Self { + Self::RecoverableUnknown + } + } + + impl std::fmt::Display for BinaryError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.get_message()) + } + } + +} + +pub use interfaces::Streamable; diff --git a/src/stream.rs b/src/stream.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/stream.rs +++ /dev/null @@ -1 +0,0 @@ - From 43ac83280d717c31338d4ec63cb1394791003a4d Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 15:31:19 -0500 Subject: [PATCH 04/26] tests(io.rs): Fix doc tests --- src/interfaces.rs | 14 +++++++------- src/io.rs | 4 ++-- src/lib.rs | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/interfaces.rs b/src/interfaces.rs index e1c58d2..2434f98 100644 --- a/src/interfaces.rs +++ b/src/interfaces.rs @@ -2,7 +2,7 @@ use crate::io::{ByteReader, ByteWriter}; /// Allows you to read from a `ByteReader` without needing to know the type. /// -/// ```no_run +/// ```ignore /// use binary_utils::io::{ByteReader, Reader}; /// /// pub struct MyStruct { @@ -46,7 +46,7 @@ pub trait Writer { /// use the `Reader` and `Writer` traits. /// /// ### New Implementation Example -/// ```no_run +/// ```ignore /// use binary_utils::io::{ByteReader, ByteWriter}; /// use binary_utils::interfaces::{Reader, Writer}; /// @@ -59,7 +59,7 @@ pub trait Writer { /// ## `Streamable` /// A trait to parse and unparse header structs from a given buffer. /// -/// ```no_run +/// ```ignore /// use binary_utils::{Streamable, error::BinaryError}; /// /// struct Foo { @@ -89,7 +89,7 @@ pub trait Streamable: Reader + Writer { /// Writes `self` to the given buffer. fn parse(&self) -> Result, crate::error::BinaryError> where - T: Sized + T: Sized, { if let Ok(v) = self.init_write() { Ok(v.as_slice().to_vec()) @@ -103,7 +103,7 @@ pub trait Streamable: Reader + Writer { /// ⚠️ This method is not fail safe, and will panic if result is Err. fn fparse(&self) -> Vec where - T: Sized + T: Sized, { self.parse().unwrap() } @@ -111,7 +111,7 @@ pub trait Streamable: Reader + Writer { /// Reads `self` from the given buffer. fn compose(source: &[u8], position: &mut usize) -> Result where - T: Sized + T: Sized, { let mut reader = ByteReader::from(&source[*position..]); if let Ok(v) = Self::read(&mut reader) { @@ -130,4 +130,4 @@ pub trait Streamable: Reader + Writer { { Self::compose(source, position).unwrap() } -} \ No newline at end of file +} diff --git a/src/io.rs b/src/io.rs index fe0e0ab..8203041 100644 --- a/src/io.rs +++ b/src/io.rs @@ -72,7 +72,7 @@ macro_rules! write_fn { /// /// fn main() { /// let mut buf = ByteReader::from(&[253, 255, 14, 255, 255, 15][..]); -/// if buf.peek_ahead(3) != 255 { +/// if buf.peek_ahead(3).unwrap() != 255 { /// // buffer is corrupted! /// } else { /// // read the varint @@ -174,7 +174,7 @@ impl ByteReader { /// /// fn main() { /// let mut buf = ByteReader::from(&[253, 255, 14, 255, 255, 15][..]); - /// if buf.peek_ahead(3) != 255 { + /// if buf.peek_ahead(3).unwrap() != 255 { /// // buffer is corrupted, varints can never have a leading byte less than 255 if /// // Their are bytes remaining! /// } else { diff --git a/src/lib.rs b/src/lib.rs index 6584cdc..f4f8f80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,7 +72,6 @@ pub mod error { write!(f, "{}", self.get_message()) } } - } pub use interfaces::Streamable; From b811ba833dd6a2ad8c6ba333db2fd5d2c9e86c4f Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 16:25:33 -0500 Subject: [PATCH 05/26] tests(varint): Fix tests --- tests/varint.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tests/varint.rs b/tests/varint.rs index d51e83a..d20037e 100644 --- a/tests/varint.rs +++ b/tests/varint.rs @@ -25,10 +25,9 @@ fn read_var_i32() { // -12 let mut buf = ByteReader::from([23].to_vec()); assert_eq!(buf.read_var_i32().unwrap(), -12); - // todo: fix this - // todo: for some reason this doesn't work on very large numbers, we're 1 bit off - // let mut buf = ByteReader::from([254, 255, 255, 255, 15].to_vec()); - // assert_eq!(buf.read_var_i32().unwrap(), -2147483648); + + let mut buf = ByteReader::from([255, 255, 255, 255, 15].to_vec()); + assert_eq!(buf.read_var_i32().unwrap(), -2147483648); } #[test] @@ -91,3 +90,25 @@ fn write_var_i64() { buf.write_var_i64(-9223372036854775808).unwrap(); assert_eq!(buf.as_slice(), &NEGATIVE_LONG[..]); } + + +#[test] +fn var_int_32_overflow() { + let mut buf = ByteWriter::new(); + buf.write_var_u32(2147483648).unwrap(); + assert_eq!(buf.as_slice(), &[128, 128, 128, 128, 8]); + + let mut buf = ByteReader::from(&buf.as_slice()[..]); + assert_eq!(buf.read_var_u32().unwrap(), 2147483648); + + // now into i32 + let mut buf = ByteWriter::new(); + buf.write_var_i32(i32::MIN).unwrap(); + + let mut buf = ByteReader::from(&buf.as_slice()[..]); + assert_eq!(buf.read_var_i32().unwrap(), i32::MIN); + + // validate i32 ::MAX overflow now + let mut buf = ByteWriter::new(); + buf.write_var_i32(i32::MAX).unwrap(); +} \ No newline at end of file From 1c696988d67e938e0b0eb895527894696216e891 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 16:27:18 -0500 Subject: [PATCH 06/26] chore: This bug was fixed in last commit --- src/io.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/io.rs b/src/io.rs index 8203041..d94512d 100644 --- a/src/io.rs +++ b/src/io.rs @@ -331,18 +331,9 @@ impl ByteReader { /// Reads a var-int 32-bit signed integer from the stream. /// This method is the same as `read_var_u32` but it will return a signed integer. pub fn read_var_i32(&mut self) -> Result { - // todo: fails on -2147483648, which is the minimum value for i32 - // todo: probably nothing to worry about, but should be fixed let num = self.read_var_u32()?; - // for some reason this does not work on large numbers Ok((num >> 1) as i32 ^ -((num & 1) as i32)) - - // return Ok(if num & 1 != 0 { - // !((num >> 1) as i32) - // } else { - // (num >> 1) as i32 - // }); } read_fn!(read_u64, u64, get_u64, 8); @@ -625,17 +616,6 @@ impl ByteWriter { let num = num as u32; self.write_var_u32(num << 1) }; - // let mut x = (num as u32) & u32::MAX; - // for _ in (0..35).step_by(7) { - // if x >> 7 == 0 { - // self.write_u8(x as u8)?; - // return Ok(()); - // } else { - // self.write_u8(((x & 0x7F) | 0x80) as u8)?; - // x >>= 7; - // } - // } - // return Err(Error::new(std::io::ErrorKind::InvalidData, ERR_VARINT_TOO_LONG)); } write_fn!(write_u64, u64, put_u64, 8); From fdf6327d07fb335fce607f5c8c08de1c6a491799 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 9 Apr 2023 22:22:16 -0500 Subject: [PATCH 07/26] chore: Fix bug --- src/interfaces.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/interfaces.rs b/src/interfaces.rs index 2434f98..cc989fb 100644 --- a/src/interfaces.rs +++ b/src/interfaces.rs @@ -29,13 +29,14 @@ pub trait Writer { /// Writes `Self` to a `ByteWriter`. /// /// For automatic implementations, use `#[derive(BinaryEncoder]` macro. - fn write(&self, buf: &mut ByteWriter) -> Result<&mut ByteWriter, std::io::Error>; + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error>; /// This is a utility function to write `Self` to a `ByteWriter` without /// needing to create a `ByteWriter` first. - fn init_write(&self) -> Result<&mut ByteWriter, std::io::Error> { + fn init_write(&self) -> Result { let mut buf = ByteWriter::new(); - self.write(&mut buf) + self.write(&mut buf)?; + Ok(buf) } } From 5cb32bd4abf0cbf79abe7603f2892b241933b52d Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Mon, 10 Apr 2023 22:29:03 -0500 Subject: [PATCH 08/26] feat: Add some implementation on primitive types --- src/interfaces.rs | 242 +++++++++++++++++++++++++++++++++++++++++++--- src/io.rs | 79 +++++++++++++-- 2 files changed, 302 insertions(+), 19 deletions(-) diff --git a/src/interfaces.rs b/src/interfaces.rs index cc989fb..ff03818 100644 --- a/src/interfaces.rs +++ b/src/interfaces.rs @@ -1,5 +1,33 @@ +use std::{net::{SocketAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6, SocketAddrV4}}; + use crate::io::{ByteReader, ByteWriter}; +pub type LE = std::num::Wrapping; + +macro_rules! impl_reader { + ($($t:ty, $method: tt),*) => { + $( + impl Reader<$t> for $t { + fn read(buf: &mut ByteReader) -> Result<$t, std::io::Error> { + buf.$method() + } + } + )* + }; +} + +macro_rules! impl_writer { + ($($t:ty, $method: tt),*) => { + $( + impl Writer for $t { + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + buf.$method(*self) + } + } + )* + }; +} + /// Allows you to read from a `ByteReader` without needing to know the type. /// /// ```ignore @@ -18,11 +46,132 @@ use crate::io::{ByteReader, ByteWriter}; /// } /// } /// ``` -pub trait Reader { +pub trait Reader { /// Reads `Self` from a `ByteReader`. /// /// For automatic implementations, use `#[derive(BinaryDecoder]` macro. - fn read(buf: &mut ByteReader) -> Result; + fn read(buf: &mut ByteReader) -> Result; +} + +// default implementations on primitive types. +impl_reader!( + u8, read_u8, + u16, read_u16, + u32, read_u32, + u64, read_u64, + u128, read_u128, + i8, read_i8, + i16, read_i16, + i32, read_i32, + i64, read_i64, + i128, read_i128, + f32, read_f32, + f64, read_f64, + bool, read_bool, + char, read_char, + String, read_string +); + +// little endian implementations on primitive types. +// impl_reader!( +// LE, read_u16_le, +// LE, read_u32_le, +// LE, read_u64_le, +// LE, read_u128_le, +// LE, read_i16_le, +// LE, read_i32_le, +// LE, read_i64_le, +// LE, read_i128_le, +// LE, read_f32_le, +// LE, read_f64_le +// ); + +impl Reader> for Vec +where + T: Reader + Sized, +{ + fn read(buf: &mut ByteReader) -> Result, std::io::Error> { + let len = buf.read_var_u32()?; + let mut vec = Vec::with_capacity(len as usize); + for _ in 0..len { + vec.push(T::read(buf)?); + } + Ok(vec) + } +} + +impl Reader> for Option +where + T: Reader + Sized, +{ + fn read(buf: &mut ByteReader) -> Result, std::io::Error> { + let is_some = buf.read_bool()?; + if is_some { + Ok(Some(T::read(buf)?)) + } else { + Ok(None) + } + } +} + +impl Reader for SocketAddr { + fn read(buf: &mut ByteReader) -> Result { + match buf.read_u8()? { + 4 => { + let parts = ( + buf.read_u8()?, + buf.read_u8()?, + buf.read_u8()?, + buf.read_u8()?, + ); + let port = buf.read_u16()?; + Ok(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(parts.0, parts.1, parts.2, parts.3), + port, + ))) + }, + 6 => { + let _family = buf.read_u16()?; + let port = buf.read_u16()?; + let flow = buf.read_u32()?; + let parts = ( + buf.read_u16()?, + buf.read_u16()?, + buf.read_u16()?, + buf.read_u16()?, + buf.read_u16()?, + buf.read_u16()?, + buf.read_u16()?, + buf.read_u16()?, + ); + let address = Ipv6Addr::new( + parts.0, + parts.1, + parts.2, + parts.3, + parts.4, + parts.5, + parts.6, + parts.7 + ); + let scope = buf.read_u32()?; + Ok(SocketAddr::V6( + SocketAddrV6::new( + address, + port, + flow, + scope + ) + )) + }, + _ => { + Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid IP version" + )) + } + } + } } pub trait Writer { @@ -40,6 +189,81 @@ pub trait Writer { } } +// default implementations on primitive types. +impl_writer!( + u8, write_u8, + u16, write_u16, + u32, write_u32, + u64, write_u64, + u128, write_u128, + i8, write_i8, + i16, write_i16, + i32, write_i32, + i64, write_i64, + i128, write_i128, + f32, write_f32, + f64, write_f64, + bool, write_bool, + char, write_char, + &str, write_string +); + +impl Writer for Vec +where + T: Writer + Sized, +{ + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + buf.write_var_u32(self.len() as u32)?; + for item in self { + item.write(buf)?; + } + Ok(()) + } +} + +impl Writer for Option +where + T: Writer + Sized, +{ + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + match self { + Some(item) => { + buf.write_bool(true)?; + item.write(buf)?; + }, + None => { + buf.write_bool(false)?; + } + } + Ok(()) + } +} + +impl Writer for SocketAddr { + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + match self { + SocketAddr::V4(addr) => { + buf.write_u8(4)?; + buf.write(&addr.ip().octets())?; + buf.write_u16(addr.port())?; + }, + SocketAddr::V6(addr) => { + buf.write_u8(6)?; + // family (unused by rust) + buf.write_u16(0)?; + // port + buf.write_u16(addr.port())?; + // flow + buf.write_u32(addr.flowinfo())?; + // address eg: 0:0:0:0:0:ffff:7f00:1 + buf.write(&addr.ip().octets())?; + // scope + buf.write_u32(addr.scope_id())?; + } + } + Ok(()) + } +} /// ## Deprecated /// __**This trait exists only for backwards compatibility.**__ /// @@ -88,10 +312,7 @@ pub trait Writer { /// ``` pub trait Streamable: Reader + Writer { /// Writes `self` to the given buffer. - fn parse(&self) -> Result, crate::error::BinaryError> - where - T: Sized, - { + fn parse(&self) -> Result, crate::error::BinaryError> { if let Ok(v) = self.init_write() { Ok(v.as_slice().to_vec()) } else { @@ -102,17 +323,14 @@ pub trait Streamable: Reader + Writer { /// Writes and unwraps `self` to the given buffer. /// /// ⚠️ This method is not fail safe, and will panic if result is Err. - fn fparse(&self) -> Vec - where - T: Sized, - { + fn fparse(&self) -> Vec { self.parse().unwrap() } /// Reads `self` from the given buffer. fn compose(source: &[u8], position: &mut usize) -> Result where - T: Sized, + Self: Sized { let mut reader = ByteReader::from(&source[*position..]); if let Ok(v) = Self::read(&mut reader) { @@ -127,7 +345,7 @@ pub trait Streamable: Reader + Writer { /// ⚠️ This method is not fail safe, and will panic if result is Err. fn fcompose(source: &[u8], position: &mut usize) -> T where - T: Sized, + Self: Sized { Self::compose(source, position).unwrap() } diff --git a/src/io.rs b/src/io.rs index d94512d..d050893 100644 --- a/src/io.rs +++ b/src/io.rs @@ -192,6 +192,7 @@ impl ByteReader { } read_fn!(read_u8, u8, get_u8, 1); + read_fn!(read_i8, i8, get_i8, 1); read_fn!(read_u16, u16, get_u16, 2); read_fn!(read_u16_le, u16, get_u16_le, 2); read_fn!(read_i16, i16, get_i16, 2); @@ -433,6 +434,19 @@ impl ByteReader { } } + pub fn read_char(&mut self) -> Result { + let c = self.read_u32()?; + + if let Some(c) = char::from_u32(c) { + return Ok(c); + } else { + return Err(Error::new( + std::io::ErrorKind::InvalidData, + "Invalid char", + )); + } + } + pub fn read_bool(&mut self) -> Result { if can_read!(self, 1) { return Ok(self.buf.get_u8() != 0); @@ -460,6 +474,32 @@ impl ByteReader { } } + /// Reads a varu32 sized slice from the stream. + /// For reading a slice of raw bytes, use `read` instead. + pub fn read_sized_slice(&mut self) -> Result { + let len = self.read_var_u32()?; + + if can_read!(self, len as usize) { + let b = self.buf.slice(..len as usize); + self.buf.advance(len as usize); + return Ok(b); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } + } + + /// Reads a slice from the stream into the slice passed by the caller. + /// For reading a prefixed sized slice, use `read_sized_slice` instead. + pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), std::io::Error> { + if can_read!(self, buffer.len()) { + self.buf.copy_to_slice(buffer); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::UnexpectedEof, ERR_EOB)); + } + } + + /// Returns the remaining bytes in the stream. pub fn as_slice(&self) -> &[u8] { self.buf.chunk() } @@ -569,6 +609,7 @@ impl ByteWriter { } write_fn!(write_u8, u8, put_u8, 1); + write_fn!(write_i8, i8, put_i8, 1); write_fn!(write_u16, u16, put_u16, 2); write_fn!(write_u16_le, u16, put_u16_le, 2); write_fn!(write_i16, i16, put_i16, 2); @@ -653,6 +694,11 @@ impl ByteWriter { }; } + write_fn!(write_u128, u128, put_u128, 16); + write_fn!(write_u128_le, u128, put_u128_le, 16); + write_fn!(write_i128, i128, put_i128, 16); + write_fn!(write_i128_le, i128, put_i128_le, 16); + pub fn write_uint(&mut self, num: u64, size: usize) -> Result<(), std::io::Error> { if can_write!(self, size) { self.buf.put_uint(num, size); @@ -689,13 +735,8 @@ impl ByteWriter { } } - pub fn write_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { - if can_write!(self, slice.len()) { - self.buf.put_slice(slice); - return Ok(()); - } else { - return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); - } + pub fn write_char(&mut self, c: char) -> Result<(), std::io::Error> { + self.write_u32(c as u32) } pub fn write_bool(&mut self, b: bool) -> Result<(), std::io::Error> { @@ -721,6 +762,30 @@ impl ByteWriter { } } + /// Writes a size-prefixed slice of bytes to the buffer. The slice is prefixed with a var_u32 length. + pub fn write_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { + if can_write!(self, slice.len()) { + self.write_var_u32(slice.len() as u32)?; + self.buf.put_slice(slice); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + + /// Writes a slice of bytes to the buffer + /// This is not the same as a size-prefixed slice, this is just a raw slice of bytes. + /// + /// For automatically size-prefixed slices, use `write_slice`. + pub fn write(&mut self, buf: &[u8]) -> Result<(), std::io::Error> { + if can_write!(self, buf.len()) { + self.buf.put_slice(buf); + return Ok(()); + } else { + return Err(Error::new(std::io::ErrorKind::OutOfMemory, ERR_EOM)); + } + } + pub fn as_slice(&self) -> &[u8] { self.buf.chunk() } From 1dfc6b0eb566fbc6b02be2d622c2b5ce3d136d1a Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Tue, 11 Apr 2023 00:44:48 -0500 Subject: [PATCH 09/26] bulk: Add legacy code gen feat: Add to reader and writer tests(reader.rs): add a simple test for decoding strings, var32 and options --- Cargo.toml | 2 +- bin_macro/Cargo.lock | 47 ----- bin_macro/Cargo.toml | 13 -- bin_macro/src/lib.rs | 10 -- codegen/Cargo.toml | 13 ++ .../src/stream.rs => codegen/src/legacy.rs | 32 +++- codegen/src/lib.rs | 37 ++++ src/interfaces.rs | 166 +++++++++++------- src/io.rs | 80 ++++++++- src/lib.rs | 16 +- tests/reader.rs | 20 +++ tests/varint.rs | 3 +- 12 files changed, 291 insertions(+), 148 deletions(-) delete mode 100644 bin_macro/Cargo.lock delete mode 100644 bin_macro/Cargo.toml delete mode 100644 bin_macro/src/lib.rs create mode 100644 codegen/Cargo.toml rename bin_macro/src/stream.rs => codegen/src/legacy.rs (90%) create mode 100644 codegen/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 65e81af..8b13c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" include = ["src/**/*", "README.md"] [dependencies] -bin_macro = { path = "./bin_macro" } +codegen = { path = "./codegen" } bytes = "1.4.0" [features] \ No newline at end of file diff --git a/bin_macro/Cargo.lock b/bin_macro/Cargo.lock deleted file mode 100644 index 8b34b44..0000000 --- a/bin_macro/Cargo.lock +++ /dev/null @@ -1,47 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bin_macro" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/bin_macro/Cargo.toml b/bin_macro/Cargo.toml deleted file mode 100644 index 4b3c596..0000000 --- a/bin_macro/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "bin_macro" -version = "0.1.0" -edition = "2021" -private = true - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.29" -quote = "1.0.10" -syn = { version = "1.0.80", features = [ "full" ] } diff --git a/bin_macro/src/lib.rs b/bin_macro/src/lib.rs deleted file mode 100644 index be46294..0000000 --- a/bin_macro/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; -mod stream; - -#[proc_macro_derive(BinaryStream)] -pub fn derive_stream(input: TokenStream) -> TokenStream { - stream::stream_parse(parse_macro_input!(input as DeriveInput)) - .unwrap() - .into() -} diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..2305130 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "codegen" +version = "0.1.0" +edition = "2021" +private = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.56" +quote = "1.0.26" +syn = { version = "2.0.13", features = ["full"] } diff --git a/bin_macro/src/stream.rs b/codegen/src/legacy.rs similarity index 90% rename from bin_macro/src/stream.rs rename to codegen/src/legacy.rs index f9fd8e3..0ed9a5c 100644 --- a/bin_macro/src/stream.rs +++ b/codegen/src/legacy.rs @@ -1,10 +1,11 @@ use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{Attribute, Data, DeriveInput, Error, Expr, ExprLit, Fields, Lit, LitInt, Result, Type}; pub fn stream_parse(input: DeriveInput) -> Result { let name = &input.ident; let attrs = input.attrs; + match input.data { Data::Struct(v) => { // iterate through struct fields @@ -18,8 +19,6 @@ pub fn stream_parse(input: DeriveInput) -> Result { impl Streamable for #name { fn parse(&self) -> Result, ::binary_utils::error::BinaryError> { use ::std::io::Write; - use binary_utils::varint::{VarInt, VarIntWriter}; - use binary_utils::{u24, u24Writer}; let mut writer = Vec::new(); #writes Ok(writer) @@ -27,9 +26,6 @@ pub fn stream_parse(input: DeriveInput) -> Result { fn compose(source: &[u8], position: &mut usize) -> Result { use ::std::io::Read; - use binary_utils::varint::{VarInt, VarIntReader}; - use binary_utils::{u24, u24Reader}; - Ok(Self { #reads }) @@ -160,7 +156,7 @@ pub fn stream_parse(input: DeriveInput) -> Result { Ok(quote! { #[automatically_derived] - impl Streamable for #name { + impl Streamable<#name> for #name { fn parse(&self) -> Result, ::binary_utils::error::BinaryError> { match self { #(#writers)* @@ -177,6 +173,26 @@ pub fn stream_parse(input: DeriveInput) -> Result { } } } + + impl ::binary_utils::interfaces::Writer for #name { + fn write(&self, buf: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + match self { + #(#writers)* + } + } + } + + impl ::binary_utils::interfaces::Reader<#name> for #name { + fn read(buf: &mut ::binary_utils::io::ByteReader) -> Result { + // get the repr type and read it + let v = <#enum_ty>::compose(source, offset)?; + + match v { + #(#readers)* + _ => panic!("Will not fit in enum!") + } + } + } }) } Data::Union(_) => Err(syn::Error::new( @@ -221,7 +237,7 @@ pub fn impl_streamable_lazy(name: &Ident, ty: &Type) -> (TokenStream, TokenStrea } fn find_one_attr(name: &str, attrs: Vec) -> Option { - let mut iter = attrs.iter().filter(|a| a.path.is_ident(name)); + let mut iter = attrs.iter().filter(|a| a.path().is_ident(name)); match (iter.next(), iter.next()) { (Some(v), None) => Some(v.clone()), _ => None, diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..7dbcc0a --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,37 @@ +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +mod legacy; + +/// **DEPRECATED**. +/// This is a legacy proc-macro that is used to generate a BufferStream. +/// It provides an easy way to implement the `Streamable` trait. +/// +/// ## Deprecated +/// Deprecated since `0.3.0` in favor of `BinaryReader` and `BinaryWriter`. +/// +/// Example: +/// ```ignore +/// use binary_utils::BinaryStream; +/// +/// #[derive(BinaryStream)] +/// struct Test { +/// a: u8, +/// b: u16 +/// } +/// +/// fn main() { +/// let test = Test { a: 0, b: 0 }; +/// test.parse().unwrap(); +/// } +/// ``` +#[proc_macro_derive(BinaryStream)] +pub fn derive_stream(input: TokenStream) -> TokenStream { + return syn::Error::new_spanned( + parse_macro_input!(input as DeriveInput), + "This is a legacy proc-macro that is used to generate the BinaryStream\nDeprecated: use BinaryReader, and BinaryWriter instead." + ).to_compile_error().into(); + // legacy::stream_parse(parse_macro_input!(input as DeriveInput)) + // .unwrap() + // .into() +} \ No newline at end of file diff --git a/src/interfaces.rs b/src/interfaces.rs index ff03818..70d4e75 100644 --- a/src/interfaces.rs +++ b/src/interfaces.rs @@ -1,4 +1,4 @@ -use std::{net::{SocketAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6, SocketAddrV4}}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use crate::io::{ByteReader, ByteWriter}; @@ -49,27 +49,42 @@ macro_rules! impl_writer { pub trait Reader { /// Reads `Self` from a `ByteReader`. /// - /// For automatic implementations, use `#[derive(BinaryDecoder]` macro. + /// For automatic implementations, use the `#[derive(BinaryIo)]` macro. fn read(buf: &mut ByteReader) -> Result; } // default implementations on primitive types. impl_reader!( - u8, read_u8, - u16, read_u16, - u32, read_u32, - u64, read_u64, - u128, read_u128, - i8, read_i8, - i16, read_i16, - i32, read_i32, - i64, read_i64, - i128, read_i128, - f32, read_f32, - f64, read_f64, - bool, read_bool, - char, read_char, - String, read_string + u8, + read_u8, + i8, + read_i8, + u16, + read_u16, + i16, + read_i16, + u32, + read_u32, + i32, + read_i32, + u64, + read_u64, + i64, + read_i64, + u128, + read_u128, + i128, + read_i128, + f32, + read_f32, + f64, + read_f64, + bool, + read_bool, + char, + read_char, + String, + read_string ); // little endian implementations on primitive types. @@ -129,7 +144,7 @@ impl Reader for SocketAddr { Ipv4Addr::new(parts.0, parts.1, parts.2, parts.3), port, ))) - }, + } 6 => { let _family = buf.read_u16()?; let port = buf.read_u16()?; @@ -145,35 +160,39 @@ impl Reader for SocketAddr { buf.read_u16()?, ); let address = Ipv6Addr::new( - parts.0, - parts.1, - parts.2, - parts.3, - parts.4, - parts.5, - parts.6, - parts.7 + parts.0, parts.1, parts.2, parts.3, parts.4, parts.5, parts.6, parts.7, ); let scope = buf.read_u32()?; - Ok(SocketAddr::V6( - SocketAddrV6::new( - address, - port, - flow, - scope - ) - )) - }, - _ => { - Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Invalid IP version" - )) + Ok(SocketAddr::V6(SocketAddrV6::new( + address, port, flow, scope, + ))) } + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid IP version", + )), } } } +/// Allows you to write to a `ByteWriter` without needing to know the type. +/// +/// ```ignore +/// use binary_utils::io::{ByteWriter, Writer}; +/// +/// pub struct MyStruct { +/// pub a: u8, +/// pub b: u8 +/// } +/// +/// impl Writer for MyStruct { +/// fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { +/// buf.write_u8(self.a)?; +/// buf.write_u8(self.b)?; +/// Ok(()); +/// } +/// } +/// ``` pub trait Writer { /// Writes `Self` to a `ByteWriter`. /// @@ -182,7 +201,7 @@ pub trait Writer { /// This is a utility function to write `Self` to a `ByteWriter` without /// needing to create a `ByteWriter` first. - fn init_write(&self) -> Result { + fn write_to_bytes(&self) -> Result { let mut buf = ByteWriter::new(); self.write(&mut buf)?; Ok(buf) @@ -191,23 +210,48 @@ pub trait Writer { // default implementations on primitive types. impl_writer!( - u8, write_u8, - u16, write_u16, - u32, write_u32, - u64, write_u64, - u128, write_u128, - i8, write_i8, - i16, write_i16, - i32, write_i32, - i64, write_i64, - i128, write_i128, - f32, write_f32, - f64, write_f64, - bool, write_bool, - char, write_char, - &str, write_string + u8, + write_u8, + i8, + write_i8, + u16, + write_u16, + i16, + write_i16, + u32, + write_u32, + i32, + write_i32, + u64, + write_u64, + i64, + write_i64, + u128, + write_u128, + i128, + write_i128, + f32, + write_f32, + f64, + write_f64, + bool, + write_bool, + &str, + write_string ); +impl Writer for String { + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + buf.write_string(self) + } +} + +impl Writer for char { + fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + buf.write_char(*self) + } +} + impl Writer for Vec where T: Writer + Sized, @@ -230,7 +274,7 @@ where Some(item) => { buf.write_bool(true)?; item.write(buf)?; - }, + } None => { buf.write_bool(false)?; } @@ -246,7 +290,7 @@ impl Writer for SocketAddr { buf.write_u8(4)?; buf.write(&addr.ip().octets())?; buf.write_u16(addr.port())?; - }, + } SocketAddr::V6(addr) => { buf.write_u8(6)?; // family (unused by rust) @@ -313,7 +357,7 @@ impl Writer for SocketAddr { pub trait Streamable: Reader + Writer { /// Writes `self` to the given buffer. fn parse(&self) -> Result, crate::error::BinaryError> { - if let Ok(v) = self.init_write() { + if let Ok(v) = self.write_to_bytes() { Ok(v.as_slice().to_vec()) } else { Err(crate::error::BinaryError::RecoverableUnknown) @@ -330,7 +374,7 @@ pub trait Streamable: Reader + Writer { /// Reads `self` from the given buffer. fn compose(source: &[u8], position: &mut usize) -> Result where - Self: Sized + Self: Sized, { let mut reader = ByteReader::from(&source[*position..]); if let Ok(v) = Self::read(&mut reader) { @@ -345,7 +389,7 @@ pub trait Streamable: Reader + Writer { /// ⚠️ This method is not fail safe, and will panic if result is Err. fn fcompose(source: &[u8], position: &mut usize) -> T where - Self: Sized + Self: Sized, { Self::compose(source, position).unwrap() } diff --git a/src/io.rs b/src/io.rs index d050893..e6704c2 100644 --- a/src/io.rs +++ b/src/io.rs @@ -4,6 +4,8 @@ use std::{ io::{Error, IoSlice}, }; +use crate::interfaces::{Reader, Writer}; + pub const ERR_EOB: &str = "No more bytes left to be read in buffer"; pub const ERR_EOM: &str = "Buffer is full, cannot write more bytes"; pub const ERR_VARINT_TOO_LONG: &str = "Varint is too long to be written to buffer"; @@ -440,10 +442,7 @@ impl ByteReader { if let Some(c) = char::from_u32(c) { return Ok(c); } else { - return Err(Error::new( - std::io::ErrorKind::InvalidData, - "Invalid char", - )); + return Err(Error::new(std::io::ErrorKind::InvalidData, "Invalid char")); } } @@ -474,6 +473,44 @@ impl ByteReader { } } + /// Reads an `Option` of `T` from the stream. + /// `T` must implement the `Reader` trait and be sized. + /// + /// This operation is not recoverable and will corrupt the stream if it fails. + /// If this behavior is desired, you should use `peek_ahead` when implementing + /// the `Reader` trait. + /// + /// # Example + /// ```rust + /// use binary_utils::io::ByteReader; + /// use binary_utils::io::Reader; + /// + /// pub struct HelloWorld { + /// pub magic: u32 + /// } + /// + /// impl Reader for HelloWorld { + /// fn read(reader: &mut ByteReader) -> Result { + /// Ok(HelloWorld { + /// magic: reader.read_u32()? + /// }) + /// } + /// } + /// + /// fn main() { + /// let mut reader = ByteReader::new(&[0x00, 0x00, 0x00, 0x01]); + /// let hello_world = reader.read_option::().unwrap(); + /// assert_eq!(hello_world.is_some(), true); + /// } + /// ``` + pub fn read_option>(&mut self) -> Result, std::io::Error> { + if self.read_bool()? { + return Ok(Some(T::read(self)?)); + } else { + return Ok(None); + } + } + /// Reads a varu32 sized slice from the stream. /// For reading a slice of raw bytes, use `read` instead. pub fn read_sized_slice(&mut self) -> Result { @@ -762,6 +799,41 @@ impl ByteWriter { } } + /// Writes an `Option` to the buffer. The option must implement the `Writer` trait. + /// + /// ## Example + /// ```rust + /// use binary_utils::io::ByteWriter; + /// use binary_utils::io::Writer; + /// + /// pub struct HelloWorld { + /// pub magic: u32 + /// } + /// + /// impl Writer for HelloWorld { + /// fn write(&self, buf: &mut ByteWriter) -> Result<(), std::io::Error> { + /// buf.write_u32(self.magic)?; + /// return Ok(()); + /// } + /// } + /// + /// fn main() { + /// let hello = HelloWorld { magic: 0xCAFEBABE }; + /// let mut buf = hello.write_to_bytes().unwrap(); + /// + /// println!("Hello World: {:?}", buf); + /// } + /// ``` + pub fn write_option(&mut self, option: &Option) -> Result<(), std::io::Error> { + if let Some(option) = option { + self.write_bool(true)?; + option.write(self)?; + } else { + self.write_bool(false)?; + } + return Ok(()); + } + /// Writes a size-prefixed slice of bytes to the buffer. The slice is prefixed with a var_u32 length. pub fn write_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { if can_write!(self, slice.len()) { diff --git a/src/lib.rs b/src/lib.rs index f4f8f80..482ec9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,18 @@ +/// Provides a panic-free way to read and write binary data. +/// All of the methods within this module follow the protobuf specification at . +/// +/// ## Example +/// ```no_run +/// use binary_utils::io::ByteReader; +/// +/// const VARINT: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647 +/// fn main() { +/// let mut buf = ByteReader::from(&VARINT[..]); +/// assert_eq!(buf.read_var_u32().unwrap(), 2147483647); +/// } +/// ``` pub mod interfaces; - +pub use codegen::*; /// The io module contains implementations of these traits for `bytes::Buf` and `bytes::BufMut`. /// /// Example: @@ -15,7 +28,6 @@ pub mod interfaces; /// ``` pub mod io; pub mod pool; - /// This is a legacy module that will be removed in the future. /// This module has been replaced in favor of `std::io::Error`. /// diff --git a/tests/reader.rs b/tests/reader.rs index 6a8a72a..a3687d1 100644 --- a/tests/reader.rs +++ b/tests/reader.rs @@ -1 +1,21 @@ use binary_utils::io::ByteReader; + +// A slice of bytes that is used to test the reader. +// this specific slice is encoded as the following: +// - a string with the contents of: "BinaryUtils" +// - a var_u32 with the value of: 2147483647 +// - a optional u16 with the value of 34 +const SIMPLE_TEST: &[u8] = &[ + // String + 0x0B, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x55, 0x74, 0x69, 0x6C, 0x73, // VarInt + 0xFF, 0xFF, 0xFF, 0xFF, 0x07, // Option + 0x01, 0x00, 0x22, +]; + +#[test] +fn read_simple_test() { + let mut buf = ByteReader::from(&SIMPLE_TEST[..]); + assert_eq!(buf.read_string().unwrap(), "BinaryUtils"); + assert_eq!(buf.read_var_u32().unwrap(), 2147483647); + assert_eq!(buf.read_option::().unwrap(), Some(34)); +} diff --git a/tests/varint.rs b/tests/varint.rs index d20037e..74feee7 100644 --- a/tests/varint.rs +++ b/tests/varint.rs @@ -91,7 +91,6 @@ fn write_var_i64() { assert_eq!(buf.as_slice(), &NEGATIVE_LONG[..]); } - #[test] fn var_int_32_overflow() { let mut buf = ByteWriter::new(); @@ -111,4 +110,4 @@ fn var_int_32_overflow() { // validate i32 ::MAX overflow now let mut buf = ByteWriter::new(); buf.write_var_i32(i32::MAX).unwrap(); -} \ No newline at end of file +} From 3202ee8929227cefc183114bd26db1187585db6c Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Tue, 11 Apr 2023 00:49:45 -0500 Subject: [PATCH 10/26] doc(doc-tests): Fix doc tests --- src/io.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/io.rs b/src/io.rs index e6704c2..d07cbdd 100644 --- a/src/io.rs +++ b/src/io.rs @@ -114,6 +114,7 @@ macro_rules! write_fn { /// } /// } /// ``` +#[derive(Debug, Clone)] pub struct ByteReader { pub(crate) buf: Bytes, } @@ -483,7 +484,7 @@ impl ByteReader { /// # Example /// ```rust /// use binary_utils::io::ByteReader; - /// use binary_utils::io::Reader; + /// use binary_utils::interfaces::Reader; /// /// pub struct HelloWorld { /// pub magic: u32 @@ -498,9 +499,10 @@ impl ByteReader { /// } /// /// fn main() { - /// let mut reader = ByteReader::new(&[0x00, 0x00, 0x00, 0x01]); + /// // Nothing is here! + /// let mut reader = ByteReader::from(&[0x00][..]); /// let hello_world = reader.read_option::().unwrap(); - /// assert_eq!(hello_world.is_some(), true); + /// assert_eq!(hello_world.is_some(), false); /// } /// ``` pub fn read_option>(&mut self) -> Result, std::io::Error> { @@ -586,6 +588,7 @@ impl ByteReader { /// allocate a buffer to store the bytes before writing them to the buffer. /// While you should never encounter this issue, it is possible when you run out of memory. /// This issue is marked as a todo, but is low priority. +#[derive(Debug, Clone)] pub struct ByteWriter { pub(crate) buf: BytesMut, } @@ -804,7 +807,7 @@ impl ByteWriter { /// ## Example /// ```rust /// use binary_utils::io::ByteWriter; - /// use binary_utils::io::Writer; + /// use binary_utils::interfaces::Writer; /// /// pub struct HelloWorld { /// pub magic: u32 From 334e2d4b70b914e0dec0de2c3d29bfc71d485083 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Tue, 11 Apr 2023 00:52:02 -0500 Subject: [PATCH 11/26] chore: 0.3.0 is next --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8b13c26..5f8a518 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "binary_utils" -version = "0.2.3" +version = "0.3.0" authors = ["Bavfalcon9"] edition = "2021" include = ["src/**/*", "README.md"] From 7af37823eae3eeb042abbcfc03d3f322450ca194 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Tue, 11 Apr 2023 00:53:24 -0500 Subject: [PATCH 12/26] chore: Fix comments [ci skip] --- tests/reader.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/reader.rs b/tests/reader.rs index a3687d1..32c7dbf 100644 --- a/tests/reader.rs +++ b/tests/reader.rs @@ -6,10 +6,9 @@ use binary_utils::io::ByteReader; // - a var_u32 with the value of: 2147483647 // - a optional u16 with the value of 34 const SIMPLE_TEST: &[u8] = &[ - // String - 0x0B, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x55, 0x74, 0x69, 0x6C, 0x73, // VarInt - 0xFF, 0xFF, 0xFF, 0xFF, 0x07, // Option - 0x01, 0x00, 0x22, + 0x0B, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x55, 0x74, 0x69, 0x6C, 0x73, // String + 0xFF, 0xFF, 0xFF, 0xFF, 0x07, // VarInt + 0x01, 0x00, 0x22, // Option ]; #[test] From 867faf7702ce2d2a72519c048ddf8fa0147ebcb2 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Tue, 11 Apr 2023 00:57:27 -0500 Subject: [PATCH 13/26] doc(src/io): Remove var-int docs [ci skip] --- src/io.rs | 59 +------------------------------------------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/src/io.rs b/src/io.rs index d07cbdd..b20bee0 100644 --- a/src/io.rs +++ b/src/io.rs @@ -83,7 +83,7 @@ macro_rules! write_fn { /// } /// ``` /// -/// ## Reading a struct without `Composable` +/// ## Reading a struct without `BinaryDecoder` /// This is useful if you are trying to read a struct or optional type and validate the type before /// reading the rest of the struct. /// ```rust @@ -251,63 +251,6 @@ impl ByteReader { /// This function is recoverable, meaning that if the stream ends before the /// var-int is fully read, it will return an error, and will not consume the /// bytes that were read. - /// - /// #### Decoding VarInt - /// You will need to: - /// 1. Read the next byte - /// 2. Get the 7 significant bits (next byte & 0x7F [segment bit]) (continuation bit is 0x80) - /// 3. Shift the value to the left by i bits - /// 4. Validate whether this is the last byte (next byte & 0x80 == 0) - /// 5. IF the last byte, return the value and - /// add the iterative value (current byte) to the current value. - /// - /// A visual representation of how this works: - /// ```txt - /// ======================================================== - /// Buffer: 0xdd 0xc7 0x01 - /// - /// Initial values: - /// i = 0 Current iteration - /// v = 0 Current value - /// - /// ------------------------------------------------------- - /// First iteration (i = 0): - /// ------------------------------------------------------- - /// 0xdd 0xc7 0x01 - /// ^ - b - /// b = 0xdd 11011101 - Next byte - /// b & 0x7F 1011101 - 7 significant bits - /// b << 0 1011101 - Shifted by iteration bits - /// b & 0x80 == 0 false - There are more bytes - /// v =| 1011101 (93) 1011101 - Added to the current value - /// i = i + 1: 1 - Current iteration - /// - /// ------------------------------------------------------- - /// Second iteration (i = 7): - /// ------------------------------------------------------- - /// 0xdd 0xc7 0x01 - /// ^ - b - /// b = 0xc7 11000111 - Next byte - /// b & 0x7F 1000111 - 7 significant bits - /// b << 7 - Shifted by iteration bits - /// | -> 10001110000000 - /// b & 0x80 == 0 false - There are more bytes - /// v =| b - Added to the current value - /// | -> 110001111011101 (25565) - /// - /// ------------------------------------------------------- - /// Third iteration (i = 14): - /// ------------------------------------------------------- - /// 0xdd 0xc7 0x01 - /// ^ - b - /// 0x01 00000001 - Next byte - /// b & 0x7F 0000001 - 7 significant bits - /// b << 14 - Shifted by iteration bits - /// |-> 000000100000000 - /// b & 0x80 == 0 true - This byte is the last byte - /// v = 25565 - Last bit, return the value - /// |-> 110001111011101 (25565) - /// ``` #[inline] pub fn read_var_u32(&mut self) -> Result { let mut num = 0u32; From 728f8b9cfbd49c03c8b070041c233ddd921be0ee Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 16:24:30 -0500 Subject: [PATCH 14/26] chore: Start working on better derive --- codegen/src/io/enums.rs | 9 +++++++++ codegen/src/io/mod.rs | 34 ++++++++++++++++++++++++++++++++++ codegen/src/io/structs.rs | 9 +++++++++ codegen/src/io/unions.rs | 12 ++++++++++++ codegen/src/io/util.rs | 0 codegen/src/lib.rs | 6 ++++++ src/lib.rs | 2 ++ 7 files changed, 72 insertions(+) create mode 100644 codegen/src/io/enums.rs create mode 100644 codegen/src/io/mod.rs create mode 100644 codegen/src/io/structs.rs create mode 100644 codegen/src/io/unions.rs create mode 100644 codegen/src/io/util.rs diff --git a/codegen/src/io/enums.rs b/codegen/src/io/enums.rs new file mode 100644 index 0000000..2526b99 --- /dev/null +++ b/codegen/src/io/enums.rs @@ -0,0 +1,9 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use syn::{parse_macro_input, DeriveInput, Data, Fields, DataEnum}; + +use super::AstContext; + +pub(crate) fn derive_enum(ast_ctx: AstContext, data: DataEnum, error_stream: &mut TokenStream2) -> TokenStream { + todo!() +} \ No newline at end of file diff --git a/codegen/src/io/mod.rs b/codegen/src/io/mod.rs new file mode 100644 index 0000000..5bd8946 --- /dev/null +++ b/codegen/src/io/mod.rs @@ -0,0 +1,34 @@ +pub(crate) mod enums; +pub(crate) mod structs; +pub(crate) mod unions; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput, Data}; + +pub(crate) type AstContext<'a> = (&'a syn::Ident, &'a Vec, &'a syn::Generics, &'a syn::Visibility); + +// BinaryEncoder is a derive macro that implements `::binary_utils::interfaces::Reader` and `::binary_utils::interfaces::Writer` +pub(crate) fn binary_encoder(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ctx: AstContext = (&input.ident, &input.attrs, &input.generics, &input.vis); + + let mut err = proc_macro2::TokenStream::new(); + + let stream = match input.data { + Data::Struct(d) => { + structs::derive_struct(ctx, d, &mut err) + }, + Data::Enum(d) => { + enums::derive_enum(ctx, d, &mut err) + }, + Data::Union(d) => { + unions::derive_union(ctx, d, &mut err) + } + }; + + if err.is_empty() { + stream.into() + } else { + err.into() + } +} \ No newline at end of file diff --git a/codegen/src/io/structs.rs b/codegen/src/io/structs.rs new file mode 100644 index 0000000..a0f0a85 --- /dev/null +++ b/codegen/src/io/structs.rs @@ -0,0 +1,9 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use syn::{parse_macro_input, DeriveInput, Data, Fields, DataStruct}; + +use super::AstContext; + +pub(crate) fn derive_struct(ast_ctx: AstContext, data: DataStruct, error_stream: &mut TokenStream2) -> TokenStream { + todo!() +} \ No newline at end of file diff --git a/codegen/src/io/unions.rs b/codegen/src/io/unions.rs new file mode 100644 index 0000000..b42d846 --- /dev/null +++ b/codegen/src/io/unions.rs @@ -0,0 +1,12 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use syn::{DeriveInput, DataUnion}; + +use super::AstContext; + +pub(crate) fn derive_union(ast_ctx: AstContext, data: DataUnion, error_stream: &mut TokenStream2) -> TokenStream { + syn::Error::new_spanned( + ast_ctx.0, + "Unions are not supported by binary_utils, there is currently no way to implement the BinaryReader and BinaryWriter traits for unions." + ).to_compile_error().into() +} \ No newline at end of file diff --git a/codegen/src/io/util.rs b/codegen/src/io/util.rs new file mode 100644 index 0000000..e69de29 diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 7dbcc0a..b127ed8 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -1,6 +1,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; +mod io; mod legacy; /// **DEPRECATED**. @@ -34,4 +35,9 @@ pub fn derive_stream(input: TokenStream) -> TokenStream { // legacy::stream_parse(parse_macro_input!(input as DeriveInput)) // .unwrap() // .into() +} + +#[proc_macro_derive(BinaryIo)] +pub fn derive_binary_io(input: TokenStream) -> TokenStream { + io::binary_encoder(input) } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 482ec9b..f08ae45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ /// } /// ``` pub mod interfaces; +/// Provides a derive macro that implements `::binary_utils::interfaces::Reader` and `::binary_utils::interfaces::Writer`. +/// pub use codegen::*; /// The io module contains implementations of these traits for `bytes::Buf` and `bytes::BufMut`. /// From ef73598d4f55f51c89706a820eecd8ef75edd334 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 22:01:42 -0500 Subject: [PATCH 15/26] feat: Working derive macros for named structs --- Cargo.toml | 6 +- codegen/Cargo.toml | 2 + codegen/src/io/enums.rs | 10 +- codegen/src/io/mod.rs | 24 ++--- codegen/src/io/structs.rs | 218 +++++++++++++++++++++++++++++++++++++- codegen/src/io/unions.rs | 6 +- codegen/src/io/util.rs | 108 +++++++++++++++++++ codegen/src/lib.rs | 13 +-- src/lib.rs | 3 +- tests/binary_io_struct.rs | 90 ++++++++++++++++ 10 files changed, 448 insertions(+), 32 deletions(-) create mode 100644 tests/binary_io_struct.rs diff --git a/Cargo.toml b/Cargo.toml index 5f8a518..c025a91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" include = ["src/**/*", "README.md"] [dependencies] -codegen = { path = "./codegen" } +codegen = { path = "./codegen", optional = true } bytes = "1.4.0" -[features] \ No newline at end of file +[features] +default = [ "derive" ] +derive = [ "codegen" ] \ No newline at end of file diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 2305130..3552a9e 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -8,6 +8,8 @@ private = true proc-macro = true [dependencies] +lazy_static = "1.4.0" proc-macro2 = "1.0.56" quote = "1.0.26" +regex = "1.8.1" syn = { version = "2.0.13", features = ["full"] } diff --git a/codegen/src/io/enums.rs b/codegen/src/io/enums.rs index 2526b99..86f8a9d 100644 --- a/codegen/src/io/enums.rs +++ b/codegen/src/io/enums.rs @@ -1,9 +1,13 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use syn::{parse_macro_input, DeriveInput, Data, Fields, DataEnum}; +use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields}; use super::AstContext; -pub(crate) fn derive_enum(ast_ctx: AstContext, data: DataEnum, error_stream: &mut TokenStream2) -> TokenStream { +pub(crate) fn derive_enum( + ast_ctx: AstContext, + data: DataEnum, + error_stream: &mut TokenStream2, +) -> TokenStream { todo!() -} \ No newline at end of file +} diff --git a/codegen/src/io/mod.rs b/codegen/src/io/mod.rs index 5bd8946..18fd6ea 100644 --- a/codegen/src/io/mod.rs +++ b/codegen/src/io/mod.rs @@ -1,11 +1,17 @@ pub(crate) mod enums; pub(crate) mod structs; pub(crate) mod unions; +pub(crate) mod util; use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput, Data}; +use syn::{parse_macro_input, Data, DeriveInput}; -pub(crate) type AstContext<'a> = (&'a syn::Ident, &'a Vec, &'a syn::Generics, &'a syn::Visibility); +pub(crate) type AstContext<'a> = ( + &'a syn::Ident, + &'a Vec, + &'a syn::Generics, + &'a syn::Visibility, +); // BinaryEncoder is a derive macro that implements `::binary_utils::interfaces::Reader` and `::binary_utils::interfaces::Writer` pub(crate) fn binary_encoder(input: TokenStream) -> TokenStream { @@ -15,15 +21,9 @@ pub(crate) fn binary_encoder(input: TokenStream) -> TokenStream { let mut err = proc_macro2::TokenStream::new(); let stream = match input.data { - Data::Struct(d) => { - structs::derive_struct(ctx, d, &mut err) - }, - Data::Enum(d) => { - enums::derive_enum(ctx, d, &mut err) - }, - Data::Union(d) => { - unions::derive_union(ctx, d, &mut err) - } + Data::Struct(d) => structs::derive_struct(ctx, d, &mut err), + Data::Enum(d) => enums::derive_enum(ctx, d, &mut err), + Data::Union(d) => unions::derive_union(ctx, d, &mut err), }; if err.is_empty() { @@ -31,4 +31,4 @@ pub(crate) fn binary_encoder(input: TokenStream) -> TokenStream { } else { err.into() } -} \ No newline at end of file +} diff --git a/codegen/src/io/structs.rs b/codegen/src/io/structs.rs index a0f0a85..ca4bf92 100644 --- a/codegen/src/io/structs.rs +++ b/codegen/src/io/structs.rs @@ -1,9 +1,217 @@ +use lazy_static::lazy_static; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use syn::{parse_macro_input, DeriveInput, Data, Fields, DataStruct}; +use quote::{quote, ToTokens, TokenStreamExt}; +use regex::Regex; +use syn::{DataStruct, Fields}; -use super::AstContext; +use crate::io::util::attrs::IoAttr; -pub(crate) fn derive_struct(ast_ctx: AstContext, data: DataStruct, error_stream: &mut TokenStream2) -> TokenStream { - todo!() -} \ No newline at end of file +use super::{util::attrs::resolve_generic_type, AstContext}; +lazy_static! { + static ref REG: regex::Regex = Regex::new(r"((?:self\.)([\u0041-\u323AF_0-9]*))").unwrap(); +} + +/// Derive structs will automatically implement the `BinaryReader` and `BinaryWriter` traits for the struct. +/// +/// In the most generic example, we will parse a named struct: +/// ```ignore no_run +/// #[derive(BinaryIo)] +/// struct Test { +/// a: u8, +/// b: u16 +/// } +/// ``` +/// Where `Test` is the struct name, `a` and `b` are the field names, and `u8` and `u16` are the field types. +/// These fields will be parsed in order, and written in order. +/// +/// The macro will also support unnamed structs: +/// ```ignore no_run +/// #[derive(BinaryIo)] +/// struct Test(u8, u16); +/// ``` +/// Where `u8` and `u16` are the field types, and encoded in order. +/// Unfortunately the macro will not allow you to parse attributes on a struct with unnamed fields. +/// This is a limitation of the proc-macro system, and really shouldn't be abused. +pub(crate) fn derive_struct( + ast_ctx: AstContext, + data: DataStruct, + error_stream: &mut TokenStream2, +) -> TokenStream { + let struct_name = ast_ctx.0; + let mut writer = TokenStream2::new(); + let mut reader = TokenStream2::new(); + // let constructing_idents: Vec = Vec::new(); + + match data.fields { + Fields::Named(ref fields) => { + let field_names = fields + .named + .iter() + .filter_map(|field| match field.ident { + Some(ref ident) => Some(ident), + None => { + error_stream.append_all( + syn::Error::new_spanned( + field, + "Cannot have unnamed fields in a struct!", + ) + .to_compile_error(), + ); + None + } + }) + .collect::>(); + + for field in fields.named.iter() { + let attributes = field + .attrs + .iter() + .filter_map(|att| { + match super::util::attrs::parse_attribute(&att, error_stream) { + Ok(attr) => Some(attr), + Err(_) => None, + } + }) + .collect::>(); + + if attributes.len() > 1 { + error_stream.append_all( + syn::Error::new_spanned( + field, + "Cannot have more than one binary_utils Attribute on a field!", + ) + .to_compile_error(), + ); + return quote!().into(); + } + + // here we need to parse the field type + let field_type = &field.ty; + let field_name = &field.ident; + + if let Some(attr) = attributes.first() { + // we have an attribute, so we need to do some stuff with it before conditionally parsing. + match attr { + IoAttr::Require(id) => { + let inner_type: Option = + resolve_generic_type(field_type, "Option", error_stream); + + if inner_type.is_none() { + error_stream.append_all(syn::Error::new_spanned( + field, + "Cannot have a field with a binary_utils::Require attribute that is not an Option!" + ).to_compile_error()); + return quote!().into(); + } + + let forced_type = inner_type.unwrap(); + + writer.append_all(quote!( + if self.#id.is_some() { + _binary_writew.write(&mut (self.#field_name.unwrap()).write_to_bytes()?.as_slice())?; + } else { + // return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot write a field that is required but not present!")); + } + )); + reader.append_all(quote!( + if #id.is_none() { + // return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot read a field that is required but not present!")); + } + let #field_name = <#forced_type>::read(_binary_readerr).ok(); + )); + } + IoAttr::Satisfy(expr) => { + let inner_type: Option = + resolve_generic_type(field_type, "Option", error_stream); + + if inner_type.is_none() { + error_stream.append_all(syn::Error::new_spanned( + field, + "Cannot have a field with a binary_utils::Satisfy attribute that is not an Option!" + ).to_compile_error()); + return quote!().into(); + } + + // this is a conditional field! it requires the expression to be true when reading or writing. + let expr_tokens = expr.to_token_stream().to_string(); + let p_wexp = expr_tokens.as_str(); + + let (write_capture, read_capture) = ( + ®.replace_all(p_wexp.clone(), r"self.$2"), + ®.replace_all(p_wexp.clone(), r"$2"), + ); + let (write_expr, read_expr) = ( + syn::parse_str::(write_capture.as_ref()).unwrap(), + syn::parse_str::(read_capture.as_ref()).unwrap(), + ); + + writer.append_all(quote!( + if #write_expr { + if let Some(v) = &self.#field_name { + _binary_writew.write(&mut v.write_to_bytes()?.as_slice())?; + } else { + return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, format!("Condition for field {} was satisfied, but the field was not present!", stringify!(#field_name)))); + } + } + )); + reader.append_all(quote!( + // println!("{}: {}", stringify!(#field_name), stringify!(#read_expr)); + let #field_name = match #read_expr { + true => Some(<#inner_type>::read(_binary_readerr)?), + false => None, + }; + )); + } + IoAttr::Skip => { + // we skip this + writer.append_all(quote!( + // we skip this field + )); + reader.append_all(quote!( + // we skip this field + let #field_name: #field_type = Default::default(); + )); + continue; + } + } + } else { + // we don't have an attribute, so we just parse the field as normal interface type. + writer.append_all(quote!( + _binary_writew.write(&mut self.#field_name.write_to_bytes()?.as_slice())?; + )); + reader.append_all(quote!( + let #field_name = <#field_type>::read(_binary_readerr)?; + )); + } + } + quote! { + impl ::binary_utils::interfaces::Writer for #struct_name { + fn write(&self, _binary_writew: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + #writer + Ok(()) + } + } + impl ::binary_utils::interfaces::Reader<#struct_name> for #struct_name { + fn read(_binary_readerr: &mut ::binary_utils::io::ByteReader) -> Result<#struct_name, ::std::io::Error> { + // println!("impl Reader for {} called!\n-> {}", stringify!(#struct_name), stringify!(#reader)); + #reader + Ok(Self { + #(#field_names),* + }) + } + } + }.into() + } + Fields::Unnamed(ref _fields) => { + todo!("Implement named structs") + } + Fields::Unit => { + error_stream.append_all(syn::Error::new_spanned( + ast_ctx.0, + "Unit structs are not supported by binary_utils because they have no fields to parse or write.\nThis may change in the future, but for now, please use a tuple struct instead." + ).to_compile_error()); + return quote!().into(); + } + } +} diff --git a/codegen/src/io/unions.rs b/codegen/src/io/unions.rs index b42d846..64864c1 100644 --- a/codegen/src/io/unions.rs +++ b/codegen/src/io/unions.rs @@ -1,12 +1,12 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use syn::{DeriveInput, DataUnion}; +use syn::DataUnion; use super::AstContext; -pub(crate) fn derive_union(ast_ctx: AstContext, data: DataUnion, error_stream: &mut TokenStream2) -> TokenStream { +pub(crate) fn derive_union(ast_ctx: AstContext, _: DataUnion, _: &mut TokenStream2) -> TokenStream { syn::Error::new_spanned( ast_ctx.0, "Unions are not supported by binary_utils, there is currently no way to implement the BinaryReader and BinaryWriter traits for unions." ).to_compile_error().into() -} \ No newline at end of file +} diff --git a/codegen/src/io/util.rs b/codegen/src/io/util.rs index e69de29..183a1af 100644 --- a/codegen/src/io/util.rs +++ b/codegen/src/io/util.rs @@ -0,0 +1,108 @@ +pub(crate) mod attrs { + use proc_macro2::TokenStream as TokenStream2; + use quote::TokenStreamExt; + + use lazy_static::lazy_static; + use regex::Regex; + + lazy_static! { + static ref REG: regex::Regex = Regex::new(r"\$([\u0041-\u323AF|_]*)").unwrap(); + } + + #[derive(Clone)] + pub enum IoAttr { + Satisfy(syn::Expr), + Require(syn::Ident), + Skip, + } + + /// Parses the attributes of a struct or enum. + /// The attributes are returned in the order they were parsed in, you can return errors if you want to. + /// Some attributes do not allow conflicting attributes, such as #[skip] + pub fn parse_attribute<'a>( + attr: &'a syn::Attribute, + error_stream: &mut TokenStream2, + ) -> Result { + let path = attr.path(); + if path.is_ident("satisfy") { + // Satisfy is an attribute that allows an expression to be specified + // this is polyfilled later with `self.EXPRESSION` + match attr.parse_args::() { + Ok(expr) => { + return Ok(IoAttr::Satisfy(expr)); + } + Err(e) => { + error_stream.append_all( + syn::Error::new_spanned(attr, format!("Satisfy attribute requires an Expression!\n Example: #[satisfy(self.field == 0)]\n Error: {}", e)) + .to_compile_error(), + ); + } + } + } else if path.is_ident("require") { + // Require is an attribute that allows an identifier to be specified + // this is polyfilled later with `self.IDENTIFIER.is_some()` + match attr.parse_args::() { + Ok(ident) => { + return Ok(IoAttr::Require(ident)); + } + Err(_) => { + error_stream.append_all( + syn::Error::new_spanned(attr, "Require attribute requires an Identifier! \n Example: #[require(self.field)]") + .to_compile_error(), + ); + } + } + } else if path.is_ident("skip") { + // skip is a special attribute, it cannot be used with any other attribute + // therefore we can just return early, however we need to validate that + // there are no other attributes + return Ok(IoAttr::Skip); + } else { + error_stream.append_all( + syn::Error::new_spanned( + attr, + "Unknown attribute, did you mean 'satisfy', 'require', or 'skip'?", + ) + .to_compile_error(), + ); + } + + Err(()) + } + + /// Parses the attributes of a struct or enum. + /// todo: this is a bit of a mess, and should be cleaned up. + /// todo: There's probably a better way to resolve the type without having to do this. + pub fn resolve_generic_type<'a>( + ty: &'a syn::Type, + ident: &str, + error_stream: &mut TokenStream2, + ) -> Option { + match *ty { + syn::Type::Path(ref tp) => { + if let Some(first) = tp.path.segments.first() { + if first.ident.to_string() == ident { + if let syn::PathArguments::AngleBracketed(args) = &first.arguments { + if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { + Some(inner.clone()) + } else { + error_stream.append_all(syn::Error::new_spanned( + ty, + "Option type must have a generic argument in order to be required!" + ).to_compile_error()); + None + } + } else { + None + } + } else { + None + } + } else { + None + } + } + _ => None, + } + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index b127ed8..e93f520 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -1,8 +1,8 @@ use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; +use quote::quote; mod io; -mod legacy; +// mod legacy; /// **DEPRECATED**. /// This is a legacy proc-macro that is used to generate a BufferStream. @@ -27,9 +27,10 @@ mod legacy; /// } /// ``` #[proc_macro_derive(BinaryStream)] -pub fn derive_stream(input: TokenStream) -> TokenStream { +pub fn derive_stream(_input: TokenStream) -> TokenStream { return syn::Error::new_spanned( - parse_macro_input!(input as DeriveInput), + // parse_macro_input!(input as DeriveInput), + quote!{}, "This is a legacy proc-macro that is used to generate the BinaryStream\nDeprecated: use BinaryReader, and BinaryWriter instead." ).to_compile_error().into(); // legacy::stream_parse(parse_macro_input!(input as DeriveInput)) @@ -37,7 +38,7 @@ pub fn derive_stream(input: TokenStream) -> TokenStream { // .into() } -#[proc_macro_derive(BinaryIo)] +#[proc_macro_derive(BinaryIo, attributes(skip, require, satisfy))] pub fn derive_binary_io(input: TokenStream) -> TokenStream { io::binary_encoder(input) -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index f08ae45..905fdd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(log_syntax)] /// Provides a panic-free way to read and write binary data. /// All of the methods within this module follow the protobuf specification at . /// @@ -13,7 +14,7 @@ /// ``` pub mod interfaces; /// Provides a derive macro that implements `::binary_utils::interfaces::Reader` and `::binary_utils::interfaces::Writer`. -/// +/// pub use codegen::*; /// The io module contains implementations of these traits for `bytes::Buf` and `bytes::BufMut`. /// diff --git a/tests/binary_io_struct.rs b/tests/binary_io_struct.rs new file mode 100644 index 0000000..ab0c2f8 --- /dev/null +++ b/tests/binary_io_struct.rs @@ -0,0 +1,90 @@ +use binary_utils::interfaces::{Reader, Writer}; +use binary_utils::io::ByteReader; +use binary_utils::BinaryIo; + +#[derive(BinaryIo, Debug)] +struct ABC { + a: u8, + #[satisfy(self.a == 10)] + b: Option, + c: u8, +} + +#[test] +fn abc_derive_write() { + let b_present = ABC { + a: 10, + b: Some(9), + c: 3, + }; + assert_eq!(b_present.write_to_bytes().unwrap().as_slice(), &[10, 9, 3]); + + // B is present, but A doesn't satisfy the condition for b to be encoded. + let a_invalid = ABC { + a: 4, + b: Some(99), + c: 9, + }; + assert_eq!(a_invalid.write_to_bytes().unwrap().as_slice(), &[4, 9]); + + // B is not present, but A satisfies the condition for b to be encoded. + // This WILL fail. + let b_not_present = ABC { + a: 10, + b: None, + c: 9, + }; + assert_eq!(b_not_present.write_to_bytes().is_err(), true); +} + +#[test] +fn abc_derive_read() { + println!("B_PRESENT"); + const B_PRESENT_BUF: &[u8] = &[10, 9, 3]; + let mut reader = ByteReader::from(B_PRESENT_BUF); + let b_present = ABC::read(&mut reader).unwrap(); + + assert_eq!(b_present.a, 10); + assert_eq!(b_present.b, Some(9)); + assert_eq!(b_present.c, 3); + + println!("A_NOT_SATISFIED"); + const A_NOT_SATISFIED: &[u8] = &[4, 9]; + let mut reader = ByteReader::from(A_NOT_SATISFIED); + let a_invalid = ABC::read(&mut reader).unwrap(); + + assert_eq!(a_invalid.a, 4); + assert_eq!(a_invalid.b, None); + assert_eq!(a_invalid.c, 9); + + const B_NOT_PRESENT_BUF: &[u8] = &[10, 9]; + let mut reader = ByteReader::from(B_NOT_PRESENT_BUF); + assert_eq!(ABC::read(&mut reader).is_err(), true); +} + +#[derive(BinaryIo, Debug)] +struct CompexPacket { + #[skip] + is_ack: bool, + contains_content: bool, + #[satisfy(self.contains_content == true && self.is_ack == true)] + content: Option, + #[require(content)] + content_validated: Option, +} + +#[test] +fn complex_packet_write() { + // ack is true, but the contents are false. + let ack_true_content_false = CompexPacket { + is_ack: true, + contains_content: false, + content: None, + content_validated: None, + }; + + assert_eq!( + ack_true_content_false.write_to_bytes().unwrap().as_slice(), + &[0] + ); +} From c479480ea6307be1fb36f08fe485d18f5a70ec00 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 22:08:30 -0500 Subject: [PATCH 16/26] chore: Cleanup yucky if-else chain --- codegen/src/io/util.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/codegen/src/io/util.rs b/codegen/src/io/util.rs index 183a1af..b563b78 100644 --- a/codegen/src/io/util.rs +++ b/codegen/src/io/util.rs @@ -75,32 +75,28 @@ pub(crate) mod attrs { /// todo: There's probably a better way to resolve the type without having to do this. pub fn resolve_generic_type<'a>( ty: &'a syn::Type, - ident: &str, + name: &str, error_stream: &mut TokenStream2, ) -> Option { match *ty { syn::Type::Path(ref tp) => { if let Some(first) = tp.path.segments.first() { - if first.ident.to_string() == ident { + if first.ident.to_string() == name { if let syn::PathArguments::AngleBracketed(args) = &first.arguments { if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { - Some(inner.clone()) + return Some(inner.clone()); } else { error_stream.append_all(syn::Error::new_spanned( ty, - "Option type must have a generic argument in order to be required!" + format!("{} type must have a generic argument in order to be required!", name) ).to_compile_error()); - None + return None; } - } else { - None } - } else { - None } - } else { - None + return None; } + return None; } _ => None, } From 1fb8917bbafdc06c211c53542ebf5008d3b00b4c Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 22:09:38 -0500 Subject: [PATCH 17/26] chore: Cleanup yucky if-else chain --- codegen/src/io/util.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/codegen/src/io/util.rs b/codegen/src/io/util.rs index b563b78..0704fb0 100644 --- a/codegen/src/io/util.rs +++ b/codegen/src/io/util.rs @@ -90,11 +90,9 @@ pub(crate) mod attrs { ty, format!("{} type must have a generic argument in order to be required!", name) ).to_compile_error()); - return None; } } } - return None; } return None; } From ccab1b01da98e17abc361e3ce325b8faa933c45f Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 22:40:56 -0500 Subject: [PATCH 18/26] feat(codegen): has been replaced with . The behavior of will now fail if the required field is NOT present. --- codegen/src/io/structs.rs | 28 ++++++++++++++++++++++++++-- codegen/src/io/util.rs | 21 ++++++++++++++++++--- codegen/src/lib.rs | 2 +- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/codegen/src/io/structs.rs b/codegen/src/io/structs.rs index ca4bf92..a40a343 100644 --- a/codegen/src/io/structs.rs +++ b/codegen/src/io/structs.rs @@ -111,16 +111,40 @@ pub(crate) fn derive_struct( if self.#id.is_some() { _binary_writew.write(&mut (self.#field_name.unwrap()).write_to_bytes()?.as_slice())?; } else { - // return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot write a field that is required but not present!")); + return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot write a field that is required but not present!")); } )); reader.append_all(quote!( if #id.is_none() { - // return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot read a field that is required but not present!")); + return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot read a field that is required but not present!")); } let #field_name = <#forced_type>::read(_binary_readerr).ok(); )); } + IoAttr::IfPresent(id) => { + // behaves identically to require but does not error if the field is not present. + let inner_type: Option = + resolve_generic_type(field_type, "Option", error_stream); + + if inner_type.is_none() { + error_stream.append_all(syn::Error::new_spanned( + field, + "Cannot have a field with a binary_utils::Require attribute that is not an Option!" + ).to_compile_error()); + return quote!().into(); + } + + let forced_type = inner_type.unwrap(); + + writer.append_all(quote!( + if self.#id.is_some() { + _binary_writew.write(&mut (self.#field_name.unwrap()).write_to_bytes()?.as_slice())?; + } + )); + reader.append_all(quote!( + let #field_name = <#forced_type>::read(_binary_readerr).ok(); + )); + } IoAttr::Satisfy(expr) => { let inner_type: Option = resolve_generic_type(field_type, "Option", error_stream); diff --git a/codegen/src/io/util.rs b/codegen/src/io/util.rs index 0704fb0..73e3e2f 100644 --- a/codegen/src/io/util.rs +++ b/codegen/src/io/util.rs @@ -13,6 +13,7 @@ pub(crate) mod attrs { pub enum IoAttr { Satisfy(syn::Expr), Require(syn::Ident), + IfPresent(syn::Ident), Skip, } @@ -33,7 +34,7 @@ pub(crate) mod attrs { } Err(e) => { error_stream.append_all( - syn::Error::new_spanned(attr, format!("Satisfy attribute requires an Expression!\n Example: #[satisfy(self.field == 0)]\n Error: {}", e)) + syn::Error::new_spanned(attr, format!("'satisfy' attribute requires an Expression!\n Example: #[satisfy(self.field == 0)]\n Error: {}", e)) .to_compile_error(), ); } @@ -47,7 +48,21 @@ pub(crate) mod attrs { } Err(_) => { error_stream.append_all( - syn::Error::new_spanned(attr, "Require attribute requires an Identifier! \n Example: #[require(self.field)]") + syn::Error::new_spanned(attr, "'require' attribute requires an Identifier! \n Example: #[require(self.field)]") + .to_compile_error(), + ); + } + } + } else if path.is_ident("if_present") { + // Require is an attribute that allows an identifier to be specified + // this is polyfilled later with `self.IDENTIFIER.is_some()` + match attr.parse_args::() { + Ok(ident) => { + return Ok(IoAttr::IfPresent(ident)); + } + Err(_) => { + error_stream.append_all( + syn::Error::new_spanned(attr, "'if_present' attribute requires an Identifier! \n Example: #[if_present(self.field)]") .to_compile_error(), ); } @@ -61,7 +76,7 @@ pub(crate) mod attrs { error_stream.append_all( syn::Error::new_spanned( attr, - "Unknown attribute, did you mean 'satisfy', 'require', or 'skip'?", + "Unknown attribute, did you mean 'satisfy', 'require', 'if_present', or 'skip'?", ) .to_compile_error(), ); diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index e93f520..e85be05 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -38,7 +38,7 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { // .into() } -#[proc_macro_derive(BinaryIo, attributes(skip, require, satisfy))] +#[proc_macro_derive(BinaryIo, attributes(skip, require, if_present, satisfy))] pub fn derive_binary_io(input: TokenStream) -> TokenStream { io::binary_encoder(input) } From 44a98ad3681c6418a787411ceba39908afa2d763 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 22:48:44 -0500 Subject: [PATCH 19/26] tests(binary_io_struct.rs): change `require` to `if_present` --- codegen/src/lib.rs | 8 ++++++++ tests/binary_io_struct.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index e85be05..20e9da5 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -38,6 +38,14 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { // .into() } +/// This proc-macro implements both the `Reader` and `Writer` traits from `binary_utils::interfaces`. +/// +/// ## Structs +/// `BinaryIo` supports both Named, and Unnamed structs; however it does not support unit structs. +/// This macro will encode/decode the fields of the struct in the order they are defined, as long as they are not skipped; +/// however as an additional requirement, each field **MUST** implement** the `Reader` and `Writer` traits, if they do not, this macro will fail. +/// +/// #[proc_macro_derive(BinaryIo, attributes(skip, require, if_present, satisfy))] pub fn derive_binary_io(input: TokenStream) -> TokenStream { io::binary_encoder(input) diff --git a/tests/binary_io_struct.rs b/tests/binary_io_struct.rs index ab0c2f8..067fd56 100644 --- a/tests/binary_io_struct.rs +++ b/tests/binary_io_struct.rs @@ -69,7 +69,7 @@ struct CompexPacket { contains_content: bool, #[satisfy(self.contains_content == true && self.is_ack == true)] content: Option, - #[require(content)] + #[if_present(content)] content_validated: Option, } From 80f1b77ac4b0ae962300e5231d107ae3c668dac3 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Wed, 26 Apr 2023 23:11:33 -0500 Subject: [PATCH 20/26] doc: Add docs to derive macro --- codegen/src/lib.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 20e9da5..d027535 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -39,13 +39,119 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { } /// This proc-macro implements both the `Reader` and `Writer` traits from `binary_utils::interfaces`. +/// It is important to note that not all attributes can be used on all types, and some attributes are exclusive to certain variants. /// /// ## Structs -/// `BinaryIo` supports both Named, and Unnamed structs; however it does not support unit structs. +/// `BinaryIo` supports both Named, and Unnamed structs. However, this derive macro does not support unit structs. /// This macro will encode/decode the fields of the struct in the order they are defined, as long as they are not skipped; /// however as an additional requirement, each field **MUST** implement** the `Reader` and `Writer` traits, if they do not, this macro will fail. +/// +/// **Example:** +/// The following example will provide both a `Reader` and `Writer` implementation for the struct `ABC`, where each field is encoded as it's respective +/// type to the `Bytewriter`/`Bytereader`. +/// ```ignore +/// use binary_utils::interfaces::{Reader, Writer}; +/// use binary_utils::BinaryIo; +/// +/// #[derive(BinaryIo, Debug)] +/// struct ABC { +/// a: u8, +/// b: Option, +/// c: u8, +/// } +///``` +/// +/// Sometimes it can be more optimal to use Unnamed fields, if you do not care about the field names, and only want to encode/decode the fields in the order they are defined. +/// The behavior of this macro is the same as the previous example, except the fields are unnamed. +/// ```ignore +/// use binary_utils::interfaces::{Reader, Writer}; +/// use binary_utils::BinaryIo; +/// +/// #[derive(BinaryIo, Debug)] +/// struct ABC(u8, Option, u8); +/// ``` +/// +/// ### Attributes +/// Structs have a few exclusive attributes that can be used to control the encoding/decoding of the struct.
+/// These attributes control and modify the behavior of the `BinaryIo` macro. +///

+/// +/// #### Skip +/// The `#[skip]` attribute does as the name implies, and can be used to skip a field when encoding/decoding.
+/// This attribute can be used on both named, and unnamed fields. +/// **Syntax:** +/// ```rust +/// #[skip] +/// ``` +/// +/// +/// **Example:** +/// ```ignore +/// use binary_utils::interfaces::{Reader, Writer}; +/// use binary_utils::BinaryIo; +/// +/// #[derive(BinaryIo, Debug)] +/// struct ABC { +/// a: u8, +/// #[skip] +/// b: Option, +/// c: u8 +/// } +/// ``` +/// +/// #### Require +/// This attribute explicitly requires a field to be present when either encoding, or decoding; and will fail if the field is not present.
+/// This can be useful if you want to ensure that an optional field is present when encoding, or decoding it. +/// +/// **Syntax:** +/// ```rust +/// #[require(FIELD)] +/// ``` +/// +/// +/// **Example:** +/// In the following example, `b` is explicitly required to be present when encoding, or decoding `ABC`, and it's value is not allowed to be `None`. +/// ```ignore +/// use binary_utils::interfaces::{Reader, Writer}; +/// use binary_utils::BinaryIo; +/// +/// #[derive(BinaryIo, Debug)] +/// struct ABC { +/// a: u8, +/// b: Option, +/// #[require(b)] +/// c: Option +/// } +/// ``` +/// +/// #### If Present +/// This attribute functions identically to `#[require]`, however it does not fail if the field is not present. +/// +/// #### Satisfy +/// This attribute will fail if the expression provided does not evaluate to `true`.
+/// This attribute can be used to ensure that a field is only encoded/decoded if a certain condition is met. +/// This can be useful if you're sending something like `Authorization` or `Authentication` packets, and you want to ensure that the client is authenticated before +/// sending the packet. +/// +/// **Syntax:** +/// +/// ```rust +/// #[satisfy(EXPR)] +/// ``` +/// **Example:** +/// ```ignore +/// #[derive(BinaryIo, Debug)] +/// struct ABC { +/// a: u8, +/// #[satisfy(self.a == 10)] +/// b: Option, +/// c: u8, +/// } +/// ``` +/// --- /// -/// +/// ## Enums +/// Enums function identically to structs, however they also #[proc_macro_derive(BinaryIo, attributes(skip, require, if_present, satisfy))] pub fn derive_binary_io(input: TokenStream) -> TokenStream { io::binary_encoder(input) From da4031c91fc33496ee7706e8360e857aca5c4455 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Thu, 27 Apr 2023 00:52:44 -0500 Subject: [PATCH 21/26] feat: Add unnamed structs. --- codegen/src/io/structs.rs | 320 +++++++++++++++++++++++++------------- codegen/src/lib.rs | 37 +++-- tests/binary_io_struct.rs | 10 ++ 3 files changed, 247 insertions(+), 120 deletions(-) diff --git a/codegen/src/io/structs.rs b/codegen/src/io/structs.rs index a40a343..cafd98c 100644 --- a/codegen/src/io/structs.rs +++ b/codegen/src/io/structs.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens, TokenStreamExt}; +use quote::{quote, ToTokens, TokenStreamExt, format_ident}; use regex::Regex; use syn::{DataStruct, Fields}; @@ -41,7 +41,6 @@ pub(crate) fn derive_struct( let struct_name = ast_ctx.0; let mut writer = TokenStream2::new(); let mut reader = TokenStream2::new(); - // let constructing_idents: Vec = Vec::new(); match data.fields { Fields::Named(ref fields) => { @@ -90,125 +89,114 @@ pub(crate) fn derive_struct( let field_type = &field.ty; let field_name = &field.ident; + if field_name.is_none() { + error_stream.append_all( + syn::Error::new_spanned( + field, + "Cannot have unnamed fields in a struct with named fields!", + ) + .to_compile_error(), + ); + return quote!().into(); + } + if let Some(attr) = attributes.first() { - // we have an attribute, so we need to do some stuff with it before conditionally parsing. - match attr { - IoAttr::Require(id) => { - let inner_type: Option = - resolve_generic_type(field_type, "Option", error_stream); - - if inner_type.is_none() { - error_stream.append_all(syn::Error::new_spanned( - field, - "Cannot have a field with a binary_utils::Require attribute that is not an Option!" - ).to_compile_error()); - return quote!().into(); - } - - let forced_type = inner_type.unwrap(); - - writer.append_all(quote!( - if self.#id.is_some() { - _binary_writew.write(&mut (self.#field_name.unwrap()).write_to_bytes()?.as_slice())?; - } else { - return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot write a field that is required but not present!")); - } - )); - reader.append_all(quote!( - if #id.is_none() { - return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot read a field that is required but not present!")); - } - let #field_name = <#forced_type>::read(_binary_readerr).ok(); - )); - } - IoAttr::IfPresent(id) => { - // behaves identically to require but does not error if the field is not present. - let inner_type: Option = - resolve_generic_type(field_type, "Option", error_stream); + let name = field_name.clone(); + let n = name.clone().unwrap(); + if let Some(v) = parse_attributes(field.to_token_stream(), attr, field_type, quote!(self.#n), name.unwrap(), &mut writer, &mut reader, error_stream) { + return v.into(); + } + } else { + // we don't have an attribute, so we just parse the field as normal interface type. + writer.append_all(quote!( + _binary_writew.write(&mut self.#field_name.write_to_bytes()?.as_slice())?; + )); + reader.append_all(quote!( + let #field_name = <#field_type>::read(_binary_readerr)?; + )); + } + } + quote! { + impl ::binary_utils::interfaces::Writer for #struct_name { + fn write(&self, _binary_writew: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + #writer + Ok(()) + } + } + impl ::binary_utils::interfaces::Reader<#struct_name> for #struct_name { + fn read(_binary_readerr: &mut ::binary_utils::io::ByteReader) -> Result<#struct_name, ::std::io::Error> { + // println!("impl Reader for {} called!\n-> {}", stringify!(#struct_name), stringify!(#reader)); + #reader + Ok(Self { + #(#field_names),* + }) + } + } + }.into() + } + Fields::Unnamed(ref fields) => { + let mut read_names: Vec = Vec::new(); - if inner_type.is_none() { - error_stream.append_all(syn::Error::new_spanned( - field, - "Cannot have a field with a binary_utils::Require attribute that is not an Option!" - ).to_compile_error()); - return quote!().into(); - } - - let forced_type = inner_type.unwrap(); - - writer.append_all(quote!( - if self.#id.is_some() { - _binary_writew.write(&mut (self.#field_name.unwrap()).write_to_bytes()?.as_slice())?; - } - )); - reader.append_all(quote!( - let #field_name = <#forced_type>::read(_binary_readerr).ok(); - )); + for (i, field) in fields.unnamed.iter().enumerate() { + let attributes = field + .attrs + .iter() + .filter_map(|att| { + match super::util::attrs::parse_attribute(&att, error_stream) { + Ok(attr) => Some(attr), + Err(_) => None, } - IoAttr::Satisfy(expr) => { - let inner_type: Option = - resolve_generic_type(field_type, "Option", error_stream); + }) + .collect::>(); - if inner_type.is_none() { - error_stream.append_all(syn::Error::new_spanned( + if attributes.len() > 1 { + error_stream.append_all( + syn::Error::new_spanned( + field, + "Cannot have more than one binary_utils Attribute on a field!", + ) + .to_compile_error(), + ); + return quote!().into(); + } + + // parse the field type + let field_type = &field.ty; + let index = syn::Index::from(i); + let field_name = format_ident!("__{}_unnamed_{}", struct_name.to_string().to_lowercase(), index); + + read_names.push(field_name.clone()); + + if let Some(attr) = attributes.first() { + match *attr { + IoAttr::Skip => {} + _ => { + error_stream.append_all( + syn::Error::new_spanned( field, - "Cannot have a field with a binary_utils::Satisfy attribute that is not an Option!" - ).to_compile_error()); - return quote!().into(); - } - - // this is a conditional field! it requires the expression to be true when reading or writing. - let expr_tokens = expr.to_token_stream().to_string(); - let p_wexp = expr_tokens.as_str(); - - let (write_capture, read_capture) = ( - ®.replace_all(p_wexp.clone(), r"self.$2"), - ®.replace_all(p_wexp.clone(), r"$2"), - ); - let (write_expr, read_expr) = ( - syn::parse_str::(write_capture.as_ref()).unwrap(), - syn::parse_str::(read_capture.as_ref()).unwrap(), + "Unnamed fields only support the 'skip' attribute!", + ) + .to_compile_error(), ); - - writer.append_all(quote!( - if #write_expr { - if let Some(v) = &self.#field_name { - _binary_writew.write(&mut v.write_to_bytes()?.as_slice())?; - } else { - return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, format!("Condition for field {} was satisfied, but the field was not present!", stringify!(#field_name)))); - } - } - )); - reader.append_all(quote!( - // println!("{}: {}", stringify!(#field_name), stringify!(#read_expr)); - let #field_name = match #read_expr { - true => Some(<#inner_type>::read(_binary_readerr)?), - false => None, - }; - )); - } - IoAttr::Skip => { - // we skip this - writer.append_all(quote!( - // we skip this field - )); - reader.append_all(quote!( - // we skip this field - let #field_name: #field_type = Default::default(); - )); - continue; + return quote!().into(); } } + if let Some(v) = parse_attributes(field.to_token_stream(), attr, field_type, quote!(self.#index), field_name, &mut writer, &mut reader, error_stream) { + return v.into(); + } } else { // we don't have an attribute, so we just parse the field as normal interface type. writer.append_all(quote!( - _binary_writew.write(&mut self.#field_name.write_to_bytes()?.as_slice())?; + _binary_writew.write(&mut self.#index.write_to_bytes()?.as_slice())?; )); reader.append_all(quote!( let #field_name = <#field_type>::read(_binary_readerr)?; )); } } + // let read_names: Vec = (0..fields.unnamed.len()) + // .map(|i| syn::Ident::new(&format!("__unnamed_{}", i), proc_macro2::Span::call_site())) + // .collect(); quote! { impl ::binary_utils::interfaces::Writer for #struct_name { fn write(&self, _binary_writew: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { @@ -220,16 +208,13 @@ pub(crate) fn derive_struct( fn read(_binary_readerr: &mut ::binary_utils::io::ByteReader) -> Result<#struct_name, ::std::io::Error> { // println!("impl Reader for {} called!\n-> {}", stringify!(#struct_name), stringify!(#reader)); #reader - Ok(Self { - #(#field_names),* - }) + Ok(Self( + #(#read_names),* + )) } } }.into() } - Fields::Unnamed(ref _fields) => { - todo!("Implement named structs") - } Fields::Unit => { error_stream.append_all(syn::Error::new_spanned( ast_ctx.0, @@ -239,3 +224,118 @@ pub(crate) fn derive_struct( } } } + +fn parse_attributes<'a>(tokens: TokenStream2, attr: &'a IoAttr, ty: &'a syn::Type, write_name: TokenStream2, read_name: syn::Ident, writer: &mut TokenStream2, reader: &mut TokenStream2, error_stream: &mut TokenStream2) -> Option { + // we have an attribute, so we need to do some stuff with it before conditionally parsing. + match attr { + IoAttr::Require(id) => { + let inner_type: Option = + resolve_generic_type(ty, "Option", error_stream); + + if inner_type.is_none() { + error_stream.append_all(syn::Error::new_spanned( + tokens, + "Cannot have a field with a 'require' attribute that is not of type Option!" + ).to_compile_error()); + return quote!().into(); + } + + let forced_type = inner_type.unwrap(); + + writer.append_all(quote!( + if self.#id.is_some() { + _binary_writew.write(&mut (#write_name.unwrap()).write_to_bytes()?.as_slice())?; + } else { + return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot write a field that is required but not present!")); + } + )); + reader.append_all(quote!( + if #id.is_none() { + return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Cannot read a field that is required but not present!")); + } + let #read_name = <#forced_type>::read(_binary_readerr).ok(); + )); + + None + } + IoAttr::IfPresent(id) => { + // behaves identically to require but does not error if the field is not present. + let inner_type: Option = + resolve_generic_type(ty, "Option", error_stream); + + if inner_type.is_none() { + error_stream.append_all(syn::Error::new_spanned( + tokens, + "Cannot have a field with a 'if_present' attribute that is not of type 'Option'!" + ).to_compile_error()); + return quote!().into(); + } + + let forced_type = inner_type.unwrap(); + + writer.append_all(quote!( + if self.#id.is_some() { + _binary_writew.write(&mut (#write_name.unwrap()).write_to_bytes()?.as_slice())?; + } + )); + reader.append_all(quote!( + let #read_name = <#forced_type>::read(_binary_readerr).ok(); + )); + None + } + IoAttr::Satisfy(expr) => { + let inner_type: Option = + resolve_generic_type(ty, "Option", error_stream); + + if inner_type.is_none() { + error_stream.append_all(syn::Error::new_spanned( + tokens, + "Cannot have a field with a 'satisfy' attribute that is not of type 'Option'!" + ).to_compile_error()); + return quote!().into(); + } + + // this is a conditional field! it requires the expression to be true when reading or writing. + let expr_tokens = expr.to_token_stream().to_string(); + let p_wexp = expr_tokens.as_str(); + + let (write_capture, read_capture) = ( + ®.replace_all(p_wexp.clone(), r"self.$2"), + ®.replace_all(p_wexp.clone(), r"$2"), + ); + let (write_expr, read_expr) = ( + syn::parse_str::(write_capture.as_ref()).unwrap(), + syn::parse_str::(read_capture.as_ref()).unwrap(), + ); + + writer.append_all(quote!( + if #write_expr { + if let Some(v) = &#write_name { + _binary_writew.write(&mut v.write_to_bytes()?.as_slice())?; + } else { + return Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, format!("Condition for field {} was satisfied, but the field was not present!", stringify!(#write_name)))); + } + } + )); + reader.append_all(quote!( + // println!("{}: {}", stringify!(#field_name), stringify!(#read_expr)); + let #read_name = match #read_expr { + true => Some(<#inner_type>::read(_binary_readerr)?), + false => None, + }; + )); + None + } + IoAttr::Skip => { + // we skip this + writer.append_all(quote!( + // we skip this field + )); + reader.append_all(quote!( + // we skip this field + let #read_name: #ty = Default::default(); + )); + None + } + } +} \ No newline at end of file diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index d027535..bd665bb 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -70,20 +70,30 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// #[derive(BinaryIo, Debug)] /// struct ABC(u8, Option, u8); /// ``` +/// --- +/// +/// ## Enums +/// Enums description has not been written as of yet... +/// +/// --- /// -/// ### Attributes -/// Structs have a few exclusive attributes that can be used to control the encoding/decoding of the struct.
+/// ## Attributes +/// Structs and enums have a few exclusive attributes that can be used to control the encoding/decoding of the struct.
/// These attributes control and modify the behavior of the `BinaryIo` macro. ///

/// -/// #### Skip +/// ### Skip /// The `#[skip]` attribute does as the name implies, and can be used to skip a field when encoding/decoding.
-/// This attribute can be used on both named, and unnamed fields. +/// /// **Syntax:** /// ```rust /// #[skip] /// ``` /// +/// **Compatibility:** +/// - ✅ Named Structs +/// - ✅ Unnamed Structs +/// - ✅ Enums /// /// **Example:** /// ```ignore @@ -99,7 +109,7 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// } /// ``` /// -/// #### Require +/// ### Require /// This attribute explicitly requires a field to be present when either encoding, or decoding; and will fail if the field is not present.
/// This can be useful if you want to ensure that an optional field is present when encoding, or decoding it. /// @@ -108,6 +118,10 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// #[require(FIELD)] /// ``` /// +/// **Compatibility:** +/// - ✅ Named Structs +/// - ❌ Unnamed Structs +/// - ✅ Enums /// /// **Example:** /// In the following example, `b` is explicitly required to be present when encoding, or decoding `ABC`, and it's value is not allowed to be `None`. @@ -124,10 +138,10 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// } /// ``` /// -/// #### If Present +/// ### If Present /// This attribute functions identically to `#[require]`, however it does not fail if the field is not present. /// -/// #### Satisfy +/// ### Satisfy /// This attribute will fail if the expression provided does not evaluate to `true`.
/// This attribute can be used to ensure that a field is only encoded/decoded if a certain condition is met. /// This can be useful if you're sending something like `Authorization` or `Authentication` packets, and you want to ensure that the client is authenticated before @@ -138,6 +152,11 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// ```rust /// #[satisfy(EXPR)] /// ``` +/// **Compatibility:** +/// - ✅ Named Structs +/// - ❌ Unnamed Structs +/// - ✅ Enums +/// /// **Example:** /// ```ignore /// #[derive(BinaryIo, Debug)] @@ -149,9 +168,7 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// } /// ``` /// --- -/// -/// ## Enums -/// Enums function identically to structs, however they also +/// #[proc_macro_derive(BinaryIo, attributes(skip, require, if_present, satisfy))] pub fn derive_binary_io(input: TokenStream) -> TokenStream { io::binary_encoder(input) diff --git a/tests/binary_io_struct.rs b/tests/binary_io_struct.rs index 067fd56..e1c4231 100644 --- a/tests/binary_io_struct.rs +++ b/tests/binary_io_struct.rs @@ -88,3 +88,13 @@ fn complex_packet_write() { &[0] ); } + +/// Unnamed structs +#[derive(BinaryIo, Debug, PartialEq)] +struct SpecialStruct(bool, #[skip] Option); + +#[test] +fn special_struct_write() { + let special_struct = SpecialStruct(true, None); + assert_eq!(special_struct.write_to_bytes().unwrap().as_slice(), &[1]); +} From 5d101c1cabb6c47fbf5477fdd8815e8f3036d5b3 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Thu, 27 Apr 2023 01:06:48 -0500 Subject: [PATCH 22/26] doc(README.md): Improve readme docs --- README.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cc893a..0f1fc34 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ -# BinaryUtil -A library for safely reading and writing binary data. Specifications follow the [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/encoding) encoding. \ No newline at end of file +# binary_utils +A panic-free way to read/write binary streams in rust. + +[Documentation](https://docs.rs/binary_utils/) | +[Discord](https://discord.gg/y4aWA5MQxK) + +## Usage +```rust +use binary_utils::{BinaryIo, BinaryReader, BinaryWriter}; + +#[derive(BinaryIo)] +pub struct MyStruct { + pub a: u32, + pub b: u32, +} + +fn main() { + let mut writer = BinaryWriter::new(); + let my_struct = MyStruct { a: 1, b: 2 }; + if let Err(_) = writer.write(&my_struct) { + println!("Failed to write MyStruct"); + } + + let mut reader = BinaryReader::from(writer); + if let Ok(my_struct2) = MyStruct::read(&mut reader) { + assert_eq!(my_struct, my_struct2); + } else { + println!("Failed to read MyStruct"); + } +} +``` \ No newline at end of file From ac54e15904fcec9e7defae97c56b276a9b3d5792 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Thu, 27 Apr 2023 01:08:56 -0500 Subject: [PATCH 23/26] LICENSE --- LICENSE | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ede200b --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021 Suruloon Studios + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file From ff5841821e97822b06f78efbe95a0a08185aa6af Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Sun, 30 Apr 2023 16:52:40 -0500 Subject: [PATCH 24/26] doc(codegen): Enums are now documented, impl is next [ci skip] --- Cargo.toml | 2 +- codegen/src/io/enums.rs | 8 +++- codegen/src/io/structs.rs | 4 +- codegen/src/lib.rs | 85 ++++++++++++++++++++++++++++++++++++--- src/lib.rs | 1 + tests/binary_io_struct.rs | 4 +- 6 files changed, 93 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c025a91..2603c14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "binary_utils" +name = "binary-utils" version = "0.3.0" authors = ["Bavfalcon9"] edition = "2021" diff --git a/codegen/src/io/enums.rs b/codegen/src/io/enums.rs index 86f8a9d..a9e230c 100644 --- a/codegen/src/io/enums.rs +++ b/codegen/src/io/enums.rs @@ -1,13 +1,19 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; +use regex::Regex; use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields}; +use lazy_static::lazy_static; use super::AstContext; +lazy_static! { + static ref REG: regex::Regex = Regex::new(r"((?:self\.)([\u0041-\u323AF_0-9]*))").unwrap(); +} + pub(crate) fn derive_enum( ast_ctx: AstContext, data: DataEnum, error_stream: &mut TokenStream2, ) -> TokenStream { todo!() -} +} \ No newline at end of file diff --git a/codegen/src/io/structs.rs b/codegen/src/io/structs.rs index cafd98c..7131a03 100644 --- a/codegen/src/io/structs.rs +++ b/codegen/src/io/structs.rs @@ -78,7 +78,7 @@ pub(crate) fn derive_struct( error_stream.append_all( syn::Error::new_spanned( field, - "Cannot have more than one binary_utils Attribute on a field!", + "Cannot have more than one binary_utils Attribute on a single field!", ) .to_compile_error(), ); @@ -218,7 +218,7 @@ pub(crate) fn derive_struct( Fields::Unit => { error_stream.append_all(syn::Error::new_spanned( ast_ctx.0, - "Unit structs are not supported by binary_utils because they have no fields to parse or write.\nThis may change in the future, but for now, please use a tuple struct instead." + "Unit structs are not supported by binary_utils because they have no fields to parse or write.\nThis may change in the future, but for now, please use the skip attribute." ).to_compile_error()); return quote!().into(); } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index bd665bb..812cc4b 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -7,9 +7,9 @@ mod io; /// **DEPRECATED**. /// This is a legacy proc-macro that is used to generate a BufferStream. /// It provides an easy way to implement the `Streamable` trait. +/// > ⚠️ This proc-macro has been deprecated since `0.3.0` in favor of `binary_utils::interfaces::Reader` and `binary_utils::interfaces::Writer` and will be removed in `0.4.0`. /// -/// ## Deprecated -/// Deprecated since `0.3.0` in favor of `BinaryReader` and `BinaryWriter`. +/// This proc-macro automatically implements the `Streamable` trait for the struct or enum it is applied to. /// /// Example: /// ```ignore @@ -26,6 +26,21 @@ mod io; /// test.parse().unwrap(); /// } /// ``` +/// +/// Please note that this proc-macro does not support unit structs or named enum variants, meaning a code sample like the following will not work: +/// ```warn +/// use binary_utils::BinaryStream; +/// +/// // Error: Unit structs are not supported. +/// #[derive(BinaryStream)] +/// struct Test; +/// +/// // Error: Invalid variant. +/// #[derive(BinaryStream)] +/// enum TestEnum { +/// B(Test) +/// } +/// ``` #[proc_macro_derive(BinaryStream)] pub fn derive_stream(_input: TokenStream) -> TokenStream { return syn::Error::new_spanned( @@ -73,7 +88,67 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// --- /// /// ## Enums -/// Enums description has not been written as of yet... +/// Enums function a bit differently than structs, and have a few more exclusive attributes that allow you to adjust the behavior of the macro. +/// Identically to structs, this macro will encode/decode the fields of the enum in the order they are defined, as long as they are not skipped. +/// > **Note:** Enums require the `#[repr]` attribute to be used, and the `#[repr]` attribute must be a primitive type. +/// +/// ### Unit Variants +/// Unit variants are the simplest variant, of an enum and require the `#[repr(usize)]` attribute to be used.
+/// +/// **Example:** +/// The following example will encode the `ProtcolEnum` enum as a `u8`, where each variant is encoded, by default, starting from 0. +/// +/// ```ignore +/// use binary_utils::BinaryIo; +/// use binary_utils::{Reader, Writer}; +/// +/// #[derive(BinaryIo, Debug)] +/// #[repr(u8)] +/// pub enum ProtocolEnum { +/// Basic, +/// Advanced, +/// Complex +/// } +/// ``` +/// +/// ### Unnamed Variants (Tuple) +/// Unnamed variants allow you to encode the enum with a byte header specified by the discriminant.
+/// However, this variant is limited to the same functionality as a struct. The containing data of each field +/// within the variant must implement the `Reader` and `Writer` traits. Otherwise, this macro will fail with an error. +/// +/// **Example:** +/// The following example makes use of Unnamed variants, in this case `A` to encode both `B` and `C` retrospectively. +/// Where `A::JustC` will be encoded as `0x02` with the binary data of struct `B`. +/// ```ignore +/// use binary_utils::BinaryIo; +/// use binary_utils::{Reader, Writer}; +/// +/// #[derive(BinaryIo, Debug)] +/// pub struct B { +/// foo: String, +/// bar: Vec +/// } +/// +/// #[derive(BinaryIo, Debug)] +/// pub struct C { +/// foobar: u32, +/// } +/// +/// #[derive(BinaryIo, Debug)] +/// #[repr(u8)] +/// pub enum A { +/// JustB(B) = 1, +/// JustC(C), // 2 +/// Both(B, C) // 3 +/// } +/// +/// fn main() { +/// let a = A::JustC(C { foobar: 4 }); +/// let buf = a.write_to_bytes().unwrap(); +/// +/// assert_eq!(buf, &[2, 4, 0, 0, 0]); +/// } +/// ``` /// /// --- /// @@ -121,7 +196,7 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// **Compatibility:** /// - ✅ Named Structs /// - ❌ Unnamed Structs -/// - ✅ Enums +/// - ❌ Enums /// /// **Example:** /// In the following example, `b` is explicitly required to be present when encoding, or decoding `ABC`, and it's value is not allowed to be `None`. @@ -155,7 +230,7 @@ pub fn derive_stream(_input: TokenStream) -> TokenStream { /// **Compatibility:** /// - ✅ Named Structs /// - ❌ Unnamed Structs -/// - ✅ Enums +/// - ❌ Enums /// /// **Example:** /// ```ignore diff --git a/src/lib.rs b/src/lib.rs index 905fdd8..d830321 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,3 +90,4 @@ pub mod error { } pub use interfaces::Streamable; +pub use io::{ByteReader, ByteWriter}; \ No newline at end of file diff --git a/tests/binary_io_struct.rs b/tests/binary_io_struct.rs index e1c4231..292b0b4 100644 --- a/tests/binary_io_struct.rs +++ b/tests/binary_io_struct.rs @@ -91,10 +91,10 @@ fn complex_packet_write() { /// Unnamed structs #[derive(BinaryIo, Debug, PartialEq)] -struct SpecialStruct(bool, #[skip] Option); +struct SpecialStruct(bool, #[skip] Option, #[skip] Option); #[test] fn special_struct_write() { - let special_struct = SpecialStruct(true, None); + let special_struct = SpecialStruct(true, None, None); assert_eq!(special_struct.write_to_bytes().unwrap().as_slice(), &[1]); } From 3b3a80e3ba12bbe8c9264a332c7e364a733b67bf Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Mon, 1 May 2023 15:05:31 -0500 Subject: [PATCH 25/26] feat: Enums --- codegen/src/io/enums.rs | 348 +++++++++++++++++++++++++++++++++++++- codegen/src/io/structs.rs | 4 +- src/lib.rs | 3 +- tests/binary_io_enum.rs | 32 ++++ 4 files changed, 380 insertions(+), 7 deletions(-) create mode 100644 tests/binary_io_enum.rs diff --git a/codegen/src/io/enums.rs b/codegen/src/io/enums.rs index a9e230c..d629692 100644 --- a/codegen/src/io/enums.rs +++ b/codegen/src/io/enums.rs @@ -1,13 +1,91 @@ +#![allow(dead_code)] use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, format_ident, TokenStreamExt, ToTokens}; use regex::Regex; -use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields}; +use syn::{DataEnum, Fields, Error}; use lazy_static::lazy_static; +use super::util::attrs::{parse_attribute, IoAttr}; + use super::AstContext; lazy_static! { - static ref REG: regex::Regex = Regex::new(r"((?:self\.)([\u0041-\u323AF_0-9]*))").unwrap(); + // this regex checks whether a repr attribute a valid repr attribute + static ref REG: regex::Regex = Regex::new(r"^(?:u|i)(?:size|8|16|32|32|64|128)$").unwrap(); +} + +/// A helper struct for parsing enum variants. +/// This struct is used internally, however, for those who wish to modify this struct documentation is provided. +/// +/// This struct is created AFTER the attribute of the variant is parsed. +/// Parsing Order (pesudo-code): +/// ```md +/// for (variant in enum) { +/// discriminat = parse_discriminant(variant) || current_discriminant.next(); +/// parse_attribute(variant); +/// parse_variant(variant, attributes, discriminant); // HERE +/// } +/// ``` +struct ParsedEnumVariant { + /// The name of the variant. + pub name: syn::Ident, + /// The contents to append when writing. this variant. + /// This content will be appended within the expression of the match arm. + /// IE `` in the following example: + /// ```rust + /// #[repr(u8)] + /// enum MyEnum { + /// Unit, // 0 + /// UintDiscrm = 2, + /// Variant(String, u8), // 3 + /// } + /// + /// impl Writer for MyEnum { + /// pub fn write(&self, writer: &mut ByteWruter) -> Result<(), std::io::Error> { + /// match self { + /// MyEnum::Unit => { + /// // + /// }, + /// MyEnum::UintDiscrm => { + /// // + /// }, + /// // etc... + /// MyEnum::Variant(arg1, arg2) => { + /// // You can explect each argument to be a `syn::Ident` with the prefix `arg` followed + /// // followed by the index of the argument in the variant. + /// // + /// } + /// } + /// } + /// } + /// ``` + pub write_content: TokenStream2, + /// The contents to append when reading this variant. + /// Similar to `write_content`, this content will be appended within the expression of the match arm. + /// IE `` in the following example: + /// ```rust + /// #[repr(u8)] + /// enum MyEnum { + /// Unit, // 0 + /// UintDiscrm = 2, + /// Variant(String, u8), // 3 + /// } + /// impl Reader for MyEnum { + /// fn read(&self, reader: &mut ByteReader) -> Result { + /// let discriminant = reader.read_usize()?; + /// match discriminant { + /// // here + /// 0 => {} + /// // etc... + /// } + /// } + /// } + /// ``` + pub read_content: TokenStream2, + /// The discriminant of this variant. + /// This is automatically set by the parser. + pub discriminant: syn::LitInt } pub(crate) fn derive_enum( @@ -15,5 +93,269 @@ pub(crate) fn derive_enum( data: DataEnum, error_stream: &mut TokenStream2, ) -> TokenStream { - todo!() + // The name of our enum. + let enum_name = ast_ctx.0; + + // get the repr attribute if it exists + let repr = ast_ctx.1.iter().filter(|attr| attr.path().is_ident("repr")).next(); + + // if there's no repr, we're using u8, otherwise we're using the repr specified. + let repr_type = match repr { + Some(repr) => { + let repr = repr.parse_args::().unwrap(); + // todo validate the repr + repr + }, + None => { + // we need to force the user to specify a repr attribute + error_stream.append_all( + Error::new_spanned( + &enum_name, + "Enum must have a #[repr] attribute." + ) + .to_compile_error() + ); + return TokenStream::new(); + } + }; + + if !REG.is_match(&repr_type.to_string()) { + error_stream.append_all( + Error::new_spanned( + &repr_type, + "#[repr] attribute must contain a valid C type, one of: u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize" + ) + .to_compile_error() + ); + return TokenStream::new(); + } + + let mut curr_discrim: Option = None; + + let mut variants: Vec = Vec::new(); + + for variant in data.variants.iter() { + // parse the discriminant + if let Some((_, expr)) = &variant.discriminant { + // check whether the expression is a syn::LitInt + match syn::parse::(expr.to_token_stream().into()) { + Ok(v) => { + if let Ok(discrim) = v.base10_parse::() { + curr_discrim = Some(discrim); + } else { + error_stream.append_all( + Error::new_spanned( + expr, + "Discriminant must be a primitive integer literal." + ) + .to_compile_error() + ); + return TokenStream::new(); + } + }, + Err(_) => { + error_stream.append_all( + Error::new_spanned( + expr, + "Discriminant must be a primitive integer literal." + ) + .to_compile_error() + ); + return TokenStream::new(); + } + } + } else { + // no discriminant, so we use the next discriminant + // todo parse descend vs ascend, currently the order doesnt matter. + curr_discrim = match curr_discrim { + Some(discrim) => Some(discrim + 1), + None => Some(0) + } + } + + // parse the attributes + let attributes = variant + .attrs + .iter() + .filter_map(|att| { + match parse_attribute(&att, error_stream) { + Ok(attr) => Some(attr), + Err(_) => None + } + }) + .collect::>(); + + if let Some(attr) = attributes.first() { + match *attr { + IoAttr::Skip => {}, + IoAttr::Satisfy(_) | IoAttr::IfPresent(_) | IoAttr::Require(_) => { + error_stream.append_all( + Error::new_spanned( + &variant, + "Attributes: #[satisfy], #[if_present], and #[require] are not valid on enum variants." + ) + .to_compile_error() + ); + return TokenStream::new(); + } + } + } + + // todo support parsing of named fields + // these are fields like the following + // enum MyEnum { + // Test { a: u8, b: u8 } + // } + if let Fields::Named(_) = variant.fields { + error_stream.append_all( + Error::new_spanned( + &variant.fields, + "Enums can not have named fields in their variants. See https://github.com/NetrexMC/binary-utils/issues/15" + ) + .to_compile_error() + ); + return TokenStream::new(); + } + + // we need to parse this indo an ident _ and a type + let di = format!("{}{}", curr_discrim.unwrap(), repr_type); + let discrim = syn::LitInt::new(&di, proc_macro2::Span::call_site()); + + // we need to iterate through each field and parse it. + // keep in mind, in this context we're inside of the expr within the variant + // ie: + // enum MyEnum { + // Test(HERE) + // } + variants.push(parse_enum_variant(variant, &attributes, &discrim, error_stream)); + + // this is hacky, but we need to check whether or not the error_stream has any errors. + // if it does, we need to return an empty token stream. + if !error_stream.is_empty() { + return TokenStream::new(); + } + } + + // get all write streams from variants + let write_streams = variants.iter().map(|variant| variant.write_content.clone()).collect::>(); + let read_streams = variants.iter().map(|variant| variant.read_content.clone()).collect::>(); + + quote! { + impl ::binary_utils::interfaces::Writer for #enum_name { + fn write(&self, _binary_writew: &mut ::binary_utils::io::ByteWriter) -> ::std::result::Result<(), ::std::io::Error> { + match self { + #(#write_streams)* + }; + + Ok(()) + } + } + + impl ::binary_utils::interfaces::Reader<#enum_name> for #enum_name { + fn read(_binary_readerr: &mut ::binary_utils::io::ByteReader) -> ::std::result::Result<#enum_name, ::std::io::Error> { + match <#repr_type>::read(_binary_readerr)? { + #(#read_streams)* + _ => Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Invalid enum discriminant.")) + } + } + } + }.into() +} + +fn parse_enum_variant( + variant: &syn::Variant, + _attributes: &Vec, + curr_discrim: &syn::LitInt, + error_stream: &mut TokenStream2, +) -> ParsedEnumVariant { + let mut read_content = TokenStream2::new(); + let mut write_content = TokenStream2::new(); + + let variant_name = &variant.ident; + + match variant.fields { + Fields::Unnamed(ref fields) => { + // This is the stream within the match arm + let mut read_inner = TokenStream2::new(); + let mut write_inner = TokenStream2::new(); + + let mut args: Vec = Vec::new(); + + for (i, field) in fields.unnamed.iter().enumerate() { + let inner_attrs = field + .attrs + .iter() + .filter_map(|att| { + match parse_attribute(&att, error_stream) { + Ok(attr) => Some(attr), + Err(_) => None, + } + }) + .collect::>(); + + if inner_attrs.len() != 0 { + error_stream.append_all( + syn::Error::new_spanned( + field, + "Attributes are not valid on enum variant fields at this time.", + ) + .to_compile_error(), + ); + break; + } + + let arg_type = &field.ty; + let arg_name = format_ident!("arg{}", i); + + write_inner.append_all(quote! { + _binary_writew.write(&mut #arg_name.write_to_bytes()?.as_slice())?; + }); + read_inner.append_all(quote! { + let #arg_name = <#arg_type>::read(_binary_readerr)?; + }); + + args.push(arg_name); + } + + write_content.append_all(quote!( + Self::#variant_name(#(#args),*) => { + _binary_writew.write(&mut #curr_discrim.write_to_bytes()?.as_slice())?; + #write_inner + } + )); + read_content.append_all(quote!( + #curr_discrim => { + #read_inner + Ok(Self::#variant_name(#(#args),*)) + } + )); + }, + Fields::Unit => { + // Unit variants are easy, we just read/write the discriminant. + read_content.append_all(quote! { + #curr_discrim => Ok(Self::#variant_name), + }); + write_content.append_all(quote! { + Self::#variant_name => { + _binary_writew.write(&mut #curr_discrim.write_to_bytes()?.as_slice())?; + }, + }); + }, + _ => { + error_stream.append_all( + Error::new_spanned( + &variant.fields, + "Something went really wrong.." + ) + .to_compile_error() + ); + } + } + + ParsedEnumVariant { + name: variant.ident.clone(), + read_content, + write_content, + discriminant: syn::parse::(quote!(#curr_discrim).into()).unwrap() + } } \ No newline at end of file diff --git a/codegen/src/io/structs.rs b/codegen/src/io/structs.rs index 7131a03..29e5558 100644 --- a/codegen/src/io/structs.rs +++ b/codegen/src/io/structs.rs @@ -199,13 +199,13 @@ pub(crate) fn derive_struct( // .collect(); quote! { impl ::binary_utils::interfaces::Writer for #struct_name { - fn write(&self, _binary_writew: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + fn write(&self, _binary_writew: &mut ::binary_utils::io::ByteWriter) -> ::std::result::Result<(), ::std::io::Error> { #writer Ok(()) } } impl ::binary_utils::interfaces::Reader<#struct_name> for #struct_name { - fn read(_binary_readerr: &mut ::binary_utils::io::ByteReader) -> Result<#struct_name, ::std::io::Error> { + fn read(_binary_readerr: &mut ::binary_utils::io::ByteReader) -> ::std::result::Result<#struct_name, ::std::io::Error> { // println!("impl Reader for {} called!\n-> {}", stringify!(#struct_name), stringify!(#reader)); #reader Ok(Self( diff --git a/src/lib.rs b/src/lib.rs index d830321..78198f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(log_syntax)] /// Provides a panic-free way to read and write binary data. /// All of the methods within this module follow the protobuf specification at . /// @@ -90,4 +89,4 @@ pub mod error { } pub use interfaces::Streamable; -pub use io::{ByteReader, ByteWriter}; \ No newline at end of file +pub use io::{ByteReader, ByteWriter}; diff --git a/tests/binary_io_enum.rs b/tests/binary_io_enum.rs new file mode 100644 index 0000000..1303b0a --- /dev/null +++ b/tests/binary_io_enum.rs @@ -0,0 +1,32 @@ +use binary_utils::interfaces::{Reader, Writer}; +use binary_utils::io::ByteReader; +use binary_utils::BinaryIo; + +#[derive(BinaryIo, Debug)] +#[repr(u8)] +pub enum TestPacket { + A, + B(u8, u8), + C(u8, u8, u8), +} + +#[test] +fn encode_test() { + let packet = TestPacket::B(1, 2); + + assert_eq!(packet.write_to_bytes().unwrap().as_slice(), &[1, 1, 2]); +} + +#[test] +fn decode_test() { + let buf: &[u8] = &[1, 1, 2]; + let mut reader = ByteReader::from(buf); + + match TestPacket::read(&mut reader).unwrap() { + TestPacket::B(a, b) => { + assert_eq!(a, 1); + assert_eq!(b, 2); + } + _ => panic!("Wrong packet type"), + } +} From f3592a4ade5f8cc7643cad59314a633adf10e575 Mon Sep 17 00:00:00 2001 From: Bavfalcon9 Date: Mon, 1 May 2023 17:03:52 -0500 Subject: [PATCH 26/26] bc(Streamable): streamable structs are now fully backwards compatible, v0.4.0 will remove this. --- codegen/src/legacy.rs | 100 +++++++++++++++++++++++++++--------- codegen/src/lib.rs | 22 ++++---- src/interfaces.rs | 10 ++++ tests/streamable_enums.rs | 36 +++++++++++++ tests/streamable_structs.rs | 29 +++++++++++ 5 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 tests/streamable_enums.rs create mode 100644 tests/streamable_structs.rs diff --git a/codegen/src/legacy.rs b/codegen/src/legacy.rs index 0ed9a5c..9547377 100644 --- a/codegen/src/legacy.rs +++ b/codegen/src/legacy.rs @@ -1,5 +1,5 @@ use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::{Attribute, Data, DeriveInput, Error, Expr, ExprLit, Fields, Lit, LitInt, Result, Type}; pub fn stream_parse(input: DeriveInput) -> Result { @@ -9,28 +9,49 @@ pub fn stream_parse(input: DeriveInput) -> Result { match input.data { Data::Struct(v) => { // iterate through struct fields - let (w, r) = impl_named_fields(v.fields); + let (w, r, new_reads) = impl_named_fields(v.fields); let writes = quote!(#(#w)*); let reads = quote!(#(#r),*); // get the visibility etc on each field // return a quote for block impl Ok(quote! { #[automatically_derived] - impl Streamable for #name { + impl Streamable<#name> for #name { fn parse(&self) -> Result, ::binary_utils::error::BinaryError> { - use ::std::io::Write; - let mut writer = Vec::new(); - #writes - Ok(writer) + use ::binary_utils::interfaces::{Reader, Writer}; + use ::binary_utils::io::ByteWriter; + let mut writer = ByteWriter::new(); + #writes + Ok(writer.as_slice().to_vec()) } - fn compose(source: &[u8], position: &mut usize) -> Result { + fn compose(s: &[u8], position: &mut usize) -> Result { + use ::binary_utils::interfaces::{Reader, Writer}; use ::std::io::Read; + let mut source = ::binary_utils::io::ByteReader::from(s); Ok(Self { #reads }) } } + + impl ::binary_utils::interfaces::Writer for #name { + fn write(&self, writer: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + use ::binary_utils::interfaces::{Reader, Writer}; + #writes + Ok(()) + } + } + + impl ::binary_utils::interfaces::Reader<#name> for #name { + fn read(source: &mut ::binary_utils::io::ByteReader) -> Result { + use ::binary_utils::interfaces::{Reader, Writer}; + // get the repr type and read it + Ok(Self { + #new_reads + }) + } + } }) } Data::Enum(data) => { @@ -51,6 +72,8 @@ pub fn stream_parse(input: DeriveInput) -> Result { } let (mut writers, mut readers) = (Vec::::new(), Vec::::new()); + let (mut new_writers, mut new_readers) = + (Vec::::new(), Vec::::new()); if !data.variants.iter().all(|v| match v.fields.clone() { Fields::Unit => true, @@ -75,7 +98,15 @@ pub fn stream_parse(input: DeriveInput) -> Result { let var_name = variant.ident.clone(); // writers writers.push( - quote!(Self::#var_name => Ok((#discrim as #enum_ty).parse()?),), + quote!(Self::#var_name => Ok((#discrim as #enum_ty).write_to_bytes().unwrap().as_slice().to_vec()),), + ); + new_writers.push( + quote!{ + Self::#var_name => { + source.write((#discrim as #enum_ty).write_to_bytes()?.as_slice())?; + Ok(()) + } + } ); // readers readers.push(quote!(#discrim => Ok(Self::#var_name),)); @@ -107,7 +138,15 @@ pub fn stream_parse(input: DeriveInput) -> Result { let var_name = variant.ident.clone(); // writers - writers.push(quote!(Self::#var_name => Ok((#discrim as #enum_ty).parse()?),)); + writers.push(quote!(Self::#var_name => Ok((#discrim as #enum_ty).write_to_bytes()?),)); + new_writers.push( + quote!{ + Self::#var_name => { + source.write((#discrim as #enum_ty).write_to_bytes()?.as_slice())?; + Ok(()) + } + } + ); // readers readers .push(quote!(#discrim => Ok(Self::#var_name),)); @@ -134,7 +173,15 @@ pub fn stream_parse(input: DeriveInput) -> Result { let var_name = variant.ident.clone(); // writers writers.push( - quote!(Self::#var_name => Ok((#discrim as #enum_ty).parse()?),), + quote!(Self::#var_name => Ok((#discrim as #enum_ty).write_to_bytes()?),), + ); + new_writers.push( + quote!{ + Self::#var_name => { + source.write((#discrim as #enum_ty).write_to_bytes()?.as_slice())?; + Ok(()) + } + } ); // readers readers.push(quote!(#discrim => Ok(Self::#var_name),)); @@ -156,16 +203,18 @@ pub fn stream_parse(input: DeriveInput) -> Result { Ok(quote! { #[automatically_derived] - impl Streamable<#name> for #name { + impl ::binary_utils::interfaces::Streamable<#name> for #name { fn parse(&self) -> Result, ::binary_utils::error::BinaryError> { + use ::binary_utils::interfaces::{Reader, Writer}; match self { #(#writers)* } } fn compose(source: &[u8], offset: &mut usize) -> Result { + use ::binary_utils::interfaces::{Reader, Writer}; // get the repr type and read it - let v = <#enum_ty>::compose(source, offset)?; + let v = <#enum_ty>::read(&mut ::binary_utils::io::ByteReader::from(source))?; match v { #(#readers)* @@ -175,17 +224,19 @@ pub fn stream_parse(input: DeriveInput) -> Result { } impl ::binary_utils::interfaces::Writer for #name { - fn write(&self, buf: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + fn write(&self, source: &mut ::binary_utils::io::ByteWriter) -> Result<(), ::std::io::Error> { + use ::binary_utils::interfaces::{Reader, Writer}; match self { - #(#writers)* + #(#new_writers)* } } } impl ::binary_utils::interfaces::Reader<#name> for #name { - fn read(buf: &mut ::binary_utils::io::ByteReader) -> Result { + fn read(source: &mut ::binary_utils::io::ByteReader) -> Result { + use ::binary_utils::interfaces::{Reader, Writer}; // get the repr type and read it - let v = <#enum_ty>::compose(source, offset)?; + let v = <#enum_ty>::read(source)?; match v { #(#readers)* @@ -202,16 +253,18 @@ pub fn stream_parse(input: DeriveInput) -> Result { } } -pub fn impl_named_fields(fields: Fields) -> (Vec, Vec) { +pub fn impl_named_fields(fields: Fields) -> (Vec, Vec, TokenStream) { let mut writers = Vec::::new(); let mut readers = Vec::::new(); + let mut new_reads = TokenStream::new(); match fields { Fields::Named(v) => { for field in &v.named { let field_id = field.ident.as_ref().unwrap(); - let (writer, reader) = impl_streamable_lazy(field_id, &field.ty); + let (writer, reader, nw) = impl_streamable_lazy(field_id, &field.ty); writers.push(writer); readers.push(reader); + new_reads.append_all(nw); } } Fields::Unnamed(_v) => { @@ -221,7 +274,7 @@ pub fn impl_named_fields(fields: Fields) -> (Vec, Vec) panic!("Can not use uninitalized data values.") } } - (writers, readers) + (writers, readers, new_reads) } // pub fn impl_unnamed_fields(_fields: FieldsUnnamed) -> (TokenStream, TokenStream) { @@ -229,10 +282,11 @@ pub fn impl_named_fields(fields: Fields) -> (Vec, Vec) // todo!() // } -pub fn impl_streamable_lazy(name: &Ident, ty: &Type) -> (TokenStream, TokenStream) { +pub fn impl_streamable_lazy(name: &Ident, ty: &Type) -> (TokenStream, TokenStream, TokenStream) { ( - quote! { writer.write(&self.#name.parse()?[..])?; }, - quote!(#name: <#ty>::compose(&source, position)?), + quote! { writer.write(&self.#name.write_to_bytes().unwrap().as_slice()[..])?; }, + quote!(#name: <#ty>::read(&mut source)?), + quote! { #name: <#ty>::read(source)?, }, ) } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 812cc4b..6e08395 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -1,8 +1,8 @@ use proc_macro::TokenStream; -use quote::quote; +use syn::{parse_macro_input, DeriveInput}; mod io; -// mod legacy; +mod legacy; /// **DEPRECATED**. /// This is a legacy proc-macro that is used to generate a BufferStream. @@ -42,15 +42,15 @@ mod io; /// } /// ``` #[proc_macro_derive(BinaryStream)] -pub fn derive_stream(_input: TokenStream) -> TokenStream { - return syn::Error::new_spanned( - // parse_macro_input!(input as DeriveInput), - quote!{}, - "This is a legacy proc-macro that is used to generate the BinaryStream\nDeprecated: use BinaryReader, and BinaryWriter instead." - ).to_compile_error().into(); - // legacy::stream_parse(parse_macro_input!(input as DeriveInput)) - // .unwrap() - // .into() +pub fn derive_stream(input: TokenStream) -> TokenStream { + // return syn::Error::new_spanned( + // // parse_macro_input!(input as DeriveInput), + // quote!{}, + // "This is a legacy proc-macro that is used to generate the BinaryStream\nDeprecated: use BinaryReader, and BinaryWriter instead." + // ).to_compile_error().into(); + legacy::stream_parse(parse_macro_input!(input as DeriveInput)) + .unwrap() + .into() } /// This proc-macro implements both the `Reader` and `Writer` traits from `binary_utils::interfaces`. diff --git a/src/interfaces.rs b/src/interfaces.rs index 70d4e75..786a2aa 100644 --- a/src/interfaces.rs +++ b/src/interfaces.rs @@ -28,6 +28,14 @@ macro_rules! impl_writer { }; } +macro_rules! impl_streamable { + ($($t:ty),*) => { + $( + impl Streamable<$t> for $t {} + )* + }; +} + /// Allows you to read from a `ByteReader` without needing to know the type. /// /// ```ignore @@ -394,3 +402,5 @@ pub trait Streamable: Reader + Writer { Self::compose(source, position).unwrap() } } + +impl_streamable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, char, String); diff --git a/tests/streamable_enums.rs b/tests/streamable_enums.rs new file mode 100644 index 0000000..5f489d1 --- /dev/null +++ b/tests/streamable_enums.rs @@ -0,0 +1,36 @@ +use binary_utils::{error::BinaryError, BinaryStream, Streamable}; + +#[derive(Debug, BinaryStream, PartialEq)] +#[repr(u8)] +pub enum Test { + Apple = 0, + Pair = 1, +} + +#[test] +fn read_test_from_buffer() { + let buffer: &[u8] = &[0]; + let result = Test::compose(buffer, &mut 0).unwrap(); + assert_eq!(Test::Apple, result); +} + +#[test] +fn write_read_buffer() -> Result<(), BinaryError> { + // write first + let variant = Test::Pair; + let buffer = variant.parse()?; + + assert_eq!(buffer, vec![1]); + + // read now + let compose = Test::compose(&buffer[..], &mut 0)?; + + assert!( + match compose { + Test::Pair => true, + _ => false, + }, + "Reconstruction was not equivelant to Test::Pair" + ); + Ok(()) +} diff --git a/tests/streamable_structs.rs b/tests/streamable_structs.rs new file mode 100644 index 0000000..76b8659 --- /dev/null +++ b/tests/streamable_structs.rs @@ -0,0 +1,29 @@ +use binary_utils::{BinaryStream, Streamable}; + +#[derive(Debug, BinaryStream)] +pub struct TestPacket { + pub some_int: u8, + pub some_string: u8, + // pub unknown_size: VarInt +} + +#[test] +fn construct_struct() { + let buf = vec![1, 30]; + let pk = TestPacket::compose(&buf, &mut 0).unwrap(); + assert_eq!(buf, pk.parse().unwrap()) +} + +#[test] +fn write_string() { + let string = String::from("Hello world!"); + let hello_world_vec = vec![12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]; + assert_eq!(hello_world_vec, string.parse().unwrap()); +} + +#[test] +fn read_string() { + let hello_world_vec = vec![12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]; + let string = String::compose(&hello_world_vec[..], &mut 0).unwrap(); + assert_eq!("Hello world!".to_string(), string); +}