aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2023-08-27 21:08:56 -0400
committerPieter Wuille <pieter@wuille.net>2023-09-07 09:04:55 -0400
commit91e1ef8684997fb4b3e8b64ef3935a936445066b (patch)
treee84630646df88c66c24b42821e101ba14bbe6e93
parent297c8889975a18258d6cc39b1ec1e94fed6630fb (diff)
downloadbitcoin-91e1ef8684997fb4b3e8b64ef3935a936445066b.tar.xz
test: add unit tests for V2Transport
-rw-r--r--src/test/net_tests.cpp502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index 295cb78b36..feaa0aef61 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -15,6 +15,7 @@
#include <serialize.h>
#include <span.h>
#include <streams.h>
+#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
#include <timedata.h>
@@ -1005,4 +1006,505 @@ BOOST_AUTO_TEST_CASE(advertise_local_address)
RemoveLocal(addr_cjdns);
}
+namespace {
+
+/** A class for scenario-based tests of V2Transport
+ *
+ * Each V2TransportTester encapsulates a V2Transport (the one being tested), and can be told to
+ * interact with it. To do so, it also encapsulates a BIP324Cipher to act as the other side. A
+ * second V2Transport is not used, as doing so would not permit scenarios that involve sending
+ * invalid data, or ones scenarios using BIP324 features that are not implemented on the sending
+ * side (like decoy packets).
+ */
+class V2TransportTester
+{
+ V2Transport m_transport; //!< V2Transport being tested
+ BIP324Cipher m_cipher; //!< Cipher to help with the other side
+ bool m_test_initiator; //!< Whether m_transport is the initiator (true) or responder (false)
+
+ std::vector<uint8_t> m_sent_garbage; //!< The garbage we've sent to m_transport.
+ std::vector<uint8_t> m_to_send; //!< Bytes we have queued up to send to m_transport.
+ std::vector<uint8_t> m_received; //!< Bytes we have received from m_transport.
+ std::deque<CSerializedNetMsg> m_msg_to_send; //!< Messages to be sent *by* m_transport to us.
+
+public:
+ /** Construct a tester object. test_initiator: whether the tested transport is initiator. */
+ V2TransportTester(bool test_initiator) :
+ m_transport(0, test_initiator, SER_NETWORK, INIT_PROTO_VERSION),
+ m_test_initiator(test_initiator) {}
+
+ /** Data type returned by Interact:
+ *
+ * - std::nullopt: transport error occurred
+ * - otherwise: a vector of
+ * - std::nullopt: invalid message received
+ * - otherwise: a CNetMessage retrieved
+ */
+ using InteractResult = std::optional<std::vector<std::optional<CNetMessage>>>;
+
+ /** Send/receive scheduled/available bytes and messages.
+ *
+ * This is the only function that interacts with the transport being tested; everything else is
+ * scheduling things done by Interact(), or processing things learned by it.
+ */
+ InteractResult Interact()
+ {
+ std::vector<std::optional<CNetMessage>> ret;
+ while (true) {
+ bool progress{false};
+ // Send bytes from m_to_send to the transport.
+ if (!m_to_send.empty()) {
+ Span<const uint8_t> to_send = Span{m_to_send}.first(1 + InsecureRandRange(m_to_send.size()));
+ size_t old_len = to_send.size();
+ if (!m_transport.ReceivedBytes(to_send)) {
+ return std::nullopt; // transport error occurred
+ }
+ if (old_len != to_send.size()) {
+ progress = true;
+ m_to_send.erase(m_to_send.begin(), m_to_send.begin() + (old_len - to_send.size()));
+ }
+ }
+ // Retrieve messages received by the transport.
+ if (m_transport.ReceivedMessageComplete() && (!progress || InsecureRandBool())) {
+ bool reject{false};
+ auto msg = m_transport.GetReceivedMessage({}, reject);
+ if (reject) {
+ ret.push_back(std::nullopt);
+ } else {
+ ret.push_back(std::move(msg));
+ }
+ progress = true;
+ }
+ // Enqueue a message to be sent by the transport to us.
+ if (!m_msg_to_send.empty() && (!progress || InsecureRandBool())) {
+ if (m_transport.SetMessageToSend(m_msg_to_send.front())) {
+ m_msg_to_send.pop_front();
+ progress = true;
+ }
+ }
+ // Receive bytes from the transport.
+ const auto& [recv_bytes, _more, _msg_type] = m_transport.GetBytesToSend(!m_msg_to_send.empty());
+ if (!recv_bytes.empty() && (!progress || InsecureRandBool())) {
+ size_t to_receive = 1 + InsecureRandRange(recv_bytes.size());
+ m_received.insert(m_received.end(), recv_bytes.begin(), recv_bytes.begin() + to_receive);
+ progress = true;
+ m_transport.MarkBytesSent(to_receive);
+ }
+ if (!progress) break;
+ }
+ return ret;
+ }
+
+ /** Expose the cipher. */
+ BIP324Cipher& GetCipher() { return m_cipher; }
+
+ /** Schedule bytes to be sent to the transport. */
+ void Send(Span<const uint8_t> data)
+ {
+ m_to_send.insert(m_to_send.end(), data.begin(), data.end());
+ }
+
+ /** Schedule bytes to be sent to the transport. */
+ void Send(Span<const std::byte> data) { Send(MakeUCharSpan(data)); }
+
+ /** Schedule our ellswift key to be sent to the transport. */
+ void SendKey() { Send(m_cipher.GetOurPubKey()); }
+
+ /** Schedule specified garbage to be sent to the transport. */
+ void SendGarbage(Span<const uint8_t> garbage)
+ {
+ // Remember the specified garbage (so we can use it for constructing the garbage
+ // authentication packet).
+ m_sent_garbage.assign(garbage.begin(), garbage.end());
+ // Schedule it for sending.
+ Send(m_sent_garbage);
+ }
+
+ /** Schedule garbage (of specified length) to be sent to the transport. */
+ void SendGarbage(size_t garbage_len)
+ {
+ // Generate random garbage and send it.
+ SendGarbage(g_insecure_rand_ctx.randbytes<uint8_t>(garbage_len));
+ }
+
+ /** Schedule garbage (with valid random length) to be sent to the transport. */
+ void SendGarbage()
+ {
+ SendGarbage(InsecureRandRange(V2Transport::MAX_GARBAGE_LEN + 1));
+ }
+
+ /** Schedule a message to be sent to us by the transport. */
+ void AddMessage(std::string m_type, std::vector<uint8_t> payload)
+ {
+ CSerializedNetMsg msg;
+ msg.m_type = std::move(m_type);
+ msg.data = std::move(payload);
+ m_msg_to_send.push_back(std::move(msg));
+ }
+
+ /** Expect ellswift key to have been received from transport and process it.
+ *
+ * Many other V2TransportTester functions cannot be called until after ReceiveKey() has been
+ * called, as no encryption keys are set up before that point.
+ */
+ void ReceiveKey()
+ {
+ // When processing a key, enough bytes need to have been received already.
+ BOOST_REQUIRE(m_received.size() >= EllSwiftPubKey::size());
+ // Initialize the cipher using it (acting as the opposite side of the tested transport).
+ m_cipher.Initialize(MakeByteSpan(m_received).first(EllSwiftPubKey::size()), !m_test_initiator);
+ // Strip the processed bytes off the front of the receive buffer.
+ m_received.erase(m_received.begin(), m_received.begin() + EllSwiftPubKey::size());
+ }
+
+ /** Schedule an encrypted packet with specified content/aad/ignore to be sent to transport
+ * (only after ReceiveKey). */
+ void SendPacket(Span<const uint8_t> content, Span<const uint8_t> aad = {}, bool ignore = false)
+ {
+ // Use cipher to construct ciphertext.
+ std::vector<std::byte> ciphertext;
+ ciphertext.resize(content.size() + BIP324Cipher::EXPANSION);
+ m_cipher.Encrypt(
+ /*contents=*/MakeByteSpan(content),
+ /*aad=*/MakeByteSpan(aad),
+ /*ignore=*/ignore,
+ /*output=*/ciphertext);
+ // Schedule it for sending.
+ Send(ciphertext);
+ }
+
+ /** Schedule garbage terminator and authentication packet to be sent to the transport (only
+ * after ReceiveKey). */
+ void SendGarbageTermAuth(size_t garb_auth_data_len = 0, bool garb_auth_ignore = false)
+ {
+ // Generate random data to include in the garbage authentication packet (ignored by peer).
+ auto garb_auth_data = g_insecure_rand_ctx.randbytes<uint8_t>(garb_auth_data_len);
+ // Schedule the garbage terminator to be sent.
+ Send(m_cipher.GetSendGarbageTerminator());
+ // Schedule the garbage authentication packet to be sent.
+ SendPacket(/*content=*/garb_auth_data, /*aad=*/m_sent_garbage, /*ignore=*/garb_auth_ignore);
+ }
+
+ /** Schedule version packet to be sent to the transport (only after ReceiveKey). */
+ void SendVersion(Span<const uint8_t> version_data = {}, bool vers_ignore = false)
+ {
+ SendPacket(/*content=*/version_data, /*aad=*/{}, /*ignore=*/vers_ignore);
+ }
+
+ /** Expect a packet to have been received from transport, process it, and return its contents
+ * (only after ReceiveKey). By default, decoys are skipped. */
+ std::vector<uint8_t> ReceivePacket(Span<const std::byte> aad = {}, bool skip_decoy = true)
+ {
+ std::vector<uint8_t> contents;
+ // Loop as long as there are ignored packets that are to be skipped.
+ while (true) {
+ // When processing a packet, at least enough bytes for its length descriptor must be received.
+ BOOST_REQUIRE(m_received.size() >= BIP324Cipher::LENGTH_LEN);
+ // Decrypt the content length.
+ size_t size = m_cipher.DecryptLength(MakeByteSpan(Span{m_received}.first(BIP324Cipher::LENGTH_LEN)));
+ // Check that the full packet is in the receive buffer.
+ BOOST_REQUIRE(m_received.size() >= size + BIP324Cipher::EXPANSION);
+ // Decrypt the packet contents.
+ contents.resize(size);
+ bool ignore{false};
+ bool ret = m_cipher.Decrypt(
+ /*input=*/MakeByteSpan(
+ Span{m_received}.first(size + BIP324Cipher::EXPANSION).subspan(BIP324Cipher::LENGTH_LEN)),
+ /*aad=*/aad,
+ /*ignore=*/ignore,
+ /*contents=*/MakeWritableByteSpan(contents));
+ BOOST_CHECK(ret);
+ // Strip the processed packet's bytes off the front of the receive buffer.
+ m_received.erase(m_received.begin(), m_received.begin() + size + BIP324Cipher::EXPANSION);
+ // Stop if the ignore bit is not set on this packet, or if we choose to not honor it.
+ if (!ignore || !skip_decoy) break;
+ }
+ return contents;
+ }
+
+ /** Expect garbage, garbage terminator, and garbage auth packet to have been received, and
+ * process them (only after ReceiveKey). */
+ void ReceiveGarbage()
+ {
+ // Figure out the garbage length.
+ size_t garblen;
+ for (garblen = 0; garblen <= V2Transport::MAX_GARBAGE_LEN; ++garblen) {
+ BOOST_REQUIRE(m_received.size() >= garblen + BIP324Cipher::GARBAGE_TERMINATOR_LEN);
+ auto term_span = MakeByteSpan(Span{m_received}.subspan(garblen, BIP324Cipher::GARBAGE_TERMINATOR_LEN));
+ if (term_span == m_cipher.GetReceiveGarbageTerminator()) break;
+ }
+ // Copy the garbage to a buffer.
+ std::vector<uint8_t> garbage(m_received.begin(), m_received.begin() + garblen);
+ // Strip garbage + garbage terminator off the front of the receive buffer.
+ m_received.erase(m_received.begin(), m_received.begin() + garblen + BIP324Cipher::GARBAGE_TERMINATOR_LEN);
+ // Process the expected garbage authentication packet. Such a packet still functions as one
+ // even when its ignore bit is set to true, so we do not skip decoy packets here.
+ ReceivePacket(/*aad=*/MakeByteSpan(garbage), /*skip_decoy=*/false);
+ }
+
+ /** Expect version packet to have been received, and process it (only after ReceiveKey). */
+ void ReceiveVersion()
+ {
+ auto contents = ReceivePacket();
+ // Version packets from real BIP324 peers are expected to be empty, despite the fact that
+ // this class supports *sending* non-empty version packets (to test that BIP324 peers
+ // correctly ignore version packet contents).
+ BOOST_CHECK(contents.empty());
+ }
+
+ /** Expect application packet to have been received, with specified short id and payload.
+ * (only after ReceiveKey). */
+ void ReceiveMessage(uint8_t short_id, Span<const uint8_t> payload)
+ {
+ auto ret = ReceivePacket();
+ BOOST_CHECK(ret.size() == payload.size() + 1);
+ BOOST_CHECK(ret[0] == short_id);
+ BOOST_CHECK(Span{ret}.subspan(1) == payload);
+ }
+
+ /** Expect application packet to have been received, with specified 12-char message type and
+ * payload (only after ReceiveKey). */
+ void ReceiveMessage(const std::string& m_type, Span<const uint8_t> payload)
+ {
+ auto ret = ReceivePacket();
+ BOOST_REQUIRE(ret.size() == payload.size() + 1 + CMessageHeader::COMMAND_SIZE);
+ BOOST_CHECK(ret[0] == 0);
+ for (unsigned i = 0; i < 12; ++i) {
+ if (i < m_type.size()) {
+ BOOST_CHECK(ret[1 + i] == m_type[i]);
+ } else {
+ BOOST_CHECK(ret[1 + i] == 0);
+ }
+ }
+ BOOST_CHECK(Span{ret}.subspan(1 + CMessageHeader::COMMAND_SIZE) == payload);
+ }
+
+ /** Schedule an encrypted packet with specified message type and payload to be sent to
+ * transport (only after ReceiveKey). */
+ void SendMessage(std::string mtype, Span<const uint8_t> payload)
+ {
+ // Construct contents consisting of 0x00 + 12-byte message type + payload.
+ std::vector<uint8_t> contents(1 + CMessageHeader::COMMAND_SIZE + payload.size());
+ std::copy(mtype.begin(), mtype.end(), reinterpret_cast<char*>(contents.data() + 1));
+ std::copy(payload.begin(), payload.end(), contents.begin() + 1 + CMessageHeader::COMMAND_SIZE);
+ // Send a packet with that as contents.
+ SendPacket(contents);
+ }
+
+ /** Schedule an encrypted packet with specified short message id and payload to be sent to
+ * transport (only after ReceiveKey). */
+ void SendMessage(uint8_t short_id, Span<const uint8_t> payload)
+ {
+ // Construct contents consisting of short_id + payload.
+ std::vector<uint8_t> contents(1 + payload.size());
+ contents[0] = short_id;
+ std::copy(payload.begin(), payload.end(), contents.begin() + 1);
+ // Send a packet with that as contents.
+ SendPacket(contents);
+ }
+
+ /** Introduce a bit error in the data scheduled to be sent. */
+ void Damage()
+ {
+ m_to_send[InsecureRandRange(m_to_send.size())] ^= (uint8_t{1} << InsecureRandRange(8));
+ }
+};
+
+} // namespace
+
+BOOST_AUTO_TEST_CASE(v2transport_test)
+{
+ // A mostly normal scenario, testing a transport in initiator mode.
+ for (int i = 0; i < 10; ++i) {
+ V2TransportTester tester(true);
+ auto ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.SendKey();
+ tester.SendGarbage();
+ tester.ReceiveKey();
+ tester.SendGarbageTermAuth();
+ tester.SendVersion();
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.ReceiveGarbage();
+ tester.ReceiveVersion();
+ auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000));
+ auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
+ tester.SendMessage(uint8_t(4), msg_data_1); // cmpctblock short id
+ tester.SendMessage(0, {}); // Invalidly encoded message
+ tester.SendMessage("tx", msg_data_2); // 12-character encoded message type
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->size() == 3);
+ BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "cmpctblock" && Span{(*ret)[0]->m_recv} == MakeByteSpan(msg_data_1));
+ BOOST_CHECK(!(*ret)[1]);
+ BOOST_CHECK((*ret)[2] && (*ret)[2]->m_type == "tx" && Span{(*ret)[2]->m_recv} == MakeByteSpan(msg_data_2));
+
+ // Then send a message with a bit error, expecting failure.
+ tester.SendMessage("bad", msg_data_1);
+ tester.Damage();
+ ret = tester.Interact();
+ BOOST_CHECK(!ret);
+ }
+
+ // Normal scenario, with a transport in responder node.
+ for (int i = 0; i < 10; ++i) {
+ V2TransportTester tester(false);
+ tester.SendKey();
+ tester.SendGarbage();
+ auto ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.ReceiveKey();
+ tester.SendGarbageTermAuth();
+ tester.SendVersion();
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.ReceiveGarbage();
+ tester.ReceiveVersion();
+ auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000));
+ auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
+ tester.SendMessage(uint8_t(14), msg_data_1); // inv short id
+ tester.SendMessage(uint8_t(19), msg_data_2); // pong short id
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->size() == 2);
+ BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "inv" && Span{(*ret)[0]->m_recv} == MakeByteSpan(msg_data_1));
+ BOOST_CHECK((*ret)[1] && (*ret)[1]->m_type == "pong" && Span{(*ret)[1]->m_recv} == MakeByteSpan(msg_data_2));
+
+ // Then send a too-large message.
+ auto msg_data_3 = g_insecure_rand_ctx.randbytes<uint8_t>(4005000);
+ tester.SendMessage(uint8_t(11), msg_data_3); // getdata short id
+ ret = tester.Interact();
+ BOOST_CHECK(!ret);
+ }
+
+ // Various valid but unusual scenarios.
+ for (int i = 0; i < 50; ++i) {
+ /** Whether an initiator or responder is being tested. */
+ bool initiator = InsecureRandBool();
+ /** Use either 0 bytes or the maximum possible (4095 bytes) garbage length. */
+ size_t garb_len = InsecureRandBool() ? 0 : V2Transport::MAX_GARBAGE_LEN;
+ /** Sometimes, use non-empty contents in the garbage authentication packet (which is to be ignored). */
+ size_t garb_auth_data_len = InsecureRandBool() ? 0 : InsecureRandRange(100000);
+ /** Whether to set the ignore bit on the garbage authentication packet (it still functions as garbage authentication). */
+ bool garb_ignore = InsecureRandBool();
+ /** How many decoy packets to send before the version packet. */
+ unsigned num_ignore_version = InsecureRandRange(10);
+ /** What data to send in the version packet (ignored by BIP324 peers, but reserved for future extensions). */
+ auto ver_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandBool() ? 0 : InsecureRandRange(1000));
+ /** Whether to immediately send key and garbage out (required for responders, optional otherwise). */
+ bool send_immediately = !initiator || InsecureRandBool();
+ /** How many decoy packets to send before the first and second real message. */
+ unsigned num_decoys_1 = InsecureRandRange(1000), num_decoys_2 = InsecureRandRange(1000);
+ V2TransportTester tester(initiator);
+ if (send_immediately) {
+ tester.SendKey();
+ tester.SendGarbage(garb_len);
+ }
+ auto ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ if (!send_immediately) {
+ tester.SendKey();
+ tester.SendGarbage(garb_len);
+ }
+ tester.ReceiveKey();
+ tester.SendGarbageTermAuth(garb_auth_data_len, garb_ignore);
+ for (unsigned v = 0; v < num_ignore_version; ++v) {
+ size_t ver_ign_data_len = InsecureRandBool() ? 0 : InsecureRandRange(1000);
+ auto ver_ign_data = g_insecure_rand_ctx.randbytes<uint8_t>(ver_ign_data_len);
+ tester.SendVersion(ver_ign_data, true);
+ }
+ tester.SendVersion(ver_data, false);
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.ReceiveGarbage();
+ tester.ReceiveVersion();
+ for (unsigned d = 0; d < num_decoys_1; ++d) {
+ auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
+ tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true);
+ }
+ auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(4000000));
+ tester.SendMessage(uint8_t(28), msg_data_1);
+ for (unsigned d = 0; d < num_decoys_2; ++d) {
+ auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
+ tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true);
+ }
+ auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
+ tester.SendMessage(uint8_t(13), msg_data_2); // headers short id
+ // Send invalidly-encoded message
+ tester.SendMessage(std::string("blocktxn\x00\x00\x00a", CMessageHeader::COMMAND_SIZE), {});
+ tester.SendMessage("foobar", {}); // test receiving unknown message type
+ tester.AddMessage("barfoo", {}); // test sending unknown message type
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->size() == 4);
+ BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "addrv2" && Span{(*ret)[0]->m_recv} == MakeByteSpan(msg_data_1));
+ BOOST_CHECK((*ret)[1] && (*ret)[1]->m_type == "headers" && Span{(*ret)[1]->m_recv} == MakeByteSpan(msg_data_2));
+ BOOST_CHECK(!(*ret)[2]);
+ BOOST_CHECK((*ret)[3] && (*ret)[3]->m_type == "foobar" && (*ret)[3]->m_recv.empty());
+ tester.ReceiveMessage("barfoo", {});
+ }
+
+ // Too long garbage (initiator).
+ {
+ V2TransportTester tester(true);
+ auto ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.SendKey();
+ tester.SendGarbage(V2Transport::MAX_GARBAGE_LEN + 1);
+ tester.ReceiveKey();
+ tester.SendGarbageTermAuth();
+ ret = tester.Interact();
+ BOOST_CHECK(!ret);
+ }
+
+ // Too long garbage (responder).
+ {
+ V2TransportTester tester(false);
+ tester.SendKey();
+ tester.SendGarbage(V2Transport::MAX_GARBAGE_LEN + 1);
+ auto ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.ReceiveKey();
+ tester.SendGarbageTermAuth();
+ ret = tester.Interact();
+ BOOST_CHECK(!ret);
+ }
+
+ // Send garbage that includes the first 15 garbage terminator bytes somewhere.
+ {
+ V2TransportTester tester(true);
+ auto ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.SendKey();
+ tester.ReceiveKey();
+ /** The number of random garbage bytes before the included first 15 bytes of terminator. */
+ size_t len_before = InsecureRandRange(V2Transport::MAX_GARBAGE_LEN - 16 + 1);
+ /** The number of random garbage bytes after it. */
+ size_t len_after = InsecureRandRange(V2Transport::MAX_GARBAGE_LEN - 16 - len_before + 1);
+ // Construct len_before + 16 + len_after random bytes.
+ auto garbage = g_insecure_rand_ctx.randbytes<uint8_t>(len_before + 16 + len_after);
+ // Replace the designed 16 bytes in the middle with the to-be-sent garbage terminator.
+ auto garb_term = MakeUCharSpan(tester.GetCipher().GetSendGarbageTerminator());
+ std::copy(garb_term.begin(), garb_term.begin() + 16, garbage.begin() + len_before);
+ // Introduce a bit error in the last byte of that copied garbage terminator, making only
+ // the first 15 of them match.
+ garbage[len_before + 15] ^= (uint8_t(1) << InsecureRandRange(8));
+ tester.SendGarbage(garbage);
+ tester.SendGarbageTermAuth();
+ tester.SendVersion();
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->empty());
+ tester.ReceiveGarbage();
+ tester.ReceiveVersion();
+ auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that receiving 4M payload works
+ auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that sending 4M payload works
+ tester.SendMessage(uint8_t(InsecureRandRange(223) + 33), {}); // unknown short id
+ tester.SendMessage(uint8_t(2), msg_data_1); // "block" short id
+ tester.AddMessage("blocktxn", msg_data_2); // schedule blocktxn to be sent to us
+ ret = tester.Interact();
+ BOOST_REQUIRE(ret && ret->size() == 2);
+ BOOST_CHECK(!(*ret)[0]);
+ BOOST_CHECK((*ret)[1] && (*ret)[1]->m_type == "block" && Span{(*ret)[1]->m_recv} == MakeByteSpan(msg_data_1));
+ tester.ReceiveMessage(uint8_t(3), msg_data_2); // "blocktxn" short id
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()