|
9 | 9 | #include <protocol.h>
|
10 | 10 | #include <test/fuzz/FuzzedDataProvider.h>
|
11 | 11 | #include <test/fuzz/fuzz.h>
|
| 12 | +#include <test/fuzz/util.h> |
| 13 | +#include <test/util/xoroshiro128plusplus.h> |
12 | 14 | #include <util/chaintype.h>
|
13 | 15 |
|
14 | 16 | #include <cassert>
|
|
17 | 19 | #include <optional>
|
18 | 20 | #include <vector>
|
19 | 21 |
|
| 22 | +namespace { |
| 23 | + |
| 24 | +std::vector<std::string> g_all_messages; |
| 25 | + |
20 | 26 | void initialize_p2p_transport_serialization()
|
21 | 27 | {
|
22 | 28 | SelectParams(ChainType::REGTEST);
|
| 29 | + g_all_messages = getAllNetMessageTypes(); |
| 30 | + std::sort(g_all_messages.begin(), g_all_messages.end()); |
23 | 31 | }
|
24 | 32 |
|
| 33 | +} // namespace |
| 34 | + |
25 | 35 | FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serialization)
|
26 | 36 | {
|
27 | 37 | // Construct transports for both sides, with dummy NodeIds.
|
@@ -92,3 +102,234 @@ FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serial
|
92 | 102 | }
|
93 | 103 | }
|
94 | 104 | }
|
| 105 | + |
| 106 | +namespace { |
| 107 | + |
| 108 | +template<typename R> |
| 109 | +void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider) |
| 110 | +{ |
| 111 | + // Simulation test with two Transport objects, which send messages to each other, with |
| 112 | + // sending and receiving fragmented into multiple pieces that may be interleaved. It primarily |
| 113 | + // verifies that the sending and receiving side are compatible with each other, plus a few |
| 114 | + // sanity checks. It does not attempt to introduce errors in the communicated data. |
| 115 | + |
| 116 | + // Put the transports in an array for by-index access. |
| 117 | + const std::array<Transport*, 2> transports = {&initiator, &responder}; |
| 118 | + |
| 119 | + // Two vectors representing in-flight bytes. inflight[i] is from transport[i] to transport[!i]. |
| 120 | + std::array<std::vector<uint8_t>, 2> in_flight; |
| 121 | + |
| 122 | + // Two queues with expected messages. expected[i] is expected to arrive in transport[!i]. |
| 123 | + std::array<std::deque<CSerializedNetMsg>, 2> expected; |
| 124 | + |
| 125 | + // Vectors with bytes last returned by GetBytesToSend() on transport[i]. |
| 126 | + std::array<std::vector<uint8_t>, 2> to_send; |
| 127 | + |
| 128 | + // Last returned 'more' values (if still relevant) by transport[i]->GetBytesToSend(). |
| 129 | + std::array<std::optional<bool>, 2> last_more; |
| 130 | + |
| 131 | + // Whether more bytes to be sent are expected on transport[i]. |
| 132 | + std::array<std::optional<bool>, 2> expect_more; |
| 133 | + |
| 134 | + // Function to consume a message type. |
| 135 | + auto msg_type_fn = [&]() { |
| 136 | + uint8_t v = provider.ConsumeIntegral<uint8_t>(); |
| 137 | + if (v == 0xFF) { |
| 138 | + // If v is 0xFF, construct a valid (but possibly unknown) message type from the fuzz |
| 139 | + // data. |
| 140 | + std::string ret; |
| 141 | + while (ret.size() < CMessageHeader::COMMAND_SIZE) { |
| 142 | + char c = provider.ConsumeIntegral<char>(); |
| 143 | + // Match the allowed characters in CMessageHeader::IsCommandValid(). Any other |
| 144 | + // character is interpreted as end. |
| 145 | + if (c < ' ' || c > 0x7E) break; |
| 146 | + ret += c; |
| 147 | + } |
| 148 | + return ret; |
| 149 | + } else { |
| 150 | + // Otherwise, use it as index into the list of known messages. |
| 151 | + return g_all_messages[v % g_all_messages.size()]; |
| 152 | + } |
| 153 | + }; |
| 154 | + |
| 155 | + // Function to construct a CSerializedNetMsg to send. |
| 156 | + auto make_msg_fn = [&](bool first) { |
| 157 | + CSerializedNetMsg msg; |
| 158 | + if (first) { |
| 159 | + // Always send a "version" message as first one. |
| 160 | + msg.m_type = "version"; |
| 161 | + } else { |
| 162 | + msg.m_type = msg_type_fn(); |
| 163 | + } |
| 164 | + // Determine size of message to send (limited to 75 kB for performance reasons). |
| 165 | + size_t size = provider.ConsumeIntegralInRange<uint32_t>(0, 75000); |
| 166 | + // Get payload of message from RNG. |
| 167 | + msg.data.resize(size); |
| 168 | + for (auto& v : msg.data) v = uint8_t(rng()); |
| 169 | + // Return. |
| 170 | + return msg; |
| 171 | + }; |
| 172 | + |
| 173 | + // The next message to be sent (initially version messages, but will be replaced once sent). |
| 174 | + std::array<CSerializedNetMsg, 2> next_msg = { |
| 175 | + make_msg_fn(/*first=*/true), |
| 176 | + make_msg_fn(/*first=*/true) |
| 177 | + }; |
| 178 | + |
| 179 | + // Wrapper around transport[i]->GetBytesToSend() that performs sanity checks. |
| 180 | + auto bytes_to_send_fn = [&](int side) -> Transport::BytesToSend { |
| 181 | + const auto& [bytes, more, msg_type] = transports[side]->GetBytesToSend(); |
| 182 | + // Compare with expected more. |
| 183 | + if (expect_more[side].has_value()) assert(!bytes.empty() == *expect_more[side]); |
| 184 | + // Compare with previously reported output. |
| 185 | + assert(to_send[side].size() <= bytes.size()); |
| 186 | + assert(to_send[side] == Span{bytes}.first(to_send[side].size())); |
| 187 | + to_send[side].resize(bytes.size()); |
| 188 | + std::copy(bytes.begin(), bytes.end(), to_send[side].begin()); |
| 189 | + // Remember 'more' result. |
| 190 | + last_more[side] = {more}; |
| 191 | + // Return. |
| 192 | + return {bytes, more, msg_type}; |
| 193 | + }; |
| 194 | + |
| 195 | + // Function to make side send a new message. |
| 196 | + auto new_msg_fn = [&](int side) { |
| 197 | + // Don't do anything if there are too many unreceived messages already. |
| 198 | + if (expected[side].size() >= 16) return; |
| 199 | + // Try to send (a copy of) the message in next_msg[side]. |
| 200 | + CSerializedNetMsg msg = next_msg[side].Copy(); |
| 201 | + bool queued = transports[side]->SetMessageToSend(msg); |
| 202 | + // Update expected more data. |
| 203 | + expect_more[side] = std::nullopt; |
| 204 | + // Verify consistency of GetBytesToSend after SetMessageToSend |
| 205 | + bytes_to_send_fn(/*side=*/side); |
| 206 | + if (queued) { |
| 207 | + // Remember that this message is now expected by the receiver. |
| 208 | + expected[side].emplace_back(std::move(next_msg[side])); |
| 209 | + // Construct a new next message to send. |
| 210 | + next_msg[side] = make_msg_fn(/*first=*/false); |
| 211 | + } |
| 212 | + }; |
| 213 | + |
| 214 | + // Function to make side send out bytes (if any). |
| 215 | + auto send_fn = [&](int side, bool everything = false) { |
| 216 | + const auto& [bytes, more, msg_type] = bytes_to_send_fn(/*side=*/side); |
| 217 | + // Don't do anything if no bytes to send. |
| 218 | + if (bytes.empty()) return false; |
| 219 | + size_t send_now = everything ? bytes.size() : provider.ConsumeIntegralInRange<size_t>(0, bytes.size()); |
| 220 | + if (send_now == 0) return false; |
| 221 | + // Add bytes to the in-flight queue, and mark those bytes as consumed. |
| 222 | + in_flight[side].insert(in_flight[side].end(), bytes.begin(), bytes.begin() + send_now); |
| 223 | + transports[side]->MarkBytesSent(send_now); |
| 224 | + // If all to-be-sent bytes were sent, move last_more data to expect_more data. |
| 225 | + if (send_now == bytes.size()) { |
| 226 | + expect_more[side] = last_more[side]; |
| 227 | + } |
| 228 | + // Remove the bytes from the last reported to-be-sent vector. |
| 229 | + assert(to_send[side].size() >= send_now); |
| 230 | + to_send[side].erase(to_send[side].begin(), to_send[side].begin() + send_now); |
| 231 | + // Verify that GetBytesToSend gives a result consistent with earlier. |
| 232 | + bytes_to_send_fn(/*side=*/side); |
| 233 | + // Return whether anything was sent. |
| 234 | + return send_now > 0; |
| 235 | + }; |
| 236 | + |
| 237 | + // Function to make !side receive bytes (if any). |
| 238 | + auto recv_fn = [&](int side, bool everything = false) { |
| 239 | + // Don't do anything if no bytes in flight. |
| 240 | + if (in_flight[side].empty()) return false; |
| 241 | + // Decide span to receive |
| 242 | + size_t to_recv_len = in_flight[side].size(); |
| 243 | + if (!everything) to_recv_len = provider.ConsumeIntegralInRange<size_t>(0, to_recv_len); |
| 244 | + Span<const uint8_t> to_recv = Span{in_flight[side]}.first(to_recv_len); |
| 245 | + // Process those bytes |
| 246 | + while (!to_recv.empty()) { |
| 247 | + size_t old_len = to_recv.size(); |
| 248 | + bool ret = transports[!side]->ReceivedBytes(to_recv); |
| 249 | + // Bytes must always be accepted, as this test does not introduce any errors in |
| 250 | + // communication. |
| 251 | + assert(ret); |
| 252 | + // Clear cached expected 'more' information: if certainly no more data was to be sent |
| 253 | + // before, receiving bytes makes this uncertain. |
| 254 | + if (expect_more[!side] == false) expect_more[!side] = std::nullopt; |
| 255 | + // Verify consistency of GetBytesToSend after ReceivedBytes |
| 256 | + bytes_to_send_fn(/*side=*/!side); |
| 257 | + bool progress = to_recv.size() < old_len; |
| 258 | + if (transports[!side]->ReceivedMessageComplete()) { |
| 259 | + bool reject{false}; |
| 260 | + auto received = transports[!side]->GetReceivedMessage({}, reject); |
| 261 | + // Receiving must succeed. |
| 262 | + assert(!reject); |
| 263 | + // There must be a corresponding expected message. |
| 264 | + assert(!expected[side].empty()); |
| 265 | + // The m_message_size field must be correct. |
| 266 | + assert(received.m_message_size == received.m_recv.size()); |
| 267 | + // The m_type must match what is expected. |
| 268 | + assert(received.m_type == expected[side].front().m_type); |
| 269 | + // The data must match what is expected. |
| 270 | + assert(MakeByteSpan(received.m_recv) == MakeByteSpan(expected[side].front().data)); |
| 271 | + expected[side].pop_front(); |
| 272 | + progress = true; |
| 273 | + } |
| 274 | + // Progress must be made (by processing incoming bytes and/or returning complete |
| 275 | + // messages) until all received bytes are processed. |
| 276 | + assert(progress); |
| 277 | + } |
| 278 | + // Remove the processed bytes from the in_flight buffer. |
| 279 | + in_flight[side].erase(in_flight[side].begin(), in_flight[side].begin() + to_recv_len); |
| 280 | + // Return whether anything was received. |
| 281 | + return to_recv_len > 0; |
| 282 | + }; |
| 283 | + |
| 284 | + // Main loop, interleaving new messages, sends, and receives. |
| 285 | + LIMITED_WHILE(provider.remaining_bytes(), 1000) { |
| 286 | + CallOneOf(provider, |
| 287 | + // (Try to) give the next message to the transport. |
| 288 | + [&] { new_msg_fn(/*side=*/0); }, |
| 289 | + [&] { new_msg_fn(/*side=*/1); }, |
| 290 | + // (Try to) send some bytes from the transport to the network. |
| 291 | + [&] { send_fn(/*side=*/0); }, |
| 292 | + [&] { send_fn(/*side=*/1); }, |
| 293 | + // (Try to) receive bytes from the network, converting to messages. |
| 294 | + [&] { recv_fn(/*side=*/0); }, |
| 295 | + [&] { recv_fn(/*side=*/1); } |
| 296 | + ); |
| 297 | + } |
| 298 | + |
| 299 | + // When we're done, perform sends and receives of existing messages to flush anything already |
| 300 | + // in flight. |
| 301 | + while (true) { |
| 302 | + bool any = false; |
| 303 | + if (send_fn(/*side=*/0, /*everything=*/true)) any = true; |
| 304 | + if (send_fn(/*side=*/1, /*everything=*/true)) any = true; |
| 305 | + if (recv_fn(/*side=*/0, /*everything=*/true)) any = true; |
| 306 | + if (recv_fn(/*side=*/1, /*everything=*/true)) any = true; |
| 307 | + if (!any) break; |
| 308 | + } |
| 309 | + |
| 310 | + // Make sure nothing is left in flight. |
| 311 | + assert(in_flight[0].empty()); |
| 312 | + assert(in_flight[1].empty()); |
| 313 | + |
| 314 | + // Make sure all expected messages were received. |
| 315 | + assert(expected[0].empty()); |
| 316 | + assert(expected[1].empty()); |
| 317 | +} |
| 318 | + |
| 319 | +std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept |
| 320 | +{ |
| 321 | + return std::make_unique<V1Transport>(nodeid, SER_NETWORK, INIT_PROTO_VERSION); |
| 322 | +} |
| 323 | + |
| 324 | +} // namespace |
| 325 | + |
| 326 | +FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serialization) |
| 327 | +{ |
| 328 | + // Test with two V1 transports talking to each other. |
| 329 | + FuzzedDataProvider provider{buffer.data(), buffer.size()}; |
| 330 | + XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>()); |
| 331 | + auto t1 = MakeV1Transport(NodeId{0}); |
| 332 | + auto t2 = MakeV1Transport(NodeId{1}); |
| 333 | + if (!t1 || !t2) return; |
| 334 | + SimulationTest(*t1, *t2, rng, provider); |
| 335 | +} |
0 commit comments