ssz/decode.rs
1use super::*;
2use smallvec::{smallvec, SmallVec};
3use std::cmp::Ordering;
4
5type SmallVec8<T> = SmallVec<[T; 8]>;
6
7pub mod impls;
8
9/// Returned when SSZ decoding fails.
10#[derive(Debug, PartialEq, Clone)]
11pub enum DecodeError {
12 /// The bytes supplied were too short to be decoded into the specified type.
13 InvalidByteLength { len: usize, expected: usize },
14 /// The given bytes were too short to be read as a length prefix.
15 InvalidLengthPrefix { len: usize, expected: usize },
16 /// A length offset pointed to a byte that was out-of-bounds (OOB).
17 ///
18 /// A bytes may be OOB for the following reasons:
19 ///
20 /// - It is `>= bytes.len()`.
21 /// - When decoding variable length items, the 1st offset points "backwards" into the fixed
22 /// length items (i.e., `length[0] < BYTES_PER_LENGTH_OFFSET`).
23 /// - When decoding variable-length items, the `n`'th offset was less than the `n-1`'th offset.
24 OutOfBoundsByte { i: usize },
25 /// An offset points “backwards” into the fixed-bytes portion of the message, essentially
26 /// double-decoding bytes that will also be decoded as fixed-length.
27 ///
28 /// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#1-Offset-into-fixed-portion
29 OffsetIntoFixedPortion(usize),
30 /// The first offset does not point to the byte that follows the fixed byte portion,
31 /// essentially skipping a variable-length byte.
32 ///
33 /// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#2-Skip-first-variable-byte
34 OffsetSkipsVariableBytes(usize),
35 /// An offset points to bytes prior to the previous offset. Depending on how you look at it,
36 /// this either double-decodes bytes or makes the first offset a negative-length.
37 ///
38 /// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#3-Offsets-are-decreasing
39 OffsetsAreDecreasing(usize),
40 /// An offset references byte indices that do not exist in the source bytes.
41 ///
42 /// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#4-Offsets-are-out-of-bounds
43 OffsetOutOfBounds(usize),
44 /// A variable-length list does not have a fixed portion that is cleanly divisible by
45 /// `BYTES_PER_LENGTH_OFFSET`.
46 InvalidListFixedBytesLen(usize),
47 /// Some item has a `ssz_fixed_len` of zero. This is illegal.
48 ZeroLengthItem,
49 /// The given bytes were invalid for some application-level reason.
50 BytesInvalid(String),
51 /// The given union selector is out of bounds.
52 UnionSelectorInvalid(u8),
53}
54
55/// Performs checks on the `offset` based upon the other parameters provided.
56///
57/// ## Detail
58///
59/// - `offset`: the offset bytes (e.g., result of `read_offset(..)`).
60/// - `previous_offset`: unless this is the first offset in the SSZ object, the value of the
61/// previously-read offset. Used to ensure offsets are not decreasing.
62/// - `num_bytes`: the total number of bytes in the SSZ object. Used to ensure the offset is not
63/// out of bounds.
64/// - `num_fixed_bytes`: the number of fixed-bytes in the struct, if it is known. Used to ensure
65/// that the first offset doesn't skip any variable bytes.
66///
67/// ## References
68///
69/// The checks here are derived from this document:
70///
71/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view
72pub fn sanitize_offset(
73 offset: usize,
74 previous_offset: Option<usize>,
75 num_bytes: usize,
76 num_fixed_bytes: Option<usize>,
77) -> Result<usize, DecodeError> {
78 if num_fixed_bytes.map_or(false, |fixed_bytes| offset < fixed_bytes) {
79 Err(DecodeError::OffsetIntoFixedPortion(offset))
80 } else if previous_offset.is_none()
81 && num_fixed_bytes.map_or(false, |fixed_bytes| offset != fixed_bytes)
82 {
83 Err(DecodeError::OffsetSkipsVariableBytes(offset))
84 } else if offset > num_bytes {
85 Err(DecodeError::OffsetOutOfBounds(offset))
86 } else if previous_offset.map_or(false, |prev| prev > offset) {
87 Err(DecodeError::OffsetsAreDecreasing(offset))
88 } else {
89 Ok(offset)
90 }
91}
92
93/// Provides SSZ decoding (de-serialization) via the `from_ssz_bytes(&bytes)` method.
94///
95/// See `examples/` for manual implementations or the crate root for implementations using
96/// `#[derive(Decode)]`.
97pub trait Decode: Sized {
98 /// Returns `true` if this object has a fixed-length.
99 ///
100 /// I.e., there are no variable length items in this object or any of it's contained objects.
101 fn is_ssz_fixed_len() -> bool;
102
103 /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
104 ///
105 /// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
106 /// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
107 /// represents their length.
108 fn ssz_fixed_len() -> usize {
109 BYTES_PER_LENGTH_OFFSET
110 }
111
112 /// Attempts to decode `Self` from `bytes`, returning a `DecodeError` on failure.
113 ///
114 /// The supplied bytes must be the exact length required to decode `Self`, excess bytes will
115 /// result in an error.
116 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError>;
117}
118
119#[derive(Copy, Clone, Debug)]
120pub struct Offset {
121 position: usize,
122 offset: usize,
123}
124
125/// Builds an `SszDecoder`.
126///
127/// The purpose of this struct is to split some SSZ bytes into individual slices. The builder is
128/// then converted into a `SszDecoder` which decodes those values into object instances.
129///
130/// See [`SszDecoder`](struct.SszDecoder.html) for usage examples.
131pub struct SszDecoderBuilder<'a> {
132 bytes: &'a [u8],
133 items: SmallVec8<&'a [u8]>,
134 offsets: SmallVec8<Offset>,
135 items_index: usize,
136}
137
138impl<'a> SszDecoderBuilder<'a> {
139 /// Instantiate a new builder that should build a `SszDecoder` over the given `bytes` which
140 /// are assumed to be the SSZ encoding of some object.
141 pub fn new(bytes: &'a [u8]) -> Self {
142 Self {
143 bytes,
144 items: smallvec![],
145 offsets: smallvec![],
146 items_index: 0,
147 }
148 }
149
150 /// Registers a variable-length object as the next item in `bytes`, without specifying the
151 /// actual type.
152 ///
153 /// ## Notes
154 ///
155 /// Use of this function is generally discouraged since it cannot detect if some type changes
156 /// from variable to fixed length.
157 ///
158 /// Use `Self::register_type` wherever possible.
159 pub fn register_anonymous_variable_length_item(&mut self) -> Result<(), DecodeError> {
160 struct Anonymous;
161
162 impl Decode for Anonymous {
163 fn is_ssz_fixed_len() -> bool {
164 false
165 }
166
167 fn from_ssz_bytes(_bytes: &[u8]) -> Result<Self, DecodeError> {
168 unreachable!("Anonymous should never be decoded")
169 }
170 }
171
172 self.register_type::<Anonymous>()
173 }
174
175 /// Declares that some type `T` is the next item in `bytes`.
176 pub fn register_type<T: Decode>(&mut self) -> Result<(), DecodeError> {
177 self.register_type_parameterized(T::is_ssz_fixed_len(), T::ssz_fixed_len())
178 }
179
180 /// Declares that a type with the given parameters is the next item in `bytes`.
181 pub fn register_type_parameterized(
182 &mut self,
183 is_ssz_fixed_len: bool,
184 ssz_fixed_len: usize,
185 ) -> Result<(), DecodeError> {
186 if is_ssz_fixed_len {
187 let start = self.items_index;
188 self.items_index += ssz_fixed_len;
189
190 let slice = self.bytes.get(start..self.items_index).ok_or_else(|| {
191 DecodeError::InvalidByteLength {
192 len: self.bytes.len(),
193 expected: self.items_index,
194 }
195 })?;
196
197 self.items.push(slice);
198 } else {
199 self.offsets.push(Offset {
200 position: self.items.len(),
201 offset: sanitize_offset(
202 read_offset(&self.bytes[self.items_index..])?,
203 self.offsets.last().map(|o| o.offset),
204 self.bytes.len(),
205 None,
206 )?,
207 });
208
209 // Push an empty slice into items; it will be replaced later.
210 self.items.push(&[]);
211
212 self.items_index += BYTES_PER_LENGTH_OFFSET;
213 }
214
215 Ok(())
216 }
217
218 fn finalize(&mut self) -> Result<(), DecodeError> {
219 if let Some(first_offset) = self.offsets.first().map(|o| o.offset) {
220 // Check to ensure the first offset points to the byte immediately following the
221 // fixed-length bytes.
222 match first_offset.cmp(&self.items_index) {
223 Ordering::Less => return Err(DecodeError::OffsetIntoFixedPortion(first_offset)),
224 Ordering::Greater => {
225 return Err(DecodeError::OffsetSkipsVariableBytes(first_offset))
226 }
227 Ordering::Equal => (),
228 }
229
230 // Iterate through each pair of offsets, grabbing the slice between each of the offsets.
231 for pair in self.offsets.windows(2) {
232 let a = pair[0];
233 let b = pair[1];
234
235 self.items[a.position] = &self.bytes[a.offset..b.offset];
236 }
237
238 // Handle the last offset, pushing a slice from it's start through to the end of
239 // `self.bytes`.
240 if let Some(last) = self.offsets.last() {
241 self.items[last.position] = &self.bytes[last.offset..]
242 }
243 } else {
244 // If the container is fixed-length, ensure there are no excess bytes.
245 if self.items_index != self.bytes.len() {
246 return Err(DecodeError::InvalidByteLength {
247 len: self.bytes.len(),
248 expected: self.items_index,
249 });
250 }
251 }
252
253 Ok(())
254 }
255
256 /// Finalizes the builder, returning a `SszDecoder` that may be used to instantiate objects.
257 pub fn build(mut self) -> Result<SszDecoder<'a>, DecodeError> {
258 self.finalize()?;
259
260 Ok(SszDecoder { items: self.items })
261 }
262}
263
264/// Decodes some slices of SSZ into object instances. Should be instantiated using
265/// [`SszDecoderBuilder`](struct.SszDecoderBuilder.html).
266///
267/// ## Example
268///
269/// ```rust
270/// use ssz_derive::{Encode, Decode};
271/// use ssz::{Decode, Encode, SszDecoder, SszDecoderBuilder};
272///
273/// #[derive(PartialEq, Debug, Encode, Decode)]
274/// struct Foo {
275/// a: u64,
276/// b: Vec<u16>,
277/// }
278///
279/// fn ssz_decoding_example() {
280/// let foo = Foo {
281/// a: 42,
282/// b: vec![1, 3, 3, 7]
283/// };
284///
285/// let bytes = foo.as_ssz_bytes();
286///
287/// let mut builder = SszDecoderBuilder::new(&bytes);
288///
289/// builder.register_type::<u64>().unwrap();
290/// builder.register_type::<Vec<u16>>().unwrap();
291///
292/// let mut decoder = builder.build().unwrap();
293///
294/// let decoded_foo = Foo {
295/// a: decoder.decode_next().unwrap(),
296/// b: decoder.decode_next().unwrap(),
297/// };
298///
299/// assert_eq!(foo, decoded_foo);
300/// }
301///
302/// ```
303pub struct SszDecoder<'a> {
304 items: SmallVec8<&'a [u8]>,
305}
306
307impl<'a> SszDecoder<'a> {
308 /// Decodes the next item.
309 ///
310 /// # Panics
311 ///
312 /// Panics when attempting to decode more items than actually exist.
313 pub fn decode_next<T: Decode>(&mut self) -> Result<T, DecodeError> {
314 self.decode_next_with(|slice| T::from_ssz_bytes(slice))
315 }
316
317 /// Decodes the next item using the provided function.
318 pub fn decode_next_with<T, F>(&mut self, f: F) -> Result<T, DecodeError>
319 where
320 F: FnOnce(&'a [u8]) -> Result<T, DecodeError>,
321 {
322 f(self.items.remove(0))
323 }
324}
325
326/// Takes `bytes`, assuming it is the encoding for a SSZ union, and returns the union-selector and
327/// the body (trailing bytes).
328///
329/// ## Errors
330///
331/// Returns an error if:
332///
333/// - `bytes` is empty.
334/// - the union selector is not a valid value (i.e., larger than the maximum number of variants.
335pub fn split_union_bytes(bytes: &[u8]) -> Result<(UnionSelector, &[u8]), DecodeError> {
336 let selector = bytes
337 .first()
338 .copied()
339 .ok_or(DecodeError::OutOfBoundsByte { i: 0 })
340 .and_then(UnionSelector::new)?;
341 let body = bytes
342 .get(1..)
343 .ok_or(DecodeError::OutOfBoundsByte { i: 1 })?;
344 Ok((selector, body))
345}
346
347/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >=
348/// BYTES_PER_LENGTH_OFFSET`.
349pub fn read_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
350 decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or_else(|| {
351 DecodeError::InvalidLengthPrefix {
352 len: bytes.len(),
353 expected: BYTES_PER_LENGTH_OFFSET,
354 }
355 })?)
356}
357
358/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() !=
359/// BYTES_PER_LENGTH_OFFSET`.
360fn decode_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
361 let len = bytes.len();
362 let expected = BYTES_PER_LENGTH_OFFSET;
363
364 if len != expected {
365 Err(DecodeError::InvalidLengthPrefix { len, expected })
366 } else {
367 let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default();
368 array.clone_from_slice(bytes);
369
370 Ok(u32::from_le_bytes(array) as usize)
371 }
372}