Skip to main content

eth_valkyoth_codec/rlp/
list.rs

1use crate::{
2    DecodeAccumulator, DecodeError, DecodeLimits, checked_len_add, require_exact_consumption,
3    require_range_in_bounds,
4};
5
6use super::{
7    LONG_LIST_OFFSET, RlpScalar, RlpScalarForm, SHORT_LIST_OFFSET, SHORT_STRING_LIMIT,
8    decode_long_string, decode_short_string, parse_payload_len, payload,
9};
10
11/// Hard cap on RLP list traversal depth regardless of the active decode limits.
12///
13/// Inputs nested deeper than this return [`DecodeError::NestingTooDeep`] even
14/// when [`DecodeLimits::max_nesting_depth`] is higher.
15pub const MAX_RLP_LIST_TRAVERSAL_DEPTH: usize = 128;
16
17/// Canonical RLP list form used by the decoder.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum RlpListForm {
20    /// A list with a one-byte RLP header.
21    ShortList,
22    /// A list with a length-of-length RLP header.
23    LongList,
24}
25
26/// Borrowed RLP list payload.
27///
28/// Fields are private so downstream crates cannot construct unvalidated
29/// decoded values and feed them into trusted re-encoding paths.
30#[derive(Clone, Copy, Debug)]
31pub struct RlpList<'a> {
32    payload: &'a [u8],
33    encoded_len: usize,
34    header_len: usize,
35    item_count: usize,
36    form: RlpListForm,
37    limits: DecodeLimits,
38}
39
40impl<'a> RlpList<'a> {
41    /// Returns the concatenated encoded child items.
42    #[must_use]
43    pub const fn payload(self) -> &'a [u8] {
44        self.payload
45    }
46
47    /// Returns the total encoded list length in bytes.
48    #[must_use]
49    pub const fn encoded_len(self) -> usize {
50        self.encoded_len
51    }
52
53    /// Returns the RLP header length in bytes.
54    #[must_use]
55    pub const fn header_len(self) -> usize {
56        self.header_len
57    }
58
59    /// Returns the number of immediate child items.
60    #[must_use]
61    pub const fn item_count(self) -> usize {
62        self.item_count
63    }
64
65    /// Returns the canonical list form that was decoded.
66    #[must_use]
67    pub const fn form(self) -> RlpListForm {
68        self.form
69    }
70
71    /// Returns true when the list has no child items.
72    #[must_use]
73    pub const fn is_empty(self) -> bool {
74        self.item_count == 0
75    }
76
77    /// Returns an iterator over the immediate child items in this list.
78    #[must_use]
79    pub const fn items(self) -> RlpListItems<'a> {
80        RlpListItems {
81            input: self.payload,
82            cursor: 0,
83            remaining: self.item_count,
84            limits: self.limits,
85        }
86    }
87}
88
89impl PartialEq for RlpList<'_> {
90    fn eq(&self, other: &Self) -> bool {
91        self.payload == other.payload
92            && self.encoded_len == other.encoded_len
93            && self.header_len == other.header_len
94            && self.item_count == other.item_count
95            && self.form == other.form
96    }
97}
98
99impl Eq for RlpList<'_> {}
100
101/// Borrowed RLP item yielded by [`RlpListItems`].
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum RlpItem<'a> {
104    /// A scalar byte string item.
105    Scalar(RlpScalar<'a>),
106    /// A list item.
107    List(RlpList<'a>),
108}
109
110impl<'a> RlpItem<'a> {
111    /// Returns the total encoded item length in bytes.
112    #[must_use]
113    pub const fn encoded_len(self) -> usize {
114        match self {
115            Self::Scalar(scalar) => scalar.encoded_len(),
116            Self::List(list) => list.encoded_len(),
117        }
118    }
119
120    /// Returns the RLP header length in bytes.
121    #[must_use]
122    pub const fn header_len(self) -> usize {
123        match self {
124            Self::Scalar(scalar) => scalar.header_len(),
125            Self::List(list) => list.header_len(),
126        }
127    }
128
129    /// Returns true when this item is a scalar byte string.
130    #[must_use]
131    pub const fn is_scalar(self) -> bool {
132        matches!(self, Self::Scalar(_))
133    }
134
135    /// Returns true when this item is a list.
136    #[must_use]
137    pub const fn is_list(self) -> bool {
138        matches!(self, Self::List(_))
139    }
140
141    /// Returns the scalar item, if this item is a scalar byte string.
142    #[must_use]
143    pub const fn as_scalar(&self) -> Option<RlpScalar<'a>> {
144        match self {
145            Self::Scalar(scalar) => Some(*scalar),
146            Self::List(_) => None,
147        }
148    }
149
150    /// Returns the list item, if this item is a list.
151    #[must_use]
152    pub const fn as_list(&self) -> Option<RlpList<'a>> {
153        match self {
154            Self::Scalar(_) => None,
155            Self::List(list) => Some(*list),
156        }
157    }
158}
159
160/// Iterator over immediate child items in a decoded RLP list.
161#[derive(Clone, Debug)]
162pub struct RlpListItems<'a> {
163    input: &'a [u8],
164    cursor: usize,
165    remaining: usize,
166    limits: DecodeLimits,
167}
168
169impl<'a> RlpListItems<'a> {
170    /// Returns the number of child items not yielded yet.
171    #[must_use]
172    pub const fn remaining(&self) -> usize {
173        self.remaining
174    }
175}
176
177impl<'a> Iterator for RlpListItems<'a> {
178    type Item = Result<RlpItem<'a>, DecodeError>;
179
180    fn next(&mut self) -> Option<Self::Item> {
181        if self.remaining == 0 {
182            return None;
183        }
184
185        match parse_item(self.input, self.cursor, self.input.len())
186            .and_then(|item| item.into_rlp_item(self.input, self.cursor, self.limits))
187        {
188            Ok((item, next_cursor)) => {
189                self.cursor = next_cursor;
190                self.remaining = self.remaining.saturating_sub(1);
191                Some(Ok(item))
192            }
193            Err(error) => {
194                self.remaining = 0;
195                Some(Err(error))
196            }
197        }
198    }
199
200    fn size_hint(&self) -> (usize, Option<usize>) {
201        (self.remaining, Some(self.remaining))
202    }
203}
204
205impl ExactSizeIterator for RlpListItems<'_> {}
206
207impl core::iter::FusedIterator for RlpListItems<'_> {}
208
209/// Decodes exactly one canonical RLP list.
210pub fn decode_rlp_list<'a>(
211    input: &'a [u8],
212    limits: DecodeLimits,
213) -> Result<RlpList<'a>, DecodeError> {
214    let mut accumulator = limits.accumulator();
215    let list = decode_rlp_list_partial(input, &mut accumulator)?;
216    require_exact_consumption(list.encoded_len, input.len())?;
217    Ok(list)
218}
219
220/// Decodes one canonical RLP list from the start of `input`.
221///
222/// Warning: this intentionally accepts trailing bytes. Use [`decode_rlp_list`]
223/// when the full input must be consumed.
224///
225/// The input-length budget check applies to the full `input` slice, not only
226/// the consumed list bytes. Callers that decode from a larger outer buffer must
227/// pre-slice before calling this helper.
228pub fn decode_rlp_list_partial<'a>(
229    input: &'a [u8],
230    accumulator: &mut DecodeAccumulator,
231) -> Result<RlpList<'a>, DecodeError> {
232    accumulator.check_input_len(input.len())?;
233    accumulator.account_items(1)?;
234    accumulator.check_nesting_depth(1)?;
235
236    let prefix = *input.first().ok_or(DecodeError::Malformed)?;
237    let list = match prefix {
238        SHORT_LIST_OFFSET..=LONG_LIST_OFFSET => {
239            decode_short_list(input, prefix, accumulator.limits())?
240        }
241        0xf8..=0xff => decode_long_list(input, prefix, accumulator.limits())?,
242        0x00..=0xbf => return Err(DecodeError::UnexpectedScalar),
243    };
244    let item_count = validate_list_payload(list.payload, accumulator)?;
245    accumulator.check_list_count(item_count)?;
246    Ok(RlpList { item_count, ..list })
247}
248
249fn decode_short_list(
250    input: &[u8],
251    prefix: u8,
252    limits: DecodeLimits,
253) -> Result<RlpList<'_>, DecodeError> {
254    let payload_len = usize::from(prefix.saturating_sub(SHORT_LIST_OFFSET));
255    let payload = payload(input, 1, payload_len)?;
256    Ok(RlpList {
257        payload,
258        encoded_len: checked_len_add(1, payload_len)?,
259        header_len: 1,
260        item_count: 0,
261        form: RlpListForm::ShortList,
262        limits,
263    })
264}
265
266fn decode_long_list(
267    input: &[u8],
268    prefix: u8,
269    limits: DecodeLimits,
270) -> Result<RlpList<'_>, DecodeError> {
271    let len_of_len = usize::from(prefix.saturating_sub(LONG_LIST_OFFSET));
272    let payload_len = parse_payload_len(input, 1, len_of_len)?;
273    if payload_len <= SHORT_STRING_LIMIT {
274        return Err(DecodeError::Malformed);
275    }
276    let header_len = checked_len_add(1, len_of_len)?;
277    let payload = payload(input, header_len, payload_len)?;
278    Ok(RlpList {
279        payload,
280        encoded_len: checked_len_add(header_len, payload_len)?,
281        header_len,
282        item_count: 0,
283        form: RlpListForm::LongList,
284        limits,
285    })
286}
287
288#[derive(Clone, Copy)]
289struct ListFrame {
290    end: usize,
291    count: usize,
292}
293
294pub(super) fn validate_list_payload(
295    input: &[u8],
296    accumulator: &mut DecodeAccumulator,
297) -> Result<usize, DecodeError> {
298    let mut stack = [ListFrame { end: 0, count: 0 }; MAX_RLP_LIST_TRAVERSAL_DEPTH];
299    let root = stack.get_mut(0).ok_or(DecodeError::NestingTooDeep)?;
300    *root = ListFrame {
301        end: input.len(),
302        count: 0,
303    };
304    let mut depth = 1usize;
305    let mut cursor = 0usize;
306
307    loop {
308        let frame_index = depth.checked_sub(1).ok_or(DecodeError::LengthOverflow)?;
309        let frame = stack
310            .get_mut(frame_index)
311            .ok_or(DecodeError::NestingTooDeep)?;
312
313        if cursor == frame.end {
314            let finished_count = frame.count;
315            accumulator.check_list_count(finished_count)?;
316            depth = depth.checked_sub(1).ok_or(DecodeError::LengthOverflow)?;
317            if depth == 0 {
318                return Ok(finished_count);
319            }
320            continue;
321        }
322        if cursor > frame.end {
323            return Err(DecodeError::OffsetOutOfBounds);
324        }
325
326        frame.count = checked_len_add(frame.count, 1)?;
327        accumulator.check_list_count(frame.count)?;
328
329        let item = parse_item(input, cursor, frame.end)?;
330        accumulator.account_items(1)?;
331        if matches!(item.kind, ParsedItemKind::List(_)) {
332            let next_depth = checked_len_add(depth, 1)?;
333            accumulator.check_nesting_depth(next_depth)?;
334            if next_depth > MAX_RLP_LIST_TRAVERSAL_DEPTH {
335                return Err(DecodeError::NestingTooDeep);
336            }
337            let child_index = next_depth
338                .checked_sub(1)
339                .ok_or(DecodeError::LengthOverflow)?;
340            let child = stack
341                .get_mut(child_index)
342                .ok_or(DecodeError::NestingTooDeep)?;
343            *child = ListFrame {
344                end: item.payload_end,
345                count: 0,
346            };
347            depth = next_depth;
348            cursor = item.payload_start;
349        } else {
350            cursor = item.item_end;
351        }
352    }
353}
354
355struct ParsedItem {
356    kind: ParsedItemKind,
357    header_len: usize,
358    payload_start: usize,
359    payload_end: usize,
360    item_end: usize,
361}
362
363impl ParsedItem {
364    fn into_rlp_item<'a>(
365        self,
366        input: &'a [u8],
367        offset: usize,
368        limits: DecodeLimits,
369    ) -> Result<(RlpItem<'a>, usize), DecodeError> {
370        let payload = input
371            .get(self.payload_start..self.payload_end)
372            .ok_or(DecodeError::OffsetOutOfBounds)?;
373        let encoded_len = self
374            .item_end
375            .checked_sub(offset)
376            .ok_or(DecodeError::LengthOverflow)?;
377        let item = match self.kind {
378            ParsedItemKind::Scalar(form) => RlpItem::Scalar(RlpScalar {
379                payload,
380                encoded_len,
381                header_len: self.header_len,
382                form,
383            }),
384            ParsedItemKind::List(form) => RlpItem::List(RlpList {
385                payload,
386                encoded_len,
387                header_len: self.header_len,
388                item_count: count_immediate_items(payload, limits)?,
389                form,
390                limits,
391            }),
392        };
393        Ok((item, self.item_end))
394    }
395}
396
397#[derive(Clone, Copy)]
398enum ParsedItemKind {
399    Scalar(RlpScalarForm),
400    List(RlpListForm),
401}
402
403fn parse_item(
404    input: &[u8],
405    offset: usize,
406    container_end: usize,
407) -> Result<ParsedItem, DecodeError> {
408    let local = input
409        .get(offset..container_end)
410        .ok_or(DecodeError::OffsetOutOfBounds)?;
411    let prefix = *local.first().ok_or(DecodeError::Malformed)?;
412    let (kind, header_len, payload_len) = match prefix {
413        0x00..=0x7f => (ParsedItemKind::Scalar(RlpScalarForm::SingleByte), 0, 1),
414        super::SHORT_STRING_OFFSET..=super::LONG_STRING_OFFSET => {
415            let scalar = decode_short_string(local, prefix)?;
416            (
417                ParsedItemKind::Scalar(scalar.form()),
418                scalar.header_len(),
419                scalar.payload().len(),
420            )
421        }
422        0xb8..=0xbf => {
423            let scalar = decode_long_string(local, prefix)?;
424            (
425                ParsedItemKind::Scalar(scalar.form()),
426                scalar.header_len(),
427                scalar.payload().len(),
428            )
429        }
430        SHORT_LIST_OFFSET..=LONG_LIST_OFFSET => {
431            let payload_len = usize::from(prefix.saturating_sub(SHORT_LIST_OFFSET));
432            payload(local, 1, payload_len)?;
433            (ParsedItemKind::List(RlpListForm::ShortList), 1, payload_len)
434        }
435        0xf8..=0xff => {
436            let len_of_len = usize::from(prefix.saturating_sub(LONG_LIST_OFFSET));
437            let payload_len = parse_payload_len(local, 1, len_of_len)?;
438            if payload_len <= SHORT_STRING_LIMIT {
439                return Err(DecodeError::Malformed);
440            }
441            let header_len = checked_len_add(1, len_of_len)?;
442            payload(local, header_len, payload_len)?;
443            (
444                ParsedItemKind::List(RlpListForm::LongList),
445                header_len,
446                payload_len,
447            )
448        }
449    };
450    let payload_start = checked_len_add(offset, header_len)?;
451    let payload_end = checked_len_add(payload_start, payload_len)?;
452    let item_end = if header_len == 0 {
453        checked_len_add(offset, 1)?
454    } else {
455        payload_end
456    };
457    require_range_in_bounds(offset, item_end.saturating_sub(offset), container_end)?;
458    Ok(ParsedItem {
459        kind,
460        header_len,
461        payload_start,
462        payload_end,
463        item_end,
464    })
465}
466
467fn count_immediate_items(input: &[u8], limits: DecodeLimits) -> Result<usize, DecodeError> {
468    // Iteration-phase recounting is bounded independently from the
469    // decode-phase accumulator used by partial streaming callers.
470    let mut accumulator = limits.accumulator();
471    accumulator.check_input_len(input.len())?;
472    let mut count = 0usize;
473    let mut cursor = 0usize;
474    while cursor < input.len() {
475        let item = parse_item(input, cursor, input.len())?;
476        count = checked_len_add(count, 1)?;
477        accumulator.account_items(1)?;
478        accumulator.check_list_count(count)?;
479        cursor = item.item_end;
480    }
481    if cursor == input.len() {
482        Ok(count)
483    } else {
484        Err(DecodeError::OffsetOutOfBounds)
485    }
486}