protocol/rlpx/protocol/messages.cpp
Namespaces
Functions
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