diff --git a/Cargo.lock b/Cargo.lock index 223c8cd..f61727c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,7 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serac" -version = "0.2.0" +version = "0.2.1" dependencies = [ "cortex-m", "cortex-m-rt", diff --git a/serac/Cargo.toml b/serac/Cargo.toml index e8555a1..cbd61ed 100644 --- a/serac/Cargo.toml +++ b/serac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serac" -version = "0.2.0" +version = "0.2.1" edition = "2024" description = "A static, modular, and light-weight serialization framework." license = "CC-BY-NC-SA-4.0" diff --git a/serac/README.md b/serac/README.md new file mode 100644 index 0000000..a2c9435 --- /dev/null +++ b/serac/README.md @@ -0,0 +1,80 @@ +# Serac + +A static, modular, and light-weight serialization framework. + +*Encoders* specify what kind of serialization medium they can target, and define the +serialization or deserialization process for that medium. + +Serac comes with one built-in encoder *Vanilla*. + +## Why not serde? + +Serac's value proposition is its ability to perform static analysis on serialization +participants to either refine the failure space, or guarantee infallibility. + +## Examples + +### Serialize a number + +```rust +use serac::{buf, SerializeIter}; + +let number = 0xdeadbeefu32; + +let mut buf = buf!(u32); // [0; 4] in this case. +number.serialize_iter(&mut buf).unwrap(); + +let readback = SerializeIter::deserialize_iter(&buf).unwrap(); +assert_eq(number, readback); +``` +> The "Vanilla" encoding is used by default unless otherwise specified. + +In the above example, there was an `unwrap` used on the result of `serialize_iter` +because the buffer *could* have been too short. + +Using `SerializeBuf`, we can avoid this: + +```rust +use serac::{buf, SerializeBuf}; + +let number = 0xdeadbeefu32; + +let mut buf = buf!(u32); +number.serialize_buf(&mut buf); // it is statically known that "u32" fits in "[u8; 4]" + +// it is *not* statically known that all values of "[u8; 4]" produce a valid "u32", +// and only that failure mode is expressed +let readback = SerializeBuf::deserialize_buf(&buf).unwrap(); +assert_eq(number, readback); +``` + +Many built in types like numbers, tuples, and arrays implement both `SerializeIter` +and `SerializeBuf`. + +### Serialize a custom type + +```rust +use serac::{buf, encoding::vanilla, SerializeBuf}; + +const BE: u8 = 0xbe; + +#[repr(u8)] +#[derive(Debug, PartialEq, vanilla::SerializeIter, vanilla::SerializeBuf)] +enum Foo { + A, + B(u8, i16) = 0xde, + C, + D { bar: u16, t: i8 } = BE, +} + +let foo = Foo::D { bar: 0xaa, t: -1 }; + +let mut buf = buf!(Foo); +foo.serialize_buf(&mut buf); + +let readback = SerializeBuf::deserialize_buf(&buf).unwrap(); +assert_eq(foo, readback); +``` + +This example shows a crazy enum with lots of fancy things going on, which is able +to be serialized by serac. diff --git a/serac/src/encoding.rs b/serac/src/encoding.rs index 5375f41..634b0c4 100644 --- a/serac/src/encoding.rs +++ b/serac/src/encoding.rs @@ -9,6 +9,6 @@ pub trait Encoding { /// i.e. `u8` for `[u8; ...]` mediums. type Word; - /// The serialized form converted to and from by this encoding scheme. + /// The serialized form targeted by this encoding scheme. type Serialized; } diff --git a/serac/src/lib.rs b/serac/src/lib.rs index 4e8b999..bf70b52 100644 --- a/serac/src/lib.rs +++ b/serac/src/lib.rs @@ -13,19 +13,28 @@ pub mod error { use crate as serac; use serac::encoding::vanilla; + /// The encoder reached the end of the input before serialization/deserialization + /// was complete. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndOfInput; + /// The contents of the serialization medium produced an invalid deserialized + /// value. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Invalid; + /// Deserialization failed. #[repr(u8)] #[derive(Debug, Clone, Copy, vanilla::SerializeIter, vanilla::SerializeBuf)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { + /// The encoder reached the end of the input before deserialization was + /// complete. EndOfInput, + /// The contents of the serialization medium produced an invalid deserialized + /// value. Invalid, } @@ -42,12 +51,10 @@ pub mod error { } } -/// This trait defines a highly adaptable interface -/// for serializing and deserializing types to and -/// from a serialization medium via iterators. +/// This trait defines a highly adaptable interface for serializing and deserializing +/// types to and from a serialization medium via iterators. pub trait SerializeIter: Sized { - /// Serialize the implementer type to a - /// serialization medium via an iterator. + /// Serialize the implementer type to a serialization medium via an iterator. fn serialize_iter<'a>( &self, dst: impl IntoIterator, @@ -55,8 +62,7 @@ pub trait SerializeIter: Sized { where E::Word: 'a; - /// Deserialize the implementer type from a - /// serialization medium via an iterator. + /// Deserialize the implementer type from a serialization medium via an iterator. fn deserialize_iter<'a>( src: impl IntoIterator, ) -> Result @@ -64,23 +70,19 @@ pub trait SerializeIter: Sized { E::Word: 'a; } -/// This trait defines a more rigid/static serialization -/// interface. +/// This trait defines a more rigid/static serialization interface. /// -/// Types that implement this trait can be serialized to -/// and from buffers with an exact length. This length -/// being the maximum needed for any value of the +/// Types that implement this trait can be serialized to and from buffers with an +/// exact length. This length being the minimum needed for any value of the /// implementer type. /// -/// To implement this trait, the type must already implement -/// `SerializeIter` and the implementer must compute -/// the necessary length of the serialization medium. +/// To implement this trait, the type must already implement `SerializeIter` and the +/// implementer must compute the necessary length of the serialization medium. /// /// # Safety /// -/// The length of the associated `Serialized` type is critical. -/// An insufficient length *will* result in UB. Best to leave -/// this implementation to the procedural macro. +/// The value of the associated `SIZE` constant is critical. An insufficient size +/// *will* result in UB. Best to leave this implementation to the procedural macro. pub unsafe trait SerializeBuf: SerializeIter { /// The size of the implementor when serialized, according to the encoding /// scheme. diff --git a/serac/src/medium.rs b/serac/src/medium.rs index 4bcb390..b47ae34 100644 --- a/serac/src/medium.rs +++ b/serac/src/medium.rs @@ -1,10 +1,6 @@ -use crate::encoding::{vanilla::Vanilla, Encoding}; +use crate::encoding::{Encoding, vanilla::Vanilla}; -// TODO: iters should be associated types defined -// by implementors - -/// Types implement this trait to be used -/// as serialization mediums. +/// Types implement this trait to be used as serialization mediums. pub trait Medium { fn default() -> Self; }