ssz/encode.rs
1use super::*;
2
3mod impls;
4
5/// Provides SSZ encoding (serialization) via the `as_ssz_bytes(&self)` method.
6///
7/// See `examples/` for manual implementations or the crate root for implementations using
8/// `#[derive(Encode)]`.
9pub trait Encode {
10 /// Returns `true` if this object has a fixed-length.
11 ///
12 /// I.e., there are no variable length items in this object or any of it's contained objects.
13 fn is_ssz_fixed_len() -> bool;
14
15 /// Append the encoding `self` to `buf`.
16 ///
17 /// Note, variable length objects need only to append their "variable length" portion, they do
18 /// not need to provide their offset.
19 fn ssz_append(&self, buf: &mut Vec<u8>);
20
21 /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
22 ///
23 /// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
24 /// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
25 /// represents their length.
26 fn ssz_fixed_len() -> usize {
27 BYTES_PER_LENGTH_OFFSET
28 }
29
30 /// Returns the size (in bytes) when `self` is serialized.
31 ///
32 /// Returns the same value as `self.as_ssz_bytes().len()` but this method is significantly more
33 /// efficient.
34 fn ssz_bytes_len(&self) -> usize;
35
36 /// Returns the full-form encoding of this object.
37 ///
38 /// The default implementation of this method should suffice for most cases.
39 fn as_ssz_bytes(&self) -> Vec<u8> {
40 let mut buf = vec![];
41
42 self.ssz_append(&mut buf);
43
44 buf
45 }
46}
47
48/// Allow for encoding an ordered series of distinct or indistinct objects as SSZ bytes.
49///
50/// **You must call `finalize(..)` after the final `append(..)` call** to ensure the bytes are
51/// written to `buf`.
52///
53/// ## Example
54///
55/// Use `SszEncoder` to produce identical output to `foo.as_ssz_bytes()`:
56///
57/// ```rust
58/// use ssz_derive::{Encode, Decode};
59/// use ssz::{Decode, Encode, SszEncoder};
60///
61/// #[derive(PartialEq, Debug, Encode, Decode)]
62/// struct Foo {
63/// a: u64,
64/// b: Vec<u16>,
65/// }
66///
67/// fn ssz_encode_example() {
68/// let foo = Foo {
69/// a: 42,
70/// b: vec![1, 3, 3, 7]
71/// };
72///
73/// let mut buf: Vec<u8> = vec![];
74/// let offset = <u64 as Encode>::ssz_fixed_len() + <Vec<u16> as Encode>::ssz_fixed_len();
75///
76/// let mut encoder = SszEncoder::container(&mut buf, offset);
77///
78/// encoder.append(&foo.a);
79/// encoder.append(&foo.b);
80///
81/// encoder.finalize();
82///
83/// assert_eq!(foo.as_ssz_bytes(), buf);
84/// }
85///
86/// ```
87pub struct SszEncoder<'a> {
88 offset: usize,
89 buf: &'a mut Vec<u8>,
90 variable_bytes: Vec<u8>,
91}
92
93impl<'a> SszEncoder<'a> {
94 /// Instantiate a new encoder for encoding a SSZ container.
95 pub fn container(buf: &'a mut Vec<u8>, num_fixed_bytes: usize) -> Self {
96 buf.reserve(num_fixed_bytes);
97
98 Self {
99 offset: num_fixed_bytes,
100 buf,
101 variable_bytes: vec![],
102 }
103 }
104
105 /// Append some `item` to the SSZ bytes.
106 pub fn append<T: Encode>(&mut self, item: &T) {
107 self.append_parameterized(T::is_ssz_fixed_len(), |buf| item.ssz_append(buf))
108 }
109
110 /// Uses `ssz_append` to append the encoding of some item to the SSZ bytes.
111 pub fn append_parameterized<F>(&mut self, is_ssz_fixed_len: bool, ssz_append: F)
112 where
113 F: Fn(&mut Vec<u8>),
114 {
115 if is_ssz_fixed_len {
116 ssz_append(self.buf);
117 } else {
118 self.buf
119 .extend_from_slice(&encode_length(self.offset + self.variable_bytes.len()));
120
121 ssz_append(&mut self.variable_bytes);
122 }
123 }
124
125 /// Write the variable bytes to `self.bytes`.
126 ///
127 /// This method must be called after the final `append(..)` call when serializing
128 /// variable-length items.
129 pub fn finalize(&mut self) -> &mut Vec<u8> {
130 self.buf.append(&mut self.variable_bytes);
131
132 self.buf
133 }
134}
135
136/// Encode `len` as a little-endian byte array of `BYTES_PER_LENGTH_OFFSET` length.
137///
138/// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised.
139pub fn encode_length(len: usize) -> [u8; BYTES_PER_LENGTH_OFFSET] {
140 // Note: it is possible for `len` to be larger than what can be encoded in
141 // `BYTES_PER_LENGTH_OFFSET` bytes, triggering this debug assertion.
142 //
143 // These are the alternatives to using a `debug_assert` here:
144 //
145 // 1. Use `assert`.
146 // 2. Push an error to the caller (e.g., `Option` or `Result`).
147 // 3. Ignore it completely.
148 //
149 // I have avoided (1) because it's basically a choice between "produce invalid SSZ" or "kill
150 // the entire program". I figure it may be possible for an attacker to trigger this assert and
151 // take the program down -- I think producing invalid SSZ is a better option than this.
152 //
153 // I have avoided (2) because this error will need to be propagated upstream, making encoding a
154 // function which may fail. I don't think this is ergonomic and the upsides don't outweigh the
155 // downsides.
156 //
157 // I figure a `debug_assertion` is better than (3) as it will give us a change to detect the
158 // error during testing.
159 //
160 // If you have a different opinion, feel free to start an issue and tag @paulhauner.
161 debug_assert!(len <= MAX_LENGTH_VALUE);
162
163 let mut bytes = [0; BYTES_PER_LENGTH_OFFSET];
164 bytes.copy_from_slice(&len.to_le_bytes()[0..BYTES_PER_LENGTH_OFFSET]);
165 bytes
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_encode_length() {
174 assert_eq!(encode_length(0), [0; 4]);
175
176 assert_eq!(encode_length(1), [1, 0, 0, 0]);
177
178 assert_eq!(
179 encode_length(MAX_LENGTH_VALUE),
180 [255; BYTES_PER_LENGTH_OFFSET]
181 );
182 }
183
184 #[test]
185 #[should_panic]
186 #[cfg(debug_assertions)]
187 fn test_encode_length_above_max_debug_panics() {
188 encode_length(MAX_LENGTH_VALUE + 1);
189 }
190
191 #[test]
192 #[cfg(not(debug_assertions))]
193 fn test_encode_length_above_max_not_debug_does_not_panic() {
194 assert_eq!(&encode_length(MAX_LENGTH_VALUE + 1)[..], &[0; 4]);
195 }
196}