Skip to content

rlp/rlp_decoder.cpp

Namespaces

Name
rlp

Source code

#include <rlp/rlp_decoder.hpp> // Direct include
#include <rlp/endian.hpp>  // Direct include
#include <cstring>     // For memcpy
#include <stdexcept>   // For std::length_error

namespace rlp {

namespace { // Anonymous namespace for internal helpers

// Decodes RLP header FROM THE START of the provided view 'v'
// Modifies 'v' to remove the consumed header bytes.
// Returns header info including header length itself.
[[nodiscard]] Result<Header> decode_header_impl(ByteView& v) noexcept {
    if ( v.empty() ) {
        return DecodingError::kInputTooShort;
    }

    Header h{};
    h.list = false;
    h.payload_size_bytes = 0;
    h.header_size_bytes = 0;
    const uint8_t b{v[0]};
    const size_t input_len = v.length();

    // Reserved bytes (0xf9–0xff) as single bytes are always invalid
    if ( input_len == 1 && b >= 0xf9 ) {
        return DecodingError::kMalformedHeader;
    }

    if ( b < kShortStringOffset ) { // 0x80
        // Single byte literal (valid for 0x00–0x7f)
        h.payload_size_bytes = 1;
        h.header_size_bytes = 0; // Header is implicit
        // Do not consume 'v' here, payload is the byte itself
    } else if ( b <= kMaxShortStringLen + kShortStringOffset ) { // 0xB7
        // Short string
        h.header_size_bytes = 1;
        if ( input_len < h.header_size_bytes ) return DecodingError::kInputTooShort;
        h.payload_size_bytes = b - kShortStringOffset;
        if ( h.payload_size_bytes == 1 ) {
            if ( input_len < kSingleByteStringSize ) return DecodingError::kInputTooShort;
            if ( static_cast<uint8_t>(v[1]) < kShortStringOffset ) {
                return DecodingError::kNonCanonicalSize; // byte < 0x80 must be encoded as itself
            }
        }
        v.remove_prefix(h.header_size_bytes); // Consume header byte
    } else if ( b < kShortListOffset ) { // 0xC0
        // Long string
        h.header_size_bytes = 1 + (b - kLongStringOffset);
        if ( input_len < h.header_size_bytes ) return DecodingError::kInputTooShort;

        uint64_t len64{0};
        ByteView len_bytes = v.substr(1, h.header_size_bytes - 1);

        auto len_res = endian::from_big_compact(len_bytes, len64);
        if ( !len_res ) return len_res.error();

        if ( len64 <= kMaxShortStringLen ) { // Must use short form if length <= 55
            return DecodingError::kNonCanonicalSize;
        }
        // Check for overflow only on platforms where size_t is narrower than uint64_t.
#if SIZE_MAX < UINT64_MAX
        if ( len64 > static_cast<uint64_t>(std::numeric_limits<size_t>::max()) ) {
            return DecodingError::kOverflow;
        }
#endif
        h.payload_size_bytes = static_cast<size_t>(len64);
        v.remove_prefix(h.header_size_bytes); // Consume header + length bytes
    } else if ( b <= kMaxShortListLen + kShortListOffset ) { // 0xF7
        // Short list
        h.list = true;
        h.header_size_bytes = 1;
        if ( input_len < h.header_size_bytes ) return DecodingError::kInputTooShort;
        h.payload_size_bytes = b - kShortListOffset;
        v.remove_prefix(h.header_size_bytes); // Consume header byte
    } else {
        // Long list
        h.list = true;
        h.header_size_bytes = 1 + (b - kLongListOffset);
        if ( input_len < h.header_size_bytes ) return DecodingError::kInputTooShort;

        uint64_t len64{0};
        ByteView len_bytes = v.substr(1, h.header_size_bytes - 1);

        auto len_res = endian::from_big_compact(len_bytes, len64);
        if ( !len_res ) return len_res.error();

        if ( len64 <= kMaxShortListLen ) { // Must use short form if length <= 55
            return DecodingError::kNonCanonicalSize;
        }
#if SIZE_MAX < UINT64_MAX
        if ( len64 > static_cast<uint64_t>(std::numeric_limits<size_t>::max()) ) {
            return DecodingError::kOverflow;
        }
#endif
        h.payload_size_bytes = static_cast<size_t>(len64);
        v.remove_prefix(h.header_size_bytes); // Consume header + length bytes
    }

    // Final check: Is remaining data sufficient for the payload?
    // Exception: single byte literal case already checked implicitly by v not being empty.
    if ( b >= kShortStringOffset ) { // Check only if header was consumed
        if ( v.length() < h.payload_size_bytes ) {
            return DecodingError::kInputTooShort;
        }
    } else { // Single byte literal case, check original length implicitly via initial check
        if ( input_len < 1 ) return DecodingError::kInputTooShort; // Should be caught by initial empty check
    }


    return h;
}


} // namespace


// --- Constructor ---
RlpDecoder::RlpDecoder(ByteView data) noexcept : view_(data) {}

// --- State Checks ---
bool RlpDecoder::IsFinished() const noexcept {
    return view_.empty();
}

ByteView RlpDecoder::Remaining() const noexcept {
    return view_;
}

// --- Type Checks (Peek) ---
Result<bool> RlpDecoder::IsList() const noexcept {
    if ( view_.empty() ) return DecodingError::kInputTooShort;
    uint8_t b = view_[0];
    return (b >= kShortListOffset); // Covers short and long lists
}

Result<bool> RlpDecoder::IsString() const noexcept {
    if ( view_.empty() ) return DecodingError::kInputTooShort;
    uint8_t b = view_[0];
    return (b < kShortListOffset); // Covers single byte, short string, long string
}

Result<size_t> RlpDecoder::PeekPayloadSizeBytes() const noexcept {
    ByteView temp_view = view_; // Copy view to peek without consuming
    BOOST_OUTCOME_TRY(auto h, decode_header_impl(temp_view));
    return h.payload_size_bytes;
}

Result<Header> RlpDecoder::PeekHeader() const noexcept {
    ByteView temp_view = view_; // Copy view to peek without consuming
    return decode_header_impl(temp_view); // Decode from the copy
}

// --- Read Basic Types (Consume) ---

DecodingResult RlpDecoder::read(Bytes& out) noexcept {
    BOOST_OUTCOME_TRY(auto h, PeekHeader()); // Peek header first

    if ( h.list ) {
        return DecodingError::kUnexpectedList;
    }

    BOOST_OUTCOME_TRY(skip_header_internal()); // Consume header from member view_

    if ( view_.length() < h.payload_size_bytes ) return DecodingError::kInputTooShort; // Double check

    // Assign payload
    out.assign(reinterpret_cast<const uint8_t*>(view_.data()), h.payload_size_bytes);

    // Consume payload
    view_.remove_prefix(h.payload_size_bytes);

    return outcome::success(); // Success
}

// Explicit overload for uint256
DecodingResult RlpDecoder::read(intx::uint256& out) noexcept {
    return read_uint256(out); // Call private implementation detail
}

// --- List Handling (Consume) ---

Result<size_t> RlpDecoder::ReadListHeaderBytes() noexcept {
    BOOST_OUTCOME_TRY(auto h, PeekHeader()); // Peek first

    if ( !h.list ) {
        return DecodingError::kUnexpectedString;
    }

    BOOST_OUTCOME_TRY(skip_header_internal()); // Consume header from member view_

    return h.payload_size_bytes; // Return payload length in bytes
}

DecodingResult RlpDecoder::SkipItem() noexcept {
    BOOST_OUTCOME_TRY(auto h, PeekHeader()); // Peek first

    BOOST_OUTCOME_TRY(skip_header_internal()); // Consume header

    // Consume payload
    if ( view_.length() < h.payload_size_bytes ) return DecodingError::kInputTooShort;
    view_.remove_prefix(h.payload_size_bytes);

    return outcome::success(); // Success
}

// --- Internal Helpers (Implementation) ---

// Decodes header from VIEW 'v', advances 'v' past header bytes
Result<Header> RlpDecoder::decode_header_internal(ByteView& v) const noexcept {
    // Use the static helper implementation
    return decode_header_impl(v);
}

// Consumes header from MEMBER view_
DecodingResult RlpDecoder::skip_header_internal() noexcept {
    if ( view_.empty() ) return DecodingError::kInputTooShort;
    uint8_t b = view_[0];
    size_t header_len = 0;

    if ( b < kShortStringOffset ) { // Single byte literal
        header_len = 0; // No header bytes to skip
    } else if ( b <= kMaxShortStringLen + kShortStringOffset ) { // Short string
        header_len = 1;
    } else if ( b < kShortListOffset ) { // Long string
        header_len = 1 + (b - kLongStringOffset);
    } else if ( b <= kMaxShortListLen + kShortListOffset ) { // Short list
        header_len = 1;
    } else { // Long list
        header_len = 1 + (b - kLongListOffset);
    }

    if ( view_.length() < header_len ) return DecodingError::kInputTooShort;
    view_.remove_prefix(header_len);
    return outcome::success();
}


// --- Private Implementations for read ---
// Needs to be explicitly instantiated for common types if read<T> is non-template
// Since read<T> IS a template in the header, the implementation MUST be in the header.
// Let's remove the read_integral and read_uint256 private methods.
// The only non-template read methods needing impl here are read(Bytes&) and read(bool&).

    DecodingResult RlpDecoder::read_uint256(intx::uint256& out) noexcept {
    // *** Implement directly instead of calling public template ***
    BOOST_OUTCOME_TRY(auto h, PeekHeader()); // Peek first

    if ( h.list ) {
        return DecodingError::kUnexpectedList;
    }

    ByteView payload_view = view_.substr(h.header_size_bytes, h.payload_size_bytes); // View payload

    // Perform checks on payload_view before decoding
    if ( h.payload_size_bytes > 1 && payload_view[0] == 0 ) {
        return DecodingError::kLeadingZero;
    }
    // Allow 0x00 to decode to 0, handled by from_big_compact.
    // Redundant check removed for clarity.

    if ( h.payload_size_bytes > 32 ) { // Max bytes for uint256
        return DecodingError::kOverflow;
    }

    // Decode from payload_view using endian function
    BOOST_OUTCOME_TRY(endian::from_big_compact(payload_view, out));

    // Check canonical single byte encoding AFTER decoding value
    if ( h.payload_size_bytes == 1 && out < kRlpSingleByteThreshold ) {
        // Ensure header was just the byte itself (header_size_bytes == 0)
        if ( h.header_size_bytes > 0 ) {
            return DecodingError::kNonCanonicalSize;
        }
    }

    // Consume header + payload from the main view_
    if ( view_.length() < h.header_size_bytes + h.payload_size_bytes ) return DecodingError::kInputTooShort;
    view_.remove_prefix(h.header_size_bytes + h.payload_size_bytes);

    return outcome::success(); // Success
}

// --- Streaming Support Implementation ---

Result<ByteView> RlpDecoder::PeekPayload() const noexcept {
    BOOST_OUTCOME_TRY(auto h, PeekHeader());

    if (h.list) {
        return DecodingError::kUnexpectedList;
    }

    if (view_.length() < h.header_size_bytes + h.payload_size_bytes) {
        return DecodingError::kInputTooShort;
    }

    // Return view of payload without consuming
    return view_.substr(h.header_size_bytes, h.payload_size_bytes);
}


} // namespace rlp

Updated on 2026-04-13 at 23:22:46 -0700