diff --git a/eth2/utils/ssz2/examples/struct_definition.rs b/eth2/utils/ssz2/examples/struct_definition.rs new file mode 100644 index 0000000000..a449cf9b02 --- /dev/null +++ b/eth2/utils/ssz2/examples/struct_definition.rs @@ -0,0 +1,58 @@ +use ssz2::{Decodable, DecodeError, Encodable, SszDecoderBuilder, SszStream}; + +pub struct Foo { + a: u16, + b: Vec, + c: u16, +} + +impl Encodable for Foo { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() && as Encodable>::is_ssz_fixed_len() + } + + fn as_ssz_bytes(&self) -> Vec { + let mut stream = SszStream::new(); + + stream.append(&self.a); + stream.append(&self.b); + stream.append(&self.c); + + stream.drain() + } +} + +impl Decodable for Foo { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() && as Decodable>::is_ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let mut builder = SszDecoderBuilder::new(bytes); + + builder.register_type::()?; + builder.register_type::>()?; + builder.register_type::()?; + + let mut decoder = builder.build()?; + + Ok(Self { + a: decoder.decode_next()?, + b: decoder.decode_next()?, + c: decoder.decode_next()?, + }) + } +} + +fn main() { + let foo = Foo { + a: 42, + b: vec![0, 1, 2, 3], + c: 11, + }; + + assert_eq!( + foo.as_ssz_bytes(), + vec![42, 0, 8, 0, 0, 0, 11, 0, 0, 1, 2, 3] + ); +} diff --git a/eth2/utils/ssz2/src/decode.rs b/eth2/utils/ssz2/src/decode.rs index db7c68d3cc..b8e82bdf54 100644 --- a/eth2/utils/ssz2/src/decode.rs +++ b/eth2/utils/ssz2/src/decode.rs @@ -36,6 +36,144 @@ pub trait Decodable: Sized { fn from_ssz_bytes(bytes: &[u8]) -> Result; } +#[derive(Copy, Clone)] +pub struct Offset { + position: usize, + offset: usize, +} + +pub struct SszDecoderBuilder<'a> { + bytes: &'a [u8], + items: Vec<&'a [u8]>, + offsets: Vec, + items_index: usize, +} + +impl<'a> SszDecoderBuilder<'a> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { + bytes, + items: vec![], + offsets: vec![], + items_index: 0, + } + } + + pub fn register_type(&mut self) -> Result<(), DecodeError> { + if T::is_ssz_fixed_len() { + let start = self.items_index; + self.items_index += T::ssz_fixed_len(); + + let slice = self.bytes.get(start..self.items_index).ok_or_else(|| { + DecodeError::InvalidByteLength { + len: self.bytes.len(), + expected: self.items_index, + } + })?; + + self.items.push(slice); + } else { + let offset = read_offset(&self.bytes[self.items_index..])?; + + let previous_offset = self + .offsets + .last() + .and_then(|o| Some(o.offset)) + .unwrap_or_else(|| BYTES_PER_LENGTH_OFFSET); + + if previous_offset > offset { + return Err(DecodeError::OutOfBoundsByte { i: offset }); + } else if offset >= self.bytes.len() { + return Err(DecodeError::OutOfBoundsByte { i: offset }); + } + + self.offsets.push(Offset { + position: self.items.len(), + offset, + }); + + self.items_index += BYTES_PER_LENGTH_OFFSET; + } + + Ok(()) + } + + fn apply_offsets(&mut self) -> Result<(), DecodeError> { + if !self.offsets.is_empty() { + let mut insertions = 0; + let mut running_offset = self.offsets[0].offset; + + for i in 1..self.offsets.len() { + let (slice_option, position) = if i == self.offsets.len() { + (self.bytes.get(running_offset..), self.offsets.len()) + } else { + let offset = self.offsets[i]; + let start = running_offset; + running_offset = offset.offset; + + (self.bytes.get(start..running_offset), offset.position) + }; + + let slice = slice_option + .ok_or_else(|| DecodeError::OutOfBoundsByte { i: running_offset })?; + + self.items.insert(position + insertions, slice); + insertions += 1; + } + } + + Ok(()) + } + + pub fn build(mut self) -> Result, DecodeError> { + self.apply_offsets()?; + + Ok(SszDecoder { items: self.items }) + } +} + +pub struct SszDecoder<'a> { + items: Vec<&'a [u8]>, +} + +impl<'a> SszDecoder<'a> { + /// Decodes the next item. + /// + /// # Panics + /// + /// Panics when attempting to decode more items than actually exist. + pub fn decode_next(&mut self) -> Result { + T::from_ssz_bytes(self.items.remove(0)) + } +} + +/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >= +/// BYTES_PER_LENGTH_OFFSET`. +fn read_offset(bytes: &[u8]) -> Result { + decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or_else(|| { + DecodeError::InvalidLengthPrefix { + len: bytes.len(), + expected: BYTES_PER_LENGTH_OFFSET, + } + })?) +} + +/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() != +/// BYTES_PER_LENGTH_OFFSET`. +fn decode_offset(bytes: &[u8]) -> Result { + let len = bytes.len(); + let expected = BYTES_PER_LENGTH_OFFSET; + + if len != expected { + Err(DecodeError::InvalidLengthPrefix { len, expected }) + } else { + let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default(); + array.clone_from_slice(bytes); + + Ok(u32::from_le_bytes(array) as usize) + } +} + /* /// Decode the given bytes for the given type diff --git a/eth2/utils/ssz2/src/decode/impls.rs b/eth2/utils/ssz2/src/decode/impls.rs index a00de33aa8..366d9d6b67 100644 --- a/eth2/utils/ssz2/src/decode/impls.rs +++ b/eth2/utils/ssz2/src/decode/impls.rs @@ -28,6 +28,7 @@ macro_rules! impl_decodable_for_uint { }; } +impl_decodable_for_uint!(u8, 8); impl_decodable_for_uint!(u16, 16); impl_decodable_for_uint!(u32, 32); impl_decodable_for_uint!(u64, 64); @@ -94,33 +95,6 @@ impl Decodable for Vec { } } -/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >= -/// BYTES_PER_LENGTH_OFFSET`. -fn read_offset(bytes: &[u8]) -> Result { - decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or_else(|| { - DecodeError::InvalidLengthPrefix { - len: bytes.len(), - expected: BYTES_PER_LENGTH_OFFSET, - } - })?) -} - -/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() != -/// BYTES_PER_LENGTH_OFFSET`. -pub fn decode_offset(bytes: &[u8]) -> Result { - let len = bytes.len(); - let expected = BYTES_PER_LENGTH_OFFSET; - - if len != expected { - Err(DecodeError::InvalidLengthPrefix { len, expected }) - } else { - let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default(); - array.clone_from_slice(bytes); - - Ok(u32::from_le_bytes(array) as usize) - } -} - /* use super::decode::decode_ssz_list; use super::ethereum_types::{Address, H256}; diff --git a/eth2/utils/ssz2/src/encode.rs b/eth2/utils/ssz2/src/encode.rs index ad8456e157..6650f8e90a 100644 --- a/eth2/utils/ssz2/src/encode.rs +++ b/eth2/utils/ssz2/src/encode.rs @@ -17,19 +17,26 @@ pub trait Encodable { } } +pub struct VariableLengths { + pub fixed_bytes_position: usize, + pub variable_bytes_length: usize, +} + /// Provides a buffer for appending SSZ values. #[derive(Default)] pub struct SszStream { - fixed: Vec, - variable: Vec, + fixed_bytes: Vec, + variable_bytes: Vec, + variable_lengths: Vec, } impl SszStream { /// Create a new, empty stream for writing SSZ values. pub fn new() -> Self { SszStream { - fixed: vec![], - variable: vec![], + fixed_bytes: vec![], + variable_bytes: vec![], + variable_lengths: vec![], } } @@ -38,18 +45,42 @@ impl SszStream { let mut bytes = item.as_ssz_bytes(); if T::is_ssz_fixed_len() { - self.fixed.append(&mut bytes); + self.fixed_bytes.append(&mut bytes); } else { - self.fixed.append(&mut encode_length(bytes.len())); - self.variable.append(&mut bytes); + self.variable_lengths.push(VariableLengths { + fixed_bytes_position: self.fixed_bytes.len(), + variable_bytes_length: bytes.len(), + }); + + self.fixed_bytes + .append(&mut vec![0; BYTES_PER_LENGTH_OFFSET]); + self.variable_bytes.append(&mut bytes); + } + } + + /// Update the offsets (if any) in the fixed-length bytes to correctly point to the values in + /// the variable length part. + pub fn apply_offsets(&mut self) { + let mut running_offset = self.fixed_bytes.len(); + + for v in &self.variable_lengths { + let offset = running_offset; + running_offset += v.variable_bytes_length; + + self.fixed_bytes.splice( + v.fixed_bytes_position..v.fixed_bytes_position + BYTES_PER_LENGTH_OFFSET, + encode_length(offset), + ); } } /// Append the variable-length bytes to the fixed-length bytes and return the result. pub fn drain(mut self) -> Vec { - self.fixed.append(&mut self.variable); + self.apply_offsets(); - self.fixed + self.fixed_bytes.append(&mut self.variable_bytes); + + self.fixed_bytes } } diff --git a/eth2/utils/ssz2/src/lib.rs b/eth2/utils/ssz2/src/lib.rs index 572f42de0f..b38c01e807 100644 --- a/eth2/utils/ssz2/src/lib.rs +++ b/eth2/utils/ssz2/src/lib.rs @@ -16,7 +16,7 @@ pub mod decode; mod decode; mod encode; -pub use decode::{Decodable, DecodeError}; +pub use decode::{Decodable, DecodeError, SszDecoderBuilder}; pub use encode::{Encodable, SszStream}; pub const BYTES_PER_LENGTH_OFFSET: usize = 4;