#include <chainparams.h>
#include <hash.h>
#include <net.h>
#include <netmessagemaker.h>
#include <protocol.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <util/chaintype.h>
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <optional>
#include <vector>
namespace {
auto g_all_messages = ALL_NET_MESSAGE_TYPES;
void initialize_p2p_transport_serialization()
{
static ECC_Context ecc_context{};
SelectParams(ChainType::REGTEST);
std::sort(g_all_messages.begin(), g_all_messages.end());
}
}
FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serialization)
{
V1Transport recv_transport{NodeId{0}};
V1Transport send_transport{NodeId{1}};
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
auto checksum_assist = fuzzed_data_provider.ConsumeBool();
auto magic_bytes_assist = fuzzed_data_provider.ConsumeBool();
std::vector<uint8_t> mutable_msg_bytes;
auto header_bytes_remaining = CMessageHeader::HEADER_SIZE;
if (magic_bytes_assist) {
auto msg_start = Params().MessageStart();
for (size_t i = 0; i < CMessageHeader::MESSAGE_SIZE_SIZE; ++i) {
mutable_msg_bytes.push_back(msg_start[i]);
}
header_bytes_remaining -= CMessageHeader::MESSAGE_SIZE_SIZE;
}
if (checksum_assist) {
header_bytes_remaining -= CMessageHeader::CHECKSUM_SIZE;
}
auto header_random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(header_bytes_remaining);
mutable_msg_bytes.insert(mutable_msg_bytes.end(), header_random_bytes.begin(), header_random_bytes.end());
auto payload_bytes = fuzzed_data_provider.ConsumeRemainingBytes<uint8_t>();
if (checksum_assist && mutable_msg_bytes.size() == CMessageHeader::CHECKSUM_OFFSET) {
CHash256 hasher;
unsigned char hsh[32];
hasher.Write(payload_bytes);
hasher.Finalize(hsh);
for (size_t i = 0; i < CMessageHeader::CHECKSUM_SIZE; ++i) {
mutable_msg_bytes.push_back(hsh[i]);
}
}
mutable_msg_bytes.insert(mutable_msg_bytes.end(), payload_bytes.begin(), payload_bytes.end());
std::span<const uint8_t> msg_bytes{mutable_msg_bytes};
while (msg_bytes.size() > 0) {
if (!recv_transport.ReceivedBytes(msg_bytes)) {
break;
}
if (recv_transport.ReceivedMessageComplete()) {
const std::chrono::microseconds m_time{std::numeric_limits<int64_t>::max()};
bool reject_message{false};
CNetMessage msg = recv_transport.GetReceivedMessage(m_time, reject_message);
assert(msg.m_type.size() <= CMessageHeader::MESSAGE_TYPE_SIZE);
assert(msg.m_raw_message_size <= mutable_msg_bytes.size());
assert(msg.m_raw_message_size == CMessageHeader::HEADER_SIZE + msg.m_message_size);
assert(msg.m_time == m_time);
std::vector<unsigned char> header;
auto msg2 = NetMsg::Make(msg.m_type, std::span{msg.m_recv});
bool queued = send_transport.SetMessageToSend(msg2);
assert(queued);
std::optional<bool> known_more;
while (true) {
const auto& [to_send, more, _msg_type] = send_transport.GetBytesToSend(false);
if (known_more) assert(!to_send.empty() == *known_more);
if (to_send.empty()) break;
send_transport.MarkBytesSent(to_send.size());
known_more = more;
}
}
}
}
namespace {
template<RandomNumberGenerator R>
void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider)
{
const std::array<Transport*, 2> transports = {&initiator, &responder};
std::array<std::vector<uint8_t>, 2> in_flight;
std::array<std::deque<CSerializedNetMsg>, 2> expected;
std::array<std::vector<uint8_t>, 2> to_send;
std::array<std::optional<bool>, 2> last_more, last_more_next;
std::array<std::optional<bool>, 2> expect_more, expect_more_next;
auto msg_type_fn = [&]() {
uint8_t v = provider.ConsumeIntegral<uint8_t>();
if (v == 0xFF) {
std::string ret;
while (ret.size() < CMessageHeader::MESSAGE_TYPE_SIZE) {
char c = provider.ConsumeIntegral<char>();
if (c < ' ' || c > 0x7E) break;
ret += c;
}
return ret;
} else {
return g_all_messages[v % g_all_messages.size()];
}
};
auto make_msg_fn = [&](bool first) {
CSerializedNetMsg msg;
if (first) {
msg.m_type = "version";
} else {
msg.m_type = msg_type_fn();
}
size_t size = provider.ConsumeIntegralInRange<uint32_t>(0, 75000);
msg.data = rng.randbytes(size);
return msg;
};
std::array<CSerializedNetMsg, 2> next_msg = {
make_msg_fn(true),
make_msg_fn(true)
};
auto bytes_to_send_fn = [&](int side) -> Transport::BytesToSend {
const auto& [bytes, more_nonext, msg_type] = transports[side]->GetBytesToSend(false);
const auto& [bytes_next, more_next, msg_type_next] = transports[side]->GetBytesToSend(true);
if (expect_more[side].has_value()) assert(!bytes.empty() == *expect_more[side]);
assert(std::ranges::equal(bytes, bytes_next));
assert(msg_type == msg_type_next);
if (more_nonext) assert(more_next);
assert(to_send[side].size() <= bytes.size());
assert(std::ranges::equal(to_send[side], std::span{bytes}.first(to_send[side].size())));
to_send[side].resize(bytes.size());
std::copy(bytes.begin(), bytes.end(), to_send[side].begin());
last_more[side] = {more_nonext};
last_more_next[side] = {more_next};
return {bytes, more_nonext, msg_type};
};
auto new_msg_fn = [&](int side) {
if (expected[side].size() >= 16) return;
CSerializedNetMsg msg = next_msg[side].Copy();
bool queued = transports[side]->SetMessageToSend(msg);
expect_more[side] = expect_more_next[side];
expect_more_next[side] = std::nullopt;
bytes_to_send_fn(side);
if (queued) {
expected[side].emplace_back(std::move(next_msg[side]));
next_msg[side] = make_msg_fn(false);
}
};
auto send_fn = [&](int side, bool everything = false) {
const auto& [bytes, more, msg_type] = bytes_to_send_fn(side);
if (bytes.empty()) return false;
size_t send_now = everything ? bytes.size() : provider.ConsumeIntegralInRange<size_t>(0, bytes.size());
if (send_now == 0) return false;
in_flight[side].insert(in_flight[side].end(), bytes.begin(), bytes.begin() + send_now);
transports[side]->MarkBytesSent(send_now);
if (send_now == bytes.size()) {
expect_more[side] = last_more[side];
expect_more_next[side] = last_more_next[side];
}
assert(to_send[side].size() >= send_now);
to_send[side].erase(to_send[side].begin(), to_send[side].begin() + send_now);
bytes_to_send_fn(side);
return send_now > 0;
};
auto recv_fn = [&](int side, bool everything = false) {
if (in_flight[side].empty()) return false;
size_t to_recv_len = in_flight[side].size();
if (!everything) to_recv_len = provider.ConsumeIntegralInRange<size_t>(0, to_recv_len);
std::span<const uint8_t> to_recv = std::span{in_flight[side]}.first(to_recv_len);
while (!to_recv.empty()) {
size_t old_len = to_recv.size();
bool ret = transports[!side]->ReceivedBytes(to_recv);
assert(ret);
if (expect_more[!side] == false) expect_more[!side] = std::nullopt;
if (expect_more_next[!side] == false) expect_more_next[!side] = std::nullopt;
bytes_to_send_fn(!side);
bool progress = to_recv.size() < old_len;
if (transports[!side]->ReceivedMessageComplete()) {
bool reject{false};
auto received = transports[!side]->GetReceivedMessage({}, reject);
assert(!reject);
assert(!expected[side].empty());
assert(received.m_message_size == received.m_recv.size());
assert(received.m_type == expected[side].front().m_type);
assert(std::ranges::equal(received.m_recv, MakeByteSpan(expected[side].front().data)));
expected[side].pop_front();
progress = true;
}
assert(progress);
}
in_flight[side].erase(in_flight[side].begin(), in_flight[side].begin() + to_recv_len);
return to_recv_len > 0;
};
LIMITED_WHILE(provider.remaining_bytes(), 1000) {
CallOneOf(provider,
[&] { new_msg_fn(0); },
[&] { new_msg_fn(1); },
[&] { send_fn(0); },
[&] { send_fn(1); },
[&] { recv_fn(0); },
[&] { recv_fn(1); }
);
}
while (true) {
bool any = false;
if (send_fn(0, true)) any = true;
if (send_fn(1, true)) any = true;
if (recv_fn(0, true)) any = true;
if (recv_fn(1, true)) any = true;
if (!any) break;
}
assert(in_flight[0].empty());
assert(in_flight[1].empty());
assert(expected[0].empty());
assert(expected[1].empty());
assert(transports[0]->GetInfo().session_id == transports[1]->GetInfo().session_id);
}
std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept
{
return std::make_unique<V1Transport>(nodeid);
}
template<RandomNumberGenerator RNG>
std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider)
{
auto key = ConsumePrivateKey(provider);
if (!key.IsValid()) return {};
size_t garb_len = provider.ConsumeIntegralInRange<size_t>(0, V2Transport::MAX_GARBAGE_LEN);
std::vector<uint8_t> garb;
if (garb_len <= 64) {
garb = provider.ConsumeBytes<uint8_t>(garb_len);
garb.resize(garb_len);
} else {
garb = rng.randbytes(garb_len);
}
auto ent = provider.ConsumeBytes<std::byte>(32);
ent.resize(32);
CSHA256().Write(UCharCast(ent.data()), ent.size())
.Write(garb.data(), garb.size())
.Finalize(UCharCast(ent.data()));
return std::make_unique<V2Transport>(nodeid, initiator, key, ent, std::move(garb));
}
}
FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serialization)
{
FuzzedDataProvider provider{buffer.data(), buffer.size()};
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
auto t1 = MakeV1Transport(NodeId{0});
auto t2 = MakeV1Transport(NodeId{1});
if (!t1 || !t2) return;
SimulationTest(*t1, *t2, rng, provider);
}
FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_serialization)
{
FuzzedDataProvider provider{buffer.data(), buffer.size()};
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider);
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
if (!t1 || !t2) return;
SimulationTest(*t1, *t2, rng, provider);
}
FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_serialization)
{
FuzzedDataProvider provider{buffer.data(), buffer.size()};
InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
auto t1 = MakeV1Transport(NodeId{0});
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
if (!t1 || !t2) return;
SimulationTest(*t1, *t2, rng, provider);
}