diff --git a/eth2/utils/ssz/src/decode.rs b/eth2/utils/ssz/src/decode.rs index 4db68e27f..f2f95936d 100644 --- a/eth2/utils/ssz/src/decode.rs +++ b/eth2/utils/ssz/src/decode.rs @@ -2,6 +2,7 @@ use super::*; pub mod impls; +/// Returned when SSZ decoding fails. #[derive(Debug, PartialEq)] pub enum DecodeError { /// The bytes supplied were too short to be decoded into the specified type. @@ -21,7 +22,14 @@ pub enum DecodeError { BytesInvalid(String), } +/// Provides SSZ decoding (de-serialization) via the `from_ssz_bytes(&bytes)` method. +/// +/// See `examples/` for manual implementations or the crate root for implementations using +/// `#[derive(Decode)]`. pub trait Decodable: Sized { + /// Returns `true` if this object has a fixed-length. + /// + /// I.e., there are no variable length items in this object or any of it's contained objects. fn is_ssz_fixed_len() -> bool; /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes. @@ -33,6 +41,10 @@ pub trait Decodable: Sized { BYTES_PER_LENGTH_OFFSET } + /// Attempts to decode `Self` from `bytes`, returning a `DecodeError` on failure. + /// + /// The supplied bytes must be the exact length required to decode `Self`, excess bytes will + /// result in an error. fn from_ssz_bytes(bytes: &[u8]) -> Result; } @@ -42,6 +54,12 @@ pub struct Offset { offset: usize, } +/// Builds an `SszDecoder`. +/// +/// The purpose of this struct is to split some SSZ bytes into individual slices. The builder is +/// then converted into a `SszDecoder` which decodes those values into object instances. +/// +/// See [`SszDecoder`](struct.SszDecoder.html) for usage examples. pub struct SszDecoderBuilder<'a> { bytes: &'a [u8], items: Vec<&'a [u8]>, @@ -50,6 +68,8 @@ pub struct SszDecoderBuilder<'a> { } impl<'a> SszDecoderBuilder<'a> { + /// Instantiate a new builder that should build a `SszDecoder` over the given `bytes` which + /// are assumed to be the SSZ encoding of some object. pub fn new(bytes: &'a [u8]) -> Self { Self { bytes, @@ -59,6 +79,7 @@ impl<'a> SszDecoderBuilder<'a> { } } + /// Declares that some type `T` is the next item in `bytes`. pub fn register_type(&mut self) -> Result<(), DecodeError> { if T::is_ssz_fixed_len() { let start = self.items_index; @@ -137,6 +158,7 @@ impl<'a> SszDecoderBuilder<'a> { Ok(()) } + /// Finalizes the builder, returning a `SszDecoder` that may be used to instantiate objects. pub fn build(mut self) -> Result, DecodeError> { self.finalize()?; @@ -144,6 +166,45 @@ impl<'a> SszDecoderBuilder<'a> { } } +/// Decodes some slices of SSZ into object instances. Should be instantiated using +/// [`SszDecoderBuilder`](struct.SszDecoderBuilder.html). +/// +/// ## Example +/// +/// ```rust +/// use ssz_derive::{Encode, Decode}; +/// use ssz::{Decodable, Encodable, SszDecoder, SszDecoderBuilder}; +/// +/// #[derive(PartialEq, Debug, Encode, Decode)] +/// struct Foo { +/// a: u64, +/// b: Vec, +/// } +/// +/// fn main() { +/// let foo = Foo { +/// a: 42, +/// b: vec![1, 3, 3, 7] +/// }; +/// +/// let bytes = foo.as_ssz_bytes(); +/// +/// let mut builder = SszDecoderBuilder::new(&bytes); +/// +/// builder.register_type::().unwrap(); +/// builder.register_type::>().unwrap(); +/// +/// let mut decoder = builder.build().unwrap(); +/// +/// let decoded_foo = Foo { +/// a: decoder.decode_next().unwrap(), +/// b: decoder.decode_next().unwrap(), +/// }; +/// +/// assert_eq!(foo, decoded_foo); +/// } +/// +/// ``` pub struct SszDecoder<'a> { items: Vec<&'a [u8]>, } diff --git a/eth2/utils/ssz/src/encode.rs b/eth2/utils/ssz/src/encode.rs index 852716b0d..afcf209b9 100644 --- a/eth2/utils/ssz/src/encode.rs +++ b/eth2/utils/ssz/src/encode.rs @@ -2,9 +2,20 @@ use super::*; mod impls; +/// Provides SSZ encoding (serialization) via the `as_ssz_bytes(&self)` method. +/// +/// See `examples/` for manual implementations or the crate root for implementations using +/// `#[derive(Encode)]`. pub trait Encodable { + /// Returns `true` if this object has a fixed-length. + /// + /// I.e., there are no variable length items in this object or any of it's contained objects. fn is_ssz_fixed_len() -> bool; + /// Append the encoding `self` to `buf`. + /// + /// Note, variable length objects need only to append their "variable length" portion, they do + /// not need to provide their offset. fn ssz_append(&self, buf: &mut Vec); /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes. @@ -16,6 +27,9 @@ pub trait Encodable { BYTES_PER_LENGTH_OFFSET } + /// Returns the full-form encoding of this object. + /// + /// The default implementation of this method should suffice for most cases. fn as_ssz_bytes(&self) -> Vec { let mut buf = vec![]; @@ -29,6 +43,41 @@ pub trait Encodable { /// /// **You must call `finalize(..)` after the final `append(..)` call** to ensure the bytes are /// written to `buf`. +/// +/// ## Example +/// +/// Use `SszEncoder` to produce identical output to `foo.as_ssz_bytes()`: +/// +/// ```rust +/// use ssz_derive::{Encode, Decode}; +/// use ssz::{Decodable, Encodable, SszEncoder}; +/// +/// #[derive(PartialEq, Debug, Encode, Decode)] +/// struct Foo { +/// a: u64, +/// b: Vec, +/// } +/// +/// fn main() { +/// let foo = Foo { +/// a: 42, +/// b: vec![1, 3, 3, 7] +/// }; +/// +/// let mut buf: Vec = vec![]; +/// let offset = ::ssz_fixed_len() + as Encodable>::ssz_fixed_len(); +/// +/// let mut encoder = SszEncoder::container(&mut buf, offset); +/// +/// encoder.append(&foo.a); +/// encoder.append(&foo.b); +/// +/// encoder.finalize(); +/// +/// assert_eq!(foo.as_ssz_bytes(), buf); +/// } +/// +/// ``` pub struct SszEncoder<'a> { offset: usize, buf: &'a mut Vec, @@ -36,10 +85,14 @@ pub struct SszEncoder<'a> { } impl<'a> SszEncoder<'a> { + /// Instantiate a new encoder for encoding a SSZ list. + /// + /// Identical to `Self::container`. pub fn list(buf: &'a mut Vec, num_fixed_bytes: usize) -> Self { Self::container(buf, num_fixed_bytes) } + /// Instantiate a new encoder for encoding a SSZ container. pub fn container(buf: &'a mut Vec, num_fixed_bytes: usize) -> Self { buf.reserve(num_fixed_bytes); @@ -50,6 +103,7 @@ impl<'a> SszEncoder<'a> { } } + /// Append some `item` to the SSZ bytes. pub fn append(&mut self, item: &T) { if T::is_ssz_fixed_len() { item.ssz_append(&mut self.buf); @@ -61,6 +115,10 @@ impl<'a> SszEncoder<'a> { } } + /// Write the variable bytes to `self.bytes`. + /// + /// This method must be called after the final `append(..)` call when serializing + /// variable-length items. pub fn finalize(&mut self) -> &mut Vec { self.buf.append(&mut self.variable_bytes); diff --git a/eth2/utils/ssz/src/lib.rs b/eth2/utils/ssz/src/lib.rs index a1ecb8dee..b0c8f3b4d 100644 --- a/eth2/utils/ssz/src/lib.rs +++ b/eth2/utils/ssz/src/lib.rs @@ -1,13 +1,52 @@ +//! Provides encoding (serialization) and decoding (deserialization) in the SimpleSerialize (SSZ) +//! format designed for use in Ethereum 2.0. +//! +//! Conforms to +//! [v0.6.1](https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/specs/simple-serialize.md) of the +//! Ethereum 2.0 specification. +//! +//! ## Example +//! +//! ```rust +//! use ssz_derive::{Encode, Decode}; +//! use ssz::{Decodable, Encodable}; +//! +//! #[derive(PartialEq, Debug, Encode, Decode)] +//! struct Foo { +//! a: u64, +//! b: Vec, +//! } +//! +//! fn main() { +//! let foo = Foo { +//! a: 42, +//! b: vec![1, 3, 3, 7] +//! }; +//! +//! let ssz_bytes: Vec = foo.as_ssz_bytes(); +//! +//! let decoded_foo = Foo::from_ssz_bytes(&ssz_bytes).unwrap(); +//! +//! assert_eq!(foo, decoded_foo); +//! } +//! +//! ``` +//! +//! See `examples/` for manual implementations of the `Encodable` and `Decodable` traits. + mod decode; mod encode; mod macros; pub use decode::{ - impls::decode_list_of_variable_length_items, Decodable, DecodeError, SszDecoderBuilder, + impls::decode_list_of_variable_length_items, Decodable, DecodeError, SszDecoder, + SszDecoderBuilder, }; pub use encode::{Encodable, SszEncoder}; +/// The number of bytes used to represent an offset. pub const BYTES_PER_LENGTH_OFFSET: usize = 4; +/// The maximum value that can be represented using `BYTES_PER_LENGTH_OFFSET`. pub const MAX_LENGTH_VALUE: usize = (1 << (BYTES_PER_LENGTH_OFFSET * 8)) - 1; /// Convenience function to SSZ encode an object supporting ssz::Encode. diff --git a/eth2/utils/ssz/src/macros.rs b/eth2/utils/ssz/src/macros.rs index dfe4258d2..48077674c 100644 --- a/eth2/utils/ssz/src/macros.rs +++ b/eth2/utils/ssz/src/macros.rs @@ -1,3 +1,8 @@ +/// Implements `Encodable` for `$impl_type` using an implementation of `From<$impl_type> for +/// $from_type`. +/// +/// In effect, this allows for easy implementation of `Encodable` for some type that implements a +/// `From` conversion into another type that already has `Encodable` implemented. #[macro_export] macro_rules! impl_encode_via_from { ($impl_type: ty, $from_type: ty) => { @@ -19,6 +24,11 @@ macro_rules! impl_encode_via_from { }; } +/// Implements `Decodable` for `$impl_type` using an implementation of `From<$impl_type> for +/// $from_type`. +/// +/// In effect, this allows for easy implementation of `Decodable` for some type that implements a +/// `From` conversion into another type that already has `Decodable` implemented. #[macro_export] macro_rules! impl_decode_via_from { ($impl_type: ty, $from_type: tt) => {