From ebf6bde22d2229cdd8e457d678e7db54addeb67b Mon Sep 17 00:00:00 2001 From: Numbers Date: Thu, 29 May 2025 03:45:41 +0200 Subject: [PATCH] init --- .gitignore | 1 + Cargo.toml | 21 +++++ derive_macro/Cargo.toml | 13 +++ derive_macro/src/lib.rs | 139 ++++++++++++++++++++++++++++++++ src/impls/lib_alloc.rs | 173 ++++++++++++++++++++++++++++++++++++++++ src/impls/lib_std.rs | 42 ++++++++++ src/impls/magic.rs | 27 +++++++ src/impls/primitives.rs | 129 ++++++++++++++++++++++++++++++ src/lib.rs | 76 ++++++++++++++++++ src/streams.rs | 33 ++++++++ 10 files changed, 654 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 derive_macro/Cargo.toml create mode 100644 derive_macro/src/lib.rs create mode 100644 src/impls/lib_alloc.rs create mode 100644 src/impls/lib_std.rs create mode 100644 src/impls/magic.rs create mode 100644 src/impls/primitives.rs create mode 100644 src/lib.rs create mode 100644 src/streams.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6ad89d3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +members = ["derive_macro"] + +[package] +name = "otw" +version = "0.1.0" +edition = "2024" + + + +[features] +default = ["alloc", "extra-validation"] + +extra-validation = [] +alloc = [] +std = [] + + +[dependencies] +derive_macro = { path = "derive_macro" } +e = { git = "https://git.intege.rs/xlib/e.git" } \ No newline at end of file diff --git a/derive_macro/Cargo.toml b/derive_macro/Cargo.toml new file mode 100644 index 0000000..9df808a --- /dev/null +++ b/derive_macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "derive_macro" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0.101", features = ["full", "extra-traits"] } +proc-macro2 = "1.0" +quote = "1" + diff --git a/derive_macro/src/lib.rs b/derive_macro/src/lib.rs new file mode 100644 index 0000000..963b9f8 --- /dev/null +++ b/derive_macro/src/lib.rs @@ -0,0 +1,139 @@ +#![feature(try_blocks)] + +use proc_macro::TokenStream; +use std::fmt::Write; +use proc_macro2::Ident; +use quote::ToTokens; +use syn::{Data, DataStruct, DeriveInput, Fields, parse_macro_input}; + +#[proc_macro_derive(OverTheWire)] +pub fn derive_bin_serializer(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match input.data { + Data::Struct(data) => { derive_struct(input.ident, &data).unwrap() } + Data::Enum(_data) => { panic!("Cannot derive OverTheWire for Enums (yet)"); } + Data::Union(_data) => { panic!("Cannot derive OverTheWire for Unions"); } + } +} + +#[allow(unused_variables)] +fn derive_struct(name: Ident, data: &DataStruct) -> Result { + let mut output = String::new(); + + // collect the fields... + let input_fields = match &data.fields { + + // field_name: + Fields::Named(fields) => + fields.named.iter().filter_map( + |n| { n.ident.as_ref().map(|i| i.to_string()) } + ).collect::>(), + + // field names are 0, 1, 2, etc. + Fields::Unnamed(fields) => + (0..fields.unnamed.len()) + .map(|i| i.to_string()) + .collect::>(), + + // no fields to worry about + Fields::Unit => Vec::new(), + }; + + + const PAD: &str = " "; + let deserialize = match &data.fields { + Fields::Named(_) => { + let mut output = String::new(); + writeln!(&mut output, "{PAD}Ok(Self{{")?; + for fname in &input_fields { + writeln!(&mut output, "{PAD} {fname}: otw::OverTheWire::deserialize(reader)?,")? + } + writeln!(&mut output, "{PAD}}})")?; + output + } + + Fields::Unnamed(_) => { + let mut output = String::new(); + writeln!(&mut output, "{PAD}Ok(Self(")?; + for fname in &input_fields { + writeln!(&mut output, "{PAD} otw::OverTheWire::deserialize(reader)?,")? + } + writeln!(&mut output, "{PAD}))")?; + output + } + + Fields::Unit => "Ok(Self)".to_string(), + }; + + let serialize = match data.fields { + Fields::Named(_) | Fields::Unnamed(_) => { + let mut output = String::new(); + for fname in &input_fields { + writeln!(&mut output, "{PAD}self.{fname}.serialize(writer)?;")? + } + writeln!(&mut output, "{PAD}Ok(())")?; + output + } + Fields::Unit => "Ok(())".to_string(), + }; + + + let input_types = match &data.fields { + + // field_name: + Fields::Named(fields) => + fields.named.iter() + .map(|n| { n.ty.to_token_stream().to_string() }) + .collect::>(), + + // field names are 0, 1, 2, etc. + Fields::Unnamed(fields) => fields.unnamed.iter() + .map(|f|f.ty.to_token_stream().to_string()) + .collect::>(), + + // no fields to worry about + Fields::Unit => Vec::new(), + }; + + + let size_hint = match data.fields { + Fields::Named(_) | Fields::Unnamed(_) => { + let mut output = String::new(); + writeln!(&mut output, "{PAD}0usize")?; + for ftype in &input_types { + writeln!(&mut output, "{PAD} .saturating_add(otw::min_wire_size::<{ftype}>())")? + } + output + } + Fields::Unit => "Ok(())".to_string(), + }; + + writeln!(&mut output, "#[automatically_derived]")?; + writeln!(&mut output, "impl otw::OverTheWire for {name} {{")?; + writeln!(&mut output, " fn serialize(&self, writer: &mut T) -> e::Result<()> {{")?; + writeln!(&mut output, "{serialize}")?; + writeln!(&mut output, " }}")?; + writeln!(&mut output, " fn deserialize(reader: &mut T) -> e::Result {{")?; + writeln!(&mut output, "{deserialize}")?; + writeln!(&mut output, " }}")?; + writeln!(&mut output, " fn size_hint() -> usize {{")?; + writeln!(&mut output, "{size_hint}")?; + writeln!(&mut output, " }}")?; + + + /* + fn size_hint() -> usize { + size_of::() + } + */ + + writeln!(&mut output, "}}")?; + + + if let Err(error) = output.parse::() { + panic!("{}", output); + } + + Ok(output.parse().unwrap()) +} \ No newline at end of file diff --git a/src/impls/lib_alloc.rs b/src/impls/lib_alloc.rs new file mode 100644 index 0000000..a02ad42 --- /dev/null +++ b/src/impls/lib_alloc.rs @@ -0,0 +1,173 @@ +use crate::{MalformedData, OverTheWire, Reader, Writer}; + +use alloc::{ + + // heap ptr's + boxed::Box, + sync::Arc, + rc::Rc, + + // collections + collections::BTreeMap, + string::String, + vec::Vec, + +}; + + +impl OverTheWire for Box { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + OTW::serialize(&*self, writer) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + Ok(Box::new(OTW::deserialize(reader)?)) + } + + #[inline] + fn size_hint() -> usize { + OTW::size_hint() + } +} + +impl OverTheWire for Rc { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + OTW::serialize(&*self, writer) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + Ok(Rc::new(OTW::deserialize(reader)?)) + } + + #[inline] + fn size_hint() -> usize { + OTW::size_hint() + } + +} + +impl OverTheWire for Arc { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + OTW::serialize(&*self, writer) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + Ok(Arc::new(OTW::deserialize(reader)?)) + } + + #[inline] + fn size_hint() -> usize { + OTW::size_hint() + } +} + +impl OverTheWire for String { + + fn serialize(&self, writer: &mut T) -> e::Result<()> { + (self.len() as u32).serialize(writer)?; + writer.write(self.as_bytes())?; + Ok(()) + } + + fn deserialize(reader: &mut T) -> e::Result { + let len = u32::deserialize(reader)?; + + #[cfg(feature = "extra-validation")] + if len as usize > reader.remainder_hint() { + return Err(MalformedData)?; + } + + let mut vec = Vec::with_capacity(len as usize); + unsafe { vec.set_len(len as usize) }; + reader.read(vec.as_mut_slice())?; + + let s = String::from_utf8(vec) + .map_err(|_|MalformedData)?; + + Ok(s) + } + + fn size_hint() -> usize { + u32::size_hint() + } +} + + +impl OverTheWire for Vec { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + (self.len() as u32).serialize(writer)?; + for v in self { + v.serialize(writer)?; + } + Ok(()) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + let len = u32::deserialize(reader)?; + + #[cfg(feature = "extra-validation")] + if OTW::size_hint().saturating_mul(len as usize) > reader.remainder_hint() { + return Err(MalformedData)?; + } + + let mut buffer = Vec::with_capacity(len as usize); + for _ in 0..len { + buffer.push(OTW::deserialize(reader)?); + } + Ok(buffer) + } + + fn size_hint() -> usize { + u32::size_hint() + } +} + +impl OverTheWire for BTreeMap { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + (self.len() as u32).serialize(writer)?; + for (k,v) in self { + k.serialize(writer)?; + v.serialize(writer)?; + } + Ok(()) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + let len = u32::deserialize(reader)?; + + + #[cfg(feature = "extra-validation")] + if K::size_hint() + .saturating_add(V::size_hint()) + .saturating_mul(len as usize) > reader.remainder_hint() { + return Err(MalformedData)?; + } + + let mut buffer = BTreeMap::::new(); + for _ in 0..len { + let k = K::deserialize(reader)?; + let v = V::deserialize(reader)?; + buffer.insert(k,v); + } + Ok(buffer) + } + + fn size_hint() -> usize { + u32::size_hint() + } +} \ No newline at end of file diff --git a/src/impls/lib_std.rs b/src/impls/lib_std.rs new file mode 100644 index 0000000..4cf4100 --- /dev/null +++ b/src/impls/lib_std.rs @@ -0,0 +1,42 @@ +use crate::{MalformedData, OverTheWire, Reader, Writer}; +use std::collections::HashMap; +use std::hash::Hash; + +impl OverTheWire for HashMap { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + (self.len() as u32).serialize(writer)?; + for (k,v) in self { + k.serialize(writer)?; + v.serialize(writer)?; + } + Ok(()) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + let len = u32::deserialize(reader)?; + + + #[cfg(feature = "extra-validation")] + if K::size_hint() + .saturating_add(V::size_hint()) + .saturating_mul(len as usize) > reader.remainder_hint() { + return Err(MalformedData)?; + } + + let mut buffer = HashMap::::new(); + for _ in 0..len { + let k = K::deserialize(reader)?; + let v = V::deserialize(reader)?; + buffer.insert(k,v); + } + Ok(buffer) + } + + #[inline] + fn size_hint() -> usize { + u32::size_hint() + } +} \ No newline at end of file diff --git a/src/impls/magic.rs b/src/impls/magic.rs new file mode 100644 index 0000000..00dfec7 --- /dev/null +++ b/src/impls/magic.rs @@ -0,0 +1,27 @@ +use crate::{MalformedData, OverTheWire, Reader, Writer}; + + +/// Zero-sized type that is written as a 4-byte magic number used for validation +pub struct Magic; + +impl OverTheWire for Magic { + + fn serialize(&self, writer: &mut T) -> e::Result<()> { + writer.write(V.to_le_bytes().as_slice()) + } + + fn deserialize(reader: &mut T) -> e::Result { + let _read = u32::from_le_bytes(reader.bytes::<4>()?); + + #[cfg(feature = "extra-validation")] + if _read != V { return Err(MalformedData)? } + + Ok(Self) + } + + fn size_hint() -> usize { + 4 + } +} + + diff --git a/src/impls/primitives.rs b/src/impls/primitives.rs new file mode 100644 index 0000000..1a52820 --- /dev/null +++ b/src/impls/primitives.rs @@ -0,0 +1,129 @@ +use crate::{OverTheWire, Reader, Writer}; + +//╶───╴Numbers╶──────────────────────────────────────────────────────────────╴ + +macro_rules! impl_integer { + ($($t:ty),+) => { + $(impl OverTheWire for $t { + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + writer.write(self.to_le_bytes().as_slice()) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + Ok(Self::from_le_bytes(reader.bytes()?)) + } + + fn size_hint() -> usize { + size_of::() + } + + })+ + }; +} + + +impl_integer!(u8, u16, u32, u64, u128); +impl_integer!(i8, i16, i32, i64, i128); +impl_integer!(f32, f64); + +//╶───╴Boolean╶──────────────────────────────────────────────────────────────╴ + +/// Implement for booleans +impl OverTheWire for bool { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + writer.write(&[*self as u8]) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + Ok(reader.bytes::<1>()?[0] != 0) + } + + fn size_hint() -> usize { 1 } +} + +/// Implement for unit types... +impl OverTheWire for () { + fn serialize(&self, _: &mut T) -> e::Result<()> { Ok(()) } + fn deserialize(_: &mut T) -> e::Result { Ok(()) } + fn size_hint() -> usize { 0 } +} + +//╶───╴Arrays╶───────────────────────────────────────────────────────────────╴ + +impl OverTheWire for [OTW; S] { + + #[inline] + fn serialize(&self, writer: &mut T) -> e::Result<()> { + for i in self { + i.serialize(writer)? + } + Ok(()) + } + + #[inline] + fn deserialize(reader: &mut T) -> e::Result { + let mut array = core::mem::MaybeUninit::<[OTW; S]>::uninit(); + + for i in 0..S { + match OTW::deserialize(reader) { + Ok(v) => unsafe { + // assign the value directly into the uninit array... + *array.as_mut_ptr().cast::().add(i) = v; + } + Err(error) => unsafe { + + // drop only the value's that were assigned + for i in 0..i { + core::ptr::drop_in_place(array.as_mut_ptr().cast::().add(i)) + } + + // forward the error + return Err(error); + } + } + } + + Ok(unsafe { array.assume_init() }) + } + + fn size_hint() -> usize { + OTW::size_hint().saturating_mul(S) + } +} + +//╶───╴Tuples╶───────────────────────────────────────────────────────────────╴ + +macro_rules! impl_tuple { + ( $($t:ident : $l:tt),+ ) => { + impl < $($t: OverTheWire),+ > OverTheWire for ($($t),+, ) { + fn serialize(&self, writer: &mut T) -> e::Result<()> { + $(self.$l.serialize(writer)?;)+ + Ok(()) + } + fn deserialize(reader: &mut T) -> e::Result { + Ok(( + $($t::deserialize(reader)?),+, + )) + } + fn size_hint() -> usize { + 0usize $( .saturating_add($t::size_hint()) )+ + } + } + }; +} + +impl_tuple!( K0:0 ); +impl_tuple!( K0:0, K1:1 ); +impl_tuple!( K0:0, K1:1, K2:2 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3, K4:4 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3, K4:4, K5:5 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3, K4:4, K5:5, K6:6 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3, K4:4, K5:5, K6:6, K7:7 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3, K4:4, K5:5, K6:6, K7:7, K8:8 ); +impl_tuple!( K0:0, K1:1, K2:2, K3:3, K4:4, K5:5, K6:6, K7:7, K8:8, K9:9 ); \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..dc90b05 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,76 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +/* + TODO - Future Improvements + implement var-int support for length & integers + + + */ + +#[cfg(feature = "alloc")] +extern crate alloc; + +//╶───╴Modules╶──────────────────────────────────────────────────────────────╴ + +mod impls { + + mod primitives; + + mod magic; + + #[cfg(feature = "alloc")] + mod lib_alloc; + + #[cfg(feature = "std")] + mod lib_std; + +} + + +mod streams; + + +//╶───╴Core Traits╶──────────────────────────────────────────────────────────╴ + +e::new_exception!(MalformedData); + +pub trait Writer { + fn write(&mut self, bytes: &[u8]) -> e::Result<()>; +} + +pub trait Reader { + fn read(&mut self, bytes: &mut [u8]) -> e::Result<()>; + + #[inline(always)] + fn bytes(&mut self) -> e::Result<[u8;S]> { + Ok(unsafe { + let mut buffer = core::mem::MaybeUninit::<[u8;S]>::uninit(); + self.read(buffer.assume_init_mut().as_mut_slice())?; + buffer.assume_init() + }) + } + + /// used for validating large arrays of data... + fn remainder_hint(&self) -> usize; + +} + +pub use derive_macro::OverTheWire; + +pub trait OverTheWire: Sized { + fn serialize(&self, writer: &mut T) -> e::Result<()>; + fn deserialize(reader: &mut T) -> e::Result; + + /// the minimum size of this type, used for extra validation + fn size_hint() -> usize; +} + +#[inline(always)] +pub fn min_wire_size() -> usize { + T::size_hint() +} + + + + + diff --git a/src/streams.rs b/src/streams.rs new file mode 100644 index 0000000..b0cfb7f --- /dev/null +++ b/src/streams.rs @@ -0,0 +1,33 @@ +use crate::{MalformedData, Reader, Writer}; + +impl Reader for &[u8] { + + #[inline(always)] + fn read(&mut self, bytes: &mut [u8]) -> e::Result<()> { + if bytes.len() > self.len() { Err(MalformedData)? } + + unsafe { + core::ptr::copy_nonoverlapping( + self.as_ptr(), + bytes.as_mut_ptr(), + bytes.len() + ) + } + + *self = &self[bytes.len()..]; + Ok(()) + } + + #[inline(always)] + fn remainder_hint(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "alloc")] +impl Writer for alloc::vec::Vec { + fn write(&mut self, bytes: &[u8]) -> e::Result<()> { + self.extend_from_slice(bytes); + Ok(()) + } +}