From 7a2ab2e9aa931ebad5db9af30b392ecdebdc9708 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 4 Jun 2019 12:03:54 +1000 Subject: [PATCH] Add support for SSZ union type via Option --- eth2/utils/ssz/src/decode.rs | 6 +++ eth2/utils/ssz/src/decode/impls.rs | 30 ++++++++++++++ eth2/utils/ssz/src/encode.rs | 7 ++++ eth2/utils/ssz/src/encode/impls.rs | 36 +++++++++++++++++ eth2/utils/ssz/tests/tests.rs | 63 +++++++++++++++++++++++++++++- 5 files changed, 141 insertions(+), 1 deletion(-) diff --git a/eth2/utils/ssz/src/decode.rs b/eth2/utils/ssz/src/decode.rs index 6934f1708..77144092b 100644 --- a/eth2/utils/ssz/src/decode.rs +++ b/eth2/utils/ssz/src/decode.rs @@ -218,6 +218,12 @@ impl<'a> SszDecoder<'a> { } } +/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte union index from `bytes`, where `bytes.len() >= +/// BYTES_PER_LENGTH_OFFSET`. +pub fn read_union_index(bytes: &[u8]) -> Result { + read_offset(bytes) +} + /// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >= /// BYTES_PER_LENGTH_OFFSET`. fn read_offset(bytes: &[u8]) -> Result { diff --git a/eth2/utils/ssz/src/decode/impls.rs b/eth2/utils/ssz/src/decode/impls.rs index 3e567b967..7f1ad26ee 100644 --- a/eth2/utils/ssz/src/decode/impls.rs +++ b/eth2/utils/ssz/src/decode/impls.rs @@ -62,6 +62,36 @@ impl Decode for bool { } } +/// The SSZ union type. +impl Decode for Option { + fn is_ssz_fixed_len() -> bool { + false + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + if bytes.len() < BYTES_PER_LENGTH_OFFSET { + return Err(DecodeError::InvalidByteLength { + len: bytes.len(), + expected: BYTES_PER_LENGTH_OFFSET, + }); + } + + let (index_bytes, value_bytes) = bytes.split_at(BYTES_PER_LENGTH_OFFSET); + + let index = read_union_index(index_bytes)?; + if index == 0 { + Ok(None) + } else if index == 1 { + Ok(Some(T::from_ssz_bytes(value_bytes)?)) + } else { + Err(DecodeError::BytesInvalid(format!( + "{} is not a valid union index for Option", + index + ))) + } + } +} + impl Decode for H256 { fn is_ssz_fixed_len() -> bool { true diff --git a/eth2/utils/ssz/src/encode.rs b/eth2/utils/ssz/src/encode.rs index 257ece2a2..6ceb08deb 100644 --- a/eth2/utils/ssz/src/encode.rs +++ b/eth2/utils/ssz/src/encode.rs @@ -126,6 +126,13 @@ impl<'a> SszEncoder<'a> { } } +/// Encode `index` as a little-endian byte vec of `BYTES_PER_LENGTH_OFFSET` length. +/// +/// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised. +pub fn encode_union_index(index: usize) -> Vec { + encode_length(index) +} + /// Encode `len` as a little-endian byte vec of `BYTES_PER_LENGTH_OFFSET` length. /// /// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised. diff --git a/eth2/utils/ssz/src/encode/impls.rs b/eth2/utils/ssz/src/encode/impls.rs index 0d6891c5e..8dbcf1707 100644 --- a/eth2/utils/ssz/src/encode/impls.rs +++ b/eth2/utils/ssz/src/encode/impls.rs @@ -25,6 +25,23 @@ impl_encodable_for_uint!(u32, 32); impl_encodable_for_uint!(u64, 64); impl_encodable_for_uint!(usize, 64); +/// The SSZ "union" type. +impl Encode for Option { + fn is_ssz_fixed_len() -> bool { + false + } + + fn ssz_append(&self, buf: &mut Vec) { + match self { + None => buf.append(&mut encode_union_index(0)), + Some(t) => { + buf.append(&mut encode_union_index(1)); + t.ssz_append(buf); + } + } + } +} + impl Encode for Vec { fn is_ssz_fixed_len() -> bool { false @@ -168,6 +185,25 @@ mod tests { ); } + #[test] + fn ssz_encode_option_u16() { + assert_eq!(Some(65535_u16).as_ssz_bytes(), vec![1, 0, 0, 0, 255, 255]); + + let none: Option = None; + assert_eq!(none.as_ssz_bytes(), vec![0, 0, 0, 0]); + } + + #[test] + fn ssz_encode_option_vec_u16() { + assert_eq!( + Some(vec![0_u16, 1]).as_ssz_bytes(), + vec![1, 0, 0, 0, 0, 0, 1, 0] + ); + + let none: Option> = None; + assert_eq!(none.as_ssz_bytes(), vec![0, 0, 0, 0]); + } + #[test] fn ssz_encode_u8() { assert_eq!(0_u8.as_ssz_bytes(), vec![0]); diff --git a/eth2/utils/ssz/tests/tests.rs b/eth2/utils/ssz/tests/tests.rs index ed318d924..06c689817 100644 --- a/eth2/utils/ssz/tests/tests.rs +++ b/eth2/utils/ssz/tests/tests.rs @@ -276,7 +276,7 @@ mod round_trip { fn offsets_decreasing() { let bytes = vec![ // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - // | offset | ofset | offset | variable + // | offset | offset | offset | variable 01, 00, 14, 00, 00, 00, 15, 00, 00, 00, 14, 00, 00, 00, 00, 00, ]; @@ -285,4 +285,65 @@ mod round_trip { Err(DecodeError::OutOfBoundsByte { i: 14 }) ); } + + #[derive(Debug, PartialEq, Encode, Decode)] + struct TwoVariableLenOptions { + a: u16, + b: Option, + c: Option>, + d: Option>, + } + + #[test] + fn two_variable_len_options_encoding() { + let s = TwoVariableLenOptions { + a: 42, + b: None, + c: Some(vec![0]), + d: None, + }; + + let bytes = vec![ + // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + // | option | offset | offset | option = vec![ + TwoVariableLenOptions { + a: 42, + b: Some(12), + c: Some(vec![0]), + d: Some(vec![1]), + }, + TwoVariableLenOptions { + a: 42, + b: Some(12), + c: Some(vec![0]), + d: None, + }, + TwoVariableLenOptions { + a: 42, + b: None, + c: Some(vec![0]), + d: None, + }, + TwoVariableLenOptions { + a: 42, + b: None, + c: None, + d: None, + }, + ]; + + round_trip(vec); + } }