Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions src/serde/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ add_library(enr
target_link_libraries(enr
cppcodec
p2p::libp2p
p2p::p2p_key_validator
)
192 changes: 134 additions & 58 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,36 +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>();
}
assert(rlp.empty());
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
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
add_subdirectory(app)
add_subdirectory(blockchain)
add_subdirectory(storage)
add_subdirectory(serde)
14 changes: 14 additions & 0 deletions tests/unit/serde/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#
# Copyright Quadrivium LLC
# All Rights Reserved
# SPDX-License-Identifier: Apache-2.0
#

addtest(enr_test
enr_test.cpp
)

target_link_libraries(enr_test
enr
)

Loading
Loading