diff --git a/eth2/utils/ssz/src/decode.rs b/eth2/utils/ssz/src/decode.rs index 410ff69c2a..194a3360a0 100644 --- a/eth2/utils/ssz/src/decode.rs +++ b/eth2/utils/ssz/src/decode.rs @@ -1,6 +1,6 @@ use super::*; -mod impls; +pub mod impls; #[derive(Debug, PartialEq)] pub enum DecodeError { diff --git a/eth2/utils/ssz/src/decode/impls.rs b/eth2/utils/ssz/src/decode/impls.rs index 6539059ea1..b41c26bbf0 100644 --- a/eth2/utils/ssz/src/decode/impls.rs +++ b/eth2/utils/ssz/src/decode/impls.rs @@ -129,53 +129,62 @@ impl Decodable for Vec { .map(|chunk| T::from_ssz_bytes(chunk)) .collect() } else { - let mut next_variable_byte = read_offset(bytes)?; - - // The value of the first offset must not point back into the same bytes that defined - // it. - if next_variable_byte < BYTES_PER_LENGTH_OFFSET { - return Err(DecodeError::OutOfBoundsByte { - i: next_variable_byte, - }); - } - - let num_items = next_variable_byte / BYTES_PER_LENGTH_OFFSET; - - // The fixed-length section must be a clean multiple of `BYTES_PER_LENGTH_OFFSET`. - if next_variable_byte != num_items * BYTES_PER_LENGTH_OFFSET { - return Err(DecodeError::InvalidByteLength { - len: next_variable_byte, - expected: num_items * BYTES_PER_LENGTH_OFFSET, - }); - } - - let mut values = Vec::with_capacity(num_items); - for i in 1..=num_items { - let slice_option = if i == num_items { - bytes.get(next_variable_byte..) - } else { - let offset = read_offset(&bytes[(i * BYTES_PER_LENGTH_OFFSET)..])?; - - let start = next_variable_byte; - next_variable_byte = offset; - - // Note: the condition where `start > next_variable_byte` returns `None` which - // raises an error later in the program. - bytes.get(start..next_variable_byte) - }; - - let slice = slice_option.ok_or_else(|| DecodeError::OutOfBoundsByte { - i: next_variable_byte, - })?; - - values.push(T::from_ssz_bytes(slice)?); - } - - Ok(values) + decode_list_of_variable_length_items(bytes) } } } +/// Decodes `bytes` as if it were a list of variable-length items. +/// +/// The `ssz::SszDecoder` can also perform this functionality, however it it significantly faster +/// as it is optimized to read same-typed items whilst `ssz::SszDecoder` supports reading items of +/// differing types. +pub fn decode_list_of_variable_length_items( + bytes: &[u8], +) -> Result, DecodeError> { + let mut next_variable_byte = read_offset(bytes)?; + + // The value of the first offset must not point back into the same bytes that defined + // it. + if next_variable_byte < BYTES_PER_LENGTH_OFFSET { + return Err(DecodeError::OutOfBoundsByte { + i: next_variable_byte, + }); + } + + let num_items = next_variable_byte / BYTES_PER_LENGTH_OFFSET; + + // The fixed-length section must be a clean multiple of `BYTES_PER_LENGTH_OFFSET`. + if next_variable_byte != num_items * BYTES_PER_LENGTH_OFFSET { + return Err(DecodeError::InvalidByteLength { + len: next_variable_byte, + expected: num_items * BYTES_PER_LENGTH_OFFSET, + }); + } + + let mut values = Vec::with_capacity(num_items); + for i in 1..=num_items { + let slice_option = if i == num_items { + bytes.get(next_variable_byte..) + } else { + let offset = read_offset(&bytes[(i * BYTES_PER_LENGTH_OFFSET)..])?; + + let start = next_variable_byte; + next_variable_byte = offset; + + bytes.get(start..next_variable_byte) + }; + + let slice = slice_option.ok_or_else(|| DecodeError::OutOfBoundsByte { + i: next_variable_byte, + })?; + + values.push(T::from_ssz_bytes(slice)?); + } + + Ok(values) +} + #[cfg(test)] mod tests { use super::*; diff --git a/eth2/utils/ssz/src/lib.rs b/eth2/utils/ssz/src/lib.rs index 7fedd8e5fe..1d32e85013 100644 --- a/eth2/utils/ssz/src/lib.rs +++ b/eth2/utils/ssz/src/lib.rs @@ -1,7 +1,9 @@ mod decode; mod encode; -pub use decode::{Decodable, DecodeError, SszDecoderBuilder}; +pub use decode::{ + impls::decode_list_of_variable_length_items, Decodable, DecodeError, SszDecoderBuilder, +}; pub use encode::{Encodable, SszEncoder}; pub const BYTES_PER_LENGTH_OFFSET: usize = 4;