rlp/rlp_decoder.hpp
Namespaces
Classes
Source code
#ifndef RLP_DECODER_HPP
#define RLP_DECODER_HPP
#include <vector>
#include <boost/core/span.hpp>
#include "common.hpp"
#include "intx.hpp"
#include "endian.hpp"
namespace rlp {
class RlpDecoder {
public:
explicit RlpDecoder(ByteView data) noexcept;
// --- State Checks ---
[[nodiscard]] bool IsFinished() const noexcept; // No more data left
[[nodiscard]] ByteView Remaining() const noexcept; // View of remaining data
// --- Type Checks (Peek) ---
[[nodiscard]] Result<bool> IsList() const noexcept; // Is the next item a list?
[[nodiscard]] Result<bool> IsString() const noexcept; // Is the next item a string?
[[nodiscard]] Result<size_t> PeekPayloadSizeBytes() const noexcept; // Get next item's payload size in bytes
[[nodiscard]] Result<Header> PeekHeader() const noexcept; // Get full header info
// --- Read Basic Types (Consume) ---
// Returns error or success. Value is output parameter.
DecodingResult read(Bytes& out) noexcept; // Read next item as bytes (string payload)
DecodingResult read(intx::uint256& out) noexcept; // Explicit overload for uint256
// Template read for integral types and uint256
template <typename T>
auto read(T& out) noexcept -> std::enable_if_t<is_unsigned_integral_v<T> || std::is_same_v<T, intx::uint256> || std::is_same_v<T, bool>, DecodingResult> {
if constexpr (std::is_same_v<T, intx::uint256>) {
return read(static_cast<intx::uint256&>(out)); // Call explicit uint256 overload
} else if constexpr (std::is_same_v<T, bool>) {
return read_bool(out);
} else {
return read_integral(out);
}
}
// --- List Handling (Consume) ---
// Reads *only* the list header, returns payload length in bytes, consumes header bytes
// Name follows C++ span/ranges convention: size_bytes() for byte count
[[nodiscard]] Result<size_t> ReadListHeaderBytes() noexcept;
// Skips the next complete RLP item (header + payload)
DecodingResult SkipItem() noexcept;
// --- Static Convenience Methods ---
// Static method that decodes from ByteView and returns Result<T>
// Named 'decode' to distinguish from instance method 'read'
template <typename T>
static auto decode(ByteView& data, Leftover leftover = Leftover::kProhibit) noexcept -> std::enable_if_t<
is_rlp_decodable_v<T>,
Result<T>
>
{
T value;
RlpDecoder decoder(data);
auto result = decoder.read(value);
if (!result) {
return result.error();
}
data = decoder.Remaining();
if (leftover == Leftover::kProhibit && !decoder.IsFinished()) {
return DecodingError::kInputTooLong;
}
return value;
}
// Helper method for reading with a specified ByteView and leftover handling
template <typename T>
auto read(ByteView& data, T& out, Leftover leftover = Leftover::kProhibit) noexcept -> std::enable_if_t<is_unsigned_integral_v<T> || std::is_same_v<T, bool> || std::is_same_v<T, intx::uint256>, DecodingResult>
{
RlpDecoder temp_decoder(data);
BOOST_OUTCOME_TRY(auto h, temp_decoder.PeekHeader());
ByteView payload_view = temp_decoder.view_.substr(h.header_size_bytes, h.payload_size_bytes);
BOOST_OUTCOME_TRY(temp_decoder.check_payload<T>(h, payload_view, temp_decoder.view_));
if constexpr (std::is_same_v<T, bool>)
{
if ( h.payload_size_bytes == 1 )
{
if ( payload_view[0] == 1 )
{
out = true;
temp_decoder.view_.remove_prefix(h.header_size_bytes + h.payload_size_bytes);
}
else
{
BOOST_OUTCOME_TRY(temp_decoder.SkipItem());
return DecodingError::kOverflow;
}
}
else if ( h.payload_size_bytes == 0 )
{
if ( h.header_size_bytes == 1 && temp_decoder.view_[0] == kEmptyStringCode )
{
out = false;
temp_decoder.view_.remove_prefix(1);
}
else
{
BOOST_OUTCOME_TRY(temp_decoder.SkipItem());
return DecodingError::kOverflow;
}
}
else
{
BOOST_OUTCOME_TRY(temp_decoder.SkipItem());
return DecodingError::kOverflow;
}
}
else
{
BOOST_OUTCOME_TRY(endian::from_big_compact(payload_view, out));
temp_decoder.view_.remove_prefix(h.header_size_bytes + h.payload_size_bytes);
}
data = temp_decoder.Remaining();
if ( leftover == Leftover::kProhibit && !temp_decoder.IsFinished() )
{
return DecodingError::kInputTooLong;
}
return outcome::success();
}
template <typename T>
auto check_payload(Header& h, ByteView& payload_view, ByteView& view) noexcept -> std::enable_if_t<is_unsigned_integral_v<T> || std::is_same_v<T, bool> || std::is_same_v<T, intx::uint256>, DecodingResult>
{
if ( h.list )
{
return DecodingError::kUnexpectedList;
}
if ( h.payload_size_bytes > 1 && payload_view[0] == 0 )
{
return DecodingError::kLeadingZero;
}
if ( h.payload_size_bytes == 1 && payload_view[0] == 0 && static_cast<uint8_t>(T{0U}) >= kRlpSingleByteThreshold )
{
return DecodingError::kLeadingZero;
}
if ( h.payload_size_bytes > sizeof(T) )
{
if constexpr (sizeof(T) < 32)
{
if ( h.payload_size_bytes > sizeof(T) )
{
return DecodingError::kOverflow;
}
}
else
{
if ( h.payload_size_bytes > 32 )
{
return DecodingError::kOverflow;
}
}
}
if ( view.length() < h.header_size_bytes + h.payload_size_bytes )
{
return DecodingError::kInputTooShort;
}
if constexpr (!std::is_same_v<T, bool>)
{
T temp_out;
BOOST_OUTCOME_TRY(endian::from_big_compact(payload_view, temp_out));
if ( h.payload_size_bytes == 1 && static_cast<uint8_t>(temp_out) < kRlpSingleByteThreshold )
{
if ( h.header_size_bytes > 0 )
{
return DecodingError::kNonCanonicalSize;
}
}
}
return outcome::success();
}
// --- Convenience for Vectors (Consume) ---
// Reads a complete list assuming all items are of type T
// Note: Implementation needs to be here or in .ipp due to template
template <typename T>
DecodingResult read_vector(std::vector<T>& vec) noexcept {
BOOST_OUTCOME_TRY(size_t payload_len, ReadListHeaderBytes());
vec.clear();
ByteView list_payload = view_.substr(0, payload_len); // View only the list payload
ByteView original_list_payload = list_payload; // To check consumption
while ( !list_payload.empty() ) {
vec.emplace_back();
// Use the main read<T> method, allowing leftovers within the list payload view
auto read_res = read(list_payload, vec.back(), Leftover::kAllow);
if ( !read_res ) {
vec.pop_back(); // Clean up failed element
return read_res.error();
}
}
// Check if the entire list payload was consumed
if ( !list_payload.empty() ){
// This implies an error during item decoding that wasn't caught?
return DecodingError::kListLengthMismatch;
}
// Consume the list payload from the main view
if ( view_.length() < payload_len ) return DecodingError::kInputTooShort; // Should not happen
view_.remove_prefix(payload_len);
return outcome::success(); // Success
}
// --- Convenience for Fixed-Size Arrays/Spans (Consume) ---
// Reads next item into a fixed-size span/array
template <size_t N>
DecodingResult read(boost::span<uint8_t, N> out_span) noexcept {
BOOST_OUTCOME_TRY(auto h, PeekHeader()); // Peek first
if ( h.list ) {
return DecodingError::kUnexpectedList;
}
bool single_byte_literal = (h.payload_size_bytes == 1 && h.header_size_bytes == 0);
if ( h.payload_size_bytes != N ) {
// Allow decoding single-byte literal into span<byte, 1>
if ( !(N == 1 && single_byte_literal) ) {
return rlp::DecodingError::kUnexpectedLength;
}
}
// Now consume header (if any) and payload
BOOST_OUTCOME_TRY(skip_header_internal()); // Consume header
if (view_.length() < N) {
return DecodingError::kInputTooShort; // Not enough payload data
}
std::memcpy(out_span.data(), view_.data(), N);
view_.remove_prefix(N); // Consume payload
return outcome::success();
}
template <size_t N>
DecodingResult read(std::array<uint8_t, N>& out_array) noexcept {
return read<N>(boost::span<uint8_t, N>{out_array});
}
template <size_t N>
DecodingResult read(uint8_t (&out_c_array)[N]) noexcept {
return read<N>(boost::span<uint8_t, N>{out_c_array});
}
// --- Streaming Support for Large Payloads ---
// Get payload as ByteView without copying (for streaming)
Result<ByteView> PeekPayload() const noexcept;
private:
ByteView view_{}; // The remaining data to be decoded
// --- Internal Template Implementation for Integrals ---
// Needs to be in header if read<T> is public template method
template <typename T>
auto read_integral(T& out) noexcept -> std::enable_if_t<is_unsigned_integral_v<T>, DecodingResult> {
BOOST_OUTCOME_TRY(auto h, PeekHeader());
if (h.list) {
return DecodingError::kUnexpectedList;
}
// Check for leading zeros (except for zero value)
if (h.payload_size_bytes > 1) {
ByteView payload_view = view_.substr(h.header_size_bytes, h.payload_size_bytes);
if (payload_view[0] == 0) {
return DecodingError::kLeadingZero;
}
}
// Check for overflow
if (h.payload_size_bytes > sizeof(T)) {
return DecodingError::kOverflow;
}
// Check for non-canonical single byte encoding
if (h.payload_size_bytes == 1 && h.header_size_bytes > 0) {
ByteView payload_view = view_.substr(h.header_size_bytes, h.payload_size_bytes);
if (payload_view[0] < kRlpSingleByteThreshold) {
return DecodingError::kNonCanonicalSize;
}
}
// Check input length
if (view_.length() < h.header_size_bytes + h.payload_size_bytes) {
return DecodingError::kInputTooShort;
}
// Extract payload and decode
ByteView payload_view = view_.substr(h.header_size_bytes, h.payload_size_bytes);
BOOST_OUTCOME_TRY(endian::from_big_compact(payload_view, out));
// Consume the item
view_.remove_prefix(h.header_size_bytes + h.payload_size_bytes);
return outcome::success();
}
DecodingResult read_bool(bool& out) noexcept {
BOOST_OUTCOME_TRY(auto h, PeekHeader());
if (h.list) {
return DecodingError::kUnexpectedList;
}
// Check input length
if (view_.length() < h.header_size_bytes + h.payload_size_bytes) {
return DecodingError::kInputTooShort;
}
if (h.payload_size_bytes == 1) {
ByteView payload_view = view_.substr(h.header_size_bytes, h.payload_size_bytes);
if (payload_view[0] == 1) {
out = true;
} else if (payload_view[0] == 0) {
out = false;
} else {
return DecodingError::kOverflow;
}
} else if (h.payload_size_bytes == 0 && h.header_size_bytes == 1 && view_[0] == kEmptyStringCode) {
out = false;
} else {
return DecodingError::kOverflow;
}
// Consume the item
view_.remove_prefix(h.header_size_bytes + h.payload_size_bytes);
return outcome::success();
}
DecodingResult read_uint256(intx::uint256& out) noexcept;
// --- Internal Helpers (Declaration only) ---
// Implementations will be in rlp_decoder.cpp
Result<Header> decode_header_internal(ByteView& v) const noexcept;
DecodingResult skip_header_internal() noexcept; // Consumes header from member view_
};
} // namespace rlp
#endif // RLP_DECODER_HPP
Updated on 2026-04-13 at 23:22:46 -0700