Skip to main content

eth_valkyoth_codec/rlp/
list.rs

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