eth_valkyoth_codec/
lib.rs1#![no_std]
2#![forbid(unsafe_code)]
3use core::cmp::Ordering;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct DecodeLimits {
10 pub max_input_bytes: usize,
12 pub max_list_items: usize,
14 pub max_nesting_depth: usize,
16 pub max_total_allocation: usize,
18}
19
20impl DecodeLimits {
21 pub const STRICT: Self = Self {
23 max_input_bytes: 1 << 20,
24 max_list_items: 4096,
25 max_nesting_depth: 64,
26 max_total_allocation: 1 << 20,
27 };
28
29 pub fn check_input_len(self, len: usize) -> Result<(), DecodeError> {
31 if len > self.max_input_bytes {
32 return Err(DecodeError::InputTooLarge);
33 }
34 Ok(())
35 }
36
37 pub fn check_list_count(self, count: usize) -> Result<(), DecodeError> {
39 if count > self.max_list_items {
40 return Err(DecodeError::ListTooLong);
41 }
42 Ok(())
43 }
44
45 pub fn check_nesting_depth(self, depth: usize) -> Result<(), DecodeError> {
47 if depth > self.max_nesting_depth {
48 return Err(DecodeError::NestingTooDeep);
49 }
50 Ok(())
51 }
52
53 pub fn check_allocation(self, size: usize) -> Result<(), DecodeError> {
59 if size > self.max_total_allocation {
60 return Err(DecodeError::AllocationExceeded);
61 }
62 Ok(())
63 }
64
65 #[must_use]
67 pub const fn accumulator(self) -> DecodeAccumulator {
68 DecodeAccumulator {
69 limits: self,
70 total_allocated: 0,
71 }
72 }
73}
74
75#[derive(Debug, Eq, PartialEq)]
77pub struct DecodeAccumulator {
78 limits: DecodeLimits,
79 total_allocated: usize,
80}
81
82impl DecodeAccumulator {
83 #[must_use]
85 pub const fn limits(&self) -> DecodeLimits {
86 self.limits
87 }
88
89 #[must_use]
91 pub const fn total_allocated(&self) -> usize {
92 self.total_allocated
93 }
94
95 pub fn check_input_len(&self, len: usize) -> Result<(), DecodeError> {
97 self.limits.check_input_len(len)
98 }
99
100 pub fn check_list_count(&self, count: usize) -> Result<(), DecodeError> {
102 self.limits.check_list_count(count)
103 }
104
105 pub fn check_nesting_depth(&self, depth: usize) -> Result<(), DecodeError> {
107 self.limits.check_nesting_depth(depth)
108 }
109
110 pub fn check_allocation(&mut self, size: usize) -> Result<(), DecodeError> {
112 let new_total = self
113 .total_allocated
114 .checked_add(size)
115 .ok_or(DecodeError::AllocationExceeded)?;
116 if new_total > self.limits.max_total_allocation {
117 return Err(DecodeError::AllocationExceeded);
118 }
119 self.total_allocated = new_total;
120 Ok(())
121 }
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub enum DecodeError {
127 InputTooLarge,
129 TrailingBytes,
131 DecoderOverread,
133 Malformed,
135 ListTooLong,
137 NestingTooDeep,
139 AllocationExceeded,
141}
142
143pub fn require_exact_consumption(consumed: usize, input_len: usize) -> Result<(), DecodeError> {
145 match consumed.cmp(&input_len) {
146 Ordering::Equal => Ok(()),
147 Ordering::Less => Err(DecodeError::TrailingBytes),
148 Ordering::Greater => Err(DecodeError::DecoderOverread),
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn rejects_oversized_input() {
158 let limits = DecodeLimits {
159 max_input_bytes: 2,
160 ..DecodeLimits::STRICT
161 };
162 assert_eq!(limits.check_input_len(3), Err(DecodeError::InputTooLarge));
163 }
164
165 #[test]
166 fn rejects_oversized_list() {
167 let limits = DecodeLimits {
168 max_list_items: 2,
169 ..DecodeLimits::STRICT
170 };
171 assert_eq!(limits.check_list_count(3), Err(DecodeError::ListTooLong));
172 }
173
174 #[test]
175 fn rejects_excessive_nesting_depth() {
176 let limits = DecodeLimits {
177 max_nesting_depth: 2,
178 ..DecodeLimits::STRICT
179 };
180 assert_eq!(
181 limits.check_nesting_depth(3),
182 Err(DecodeError::NestingTooDeep)
183 );
184 }
185
186 #[test]
187 fn rejects_excessive_allocation() {
188 let limits = DecodeLimits {
189 max_total_allocation: 2,
190 ..DecodeLimits::STRICT
191 };
192 assert_eq!(
193 limits.check_allocation(3),
194 Err(DecodeError::AllocationExceeded)
195 );
196 }
197
198 #[test]
199 fn accumulator_rejects_cumulative_allocation_over_budget() {
200 let limits = DecodeLimits {
201 max_total_allocation: 4,
202 ..DecodeLimits::STRICT
203 };
204 let mut accumulator = limits.accumulator();
205
206 assert_eq!(accumulator.check_allocation(3), Ok(()));
207 assert_eq!(accumulator.total_allocated(), 3);
208 assert_eq!(
209 accumulator.check_allocation(2),
210 Err(DecodeError::AllocationExceeded)
211 );
212 assert_eq!(accumulator.total_allocated(), 3);
213 }
214
215 #[test]
216 fn accumulator_rejects_allocation_counter_overflow() {
217 let limits = DecodeLimits {
218 max_total_allocation: usize::MAX,
219 ..DecodeLimits::STRICT
220 };
221 let mut accumulator = limits.accumulator();
222
223 assert_eq!(accumulator.check_allocation(usize::MAX), Ok(()));
224 assert_eq!(
225 accumulator.check_allocation(1),
226 Err(DecodeError::AllocationExceeded)
227 );
228 }
229
230 #[test]
231 fn detects_trailing_bytes() {
232 assert_eq!(
233 require_exact_consumption(1, 2),
234 Err(DecodeError::TrailingBytes)
235 );
236 }
237
238 #[test]
239 fn detects_decoder_overread() {
240 assert_eq!(
241 require_exact_consumption(3, 2),
242 Err(DecodeError::DecoderOverread)
243 );
244 }
245}