// Copyright (c) 2014-2026 Savoir-faire Linux Inc. // SPDX-License-Identifier: MIT #include "test_networkengine.h" #ifdef _MSC_VER namespace test { CPPUNIT_TEST_SUITE_REGISTRATION(NetworkEngineTester); void NetworkEngineTester::setUp() {} void NetworkEngineTester::tearDown() {} void NetworkEngineTester::testDisabledOnMsvc() { CPPUNIT_ASSERT(true); } } // namespace test #else #include #include #ifdef _WIN32 #ifdef opendht_EXPORTS #undef opendht_EXPORTS #endif #endif #include "opendht/node.h" #include "opendht/network_engine.h" #include "opendht/network_utils.h" #include "opendht/utils.h" #include "opendht/value.h" #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wkeyword-macro" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #endif #define private public #include "opendht/dht.h" #undef private #if defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif #include "../src/parsed_message.h" #include "../src/search.h" // Hack to test internal classes #include "../src/op_cache.cpp" #include "../src/routing_table.cpp" #include "../src/dht.cpp" #include "../src/node.cpp" #include "../src/node_cache.cpp" #include "../src/network_engine.cpp" #include namespace test { CPPUNIT_TEST_SUITE_REGISTRATION(NetworkEngineTester); using namespace dht; using namespace dht::net; static constexpr size_t TEST_MTU {1280}; class TestDatagramSocket : public net::DatagramSocket { public: TestDatagramSocket() { bound_.setFamily(AF_INET); bound_.setLoopback(); bound_.setPort(4222); } int sendTo(const SockAddr& dest, const uint8_t* data, size_t size, bool replied) override { sends.emplace_back(SentPacket {dest, Blob(data, data + size), replied}); return 0; } bool hasIPv4() const override { return true; } bool hasIPv6() const override { return false; } const SockAddr& getBoundRef(sa_family_t family = AF_UNSPEC) const override { (void) family; return bound_; } void stop() override {} struct SentPacket { SockAddr dest; Blob data; bool replied; }; SockAddr bound_; std::vector sends {}; }; static Blob serializeValue(const std::string& data) { auto val = std::make_shared((const uint8_t*) data.data(), data.size()); return packMsg(val); } static Blob makeReplyHeaderPacketBlob(const InfoHash& id, Tid tid, const std::vector& valueSizes) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(3); pk.pack(KEY_R); pk.pack_map(2); pk.pack(KEY_REQ_ID); pk.pack(id); pk.pack(KEY_REQ_VALUES); pk.pack_array(valueSizes.size()); for (const auto& size : valueSizes) pk.pack(size); pk.pack(KEY_TID); pk.pack(tid); pk.pack(KEY_Y); pk.pack(KEY_R); return {buffer.data(), buffer.data() + buffer.size()}; } static Blob makeValueDataPacketBlob(Tid tid, unsigned index, size_t offset, const Blob& data) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(3); pk.pack(KEY_Y); pk.pack(KEY_V); pk.pack(KEY_TID); pk.pack(tid); pk.pack(KEY_V); pk.pack_map(1); pk.pack(index); pk.pack_map(2); pk.pack("o"); pk.pack(offset); pk.pack("d"); pk.pack_bin(data.size()); pk.pack_bin_body((const char*) data.data(), data.size()); return {buffer.data(), buffer.data() + buffer.size()}; } static Blob makeListenPacketBlob(const InfoHash& id, const InfoHash& hash, Tid tid, Tid socketId, const Blob& token) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(4); pk.pack(KEY_A); pk.pack_map(5); pk.pack(KEY_REQ_ID); pk.pack(id); pk.pack(KEY_VERSION); pk.pack(1); pk.pack(KEY_REQ_H); pk.pack(hash); pk.pack(KEY_REQ_TOKEN); packToken(pk, token); pk.pack(KEY_REQ_SID); pk.pack(socketId); pk.pack(KEY_Q); pk.pack(QUERY_LISTEN); pk.pack(KEY_TID); pk.pack(tid); pk.pack(KEY_Y); pk.pack(KEY_Q); return {buffer.data(), buffer.data() + buffer.size()}; } static SockAddr makeIPv4(const char* ip, in_port_t port) { SockAddr addr; addr.setFamily(AF_INET); addr.setAddress(ip); addr.setPort(port); return addr; } static net::NetworkEngine makeEngine(std::unique_ptr&& socket, Scheduler& scheduler, InfoHash& myid, std::mt19937_64& rd, int& onNewNodeCalls) { net::NetworkConfig config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; return net::NetworkEngine( myid, config, std::move(socket), {}, rd, scheduler, [](Sp, net::DhtProtocolException) {}, [&onNewNodeCalls](const Sp&, int) { ++onNewNodeCalls; }, [](const InfoHash&, const SockAddr&) {}, [](Sp) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, want_t) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, want_t, const Query&) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, const Blob&, Tid, const Query&, int) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, const Blob&, const std::vector>&, const time_point&) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, const Blob&, const Value::Id&) { return net::RequestAnswer {}; }); } static Sp addPendingGetRequest(net::NetworkEngine& engine, const InfoHash& id, const SockAddr& addr, Tid tid) { auto node = engine.insertNode(id, addr); auto request = std::make_shared( MessageType::GetValues, tid, node, Blob {}, [](const net::Request&, ParsedMessage&&) {}, [](const net::Request&, bool) {}); node->requested(request); return node; } void NetworkEngineTester::setUp() {} void NetworkEngineTester::tearDown() {} void NetworkEngineTester::testCompletesPartialSessionWithDataBeforeHeader() { Scheduler scheduler; std::mt19937_64 rd(0); InfoHash myid = InfoHash::getRandom(rd); InfoHash remoteId = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); auto serialized = serializeValue("fragment before header"); auto from = makeIPv4("127.0.0.2", 5000); addPendingGetRequest(engine, remoteId, from, 99); auto initialOnNewNodeCalls = onNewNodeCalls; auto fragment = makeValueDataPacketBlob(99, 0, 0, serialized); auto header = makeReplyHeaderPacketBlob(remoteId, 99, {serialized.size()}); engine.processMessage(fragment.data(), fragment.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount()); CPPUNIT_ASSERT_EQUAL(initialOnNewNodeCalls, onNewNodeCalls); engine.processMessage(header.data(), header.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls); } void NetworkEngineTester::testBuffersLargeValueBeforeHeader() { Scheduler scheduler; std::mt19937_64 rd(8); InfoHash myid = InfoHash::getRandom(rd); InfoHash remoteId = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); auto serialized = serializeValue(std::string(57 * 1024, 'l')); auto from = makeIPv4("127.0.0.2", 5008); addPendingGetRequest(engine, remoteId, from, 101); auto initialOnNewNodeCalls = onNewNodeCalls; for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) { auto end = std::min(offset + TEST_MTU, serialized.size()); Blob data(serialized.begin() + offset, serialized.begin() + end); auto fragment = makeValueDataPacketBlob(101, 0, offset, data); engine.processMessage(fragment.data(), fragment.size(), from); } CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount()); auto header = makeReplyHeaderPacketBlob(remoteId, 101, {serialized.size()}); engine.processMessage(header.data(), header.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls); } void NetworkEngineTester::testIgnoresEmptyPartialData() { Scheduler scheduler; std::mt19937_64 rd(7); InfoHash myid = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); auto packet = makeValueDataPacketBlob(100, 0, 0, {}); auto from = makeIPv4("127.0.0.2", 5007); engine.processMessage(packet.data(), packet.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); CPPUNIT_ASSERT_EQUAL(0, onNewNodeCalls); } void NetworkEngineTester::testRejectsUnsolicitedPartialReply() { Scheduler scheduler; std::mt19937_64 rd(9); InfoHash myid = InfoHash::getRandom(rd); InfoHash remoteId = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); auto from = makeIPv4("127.0.0.2", 5009); auto header = makeReplyHeaderPacketBlob(remoteId, 102, {TEST_MTU}); engine.processMessage(header.data(), header.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getNodeCacheSize()); } void NetworkEngineTester::testDropsMalformedCompletedPartialMessage() { Scheduler scheduler; std::mt19937_64 rd(10); InfoHash myid = InfoHash::getRandom(rd); InfoHash remoteId = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); auto from = makeIPv4("127.0.0.2", 5010); addPendingGetRequest(engine, remoteId, from, 103); Blob malformed {0xc1}; auto header = makeReplyHeaderPacketBlob(remoteId, 103, {malformed.size()}); auto fragment = makeValueDataPacketBlob(103, 0, 0, malformed); engine.processMessage(header.data(), header.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount()); engine.processMessage(fragment.data(), fragment.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); auto earlyFrom = makeIPv4("127.0.0.2", 5011); addPendingGetRequest(engine, remoteId, earlyFrom, 104); auto earlyHeader = makeReplyHeaderPacketBlob(remoteId, 104, {malformed.size()}); auto earlyFragment = makeValueDataPacketBlob(104, 0, 0, malformed); engine.processMessage(earlyFragment.data(), earlyFragment.size(), earlyFrom); CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount()); engine.processMessage(earlyHeader.data(), earlyHeader.size(), earlyFrom); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); } void NetworkEngineTester::testCompletesPartialSessionFromSameSource() { Scheduler scheduler; std::mt19937_64 rd(1); InfoHash myid = InfoHash::getRandom(rd); InfoHash remoteId = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); std::string data(TEST_MTU * 2 + 64, 'z'); auto serialized = serializeValue(data); auto from = makeIPv4("127.0.0.2", 5001); addPendingGetRequest(engine, remoteId, from, 7); auto initialOnNewNodeCalls = onNewNodeCalls; auto header = makeReplyHeaderPacketBlob(remoteId, 7, {serialized.size()}); engine.processMessage(header.data(), header.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount()); for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) { auto end = std::min(offset + TEST_MTU, serialized.size()); Blob fragment(serialized.begin() + offset, serialized.begin() + end); auto packet = makeValueDataPacketBlob(7, 0, offset, fragment); engine.processMessage(packet.data(), packet.size(), from); } CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls); } void NetworkEngineTester::testSeparatesPartialSessionsBySource() { Scheduler scheduler; std::mt19937_64 rd(2); InfoHash myid = InfoHash::getRandom(rd); InfoHash firstRemoteId = InfoHash::getRandom(rd); InfoHash secondRemoteId = InfoHash::getRandom(rd); int onNewNodeCalls = 0; auto engine = makeEngine(std::make_unique(), scheduler, myid, rd, onNewNodeCalls); auto serialized = serializeValue("same transaction, different peers"); auto firstFrom = makeIPv4("127.0.0.2", 5002); auto secondFrom = makeIPv4("127.0.0.3", 5002); addPendingGetRequest(engine, firstRemoteId, firstFrom, 11); addPendingGetRequest(engine, secondRemoteId, secondFrom, 11); auto initialOnNewNodeCalls = onNewNodeCalls; auto firstHeader = makeReplyHeaderPacketBlob(firstRemoteId, 11, {serialized.size()}); auto secondHeader = makeReplyHeaderPacketBlob(secondRemoteId, 11, {serialized.size()}); engine.processMessage(firstHeader.data(), firstHeader.size(), firstFrom); engine.processMessage(secondHeader.data(), secondHeader.size(), secondFrom); CPPUNIT_ASSERT_EQUAL((size_t) 2, engine.getPartialCount()); auto fragment = makeValueDataPacketBlob(11, 0, 0, serialized); engine.processMessage(fragment.data(), fragment.size(), firstFrom); CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount()); engine.processMessage(fragment.data(), fragment.size(), secondFrom); CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount()); CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls); } void NetworkEngineTester::testListenConfirmationCarriesToken() { Scheduler scheduler; std::mt19937_64 rd(3); InfoHash myid = InfoHash::getRandom(rd); InfoHash remoteId = InfoHash::getRandom(rd); InfoHash hash = InfoHash::getRandom(rd); Blob requestToken {0x01, 0x02, 0x03, 0x04}; Blob responseToken {0xaa, 0xbb, 0xcc, 0xdd, 0xee}; int onNewNodeCalls = 0; net::NetworkConfig config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; auto socket = std::make_unique(); auto* socketPtr = socket.get(); auto engine = net::NetworkEngine( myid, config, std::move(socket), {}, rd, scheduler, [](Sp, net::DhtProtocolException) {}, [&onNewNodeCalls](const Sp&, int) { ++onNewNodeCalls; }, [](const InfoHash&, const SockAddr&) {}, [](Sp) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, want_t) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, want_t, const Query&) { return net::RequestAnswer {}; }, [responseToken](Sp, const InfoHash&, const Blob&, Tid, const Query&, int) { net::RequestAnswer answer {}; answer.ntoken = responseToken; return answer; }, [](Sp, const InfoHash&, const Blob&, const std::vector>&, const time_point&) { return net::RequestAnswer {}; }, [](Sp, const InfoHash&, const Blob&, const Value::Id&) { return net::RequestAnswer {}; }); auto from = makeIPv4("127.0.0.2", 5003); auto packet = makeListenPacketBlob(remoteId, hash, 17, 23, requestToken); engine.processMessage(packet.data(), packet.size(), from); CPPUNIT_ASSERT_EQUAL((size_t) 1, socketPtr->sends.size()); const auto& sent = socketPtr->sends.front(); CPPUNIT_ASSERT(sent.dest == from); auto replyObject = msgpack::unpack((const char*) sent.data.data(), sent.data.size()); ParsedMessage reply; reply.msgpack_unpack(replyObject.get()); CPPUNIT_ASSERT(reply.type == MessageType::Reply); CPPUNIT_ASSERT_EQUAL(myid, reply.id); CPPUNIT_ASSERT(reply.token == responseToken); } void NetworkEngineTester::testListenConfirmationUpdatesSearchNodeToken() { Config config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; auto rd = std::make_unique(6); Dht localDht(std::make_unique(), config, {}, std::move(rd)); auto node = std::make_shared(InfoHash::getRandom(localDht.rd), makeIPv4("127.0.0.2", 5006), localDht.rd, false); auto sr = std::make_shared(); auto query = std::make_shared(); ValueCallback valueCallback = [](const std::vector>&, bool) { return true; }; sr->id = InfoHash::getRandom(localDht.rd); sr->af = AF_INET; sr->nextSearchStep = localDht.scheduler.add(time_point::max(), [] {}); sr->listeners.emplace(1, Dht::Search::SearchListener {query, valueCallback, {}}); auto searchNode = std::make_unique(node); auto socketId = node->openSocket([](const Sp&, net::RequestAnswer&&) {}); searchNode->listenStatus.emplace(std::piecewise_construct, std::forward_as_tuple(query), std::forward_as_tuple(valueCallback, dht::SyncCallback {}, socketId)); auto* searchNodePtr = sr->nodes.emplace_back(std::move(searchNode)).get(); CPPUNIT_ASSERT(searchNodePtr->token.empty()); CPPUNIT_ASSERT(!searchNodePtr->isSynced(localDht.scheduler.time())); net::RequestAnswer answer {}; answer.ntoken = Blob {0xaa, 0xbb, 0xcc, 0xdd}; localDht.onListenDone(node, answer, sr); CPPUNIT_ASSERT(searchNodePtr->token == answer.ntoken); /* last_get_reply should NOT be updated by listen confirmation, so that periodic find_node requests still refresh the search table. */ CPPUNIT_ASSERT(searchNodePtr->last_get_reply == time_point::min()); CPPUNIT_ASSERT(!searchNodePtr->isSynced(localDht.scheduler.time())); } void NetworkEngineTester::testListenReopensSocketAfterNodeExpiration() { Config config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; auto rd = std::make_unique(4); Dht localDht(std::make_unique(), config, {}, std::move(rd)); auto node = std::make_shared(InfoHash::getRandom(localDht.rd), makeIPv4("127.0.0.2", 5004), localDht.rd, false); auto sr = std::make_shared(); auto query = std::make_shared(); ValueCallback valueCallback = [](const std::vector>&, bool) { return true; }; dht::SyncCallback syncCallback {}; sr->id = InfoHash::getRandom(localDht.rd); sr->af = AF_INET; sr->listeners.emplace(1, Dht::Search::SearchListener {query, valueCallback, {}}); auto searchNode = std::make_unique(node); auto staleSocketId = node->openSocket([](const Sp&, net::RequestAnswer&&) {}); auto status = searchNode->listenStatus .emplace(std::piecewise_construct, std::forward_as_tuple(query), std::forward_as_tuple(valueCallback, syncCallback, staleSocketId)) .first; status->second.req = std::make_shared(net::Request::State::COMPLETED); status->second.req->reply_time = time_point::min(); node->setExpired(); CPPUNIT_ASSERT(!node->getSocket(staleSocketId)); auto* searchNodePtr = sr->nodes.emplace_back(std::move(searchNode)).get(); searchNodePtr->token = Blob {0x42}; searchNodePtr->last_get_reply = localDht.scheduler.time(); localDht.searchSynchedNodeListen(sr, *searchNodePtr); auto listenIt = searchNodePtr->listenStatus.find(query); CPPUNIT_ASSERT(listenIt != searchNodePtr->listenStatus.end()); CPPUNIT_ASSERT(listenIt->second.req); CPPUNIT_ASSERT(listenIt->second.socketId); CPPUNIT_ASSERT(listenIt->second.socketId != staleSocketId); CPPUNIT_ASSERT(node->getSocket(listenIt->second.socketId)); } void NetworkEngineTester::testUnauthorizedListenFlushClearsListenState() { Config config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; auto rd = std::make_unique(5); Dht localDht(std::make_unique(), config, {}, std::move(rd)); auto node = std::make_shared(InfoHash::getRandom(localDht.rd), makeIPv4("127.0.0.2", 5005), localDht.rd, false); auto sr = std::make_shared(); auto query = std::make_shared(); ValueCallback valueCallback = [](const std::vector>&, bool) { return true; }; dht::SyncCallback syncCallback {}; sr->id = InfoHash::getRandom(localDht.rd); sr->af = AF_INET; auto searchNode = std::make_unique(node); auto socketId = node->openSocket([](const Sp&, net::RequestAnswer&&) {}); auto listenIt = searchNode->listenStatus .emplace(std::piecewise_construct, std::forward_as_tuple(query), std::forward_as_tuple(valueCallback, syncCallback, socketId)) .first; listenIt->second.req = std::make_shared(); listenIt->second.refresh = localDht.scheduler.add(localDht.scheduler.time() + std::chrono::seconds(1), [] {}); searchNode->token = Blob {0x10, 0x11, 0x12}; searchNode->last_get_reply = localDht.scheduler.time(); auto* searchNodePtr = sr->nodes.emplace_back(std::move(searchNode)).get(); localDht.searches(AF_INET).emplace(sr->id, sr); auto failingReq = std::make_shared( MessageType::Listen, node->getNewTid(), node, Blob {}, [](const net::Request&, ParsedMessage&&) {}, [](const net::Request&, bool) {}); localDht.onError(failingReq, net::DhtProtocolException {net::DhtProtocolException::UNAUTHORIZED, net::DhtProtocolException::LISTEN_WRONG_TOKEN}); CPPUNIT_ASSERT(searchNodePtr->token.empty()); CPPUNIT_ASSERT(searchNodePtr->last_get_reply == time_point::min()); auto listenStatusIt = searchNodePtr->listenStatus.find(query); CPPUNIT_ASSERT(listenStatusIt != searchNodePtr->listenStatus.end()); CPPUNIT_ASSERT(!listenStatusIt->second.req); CPPUNIT_ASSERT(!listenStatusIt->second.refresh); } void NetworkEngineTester::testGetRequestReuse() { struct ReuseCase { const char* name; bool projected; Query requested; Query answered; bool shouldSend; }; const Select ids = Select {}.field(Value::Field::Id); const Select idTypes = Select {}.field(Value::Field::Id).field(Value::Field::ValueType); const std::vector cases { {"different projection filters", true, Query {ids, Where {}.valueType(1)}, Query {ids}, true }, {"matching projection filters", true, Query {ids, Where {}.valueType(1)}, Query {ids, Where {}.valueType(1)}, false }, {"wider matching projection", true, Query {ids, Where {}.valueType(1)}, Query {idTypes, Where {}.valueType(1)}, false }, {"narrower projection", true, Query {idTypes, Where {}.valueType(1)}, Query {ids, Where {}.valueType(1)}, true }, {"filtered answer is incomplete", true, Query {ids}, Query {ids, Where {}.valueType(1)}, true }, {"ordinary get can filter locally", false, Query {Select {}, Where {}.valueType(1)}, Query {}, false}, }; for (auto state : {net::Request::State::PENDING, net::Request::State::COMPLETED}) { for (const auto& testCase : cases) { Config config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; Dht localDht(std::make_unique(), config, {}, std::make_unique(42)); const auto now = localDht.scheduler.time(); auto search = std::make_shared(); search->id = InfoHash::getRandom(localDht.rd); search->af = AF_INET; auto requested = std::make_shared(testCase.requested); auto answered = std::make_shared(testCase.answered); Dht::Get operation {}; operation.start = now - std::chrono::seconds {1}; operation.query = requested; if (testCase.projected) operation.query_cb = [](const std::vector>&) { return true; }; else operation.get_cb = [](const std::vector>&) { return true; }; operation.done_cb = [](bool, const std::vector>&) { }; search->callbacks.emplace(operation.start, operation); auto peer = std::make_shared(InfoHash::getRandom(localDht.rd), makeIPv4("127.0.0.2", 5005), localDht.rd, false); auto searchNode = std::make_unique(peer); searchNode->last_get_reply = now; auto reply = std::make_shared(state); reply->node = peer; reply->reply_time = now; searchNode->getStatus[answered] = reply; auto searchNodePtr = search->nodes.emplace_back(std::move(searchNode)).get(); auto sent = localDht.searchSendGetValues(search, searchNodePtr, true); CPPUNIT_ASSERT_EQUAL_MESSAGE(testCase.name, testCase.shouldSend, sent != nullptr); CPPUNIT_ASSERT_EQUAL_MESSAGE(testCase.name, testCase.shouldSend, searchNodePtr->getStatus.count(requested) != 0); } } } void NetworkEngineTester::testFilteredProjectionDoesNotWaitForUnrelatedPeer() { Config config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; auto socket = std::make_unique(); auto socketPtr = socket.get(); auto random = std::make_unique(43); Dht localDht(std::move(socket), config, {}, std::move(random)); const auto now = localDht.scheduler.time(); auto search = std::make_shared(); search->id = InfoHash::getRandom(localDht.rd); search->af = AF_INET; search->refill_time = now; search->nextSearchStep = localDht.scheduler.add(time_point::max(), [] {}); auto broad = std::make_shared(Select {}.field(Value::Field::Id)); auto filtered = std::make_shared(Select {}.field(Value::Field::Id), Where {}.valueType(1)); Dht::Get broadOperation {}; broadOperation.start = now - std::chrono::seconds {2}; broadOperation.query = broad; broadOperation.query_cb = [](const std::vector>&) { return true; }; broadOperation.done_cb = [](bool, const std::vector>&) { }; search->callbacks.emplace(broadOperation.start, broadOperation); size_t filteredCallbacks = 0; Dht::Get filteredOperation {}; filteredOperation.start = now - std::chrono::seconds {1}; filteredOperation.query = filtered; filteredOperation.query_cb = [&](const std::vector>&) { ++filteredCallbacks; return true; }; filteredOperation.done_cb = [](bool, const std::vector>&) { }; search->callbacks.emplace(filteredOperation.start, filteredOperation); for (unsigned peerIndex = 0; peerIndex < 2; ++peerIndex) { auto peer = std::make_shared(InfoHash::getRandom(localDht.rd), makeIPv4(peerIndex == 0 ? "127.0.0.2" : "127.0.0.3", 5005), localDht.rd, false); auto searchNode = std::make_unique(peer); searchNode->last_get_reply = now; searchNode->token = Blob {1}; auto state = peerIndex == 0 ? net::Request::State::COMPLETED : net::Request::State::PENDING; auto request = std::make_shared(state); request->node = peer; request->reply_time = now; searchNode->getStatus[broad] = request; search->nodes.emplace_back(std::move(searchNode)); } auto& readyPeer = *search->nodes.front(); auto reply = readyPeer.getStatus.at(broad); net::RequestAnswer answer {}; answer.ntoken = Blob {1}; Value value {(const uint8_t*) "a", 1}; value.id = 42; answer.fields.emplace_back(std::make_shared(value, broad->select)); socketPtr->sends.clear(); localDht.searchNodeGetDone(*reply, std::move(answer), search, broad); localDht.searchStep(search); CPPUNIT_ASSERT_EQUAL(size_t {0}, filteredCallbacks); CPPUNIT_ASSERT(not socketPtr->sends.empty()); CPPUNIT_ASSERT(readyPeer.getStatus.count(filtered) != 0); auto filteredReply = std::make_shared(net::Request::State::COMPLETED); filteredReply->node = readyPeer.node; filteredReply->reply_time = now; readyPeer.getStatus[filtered] = filteredReply; net::RequestAnswer filteredAnswer {}; filteredAnswer.ntoken = Blob {1}; filteredAnswer.fields.emplace_back(std::make_shared(value, filtered->select)); localDht.searchNodeGetDone(*filteredReply, std::move(filteredAnswer), search, filtered); CPPUNIT_ASSERT_EQUAL(size_t {1}, filteredCallbacks); } void NetworkEngineTester::testFindNodeRequestReuse() { Config config {}; config.max_req_per_sec = -1; config.max_peer_req_per_sec = -1; Dht localDht(std::make_unique(), config, {}, std::make_unique(44)); const auto now = localDht.scheduler.time(); auto search = std::make_shared(); search->id = InfoHash::getRandom(localDht.rd); search->af = AF_INET; auto peer = std::make_shared(InfoHash::getRandom(localDht.rd), makeIPv4("127.0.0.2", 5005), localDht.rd, false); auto searchNode = std::make_unique(peer); searchNode->last_get_reply = now; auto searchNodePtr = search->nodes.emplace_back(std::move(searchNode)).get(); CPPUNIT_ASSERT(localDht.searchSendGetValues(search) == searchNodePtr); CPPUNIT_ASSERT_EQUAL(size_t {1}, searchNodePtr->getStatus.size()); CPPUNIT_ASSERT(searchNodePtr->getStatus.begin()->second->getType() == MessageType::FindNode); CPPUNIT_ASSERT(not localDht.searchSendGetValues(search)); auto reply = std::make_shared(net::Request::State::COMPLETED); reply->reply_time = now; searchNodePtr->getStatus.begin()->second = reply; CPPUNIT_ASSERT(not localDht.searchSendGetValues(search)); } } // namespace test #endif