diff --git a/Cargo.lock b/Cargo.lock index 7e6c0a6..0ec47bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -54,18 +54,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cookie-cutter" -version = "0.1.0" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "defmt", - "fill-array", - "macros", - "panic-halt", -] - [[package]] name = "cortex-m" version = "0.7.7" @@ -155,8 +143,8 @@ dependencies = [ name = "dispatch-bundle" version = "0.1.0" dependencies = [ - "cookie-cutter", "macros", + "serac", ] [[package]] @@ -182,13 +170,13 @@ dependencies = [ name = "embedded-command" version = "0.1.0" dependencies = [ - "cookie-cutter", "defmt", "embassy-futures", "embassy-sync", "embedded-hal-async", "embedded-io-async", "heapless", + "serac", ] [[package]] @@ -326,8 +314,8 @@ checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" name = "packit" version = "0.1.0" dependencies = [ - "cookie-cutter", "crc", + "serac", ] [[package]] @@ -441,6 +429,18 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serac" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "fill-array", + "macros", + "panic-halt", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 6cdea68..4e9e563 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] -resolver = "2" +resolver = "3" members = [ "dispatch-bundle", - "cookie-cutter", + "serac", "embedded-command", "macros", "packit", diff --git a/ci.sh b/ci.sh index 1a03c98..efb4065 100755 --- a/ci.sh +++ b/ci.sh @@ -5,7 +5,7 @@ set -euxo pipefail rustup toolchain install nightly --component miri TARGETS=("thumbv6m-none-eabi" "thumbv7em-none-eabi" "thumbv7em-none-eabihf") -CRATES=("macros" "cookie-cutter" "dispatch-bundle") +CRATES=("macros" "serac" "dispatch-bundle") # build @@ -34,4 +34,4 @@ cargo clippy # cookie-cutter # asm analysis -cargo build -p cookie-cutter --bin asm --target thumbv7em-none-eabihf --features binary --release +cargo build -p serac --bin asm --target thumbv7em-none-eabihf --features binary --release diff --git a/cookie-cutter/src/encoding.rs b/cookie-cutter/src/encoding.rs deleted file mode 100644 index 4300644..0000000 --- a/cookie-cutter/src/encoding.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod vanilla; - -/// Types implement this trait -/// to be used as indication of -/// a specific encoding scheme. -pub trait Encoding { - /// The fundamental word of the - /// encoding scheme. - /// - /// i.e. `u8` for `[u8; ...]` mediums. - type Word; -} diff --git a/cookie-cutter/src/medium.rs b/cookie-cutter/src/medium.rs deleted file mode 100644 index 521dc3b..0000000 --- a/cookie-cutter/src/medium.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::encoding::{vanilla::Vanilla, Encoding}; - -// TODO: iters should be associated types defined -// by implementors - -/// Types implement this trait to be used -/// as serialization mediums. -pub trait Medium { - const SIZE: usize; - - fn get_iter<'a>(&'a self) -> impl Iterator - where - E::Word: 'a; - fn get_iter_mut<'a>(&'a mut self) -> impl Iterator - where - E::Word: 'a; -} - -// Implement `Medium` for all arrays. -impl Medium for [E::Word; N] { - const SIZE: usize = N; - - fn get_iter<'a>(&'a self) -> impl Iterator - where - E::Word: 'a, - { - self.iter() - } - - fn get_iter_mut<'a>(&'a mut self) -> impl Iterator - where - E::Word: 'a, - { - self.iter_mut() - } -} diff --git a/dispatch-bundle/Cargo.toml b/dispatch-bundle/Cargo.toml index 701d94e..7fb6804 100644 --- a/dispatch-bundle/Cargo.toml +++ b/dispatch-bundle/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" packit = [] [dependencies] -macros = { path = "../macros" } +macros = { package = "embedded-command-macros", path = "../macros" } [dev-dependencies] -cookie-cutter = { path = "../cookie-cutter" } +serac = { path = "../serac" } diff --git a/embedded-command/Cargo.toml b/embedded-command/Cargo.toml index 188fdc0..2e6a060 100644 --- a/embedded-command/Cargo.toml +++ b/embedded-command/Cargo.toml @@ -10,7 +10,7 @@ embassy-sync = "0.5.0" embedded-hal-async = "1.0.0" embedded-io-async = "0.6.1" heapless = "0.8.0" -cookie-cutter = { path = "../cookie-cutter" } +serac = { path = "../serac" } defmt = { version = "0.3.10", optional = true } [features] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 27d1ea2..c8027a6 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,7 +1,10 @@ [package] -name = "macros" +name = "embedded-command-macros" version = "0.1.0" -edition = "2021" +edition = "2024" +description = "Macros for the embedded command crate family." +license = "CC-BY-NC-SA-4.0" +repository = "https://github.com/adinack/embedded-command" [lib] proc-macro = true diff --git a/macros/src/lib.rs b/macros/src/lib.rs index deda193..8368c72 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; -mod cookie_cutter; mod dispatch_bundle; +mod serac; /// Transform attached enum into a "bundle". /// @@ -20,10 +20,10 @@ pub fn bundle(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// # Note /// -/// Requires `cookie_cutter` to be in scope with that name. +/// Requires `serac` to be in scope with that name. #[proc_macro_derive(SerializeIter)] -pub fn serialize_iter_vanilla(item: TokenStream) -> TokenStream { - cookie_cutter::vanilla::serialize_iter(item) +pub fn impl_serialize_iter_vanilla(item: TokenStream) -> TokenStream { + serac::vanilla::serialize_iter(item) } /// Generates the implementation block for conforming to `SerializeBuf` of the "vanilla" flavor. @@ -32,8 +32,8 @@ pub fn serialize_iter_vanilla(item: TokenStream) -> TokenStream { /// /// # Note /// -/// Requires `cookie_cutter` to be in scope with that name. +/// Requires `serac` to be in scope with that name. #[proc_macro_derive(SerializeBuf)] -pub fn serialize_buf_vanilla(item: TokenStream) -> TokenStream { - cookie_cutter::vanilla::serialize_buf(item) +pub fn impl_serialize_buf_vanilla(item: TokenStream) -> TokenStream { + serac::vanilla::impl_serialize_buf(item) } diff --git a/macros/src/cookie_cutter.rs b/macros/src/serac.rs similarity index 100% rename from macros/src/cookie_cutter.rs rename to macros/src/serac.rs diff --git a/macros/src/cookie_cutter/vanilla.rs b/macros/src/serac/vanilla.rs similarity index 84% rename from macros/src/cookie_cutter/vanilla.rs rename to macros/src/serac/vanilla.rs index 878d739..0c190f4 100644 --- a/macros/src/cookie_cutter/vanilla.rs +++ b/macros/src/serac/vanilla.rs @@ -142,7 +142,7 @@ fn size_of_struct(s: DataStruct, info: &BodyInfo) -> TokenStream2 { if types.is_empty() { quote! { 0 } } else { - quote! { #( <<#types as #path::SerializeBuf>::Serialized as #path::medium::Medium>::SIZE )+* } + quote! { #( <#types as #path::SerializeBuf>::SIZE )+* } } } @@ -307,13 +307,9 @@ fn size_of_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> TokenStream2 { .iter() .filter_map(|variant| { if !variant.fields.is_empty() { - let types: Vec<_> = variant - .fields - .iter() - .map(|field| &field.ty) - .collect(); + let types: Vec<_> = variant.fields.iter().map(|field| &field.ty).collect(); - Some(quote! { #(<<#types as #path::SerializeBuf>::Serialized as #path::medium::Medium>::SIZE)+* }) + Some(quote! { #(<#types as #path::SerializeBuf>::SIZE)+* }) } else { None } @@ -329,7 +325,7 @@ fn size_of_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> TokenStream2 { } )* - max + <<#repr as #path::SerializeBuf>::Serialized as #path::medium::Medium>::SIZE + max + <#repr as #path::SerializeBuf>::SIZE }} } @@ -339,7 +335,7 @@ pub fn serialize_iter(item: TokenStream) -> TokenStream { let info = BodyInfo { ident: item.ident, generics: item.generics, - path: syn::parse2(quote! { cookie_cutter }).unwrap(), + path: syn::parse2(quote! { serac }).unwrap(), }; let implementation = match item.data { @@ -351,7 +347,7 @@ pub fn serialize_iter(item: TokenStream) -> TokenStream { implementation.into() } -pub fn serialize_buf(item: TokenStream) -> TokenStream { +pub fn impl_serialize_buf(item: TokenStream) -> TokenStream { let item: DeriveInput = syn::parse2(item.into()).unwrap(); if !item.generics.params.is_empty() { @@ -361,7 +357,7 @@ pub fn serialize_buf(item: TokenStream) -> TokenStream { let info = BodyInfo { ident: item.ident, generics: item.generics, - path: syn::parse2(quote! { cookie_cutter }).unwrap(), + path: syn::parse2(quote! { serac }).unwrap(), }; let size = match item.data { @@ -372,12 +368,37 @@ pub fn serialize_buf(item: TokenStream) -> TokenStream { let path = info.path; let ident = info.ident; - let (impl_generics, ty_generics, where_clause) = info.generics.split_for_impl(); - let ty = quote! { #ident #ty_generics }; quote! { - unsafe impl #impl_generics #path::SerializeBuf for #ty #ty_generics #where_clause { - type Serialized = [u8; #size]; + unsafe impl #path::SerializeBuf for #ident { + const SIZE: usize = #size; + } + + impl #ident { + /// Serialize into the serialization medium. + pub fn serialize_buf<'a>( + &self, + buf: &'a mut <#path::encoding::Vanilla as #path::Encoding>::Serialized<{ ::SIZE }>, + ) where + &'a mut <#path::encoding::Vanilla as #path::Encoding>::Serialized<{ ::SIZE }>: + IntoIterator::Word> + 'a, + { + unsafe { #path::SerializeIter::serialize_iter(self, buf).unwrap_unchecked() } + } + + /// Deserialize from the serialization medium. + pub fn deserialize_buf<'a>(src: &'a <#path::encoding::Vanilla as #path::Encoding>::Serialized<{ ::SIZE }>) -> Result + where + &'a <#path::encoding::Vanilla as #path::Encoding>::Serialized<{ ::SIZE }>: + IntoIterator::Word> + 'a, + { + #path::SerializeIter::deserialize_iter(src).or_else(|err| match err { + #path::error::Error::Invalid => Err(#path::error::Invalid), + // SAFETY: dependent on safety of trait implementation. + // `Serialized` must be of sufficient length. + #path::error::Error::EndOfInput => unsafe { ::core::hint::unreachable_unchecked() }, + }) + } } } .into() diff --git a/packit/Cargo.toml b/packit/Cargo.toml index daba602..62972e5 100644 --- a/packit/Cargo.toml +++ b/packit/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -cookie-cutter = { path = "../cookie-cutter" } +serac = { path = "../serac" } crc = "3.0.1" diff --git a/cookie-cutter/Cargo.toml b/serac/Cargo.toml similarity index 54% rename from cookie-cutter/Cargo.toml rename to serac/Cargo.toml index 12ff9d7..870a4ba 100644 --- a/cookie-cutter/Cargo.toml +++ b/serac/Cargo.toml @@ -1,10 +1,13 @@ [package] -name = "cookie-cutter" +name = "serac" version = "0.1.0" -edition = "2021" +edition = "2024" +description = "A static, modular, and light-weight serialization framework." +license = "CC-BY-NC-SA-4.0" +repository = "https://github.com/adinack/embedded-command" [dependencies] -macros = { path = "../macros", version = "0.1.0" } +macros = { package = "embedded-command-macros", version = "0.1.0" } fill-array = "0.2.1" # for binary @@ -14,8 +17,7 @@ cortex-m-rt = { version = "0.7.3", optional = true } defmt = { version = "0.3.10", optional = true } [features] -binary = ["dep:panic-halt", "dep:cortex-m", "cortex-m-rt"] -cortex-m-rt = ["dep:cortex-m-rt"] +binary = ["dep:panic-halt", "dep:cortex-m", "dep:cortex-m-rt"] defmt = ["dep:defmt"] [[bin]] diff --git a/cookie-cutter/src/bin/asm.rs b/serac/src/bin/asm.rs similarity index 100% rename from cookie-cutter/src/bin/asm.rs rename to serac/src/bin/asm.rs diff --git a/serac/src/encoding.rs b/serac/src/encoding.rs new file mode 100644 index 0000000..5375f41 --- /dev/null +++ b/serac/src/encoding.rs @@ -0,0 +1,14 @@ +pub mod vanilla; +pub use vanilla::Vanilla; + +/// Types implement this trait to be used as indication of a specific encoding +/// scheme. +pub trait Encoding { + /// The fundamental word of the encoding scheme. + /// + /// i.e. `u8` for `[u8; ...]` mediums. + type Word; + + /// The serialized form converted to and from by this encoding scheme. + type Serialized; +} diff --git a/cookie-cutter/src/encoding/vanilla.rs b/serac/src/encoding/vanilla.rs similarity index 71% rename from cookie-cutter/src/encoding/vanilla.rs rename to serac/src/encoding/vanilla.rs index 161e5fd..409fceb 100644 --- a/cookie-cutter/src/encoding/vanilla.rs +++ b/serac/src/encoding/vanilla.rs @@ -2,26 +2,34 @@ use core::{marker::PhantomData, mem::MaybeUninit}; use super::Encoding; -use crate::{error, SerializeBuf, SerializeIter}; +use crate::{error, Medium, SerializeBuf, SerializeIter}; use fill_array::fill; // export proc macro pub use macros::{SerializeBuf, SerializeIter}; pub struct Vanilla; + impl Encoding for Vanilla { type Word = u8; + type Serialized = [Self::Word; SIZE]; +} + +impl Medium for [u8; N] { + fn default() -> Self { + [0; N] + } } macro_rules! impl_number { ($TYPE:ty, $SIZE:expr) => { impl SerializeIter for $TYPE { - fn serialize_iter<'a, 'b>( - &'a self, - dst: impl IntoIterator::Word>, + fn serialize_iter<'a>( + &self, + dst: impl IntoIterator::Word>, ) -> Result<(), error::EndOfInput> where - ::Word: 'b, + ::Word: 'a, { let mut dst = dst.into_iter(); @@ -54,7 +62,7 @@ macro_rules! impl_number { // SAFETY: $SIZE must be correct as it is validated by it's usage with `from_le_bytes` unsafe impl SerializeBuf for $TYPE { - type Serialized = [u8; $SIZE]; + const SIZE: usize = $SIZE; } }; } @@ -78,12 +86,12 @@ impl_number!(f64, 8); // bool impls impl SerializeIter for bool { - fn serialize_iter<'a, 'b>( - &'a self, - dst: impl IntoIterator::Word>, + fn serialize_iter<'a>( + &self, + dst: impl IntoIterator::Word>, ) -> Result<(), error::EndOfInput> where - ::Word: 'b, + ::Word: 'a, { let mut dst = dst.into_iter(); @@ -109,18 +117,18 @@ impl SerializeIter for bool { } unsafe impl SerializeBuf for bool { - type Serialized = [u8; 1]; + const SIZE: usize = 1; } // array impls impl SerializeIter for [T; N] { - fn serialize_iter<'a, 'b>( - &'a self, - dst: impl IntoIterator::Word>, + fn serialize_iter<'a>( + &self, + dst: impl IntoIterator::Word>, ) -> Result<(), error::EndOfInput> where - ::Word: 'b, + ::Word: 'a, { let mut dst = dst.into_iter(); @@ -154,17 +162,21 @@ impl SerializeIter for [T; N] { // implementing `SerializeBuf` for generic arrays requires the "generic_const_exprs" feature +unsafe impl SerializeBuf for [T; N] { + const SIZE: usize = T::SIZE * N; +} + // tuple impls macro_rules! impl_tuple { ( $(($TYPE:ident, $NAME:ident)),+ ) => { impl<$($TYPE: SerializeIter),+> SerializeIter for ($($TYPE,)+) { - fn serialize_iter<'a, 'b>( - &'a self, - dst: impl IntoIterator::Word>, + fn serialize_iter<'a>( + &self, + dst: impl IntoIterator::Word>, ) -> Result<(), error::EndOfInput> where - ::Word: 'b, + ::Word: 'a, { let mut dst = dst.into_iter(); @@ -192,6 +204,10 @@ macro_rules! impl_tuple { Ok(($($NAME,)+)) } } + + unsafe impl<$($TYPE: SerializeBuf),+> SerializeBuf for ($($TYPE,)+) { + const SIZE: usize = $($TYPE::SIZE+)+0; + } }; } @@ -209,21 +225,21 @@ impl_tuple!((A, a), (B, b), (C, c), (D, d), (E, e), (F, f), (G, g)); // PhantomData impl (no-op) impl SerializeIter for PhantomData { - fn serialize_iter<'a, 'b>( - &'a self, - _dst: impl IntoIterator::Word>, + fn serialize_iter<'a>( + &self, + _dst: impl IntoIterator::Word>, ) -> Result<(), error::EndOfInput> where - ::Word: 'b, + ::Word: 'a, { Ok(()) } - fn deserialize_iter<'a, 'b>( - _src: impl IntoIterator::Word>, + fn deserialize_iter<'a>( + _src: impl IntoIterator::Word>, ) -> Result where - ::Word: 'b, + ::Word: 'a, { Ok(PhantomData) } @@ -232,7 +248,8 @@ impl SerializeIter for PhantomData { #[cfg(test)] mod tests { mod primitives { - use crate::{error, SerializeBuf, SerializeIter}; + use crate as serac; + use serac::{error, SerializeIter}; macro_rules! iter_test { ($TYPE:ty) => { @@ -241,22 +258,8 @@ mod tests { // introduce some basic value differences let test_num = <$TYPE>::MAX / (0xa as $TYPE); - test_num.serialize_iter(buf.iter_mut()).unwrap(); - let read_num = <$TYPE>::deserialize_iter(buf.iter()).unwrap(); - - assert_eq!(test_num, read_num); - }; - } - - macro_rules! buf_test { - ($TYPE:ty) => { - let mut buf = <$TYPE as SerializeBuf>::Serialized::default(); - - // introduce some basic value differences - let test_num = <$TYPE>::MAX / (0xa as $TYPE); - - test_num.serialize_buf(&mut buf); - let read_num = <$TYPE>::deserialize_buf(&buf).unwrap(); + test_num.serialize_iter(&mut buf).unwrap(); + let read_num = <$TYPE>::deserialize_iter(&buf).unwrap(); assert_eq!(test_num, read_num); }; @@ -283,55 +286,21 @@ mod tests { // check valid values for val in [false, true] { - val.serialize_iter(buf.iter_mut()).unwrap(); + val.serialize_iter(&mut buf).unwrap(); - assert_eq!(val, bool::deserialize_iter(buf.iter()).unwrap()); + assert_eq!(val, bool::deserialize_iter(&buf).unwrap()); } // check invalid values for num in 2..=u8::MAX { - num.serialize_iter(buf.iter_mut()).unwrap(); + num.serialize_iter(&mut buf).unwrap(); - match bool::deserialize_iter(buf.iter()) { + match bool::deserialize_iter(&buf) { Err(error::Error::Invalid) => {} _ => panic!(), } } } - - #[test] - fn buf() { - // numbers - - buf_test!(u8); - buf_test!(u16); - buf_test!(u32); - buf_test!(u64); - buf_test!(i8); - buf_test!(i16); - buf_test!(i32); - buf_test!(i64); - buf_test!(f32); - buf_test!(f64); - - // bool - - let mut buf = [0; 1]; - - // check valid values - for val in [false, true] { - val.serialize_buf(&mut buf); - - assert_eq!(val, bool::deserialize_buf(&mut buf).unwrap()); - } - - // check invalid values - for num in 2..=u8::MAX { - num.serialize_buf(&mut buf); - - assert!(bool::deserialize_buf(&buf).is_err()); - } - } } // rust analyzer cannot cope with recursive crate import @@ -339,8 +308,8 @@ mod tests { mod derive { use core::marker::PhantomData; - use crate as cookie_cutter; // for the proc macro - use cookie_cutter::{encoding::vanilla, SerializeBuf, SerializeIter}; + use crate as serac; // for the proc macro + use serac::{buf, encoding::vanilla, SerializeBuf, SerializeIter}; mod structs { use super::*; @@ -357,32 +326,51 @@ mod tests { #[derive(Debug, PartialEq, vanilla::SerializeIter, vanilla::SerializeBuf)] struct Bar(u8, Nothing, i16); + #[derive(Debug, PartialEq, vanilla::SerializeIter, vanilla::SerializeBuf)] + struct Baz { + numbers: [f32; 16], + flags: (bool, u8), + } + #[test] fn iter() { - let mut buf = ::Serialized::default(); + let mut buf = buf!(Foo); assert_eq!(3, buf.len()); let test_foo = Foo { a: 0xaa, b: -1 }; - test_foo.serialize_iter(buf.iter_mut()).unwrap(); + test_foo.serialize_iter(&mut buf).unwrap(); - let read_foo = Foo::deserialize_iter(buf.iter()).unwrap(); + let read_foo = Foo::deserialize_iter(&buf).unwrap(); assert_eq!(test_foo, read_foo); - let mut buf = ::Serialized::default(); + let mut buf = buf!(Bar); assert_eq!(3, buf.len()); let test_bar = Bar(0xaa, Nothing, -1); - test_bar.serialize_iter(buf.iter_mut()).unwrap(); + test_bar.serialize_iter(&mut buf).unwrap(); - let read_bar = Bar::deserialize_iter(buf.iter()).unwrap(); + let read_bar = Bar::deserialize_iter(&buf).unwrap(); assert_eq!(test_bar, read_bar); + + let mut buf = buf!(Baz); + assert_eq!(66, buf.len()); + + let test_baz = Baz { + numbers: [0.; 16], + flags: (false, 0xaa), + }; + test_baz.serialize_iter(&mut buf).unwrap(); + + let read_baz = Baz::deserialize_iter(&buf).unwrap(); + + assert_eq!(test_baz, read_baz); } #[test] fn buf() { - let mut buf = ::Serialized::default(); + let mut buf = buf!(Foo); assert_eq!(3, buf.len()); let test_foo = Foo { a: 0xaa, b: -1 }; @@ -392,7 +380,7 @@ mod tests { assert_eq!(test_foo, read_foo); - let mut buf = ::Serialized::default(); + let mut buf = buf!(Bar); assert_eq!(3, buf.len()); let test_bar = Bar(0xaa, Nothing, -1); @@ -401,6 +389,19 @@ mod tests { let read_bar = Bar::deserialize_buf(&buf).unwrap(); assert_eq!(test_bar, read_bar); + + let mut buf = buf!(Baz); + assert_eq!(66, buf.len()); + + let test_baz = Baz { + numbers: [0.; 16], + flags: (false, 0xaa), + }; + test_baz.serialize_buf(&mut buf); + + let read_baz = Baz::deserialize_buf(&buf).unwrap(); + + assert_eq!(test_baz, read_baz); } } @@ -420,20 +421,20 @@ mod tests { #[test] fn iter() { - let mut buf = ::Serialized::default(); + let mut buf = buf!(Foo); assert_eq!(4, buf.len()); let test_foo = Foo::D { bar: 0xaa, t: -1 }; - test_foo.serialize_iter(buf.iter_mut()).unwrap(); + test_foo.serialize_iter(&mut buf).unwrap(); - let read_foo = Foo::deserialize_iter(buf.iter()).unwrap(); + let read_foo = Foo::deserialize_iter(&buf).unwrap(); assert_eq!(test_foo, read_foo); } #[test] fn buf() { - let mut buf = ::Serialized::default(); + let mut buf = buf!(Foo); assert_eq!(4, buf.len()); let test_foo = Foo::D { bar: 0xaa, t: -1 }; @@ -479,13 +480,13 @@ mod tests { }; // buf is too small - assert!(test_bar.serialize_iter(buf.iter_mut()).is_err()); + assert!(test_bar.serialize_iter(&mut buf).is_err()); let mut buf = [0; 8]; - test_bar.serialize_iter(buf.iter_mut()).unwrap(); + test_bar.serialize_iter(&mut buf).unwrap(); - let read_bar = BarGen::deserialize_iter(buf.iter()).unwrap(); + let read_bar = SerializeIter::deserialize_iter(&buf).unwrap(); assert_eq!(test_bar, read_bar); // comparison provides type inference for deserialization! } diff --git a/cookie-cutter/src/lib.rs b/serac/src/lib.rs similarity index 65% rename from cookie-cutter/src/lib.rs rename to serac/src/lib.rs index 52227e7..3896909 100644 --- a/cookie-cutter/src/lib.rs +++ b/serac/src/lib.rs @@ -5,12 +5,14 @@ pub mod encoding; pub mod medium; -use core::hint::unreachable_unchecked; - -use encoding::{vanilla::Vanilla, Encoding}; -use medium::Medium; +pub use encoding::Encoding; +use encoding::vanilla::Vanilla; +pub use medium::Medium; pub mod error { + use crate as serac; + use serac::encoding::vanilla; + #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndOfInput; @@ -19,7 +21,8 @@ pub mod error { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Invalid; - #[derive(Debug, Clone, Copy)] + #[repr(u8)] + #[derive(Debug, Clone, Copy, vanilla::SerializeIter, vanilla::SerializeBuf)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { EndOfInput, @@ -79,25 +82,20 @@ pub trait SerializeIter: Sized { /// An insufficient length *will* result in UB. Best to leave /// this implementation to the procedural macro. pub unsafe trait SerializeBuf: SerializeIter { - /// The type respresenting the serialized form of the implementer type. - type Serialized: Medium; - - /// Serialize the implementer type to a - /// serialization medium. - fn serialize_buf(&self, dest: &mut Self::Serialized) { - // SAFETY: dependent on safety of trait implementation. - // `Serialized` must be of sufficient length. - unsafe { SerializeIter::serialize_iter(self, dest.get_iter_mut()).unwrap_unchecked() }; - } + /// The size of the implementor when serialized, according to the encoding + /// scheme. + const SIZE: usize; +} - /// Deserialize the implementer type from a - /// serialization medium. - fn deserialize_buf(src: &Self::Serialized) -> Result { - SerializeIter::deserialize_iter(src.get_iter()).or_else(|err| match err { - error::Error::Invalid => Err(error::Invalid), - // SAFETY: dependent on safety of trait implementation. - // `Serialized` must be of sufficient length. - error::Error::EndOfInput => unsafe { unreachable_unchecked() }, - }) - } +/// Create an empty buffer for the provided type serialized with the provided +/// encoding scheme. Optionally, a multiplier may be provided which is multiplied by +/// the minimum buffer size. +#[macro_export] +macro_rules! buf { + ($ty:ty: $enc:ty $(, $coef:expr)?) => { + <<$enc as serac::Encoding>::Serialized<{ <$ty as serac::SerializeBuf>::SIZE $(*$coef)? }> as serac::Medium>::default() + }; + ($ty:ty $(, $coef:expr)?) => { + buf!($ty: serac::encoding::Vanilla $(, $coef)?) + }; } diff --git a/serac/src/medium.rs b/serac/src/medium.rs new file mode 100644 index 0000000..4bcb390 --- /dev/null +++ b/serac/src/medium.rs @@ -0,0 +1,10 @@ +use crate::encoding::{vanilla::Vanilla, Encoding}; + +// TODO: iters should be associated types defined +// by implementors + +/// Types implement this trait to be used +/// as serialization mediums. +pub trait Medium { + fn default() -> Self; +}