Skip to main content

eth_valkyoth_codec/rlp/
encode.rs

1use core::convert::TryFrom;
2
3use crate::{DecodeAccumulator, DecodeError, DecodeLimits, checked_len_add};
4
5use super::{
6    LENGTH_RADIX, LONG_LIST_OFFSET, LONG_STRING_OFFSET, RlpInteger, RlpItem, RlpList, RlpScalar,
7    SHORT_LIST_OFFSET, SHORT_STRING_LIMIT, SHORT_STRING_OFFSET,
8    integer::validate_rlp_integer_payload, list::validate_list_payload,
9};
10
11/// Returns the encoded RLP byte length for a scalar byte-string payload.
12pub fn encoded_rlp_scalar_len(payload: &[u8]) -> Result<usize, DecodeError> {
13    if payload.len() == 1 && payload.first().is_some_and(|byte| *byte <= 0x7f) {
14        return Ok(1);
15    }
16    encoded_payload_len(payload.len())
17}
18
19/// Returns the encoded RLP byte length for a canonical integer payload.
20///
21/// The empty payload represents zero. Non-empty payloads must be shortest-form
22/// unsigned big-endian bytes and therefore cannot start with `0x00`.
23pub fn encoded_rlp_integer_len(payload: &[u8]) -> Result<usize, DecodeError> {
24    validate_rlp_integer_payload(payload)?;
25    encoded_rlp_scalar_len(payload)
26}
27
28/// Returns the encoded RLP byte length for a list payload.
29///
30/// The payload must be the concatenated encoded child items of the list.
31pub fn encoded_rlp_list_len(payload: &[u8], limits: DecodeLimits) -> Result<usize, DecodeError> {
32    validate_encoded_list_payload(payload, limits)?;
33    encoded_payload_len(payload.len())
34}
35
36/// Returns the RLP list-header length for a known list payload length.
37///
38/// This helper does not inspect the payload bytes. Streaming and fixed-buffer
39/// encoders use it after independently computing a payload made only from
40/// canonical child encodings.
41pub fn encoded_rlp_list_header_len(payload_len: usize) -> Result<usize, DecodeError> {
42    let total_len = encoded_payload_len(payload_len)?;
43    total_len
44        .checked_sub(payload_len)
45        .ok_or(DecodeError::LengthOverflow)
46}
47
48/// Canonically encodes a scalar byte-string payload into `output`.
49///
50/// `output` is not modified unless this function returns `Ok`.
51/// Returns the number of bytes written.
52pub fn encode_rlp_scalar(payload: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
53    encode_payload(
54        payload,
55        output,
56        SHORT_STRING_OFFSET,
57        LONG_STRING_OFFSET,
58        ScalarSingleByte::Enabled,
59    )
60}
61
62/// Canonically encodes an Ethereum integer payload into `output`.
63///
64/// `output` is not modified unless this function returns `Ok`.
65/// Returns the number of bytes written.
66pub fn encode_rlp_integer(payload: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
67    validate_rlp_integer_payload(payload)?;
68    encode_rlp_scalar(payload, output)
69}
70
71/// Canonically encodes a list payload into `output`.
72///
73/// The payload must already contain the concatenated encoded child items.
74/// `output` is not modified unless this function returns `Ok`.
75/// Returns the number of bytes written.
76pub fn encode_rlp_list_payload(
77    payload: &[u8],
78    limits: DecodeLimits,
79    output: &mut [u8],
80) -> Result<usize, DecodeError> {
81    validate_encoded_list_payload(payload, limits)?;
82    encode_rlp_list_payload_unchecked(payload, output)
83}
84
85/// Encodes only the RLP list header for a known payload length.
86///
87/// The payload must later be filled with canonical encoded child items. This
88/// helper is intended for no-allocation encoders that cannot first materialize
89/// the whole list payload in a separate buffer.
90pub fn encode_rlp_list_header(payload_len: usize, output: &mut [u8]) -> Result<usize, DecodeError> {
91    let required_len = encoded_rlp_list_header_len(payload_len)?;
92    if output.len() < required_len {
93        return Err(DecodeError::OffsetOutOfBounds);
94    }
95    if payload_len <= SHORT_STRING_LIMIT {
96        write_short_header(output, SHORT_LIST_OFFSET, payload_len)
97    } else {
98        write_long_header(output, LONG_LIST_OFFSET, payload_len)
99    }
100}
101
102fn encode_rlp_list_payload_unchecked(
103    payload: &[u8],
104    output: &mut [u8],
105) -> Result<usize, DecodeError> {
106    encode_payload(
107        payload,
108        output,
109        SHORT_LIST_OFFSET,
110        LONG_LIST_OFFSET,
111        ScalarSingleByte::Disabled,
112    )
113}
114
115/// Re-encodes a decoded canonical scalar into `output`.
116///
117/// `output` is not modified unless this function returns `Ok`.
118/// Returns the number of bytes written.
119pub fn encode_decoded_scalar(
120    scalar: RlpScalar<'_>,
121    output: &mut [u8],
122) -> Result<usize, DecodeError> {
123    encode_rlp_scalar(scalar.payload(), output)
124}
125
126/// Re-encodes a decoded canonical integer into `output`.
127///
128/// `output` is not modified unless this function returns `Ok`.
129/// Returns the number of bytes written.
130pub fn encode_decoded_integer(
131    integer: RlpInteger<'_>,
132    output: &mut [u8],
133) -> Result<usize, DecodeError> {
134    encode_rlp_integer(integer.payload(), output)
135}
136
137/// Re-encodes a decoded canonical list into `output`.
138///
139/// `output` is not modified unless this function returns `Ok`.
140/// Returns the number of bytes written.
141pub fn encode_decoded_list(list: RlpList<'_>, output: &mut [u8]) -> Result<usize, DecodeError> {
142    encode_rlp_list_payload_unchecked(list.payload(), output)
143}
144
145/// Re-encodes a decoded canonical RLP item into `output`.
146///
147/// `output` is not modified unless this function returns `Ok`.
148/// Returns the number of bytes written.
149pub fn encode_decoded_item(item: RlpItem<'_>, output: &mut [u8]) -> Result<usize, DecodeError> {
150    match item {
151        RlpItem::Scalar(scalar) => encode_decoded_scalar(scalar, output),
152        RlpItem::List(list) => encode_decoded_list(list, output),
153    }
154}
155
156#[derive(Clone, Copy)]
157enum ScalarSingleByte {
158    Enabled,
159    Disabled,
160}
161
162fn encoded_payload_len(payload_len: usize) -> Result<usize, DecodeError> {
163    if payload_len <= SHORT_STRING_LIMIT {
164        return checked_len_add(1, payload_len);
165    }
166    let len_of_len = length_of_length(payload_len)?;
167    checked_len_add(checked_len_add(1, len_of_len)?, payload_len)
168}
169
170fn encode_payload(
171    payload: &[u8],
172    output: &mut [u8],
173    short_offset: u8,
174    long_offset: u8,
175    scalar_single_byte: ScalarSingleByte,
176) -> Result<usize, DecodeError> {
177    let required_len = if matches!(scalar_single_byte, ScalarSingleByte::Enabled)
178        && payload.len() == 1
179        && payload.first().is_some_and(|byte| *byte <= 0x7f)
180    {
181        1
182    } else {
183        encoded_payload_len(payload.len())?
184    };
185    if output.len() < required_len {
186        return Err(DecodeError::OffsetOutOfBounds);
187    }
188
189    if matches!(scalar_single_byte, ScalarSingleByte::Enabled)
190        && payload.len() == 1
191        && payload.first().is_some_and(|byte| *byte <= 0x7f)
192    {
193        let byte = *payload.first().ok_or(DecodeError::OffsetOutOfBounds)?;
194        write_byte(output, byte)?;
195        return Ok(1);
196    }
197
198    let header_len = if payload.len() <= SHORT_STRING_LIMIT {
199        write_short_header(output, short_offset, payload.len())?
200    } else {
201        write_long_header(output, long_offset, payload.len())?
202    };
203    write_payload(output, header_len, payload)?;
204    checked_len_add(header_len, payload.len())
205}
206
207fn write_short_header(
208    output: &mut [u8],
209    short_offset: u8,
210    payload_len: usize,
211) -> Result<usize, DecodeError> {
212    let payload_len = u8::try_from(payload_len).map_err(|_| DecodeError::LengthOverflow)?;
213    let prefix = short_offset
214        .checked_add(payload_len)
215        .ok_or(DecodeError::LengthOverflow)?;
216    write_byte(output, prefix)?;
217    Ok(1)
218}
219
220fn write_long_header(
221    output: &mut [u8],
222    long_offset: u8,
223    payload_len: usize,
224) -> Result<usize, DecodeError> {
225    let len_of_len = length_of_length(payload_len)?;
226    let len_of_len_byte = u8::try_from(len_of_len).map_err(|_| DecodeError::LengthOverflow)?;
227    let prefix = long_offset
228        .checked_add(len_of_len_byte)
229        .ok_or(DecodeError::LengthOverflow)?;
230    let header_len = checked_len_add(1, len_of_len)?;
231    write_byte(output, prefix)?;
232
233    let len_bytes = payload_len.to_be_bytes();
234    let start = len_bytes
235        .len()
236        .checked_sub(len_of_len)
237        .ok_or(DecodeError::LengthOverflow)?;
238    let source = len_bytes
239        .get(start..)
240        .ok_or(DecodeError::OffsetOutOfBounds)?;
241    let target = output
242        .get_mut(1..header_len)
243        .ok_or(DecodeError::OffsetOutOfBounds)?;
244    target.copy_from_slice(source);
245    Ok(header_len)
246}
247
248fn write_byte(output: &mut [u8], byte: u8) -> Result<(), DecodeError> {
249    let target = output.first_mut().ok_or(DecodeError::OffsetOutOfBounds)?;
250    *target = byte;
251    Ok(())
252}
253
254fn write_payload(output: &mut [u8], header_len: usize, payload: &[u8]) -> Result<(), DecodeError> {
255    let end = checked_len_add(header_len, payload.len())?;
256    let target = output
257        .get_mut(header_len..end)
258        .ok_or(DecodeError::OffsetOutOfBounds)?;
259    target.copy_from_slice(payload);
260    Ok(())
261}
262
263fn length_of_length(payload_len: usize) -> Result<usize, DecodeError> {
264    if payload_len <= SHORT_STRING_LIMIT {
265        return Err(DecodeError::Malformed);
266    }
267
268    let mut value = payload_len;
269    let mut len = 0usize;
270    while value > 0 {
271        len = checked_len_add(len, 1)?;
272        value = value
273            .checked_div(LENGTH_RADIX)
274            // LENGTH_RADIX is the nonzero constant 256, so this branch is
275            // unreachable unless the constant changes.
276            .ok_or(DecodeError::LengthOverflow)?;
277    }
278    Ok(len)
279}
280
281fn validate_encoded_list_payload(
282    payload: &[u8],
283    limits: DecodeLimits,
284) -> Result<usize, DecodeError> {
285    let mut accumulator: DecodeAccumulator = limits.accumulator();
286    accumulator.check_input_len(payload.len())?;
287    validate_list_payload(payload, &mut accumulator)
288}