diff --git a/Cargo.lock b/Cargo.lock index 348ba64..4868841 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,7 +297,7 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serac" -version = "0.4.6" +version = "0.4.7" dependencies = [ "cortex-m", "cortex-m-rt", diff --git a/serac/Cargo.toml b/serac/Cargo.toml index e9a33e8..14285d4 100644 --- a/serac/Cargo.toml +++ b/serac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serac" -version = "0.4.6" +version = "0.4.7" edition = "2024" description = "A static, modular, and light-weight serialization framework." license = "CC-BY-NC-SA-4.0" diff --git a/serac/src/lib.rs b/serac/src/lib.rs index 3743e22..225edee 100644 --- a/serac/src/lib.rs +++ b/serac/src/lib.rs @@ -5,12 +5,14 @@ mod buf; pub mod encoding; pub mod medium; +mod transport; pub use buf::Buf; pub use encoding::Encoding; use encoding::vanilla::Vanilla; pub use macros::{SerializeBuf, impl_serialize_buf_alias as serialize_buf}; pub use medium::Medium; +pub use transport::Transport; pub mod error { use crate as serac; diff --git a/serac/src/transport.rs b/serac/src/transport.rs new file mode 100644 index 0000000..b3f0fb3 --- /dev/null +++ b/serac/src/transport.rs @@ -0,0 +1,132 @@ +use crate::{ + Buf, Encoding, SerializeBuf, SerializeIter, Size, + encoding::Vanilla, + error::{EndOfInput, Error}, +}; + +/// Establishes a *transport* representation of the implementor for transparent conversion during serialization. +/// +/// Types that implement this trait inherit the serialization capabilites and properties of the specified transport +/// type. +/// +/// If `T: Transport`, then: +/// +/// - `T: SerializeIter` where `U: SerializeIter` +/// - `T: Size` where `U: Size` +/// - `T: SerializeBuf` where `U: SerializeBuf` +/// +/// This is useful when the shape of a type is suboptimal for transport, but ergonomic in code. +/// +/// For example, `[bool; 13]: Size` (in other words, occupies 13 bytes with the vanilla +/// encoding). Rust presents no means for types to introspect, but a person could see this and know that the same +/// information only warrants 2 bytes over-the-wire. Implementing `Transport` for `[bool; 13]` would +/// cause `SerializeIter`, `Size`, and `SerializeBuf` to be implemented for `[bool; 13]` but with a size of `2` instead +/// of `13`. +pub trait Transport: Copy { + /// The transport type, which is the representation of the implementor type that is used in transport and converted + /// to/from. + type Transport; + + /// Convert the in-memory representation into the transport representation. + fn into_transport(self) -> Self::Transport; + + /// Convert the transport representation into the in-memory representation. + fn from_transport(transport: Self::Transport) -> Self; +} + +impl SerializeIter for T +where + T: Transport, + T::Transport: SerializeIter, + E: Encoding, +{ + fn ser<'a>( + &self, + dst: &mut Buf>, + ) -> Result<(), EndOfInput> + where + E::Word: 'a, + { + self.into_transport().ser(dst) + } + + fn de<'a>(src: &mut Buf>) -> Result + where + E::Word: 'a, + { + Ok(Self::from_transport(T::Transport::de(src)?)) + } +} + +unsafe impl Size for T +where + T: Transport, + T::Transport: Size, + E: Encoding, +{ + const SIZE: usize = T::Transport::SIZE; +} + +unsafe impl SerializeBuf for T +where + T: Transport, + T::Transport: SerializeBuf, + E: Encoding, +{ +} + +#[cfg(test)] +mod tests { + use crate as serac; + + use core::array; + + use serac::{SerializeBuf as _, Size as _, Transport, buf}; + + #[test] + fn transport() { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct Foo { + flags: [bool; 5], + } + + impl Transport for Foo { + type Transport = u8; + + fn into_transport(self) -> Self::Transport { + let mut word = 0; + + self.flags + .iter() + .enumerate() + .for_each(|(i, &flag)| word |= (flag as u8) << i); + + word + } + + fn from_transport(transport: Self::Transport) -> Self { + Self { + flags: array::from_fn(|i| match (transport >> i) & 1 { + 0 => false, + 1 => true, + _ => unreachable!(), + }), + } + } + } + + let mut buf = buf!(Foo); + + assert_eq!(buf.len(), u8::SIZE); + + let foo = Foo { + flags: [false, true, true, false, true], + }; + + foo.serialize_buf(&mut buf); + + let readback = Foo::deserialize_buf(&buf).unwrap(); + + assert_eq!(foo, readback); + } +}