Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 133 additions & 63 deletions src/serde/enr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,48 @@ namespace lean::rlp {
constexpr uint8_t kBytesPrefix1 = 0x80;
constexpr uint8_t kListPrefix1 = 0xc0;

enum class Error {
INVALID_RLP,
INT_OVERFLOW,
INVALID_KEY_VALUE,
KEY_NOT_FOUND,
};
Q_ENUM_ERROR_CODE(Error) {
using E = decltype(e);
switch (e) {
case E::INVALID_RLP:
return "Invalid rlp";
case E::INT_OVERFLOW:
return "Int overflow";
case E::INVALID_KEY_VALUE:
return "Invalid key-value";
case E::KEY_NOT_FOUND:
return "Key not found";
}
abort();
}

struct Decoder {
qtils::BytesIn input_;

bool empty() const {
return input_.empty();
}

qtils::BytesIn _take(size_t n) {
assert(n <= input_.size());
outcome::result<qtils::BytesIn> take(size_t n) {
if (n > input_.size()) {
return Error::INVALID_RLP;
}
auto r = input_.first(n);
input_ = input_.subspan(n);
return r;
}

template <typename T>
static T _uint(qtils::BytesIn be) {
static outcome::result<T> _uint(qtils::BytesIn be) {
if (be.size() * 8 > std::numeric_limits<int>::digits) {
return Error::INT_OVERFLOW;
}
T v = 0;
for (auto &x : be) {
v = (v << 8) | x;
Expand All @@ -41,75 +67,100 @@ namespace lean::rlp {
}

template <uint8_t base1>
qtils::BytesIn _bytes() {
outcome::result<qtils::BytesIn> _bytes() {
constexpr auto base2 = base1 + kMaxPrefix1;
assert(base1 <= input_[0]);
if (base1 > input_[0]) {
return Error::INVALID_RLP;
}
if (input_[0] <= base2) {
auto n = input_[0] - base1;
_take(1);
return _take(n);
BOOST_OUTCOME_TRY(take(1));
return take(n);
}
auto n1 = input_[0] - base2;
_take(1);
auto n2 = _uint<size_t>(_take(n1));
return _take(n2);
BOOST_OUTCOME_TRY(take(1));
BOOST_OUTCOME_TRY(auto n2_raw, take(n1));
BOOST_OUTCOME_TRY(auto n2, _uint<size_t>(n2_raw));
return take(n2);
}

bool is_list() const {
return not empty() and kListPrefix1 <= input_[0];
}

Decoder list() {
assert(not empty());
return Decoder{_bytes<kListPrefix1>()};
outcome::result<Decoder> list() {
if (empty()) {
return Error::INVALID_RLP;
}
BOOST_OUTCOME_TRY(auto raw, _bytes<kListPrefix1>());
return Decoder{raw};
}

qtils::BytesIn bytes() {
assert(not empty());
assert(input_[0] < kListPrefix1);
outcome::result<qtils::BytesIn> bytes() {
if (empty()) {
return Error::INVALID_RLP;
}
if (input_[0] >= kListPrefix1) {
return Error::INVALID_RLP;
}
if (input_[0] < kBytesPrefix1) {
return _take(1);
return take(1);
}
return _bytes<kBytesPrefix1>();
}

template <typename T>
requires std::is_default_constructible_v<T>
and requires(T t) { qtils::BytesOut{t}; }
T bytes_n() {
auto raw = bytes();
outcome::result<T> bytes_n() {
BOOST_OUTCOME_TRY(auto raw, bytes());
T r;
assert(raw.size() == r.size());
if (raw.size() != r.size()) {
return Error::INVALID_RLP;
}
memcpy(r.data(), raw.data(), r.size());
return r;
}

template <size_t N>
qtils::ByteArr<N> bytes_n() {
outcome::result<qtils::ByteArr<N>> bytes_n() {
return bytes_n<qtils::ByteArr<N>>();
}

std::string_view str() {
return qtils::byte2str(bytes());
}

void str(std::string_view expected) {
auto actual = str();
assert(actual == expected);
outcome::result<std::string_view> str() {
BOOST_OUTCOME_TRY(auto raw, bytes());
return qtils::byte2str(raw);
}

template <typename T>
T uint() {
auto be = bytes();
outcome::result<T> uint() {
BOOST_OUTCOME_TRY(auto be, bytes());
return _uint<T>(be);
}

void skip() {
outcome::result<void> skip() {
if (is_list()) {
list();
BOOST_OUTCOME_TRY(list());
} else {
bytes();
BOOST_OUTCOME_TRY(bytes());
}
return outcome::success();
}

using KeyValue = std::unordered_map<std::string_view, Decoder>;
outcome::result<KeyValue> keyValue() {
KeyValue kv;
while (not empty()) {
BOOST_OUTCOME_TRY(auto key, str());
if (empty()) {
return Error::INVALID_KEY_VALUE;
}
auto value = input_;
BOOST_OUTCOME_TRY(skip());
value = value.first(value.size() - input_.size());
kv.emplace(key, Decoder{value});
}
return kv;
}
};

Expand Down Expand Up @@ -200,6 +251,21 @@ namespace lean::rlp {
} // namespace lean::rlp

namespace lean::enr {
enum class Error {
INVALID_PREFIX,
INVALID_ID,
};
Q_ENUM_ERROR_CODE(Error) {
using E = decltype(e);
switch (e) {
case E::INVALID_PREFIX:
return "Invalid ENR prefix";
case E::INVALID_ID:
return "Invalid ENR id";
}
abort();
}

libp2p::PeerId Enr::peerId() const {
return libp2p::peerIdFromSecp256k1(public_key);
}
Expand Down Expand Up @@ -227,42 +293,46 @@ namespace lean::enr {
return {peerId(), {connectAddress()}};
}

Enr decode(std::string_view str) {
outcome::result<Enr> decode(std::string_view str) {
constexpr std::string_view s_enr{"enr:"};
assert(str.starts_with(s_enr));
if (not str.starts_with(s_enr)) {
return Error::INVALID_PREFIX;
}
str.remove_prefix(s_enr.size());
auto rlp_bytes = cppcodec::base64_url_unpadded::decode(str);
rlp::Decoder rlp{rlp_bytes};
rlp = rlp.list();
BOOST_OUTCOME_TRY(rlp, rlp.list());
Enr enr;
enr.signature = rlp.bytes_n<Secp256k1Signature>();
enr.sequence = rlp.uint<Sequence>();
std::string_view key;
key = rlp.str();
while (key != "id") {
rlp.skip();
key = rlp.str();
}
assert(key == "id");
rlp.str("v4");
key = rlp.str();
if (key == "ip") {
enr.ip = rlp.bytes_n<Ip>();
key = rlp.str();
}
assert(key == "secp256k1");
enr.public_key = rlp.bytes_n<Secp256k1PublicKey>();
if (not rlp.empty()) {
rlp.str("udp");
enr.port = rlp.uint<Port>();
}
// Parse remaining key/value pairs
while (!rlp.empty()) {
// Unknown key: skip its value
auto logger = libp2p::log::createLogger("ENR");
SL_WARN(logger, "Skipping unknown ENR key '{}'", key);
rlp.skip();
BOOST_OUTCOME_TRY(enr.signature, rlp.bytes_n<Secp256k1Signature>());
BOOST_OUTCOME_TRY(enr.sequence, rlp.uint<Sequence>());
BOOST_OUTCOME_TRY(auto kv, rlp.keyValue());

auto kv_id = kv.find("id");
if (kv_id == kv.end()) {
return rlp::Error::KEY_NOT_FOUND;
}
BOOST_OUTCOME_TRY(auto id, kv_id->second.str());
if (id != "v4") {
return Error::INVALID_ID;
}

auto kv_ip = kv.find("ip");
if (kv_ip != kv.end()) {
BOOST_OUTCOME_TRY(enr.ip, kv_ip->second.bytes_n<Ip>());
}

auto kv_secp256k1 = kv.find("secp256k1");
if (kv_secp256k1 == kv.end()) {
return rlp::Error::KEY_NOT_FOUND;
}
BOOST_OUTCOME_TRY(enr.public_key,
kv_secp256k1->second.bytes_n<Secp256k1PublicKey>());

auto kv_udp = kv.find("udp");
if (kv_udp != kv.end()) {
BOOST_OUTCOME_TRY(enr.port, kv_udp->second.uint<Port>());
}

return enr;
}

Expand Down
2 changes: 1 addition & 1 deletion src/serde/enr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace lean::enr {
libp2p::PeerInfo connectInfo() const;
};

Enr decode(std::string_view str);
outcome::result<Enr> decode(std::string_view str);

std::string encode(const Secp256k1PublicKey &public_key, Port port);
} // namespace lean::enr
10 changes: 7 additions & 3 deletions tests/unit/serde/enr_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <algorithm>
#include <array>

#include <qtils/test/outcome.hpp>

using lean::enr::Enr;
using lean::enr::Ip;
using lean::enr::Port;
Expand Down Expand Up @@ -41,7 +43,7 @@ TEST(EnrTest, EncodeDecodeRoundTrip) {
// Encoded string must start with "enr:"
ASSERT_TRUE(encoded.rfind("enr:", 0) == 0) << encoded;

auto enr = lean::enr::decode(encoded);
ASSERT_OUTCOME_SUCCESS(enr, lean::enr::decode(encoded));

// Basic fields
EXPECT_EQ(enr.sequence, 1u);
Expand Down Expand Up @@ -90,10 +92,12 @@ TEST(EnrTest, DeterministicEncoding) {

TEST(EnrTest, DecodeGivenEnrAddress) {
// Provided ENR string from user request
const char *addr =
// clang-format off
std::string_view addr =
"enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg";
// clang-format on

auto enr = lean::enr::decode(addr);
ASSERT_OUTCOME_SUCCESS(enr, lean::enr::decode(addr));

// Sequence should be positive
EXPECT_GT(enr.sequence, 0u);
Expand Down
4 changes: 2 additions & 2 deletions vcpkg-overlay/qtils/portfile.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO qdrvm/qtils
REF refs/tags/v0.1.4
SHA512 124f3711eb64df3a2e207bff8bf953ccc2dfa838f21da72a1cc77c8aec95def350e70607adf9d8e7123e56d5bffcf830052f607dfa12badc7efe463bd0be747c
REF refs/tags/v0.1.5
SHA512 12fe763fdfab70bb90fb8687efdde63bb0b04c0e3f50efea73115997ed278892e99e2b49fa13b9fa80d6e4740dc0c9942c16162c31ab3890e4eb09e1f7a81bc4
)
vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}")
vcpkg_cmake_install()
Expand Down
Loading