diff --git a/Cargo.toml b/Cargo.toml index 8d21054..4062fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,13 @@ keywords = ["binary", "encode", "decode", "serialize", "deserialize"] license = "MIT" description = "A binary serialization / deserialization strategy for transforming structs into bytes and vice versa!" -edition = "2018" \ No newline at end of file +edition = "2018" + +[features] +default = ["std"] +std = [] +alloc = [] + +[dependencies] +bincode_derive = { path = "derive" } +# serde = { version = "1.0.130", optional = true } diff --git a/derive/.gitignore b/derive/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/derive/.gitignore @@ -0,0 +1 @@ +target diff --git a/derive/Cargo.lock b/derive/Cargo.lock new file mode 100644 index 0000000..c581e3f --- /dev/null +++ b/derive/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bincode_derive" +version = "0.1.0" +dependencies = [ + "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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +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/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..2987147 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bincode_derive" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.9" + +[dependencies.syn] +version = "1.0.74" +default-features = false +features = ["parsing", "derive", "proc-macro", "printing"] diff --git a/derive/src/derive_enum.rs b/derive/src/derive_enum.rs new file mode 100644 index 0000000..73c0b08 --- /dev/null +++ b/derive/src/derive_enum.rs @@ -0,0 +1,19 @@ +use crate::Result; +use proc_macro::TokenStream; +use syn::Ident; + +pub struct DeriveEnum {} + +impl DeriveEnum { + pub fn parse(_name: Ident, _en: syn::DataEnum) -> Result { + unimplemented!() + } + + pub fn to_encodable(self) -> Result { + unimplemented!() + } + + pub fn to_decodable(self) -> Result { + unimplemented!() + } +} diff --git a/derive/src/derive_struct.rs b/derive/src/derive_struct.rs new file mode 100644 index 0000000..0bb7de3 --- /dev/null +++ b/derive/src/derive_struct.rs @@ -0,0 +1,57 @@ +use crate::Result; +use proc_macro::TokenStream; +use quote::quote; +use syn::{spanned::Spanned, Ident}; + +pub struct DeriveStruct { + name: Ident, + fields: Vec, +} + +impl DeriveStruct { + pub fn parse(name: Ident, str: syn::DataStruct) -> Result { + let fields = match str.fields { + syn::Fields::Named(fields) => fields + .named + .iter() + .map(|f| f.ident.clone().unwrap()) + .collect(), + syn::Fields::Unnamed(fields) => fields + .unnamed + .iter() + .enumerate() + .map(|(i, field)| Ident::new(&i.to_string(), field.ty.span())) + .collect(), + syn::Fields::Unit => Vec::new(), + }; + Ok(Self { name, fields }) + } + + pub fn to_encodable(self) -> Result { + let DeriveStruct { name, fields } = self; + + let fields = fields + .into_iter() + .map(|field| { + quote! { + bincode::enc::Encodeable::encode(&self. #field, &mut encoder)?; + } + }) + .collect::>(); + + let result = quote! { + impl bincode::enc::Encodeable for #name { + fn encode(&self, mut encoder: E) -> Result<(), bincode::error::Error> { + #(#fields)* + Ok(()) + } + + } + }; + Ok(result.into()) + } + + pub fn to_decodable(self) -> Result { + unimplemented!() + } +} diff --git a/derive/src/error.rs b/derive/src/error.rs new file mode 100644 index 0000000..ff1abee --- /dev/null +++ b/derive/src/error.rs @@ -0,0 +1,24 @@ +use proc_macro::TokenStream; +use quote::__private::Span; +use std::fmt; + +pub enum Error { + UnionNotSupported, +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::UnionNotSupported => write!(fmt, "Unions are not supported"), + } + } +} + +impl Error { + pub fn into_token_stream(self) -> TokenStream { + self.into_token_stream_with_span(Span::call_site()) + } + pub fn into_token_stream_with_span(self, span: Span) -> TokenStream { + syn::Error::new(span, self).into_compile_error().into() + } +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..82be10a --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,49 @@ +extern crate proc_macro; + +mod derive_enum; +mod derive_struct; +mod error; + +use derive_enum::DeriveEnum; +use derive_struct::DeriveStruct; +use error::Error; +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +type Result = std::result::Result; + +#[proc_macro_derive(Encodable)] +pub fn derive_encodable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + derive_encodable_inner(input).unwrap_or_else(|e| e.into_token_stream()) +} + +fn derive_encodable_inner(input: DeriveInput) -> Result { + match input.data { + syn::Data::Struct(struct_definition) => { + DeriveStruct::parse(input.ident, struct_definition).and_then(|str| str.to_encodable()) + } + syn::Data::Enum(enum_definition) => { + DeriveEnum::parse(input.ident, enum_definition).and_then(|str| str.to_encodable()) + } + syn::Data::Union(_) => Err(Error::UnionNotSupported), + } +} + +#[proc_macro_derive(Decodable)] +pub fn derive_decodable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + derive_decodable_inner(input).unwrap_or_else(|e| e.into_token_stream()) +} + +fn derive_decodable_inner(input: DeriveInput) -> Result { + match input.data { + syn::Data::Struct(struct_definition) => { + DeriveStruct::parse(input.ident, struct_definition).and_then(|str| str.to_decodable()) + } + syn::Data::Enum(enum_definition) => { + DeriveEnum::parse(input.ident, enum_definition).and_then(|str| str.to_decodable()) + } + syn::Data::Union(_) => Err(Error::UnionNotSupported), + } +} diff --git a/docs/spec.md b/docs/spec.md index e1fb077..ed23380 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1,6 +1,6 @@ # Serialization specification -*NOTE*: Serialization is done by `bincode_derive` by default. If you enable the `serde` flag, serialization is done by `serde-derive` instead. `serde-derive` has the same guarantees as `bincode_derive` for now. +*NOTE*: Serialization is done by `bincode_derive` by default. If you enable the `serde` flag, serialization with `serde-derive` is supported as well. `serde-derive` has the same guarantees as `bincode_derive` for now. Related issue: https://github.com/serde-rs/serde/issues/1756#issuecomment-689682123 @@ -51,7 +51,7 @@ Enums are encoded with their variant first, followed by optionally the variant f Both named and unnamed fields are serialized with their values only, and therefor encode to the same value. ```rs -#[derive(bincode::Serialize)] +#[derive(bincode::Encodable)] pub enum SomeEnum { A, B(u32), diff --git a/src/de/impls.rs b/src/de/impls.rs index 131750e..265b664 100644 --- a/src/de/impls.rs +++ b/src/de/impls.rs @@ -1,8 +1,8 @@ -use super::{Decode, Decodable}; +use super::{Decodable, Decode}; use crate::error::Error; impl Decodable for u32 { fn decode(mut decoder: D) -> Result { decoder.decode_u32() } -} \ No newline at end of file +} diff --git a/src/de/mod.rs b/src/de/mod.rs index 4b4c348..b97152c 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -9,8 +9,6 @@ use read::Reader; mod impls; pub mod read; - - pub trait Decodable: Sized { fn decode(decoder: D) -> Result; } @@ -44,5 +42,3 @@ impl<'a, 'de, R: Reader<'de>, C: Config> Decode for &'a mut Decoder { }) } } - - diff --git a/src/de/read.rs b/src/de/read.rs index 6e4270d..f9cd893 100644 --- a/src/de/read.rs +++ b/src/de/read.rs @@ -30,7 +30,7 @@ impl<'storage> SliceReader<'storage> { impl<'storage> Reader<'storage> for SliceReader<'storage> { #[inline(always)] - fn read<'a>(&'a mut self, bytes: &mut [u8]) -> Result<(), Error> { + fn read(&mut self, bytes: &mut [u8]) -> Result<(), Error> { if bytes.len() > self.slice.len() { return Err(Error::UnexpectedEnd); } @@ -48,4 +48,4 @@ impl<'storage> Reader<'storage> for SliceReader<'storage> { { Ok(visitor(self.get_byte_slice(length)?)) } -} \ No newline at end of file +} diff --git a/src/enc/impls.rs b/src/enc/impls.rs index 87a149a..da16aa2 100644 --- a/src/enc/impls.rs +++ b/src/enc/impls.rs @@ -1,8 +1,20 @@ use super::{Encode, Encodeable}; use crate::error::Error; +impl Encodeable for u8 { + fn encode(&self, mut encoder: E) -> Result<(), Error> { + encoder.encode_u8(*self) + } +} + impl Encodeable for u32 { fn encode(&self, mut encoder: E) -> Result<(), Error> { encoder.encode_u32(*self) } -} \ No newline at end of file +} + +impl Encodeable for i32 { + fn encode(&self, mut encoder: E) -> Result<(), Error> { + encoder.encode_i32(*self) + } +} diff --git a/src/enc/mod.rs b/src/enc/mod.rs index caf67a6..e6c7100 100644 --- a/src/enc/mod.rs +++ b/src/enc/mod.rs @@ -9,13 +9,28 @@ use write::Writer; mod impls; pub mod write; - pub trait Encodeable { fn encode(&self, encoder: E) -> Result<(), Error>; } - pub trait Encode { + fn encode_u8(&mut self, val: u8) -> Result<(), Error>; fn encode_u32(&mut self, val: u32) -> Result<(), Error>; + fn encode_i32(&mut self, val: i32) -> Result<(), Error>; +} + +impl<'a, T> Encode for &'a mut T +where + T: Encode, +{ + fn encode_u8(&mut self, val: u8) -> Result<(), Error> { + T::encode_u8(self, val) + } + fn encode_u32(&mut self, val: u32) -> Result<(), Error> { + T::encode_u32(self, val) + } + fn encode_i32(&mut self, val: i32) -> Result<(), Error> { + T::encode_i32(self, val) + } } pub struct Encoder { @@ -30,9 +45,17 @@ impl Encoder { config: PhantomData, } } + + pub fn into_writer(self) -> W { + self.writer + } } impl<'a, W: Writer, C: Config> Encode for &'a mut Encoder { + fn encode_u8(&mut self, val: u8) -> Result<(), Error> { + self.writer.write(&[val]) + } + fn encode_u32(&mut self, val: u32) -> Result<(), Error> { let bytes = match C::ENDIAN { Endian::Little => val.to_le_bytes(), @@ -41,4 +64,13 @@ impl<'a, W: Writer, C: Config> Encode for &'a mut Encoder { self.writer.write(&bytes) } -} \ No newline at end of file + + fn encode_i32(&mut self, val: i32) -> Result<(), Error> { + let bytes = match C::ENDIAN { + Endian::Little => val.to_le_bytes(), + Endian::Big => val.to_be_bytes(), + }; + + self.writer.write(&bytes) + } +} diff --git a/src/enc/write.rs b/src/enc/write.rs index 6d33e1a..1f8d6e7 100644 --- a/src/enc/write.rs +++ b/src/enc/write.rs @@ -6,25 +6,31 @@ pub trait Writer { pub struct SliceWriter<'storage> { slice: &'storage mut [u8], + idx: usize, } impl<'storage> SliceWriter<'storage> { pub(crate) fn new(bytes: &'storage mut [u8]) -> SliceWriter<'storage> { SliceWriter { slice: bytes, + idx: 0, } } + + pub(crate) fn bytes_written(&self) -> usize { + self.idx + } } impl<'storage> Writer for SliceWriter<'storage> { fn write(&mut self, bytes: &[u8]) -> Result<(), Error> { - if bytes.len() > self.slice.len() { + let remaining = &mut self.slice[self.idx..]; + if bytes.len() > remaining.len() { return Err(Error::UnexpectedEnd); } - let data = core::mem::take(&mut self.slice); - let (write_slice, remaining) = data.split_at_mut(bytes.len()); + self.idx += bytes.len(); + let write_slice = &mut remaining[..bytes.len()]; write_slice.copy_from_slice(bytes); - self.slice = remaining; Ok(()) } -} \ No newline at end of file +} diff --git a/src/int_encoding.rs b/src/int_encoding.rs index cb81454..f3117ed 100644 --- a/src/int_encoding.rs +++ b/src/int_encoding.rs @@ -1,4 +1,4 @@ -use crate::{error::Error, enc::write::Writer}; +use crate::{enc::write::Writer, error::Error}; pub trait IntEncoding { fn encode_u32(writer: &mut W, val: u32) -> Result<(), Error>; diff --git a/src/lib.rs b/src/lib.rs index 16aa95a..3c72acb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,15 +17,21 @@ extern crate std; pub mod config; pub mod de; -pub mod error; pub mod enc; +pub mod error; + +pub use bincode_derive::{Decodable, Encodable}; pub(crate) mod int_encoding; -pub fn encode_into_slice(val: E, dst: &mut [u8]) -> Result<(), error::Error> { +pub fn encode_into_slice( + val: E, + dst: &mut [u8], +) -> Result { let writer = enc::write::SliceWriter::new(dst); let mut encoder = enc::Encoder::<_, config::Default>::new(writer); - val.encode(&mut encoder) + val.encode(&mut encoder)?; + Ok(encoder.into_writer().bytes_written()) } pub fn decode(src: &mut [u8]) -> Result { diff --git a/tests/derive.rs b/tests/derive.rs new file mode 100644 index 0000000..35a5e8b --- /dev/null +++ b/tests/derive.rs @@ -0,0 +1,19 @@ +#[derive(bincode::Encodable, PartialEq, Debug)] +pub struct Test { + a: i32, + b: u32, + c: u8, +} + +#[test] +fn test_encodable() { + let start = Test { + a: 5i32, + b: 10u32, + c: 20u8, + }; + let mut slice = [0u8; 1024]; + let bytes_written = bincode::encode_into_slice(start, &mut slice).unwrap(); + assert_eq!(bytes_written, 9); + assert_eq!(&slice[..bytes_written], &[5, 0, 0, 0, 10, 0, 0, 0, 20]); +}