Skip to main content

eth_valkyoth_codec/
rlp.rs

1mod encode;
2mod integer;
3mod list;
4mod traits;
5
6#[cfg(test)]
7mod tests;
8
9use crate::{
10    DecodeAccumulator, DecodeError, DecodeLimits, checked_len_add, require_exact_consumption,
11    require_range_in_bounds,
12};
13
14pub use encode::{
15    encode_decoded_integer, encode_decoded_item, encode_decoded_list, encode_decoded_scalar,
16    encode_rlp_integer, encode_rlp_list_header, encode_rlp_list_payload, encode_rlp_scalar,
17    encoded_rlp_integer_len, encoded_rlp_list_header_len, encoded_rlp_list_len,
18    encoded_rlp_scalar_len,
19};
20pub use integer::{
21    MAX_RLP_U256_BYTES, RlpInteger, decode_rlp_integer, decode_rlp_integer_partial, decode_rlp_u64,
22    decode_rlp_u128, decode_rlp_u256_bytes, rlp_integer_payload_to_u64,
23    rlp_integer_payload_to_u128, rlp_integer_payload_to_u256_bytes, validate_rlp_integer_payload,
24};
25pub use list::{
26    MAX_RLP_LIST_TRAVERSAL_DEPTH, RlpItem, RlpList, RlpListForm, RlpListItems, decode_rlp_list,
27    decode_rlp_list_partial,
28};
29pub use traits::{RlpDecode, RlpDeriveError, RlpEncode, checked_encoded_len_add};
30
31pub(super) const SHORT_STRING_OFFSET: u8 = 0x80;
32/// Base for computing `len_of_len` in long-string encoding.
33///
34/// This is the last short-string prefix, not the first long-string prefix.
35pub(super) const LONG_STRING_OFFSET: u8 = 0xb7;
36pub(super) const SHORT_LIST_OFFSET: u8 = 0xc0;
37/// Base for computing `len_of_len` in long-list encoding.
38///
39/// This is the last short-list prefix, not the first long-list prefix.
40pub(super) const LONG_LIST_OFFSET: u8 = 0xf7;
41pub(super) const SHORT_STRING_LIMIT: usize = 55;
42pub(super) const LENGTH_RADIX: usize = 256;
43
44/// Canonical RLP scalar form used by the decoder.
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub enum RlpScalarForm {
47    /// A single byte in `0x00..=0x7f`, encoded as itself.
48    SingleByte,
49    /// A byte string with a one-byte RLP header.
50    ShortString,
51    /// A byte string with a length-of-length RLP header.
52    LongString,
53}
54
55/// Borrowed RLP scalar byte string.
56///
57/// Fields are private so downstream crates cannot construct unvalidated
58/// decoded values and feed them into trusted re-encoding paths.
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub struct RlpScalar<'a> {
61    payload: &'a [u8],
62    encoded_len: usize,
63    header_len: usize,
64    form: RlpScalarForm,
65}
66
67impl<'a> RlpScalar<'a> {
68    /// Returns the decoded scalar payload bytes.
69    #[must_use]
70    pub const fn payload(self) -> &'a [u8] {
71        self.payload
72    }
73
74    /// Returns the total encoded item length in bytes.
75    #[must_use]
76    pub const fn encoded_len(self) -> usize {
77        self.encoded_len
78    }
79
80    /// Returns the RLP header length in bytes.
81    #[must_use]
82    pub const fn header_len(self) -> usize {
83        self.header_len
84    }
85
86    /// Returns the canonical scalar form that was decoded.
87    #[must_use]
88    pub const fn form(self) -> RlpScalarForm {
89        self.form
90    }
91
92    /// Returns true when the decoded payload is empty.
93    #[must_use]
94    pub const fn is_empty(self) -> bool {
95        self.payload.is_empty()
96    }
97}
98
99/// Decodes exactly one canonical RLP scalar byte string.
100pub fn decode_rlp_scalar<'a>(
101    input: &'a [u8],
102    limits: DecodeLimits,
103) -> Result<RlpScalar<'a>, DecodeError> {
104    let mut accumulator = limits.accumulator();
105    let scalar = decode_rlp_scalar_partial(input, &mut accumulator)?;
106    require_exact_consumption(scalar.encoded_len, input.len())?;
107    Ok(scalar)
108}
109
110/// Decodes one canonical RLP scalar byte string from the start of `input`.
111///
112/// Warning: this intentionally accepts trailing bytes. Use
113/// [`decode_rlp_scalar`] when the full input must be consumed.
114///
115/// The input-length budget check applies to the full `input` slice, not only
116/// the consumed scalar bytes. Callers that decode from a larger outer buffer
117/// must pre-slice before calling this helper.
118///
119/// This helper does not reject trailing bytes. It is intended for nested
120/// decoders that need to consume one item while sharing cumulative budgets.
121pub fn decode_rlp_scalar_partial<'a>(
122    input: &'a [u8],
123    accumulator: &mut DecodeAccumulator,
124) -> Result<RlpScalar<'a>, DecodeError> {
125    accumulator.check_input_len(input.len())?;
126    accumulator.account_items(1)?;
127
128    let prefix = *input.first().ok_or(DecodeError::Malformed)?;
129    match prefix {
130        0x00..=0x7f => decode_single_byte(input),
131        SHORT_STRING_OFFSET..=LONG_STRING_OFFSET => decode_short_string(input, prefix),
132        0xb8..=0xbf => decode_long_string(input, prefix),
133        SHORT_LIST_OFFSET..=0xff => Err(DecodeError::UnexpectedList),
134    }
135}
136
137fn decode_single_byte(input: &[u8]) -> Result<RlpScalar<'_>, DecodeError> {
138    let payload = input.get(..1).ok_or(DecodeError::OffsetOutOfBounds)?;
139    Ok(RlpScalar {
140        payload,
141        encoded_len: 1,
142        header_len: 0,
143        form: RlpScalarForm::SingleByte,
144    })
145}
146
147pub(super) fn decode_short_string(input: &[u8], prefix: u8) -> Result<RlpScalar<'_>, DecodeError> {
148    let payload_len = usize::from(prefix.saturating_sub(SHORT_STRING_OFFSET));
149    let payload = payload(input, 1, payload_len)?;
150    if payload_len == 1 && payload.first().is_some_and(|byte| *byte <= 0x7f) {
151        return Err(DecodeError::Malformed);
152    }
153    Ok(RlpScalar {
154        payload,
155        encoded_len: checked_len_add(1, payload_len)?,
156        header_len: 1,
157        form: RlpScalarForm::ShortString,
158    })
159}
160
161pub(super) fn decode_long_string(input: &[u8], prefix: u8) -> Result<RlpScalar<'_>, DecodeError> {
162    let len_of_len = usize::from(prefix.saturating_sub(LONG_STRING_OFFSET));
163    let payload_len = parse_payload_len(input, 1, len_of_len)?;
164    if payload_len <= SHORT_STRING_LIMIT {
165        return Err(DecodeError::Malformed);
166    }
167    let header_len = checked_len_add(1, len_of_len)?;
168    let payload = payload(input, header_len, payload_len)?;
169    Ok(RlpScalar {
170        payload,
171        encoded_len: checked_len_add(header_len, payload_len)?,
172        header_len,
173        form: RlpScalarForm::LongString,
174    })
175}
176
177pub(super) fn parse_payload_len(
178    input: &[u8],
179    offset: usize,
180    len: usize,
181) -> Result<usize, DecodeError> {
182    let end = require_range_in_bounds(offset, len, input.len())?;
183    let bytes = input
184        .get(offset..end)
185        .ok_or(DecodeError::OffsetOutOfBounds)?;
186    if bytes.first().is_some_and(|byte| *byte == 0) {
187        return Err(DecodeError::Malformed);
188    }
189
190    let mut value = 0usize;
191    for byte in bytes {
192        value = value
193            .checked_mul(LENGTH_RADIX)
194            .ok_or(DecodeError::LengthOverflow)?;
195        value = value
196            .checked_add(usize::from(*byte))
197            .ok_or(DecodeError::LengthOverflow)?;
198    }
199    Ok(value)
200}
201
202pub(super) fn payload(input: &[u8], offset: usize, len: usize) -> Result<&[u8], DecodeError> {
203    let end = require_range_in_bounds(offset, len, input.len())?;
204    input.get(offset..end).ok_or(DecodeError::OffsetOutOfBounds)
205}