Add docs to SSZ

This commit is contained in:
Paul Hauner 2019-05-13 14:13:15 +10:00
parent 16a8cdb714
commit 94ab2479fe
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
4 changed files with 169 additions and 1 deletions

View File

@ -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<Self, DecodeError>;
}
@ -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<T: Decodable>(&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<SszDecoder<'a>, 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<u16>,
/// }
///
/// 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::<u64>().unwrap();
/// builder.register_type::<Vec<u16>>().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]>,
}

View File

@ -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<u8>);
/// 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<u8> {
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<u16>,
/// }
///
/// fn main() {
/// let foo = Foo {
/// a: 42,
/// b: vec![1, 3, 3, 7]
/// };
///
/// let mut buf: Vec<u8> = vec![];
/// let offset = <u64 as Encodable>::ssz_fixed_len() + <Vec<u16> 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<u8>,
@ -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<u8>, 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<u8>, 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<T: Encodable>(&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<u8> {
self.buf.append(&mut self.variable_bytes);

View File

@ -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<u16>,
//! }
//!
//! fn main() {
//! let foo = Foo {
//! a: 42,
//! b: vec![1, 3, 3, 7]
//! };
//!
//! let ssz_bytes: Vec<u8> = 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.

View File

@ -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) => {