This commit is contained in:
Numbers
2025-05-29 03:45:41 +02:00
commit ebf6bde22d
10 changed files with 654 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

21
Cargo.toml Normal file
View File

@@ -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" }

13
derive_macro/Cargo.toml Normal file
View File

@@ -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"

139
derive_macro/src/lib.rs Normal file
View File

@@ -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<TokenStream, std::fmt::Error> {
let mut output = String::new();
// collect the fields...
let input_fields = match &data.fields {
// field_name: <value>
Fields::Named(fields) =>
fields.named.iter().filter_map(
|n| { n.ident.as_ref().map(|i| i.to_string()) }
).collect::<Vec<String>>(),
// field names are 0, 1, 2, etc.
Fields::Unnamed(fields) =>
(0..fields.unnamed.len())
.map(|i| i.to_string())
.collect::<Vec<String>>(),
// 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: <value>
Fields::Named(fields) =>
fields.named.iter()
.map(|n| { n.ty.to_token_stream().to_string() })
.collect::<Vec<String>>(),
// field names are 0, 1, 2, etc.
Fields::Unnamed(fields) => fields.unnamed.iter()
.map(|f|f.ty.to_token_stream().to_string())
.collect::<Vec<String>>(),
// 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<T: otw::Writer>(&self, writer: &mut T) -> e::Result<()> {{")?;
writeln!(&mut output, "{serialize}")?;
writeln!(&mut output, " }}")?;
writeln!(&mut output, " fn deserialize<T: otw::Reader>(reader: &mut T) -> e::Result<Self> {{")?;
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::<Self>()
}
*/
writeln!(&mut output, "}}")?;
if let Err(error) = output.parse::<TokenStream>() {
panic!("{}", output);
}
Ok(output.parse().unwrap())
}

173
src/impls/lib_alloc.rs Normal file
View File

@@ -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<OTW: OverTheWire> OverTheWire for Box<OTW> {
#[inline]
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
OTW::serialize(&*self, writer)
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
Ok(Box::new(OTW::deserialize(reader)?))
}
#[inline]
fn size_hint() -> usize {
OTW::size_hint()
}
}
impl<OTW: OverTheWire> OverTheWire for Rc<OTW> {
#[inline]
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
OTW::serialize(&*self, writer)
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
Ok(Rc::new(OTW::deserialize(reader)?))
}
#[inline]
fn size_hint() -> usize {
OTW::size_hint()
}
}
impl<OTW: OverTheWire> OverTheWire for Arc<OTW> {
#[inline]
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
OTW::serialize(&*self, writer)
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
Ok(Arc::new(OTW::deserialize(reader)?))
}
#[inline]
fn size_hint() -> usize {
OTW::size_hint()
}
}
impl OverTheWire for String {
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
(self.len() as u32).serialize(writer)?;
writer.write(self.as_bytes())?;
Ok(())
}
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
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 <OTW: OverTheWire> OverTheWire for Vec<OTW> {
#[inline]
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
(self.len() as u32).serialize(writer)?;
for v in self {
v.serialize(writer)?;
}
Ok(())
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
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 <K: OverTheWire + Ord, V: OverTheWire> OverTheWire for BTreeMap<K,V> {
#[inline]
fn serialize<T: Writer>(&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<T: Reader>(reader: &mut T) -> e::Result<Self> {
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::<K,V>::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()
}
}

42
src/impls/lib_std.rs Normal file
View File

@@ -0,0 +1,42 @@
use crate::{MalformedData, OverTheWire, Reader, Writer};
use std::collections::HashMap;
use std::hash::Hash;
impl <K: OverTheWire + Hash + Eq, V: OverTheWire> OverTheWire for HashMap<K,V> {
#[inline]
fn serialize<T: Writer>(&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<T: Reader>(reader: &mut T) -> e::Result<Self> {
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::<K,V>::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()
}
}

27
src/impls/magic.rs Normal file
View File

@@ -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<const VALUE: u32>;
impl<const V: u32> OverTheWire for Magic<V> {
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
writer.write(V.to_le_bytes().as_slice())
}
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
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
}
}

129
src/impls/primitives.rs Normal file
View File

@@ -0,0 +1,129 @@
use crate::{OverTheWire, Reader, Writer};
//╶───╴Numbers╶──────────────────────────────────────────────────────────────╴
macro_rules! impl_integer {
($($t:ty),+) => {
$(impl OverTheWire for $t {
#[inline]
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
writer.write(self.to_le_bytes().as_slice())
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
Ok(Self::from_le_bytes(reader.bytes()?))
}
fn size_hint() -> usize {
size_of::<Self>()
}
})+
};
}
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<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
writer.write(&[*self as u8])
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
Ok(reader.bytes::<1>()?[0] != 0)
}
fn size_hint() -> usize { 1 }
}
/// Implement for unit types...
impl OverTheWire for () {
fn serialize<T: Writer>(&self, _: &mut T) -> e::Result<()> { Ok(()) }
fn deserialize<T: Reader>(_: &mut T) -> e::Result<Self> { Ok(()) }
fn size_hint() -> usize { 0 }
}
//╶───╴Arrays╶───────────────────────────────────────────────────────────────╴
impl<const S: usize, OTW: OverTheWire> OverTheWire for [OTW; S] {
#[inline]
fn serialize<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
for i in self {
i.serialize(writer)?
}
Ok(())
}
#[inline]
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
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::<OTW>().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::<OTW>().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<T: Writer>(&self, writer: &mut T) -> e::Result<()> {
$(self.$l.serialize(writer)?;)+
Ok(())
}
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self> {
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 );

76
src/lib.rs Normal file
View File

@@ -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<const S: usize>(&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<T: Writer>(&self, writer: &mut T) -> e::Result<()>;
fn deserialize<T: Reader>(reader: &mut T) -> e::Result<Self>;
/// the minimum size of this type, used for extra validation
fn size_hint() -> usize;
}
#[inline(always)]
pub fn min_wire_size<T: OverTheWire>() -> usize {
T::size_hint()
}

33
src/streams.rs Normal file
View File

@@ -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<u8> {
fn write(&mut self, bytes: &[u8]) -> e::Result<()> {
self.extend_from_slice(bytes);
Ok(())
}
}