Skip to content

protocol/rlpx/protocol/messages.cpp

Namespaces

Name
rlpx
rlpx::protocol

Functions

Name
uint8_t reason_to_byte(DisconnectReason reason)
DisconnectReason byte_to_reason(uint8_t byte)

Functions Documentation

function reason_to_byte

static uint8_t reason_to_byte(
    DisconnectReason reason
)

function byte_to_reason

static DisconnectReason byte_to_reason(
    uint8_t byte
)

Source code

#include <rlpx/protocol/messages.hpp>
#include <rlp/rlp_encoder.hpp>
#include <rlp/rlp_decoder.hpp>
#include <cstring>

namespace rlpx::protocol {

// Helper to convert DisconnectReason to uint8_t
static uint8_t reason_to_byte(DisconnectReason reason) noexcept {
    return static_cast<uint8_t>(reason);
}

// Helper to convert uint8_t to DisconnectReason
static DisconnectReason byte_to_reason(uint8_t byte) noexcept {
    return static_cast<DisconnectReason>(byte);
}

// HelloMessage implementation
Result<ByteBuffer> HelloMessage::encode() const noexcept {
    rlp::RlpEncoder encoder;

    // Hello message is an RLP list: [version, client_id, capabilities, port, node_id]
    if (!encoder.BeginList()) return SessionError::kInvalidMessage;

    // Protocol version
    if (!encoder.add(protocol_version)) return SessionError::kInvalidMessage;

    // Client ID (as string bytes)
    if (!encoder.add(detail::to_rlp_view(ByteView(
        reinterpret_cast<const uint8_t*>(client_id.data()), 
        client_id.size()
    )))) return SessionError::kInvalidMessage;

    // Capabilities (list of [name, version] pairs)
    if (!encoder.BeginList()) return SessionError::kInvalidMessage;
    for ( const auto& cap : capabilities ) {
        if (!encoder.BeginList()) return SessionError::kInvalidMessage;
        if (!encoder.add(detail::to_rlp_view(ByteView(
            reinterpret_cast<const uint8_t*>(cap.name.data()),
            cap.name.size()
        )))) return SessionError::kInvalidMessage;
        if (!encoder.add(cap.version)) return SessionError::kInvalidMessage;
        if (!encoder.EndList()) return SessionError::kInvalidMessage;
    }
    if (!encoder.EndList()) return SessionError::kInvalidMessage;

    // Listen port
    if (!encoder.add(listen_port)) return SessionError::kInvalidMessage;

    // Node ID (64 bytes) — encoded as RLP byte string to match go-ethereum's []byte encoding
    if (!encoder.add(detail::to_rlp_view(ByteView(node_id.data(), node_id.size())))) 
        return SessionError::kInvalidMessage;

    if (!encoder.EndList()) return SessionError::kInvalidMessage;

    auto result = encoder.GetBytes();
    if (!result) return SessionError::kInvalidMessage;
    return detail::from_rlp_bytes(*result.value());
}

Result<HelloMessage> HelloMessage::decode(ByteView rlp_data) noexcept {
    rlp::RlpDecoder decoder(detail::to_rlp_view(rlp_data));

    // Read the list header
    auto list_size_result = decoder.ReadListHeaderBytes();
    if ( !list_size_result ) {
        return SessionError::kInvalidMessage;
    }

    HelloMessage msg;

    // Read protocol version as bytes (to handle potential 0x00 case)
    rlp::Bytes version_bytes;
    auto version_read_result = decoder.read(version_bytes);
    if ( !version_read_result ) {
        return SessionError::kInvalidMessage;
    }
    msg.protocol_version = version_bytes.empty() ? 0 : version_bytes[0];

    // Read client ID
    rlp::Bytes client_id_bytes;
    auto client_id_read_result = decoder.read(client_id_bytes);
    if ( !client_id_read_result ) {
        return SessionError::kInvalidMessage;
    }
    msg.client_id = std::string(
        reinterpret_cast<const char*>(client_id_bytes.data()),
        client_id_bytes.size()
    );

    // Read capabilities list
    auto caps_list_size_result = decoder.ReadListHeaderBytes();
    if ( !caps_list_size_result ) {
        return SessionError::kInvalidMessage;
    }

    // Read each capability (which is itself a list of [name, version])
    while ( !decoder.IsFinished() ) {
        // Peek to see if this is still part of the capabilities list or the next field
        // We need to track how many items we've read
        // For simplicity, try to read a list and break if it's not a list
        auto cap_is_list = decoder.IsList();
        if ( !cap_is_list || !cap_is_list.value() ) {
            // Not a list - must be the listen_port
            break;
        }

        auto cap_list_size = decoder.ReadListHeaderBytes();
        if ( !cap_list_size ) {
            break;
        }

        Capability cap;

        // Read capability name
        rlp::Bytes name_bytes;
        if ( !decoder.read(name_bytes) ) {
            continue;
        }
        cap.name = std::string(
            reinterpret_cast<const char*>(name_bytes.data()),
            name_bytes.size()
        );

        // Read capability version as bytes
        rlp::Bytes ver_bytes;
        if ( !decoder.read(ver_bytes) ) {
            continue;
        }
        cap.version = ver_bytes.empty() ? 0 : ver_bytes[0];

        msg.capabilities.push_back(std::move(cap));
    }

    // Read listen port
    uint16_t port;
    auto port_read_result = decoder.read(port);
    if ( !port_read_result ) {
        return SessionError::kInvalidMessage;
    }
    msg.listen_port = port;

    // Read node ID (64 bytes) — go-ethereum encodes ID []byte as an RLP byte string
    rlp::Bytes id_bytes;
    if ( !decoder.read(id_bytes) || id_bytes.size() != kPublicKeySize ) {
        return SessionError::kInvalidMessage;
    }
    std::memcpy(msg.node_id.data(), id_bytes.data(), kPublicKeySize);

    return msg;
}

// DisconnectMessage implementation
Result<ByteBuffer> DisconnectMessage::encode() const noexcept {
    rlp::RlpEncoder encoder;

    // Disconnect message is a list with single element: [reason]
    if (!encoder.BeginList()) return SessionError::kInvalidMessage;
    if (!encoder.add(reason_to_byte(reason))) return SessionError::kInvalidMessage;
    if (!encoder.EndList()) return SessionError::kInvalidMessage;

    auto result = encoder.GetBytes();
    if (!result) return SessionError::kInvalidMessage;
    return detail::from_rlp_bytes(*result.value());
}

Result<DisconnectMessage> DisconnectMessage::decode(ByteView rlp_data) noexcept {
    // Many peers send Disconnect with no body (empty payload), an empty list [0xC0],
    // or a raw single byte rather than a proper RLP list [reason].  All are treated
    // as reason=0 (DisconnectRequested) so callers always get a valid message.
    DisconnectMessage msg{ DisconnectReason::kRequested };

    if (rlp_data.empty()) {
        return msg;  // reason = 0
    }

    rlp::RlpDecoder decoder(detail::to_rlp_view(rlp_data));

    auto list_size_result = decoder.ReadListHeaderBytes();
    if (!list_size_result) {
        // Not a list — treat first byte as raw reason code (non-standard but seen in the wild)
        msg.reason = byte_to_reason(static_cast<uint8_t>(rlp_data[0]));
        return msg;
    }

    if (list_size_result.value() == 0) {
        return msg;  // empty list [] — reason = 0
    }

    rlp::Bytes reason_bytes;
    if (!decoder.read(reason_bytes)) {
        return msg;  // can't read reason — default to 0
    }

    msg.reason = byte_to_reason(reason_bytes.empty() ? 0 : reason_bytes[0]);
    return msg;
}

// PingMessage implementation
Result<ByteBuffer> PingMessage::encode() const noexcept {
    rlp::RlpEncoder encoder;

    // Ping is an empty list
    if (!encoder.BeginList()) return SessionError::kInvalidMessage;
    if (!encoder.EndList()) return SessionError::kInvalidMessage;

    auto result = encoder.GetBytes();
    if (!result) return SessionError::kInvalidMessage;
    return detail::from_rlp_bytes(*result.value());
}

Result<PingMessage> PingMessage::decode(ByteView rlp_data) noexcept {
    // Ping is just an empty list - minimal validation
    return PingMessage{};
}

// PongMessage implementation
Result<ByteBuffer> PongMessage::encode() const noexcept {
    rlp::RlpEncoder encoder;

    // Pong is an empty list
    if (!encoder.BeginList()) return SessionError::kInvalidMessage;
    if (!encoder.EndList()) return SessionError::kInvalidMessage;

    auto result = encoder.GetBytes();
    if (!result) return SessionError::kInvalidMessage;
    return detail::from_rlp_bytes(*result.value());
}

Result<PongMessage> PongMessage::decode(ByteView rlp_data) noexcept {
    // Pong is just an empty list - minimal validation
    return PongMessage{};
}

} // namespace rlpx::protocol

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