tests: add peerDiscovery test
Enable the peer discovery feature (peer_discovery && peer_publish) instead of relying on the bootstrap node.
Change-Id: Ib283a1a0c934c335d3e3fd1103750c9d00090512
diff --git a/CMakeLists.txt b/CMakeLists.txt
index deda8b7..55bd01c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -383,6 +383,10 @@
target_link_libraries(tests_turnCache PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit)
add_test(NAME tests_turnCache COMMAND tests_turnCache)
+ add_executable(tests_peerDiscovery tests/peerDiscovery.cpp)
+ target_link_libraries(tests_peerDiscovery PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit)
+ add_test(NAME tests_peerDiscovery COMMAND tests_peerDiscovery)
+
#add_executable(tests_stringutils tests/testString_utils.cpp)
#target_link_libraries(tests_stringutils PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit)
#add_test(NAME tests_stringutils COMMAND tests_stringutils)
diff --git a/tests/peerDiscovery.cpp b/tests/peerDiscovery.cpp
new file mode 100644
index 0000000..fae0387
--- /dev/null
+++ b/tests/peerDiscovery.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "connectionmanager.h"
+#include "multiplexed_socket.h"
+#include "test_runner.h"
+#include "certstore.h"
+
+#include <opendht/log.h>
+#include <asio/executor_work_guard.hpp>
+#include <asio/io_context.hpp>
+#include <fmt/compile.h>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <condition_variable>
+#include <iostream>
+#include <filesystem>
+
+using namespace std::literals::chrono_literals;
+
+namespace dhtnet {
+namespace test {
+
+struct ConnectionHandler
+{
+ dht::crypto::Identity id;
+ std::shared_ptr<Logger> logger;
+ std::shared_ptr<tls::CertificateStore> certStore;
+ std::shared_ptr<dht::DhtRunner> dht;
+ std::shared_ptr<ConnectionManager> connectionManager;
+ std::shared_ptr<asio::io_context> ioContext;
+ std::shared_ptr<std::thread> ioContextRunner;
+};
+
+class PeerDiscoveryTest : public CppUnit::TestFixture
+{
+public:
+ PeerDiscoveryTest() {
+ pj_log_set_level(0);
+ pj_log_set_log_func([](int level, const char* data, int /*len*/) {});
+ testDir_ = std::filesystem::current_path() / "tmp_tests_PeerDiscoveryTest";
+ }
+ ~PeerDiscoveryTest() {}
+ static std::string name() { return "PeerDiscoveryTest"; }
+ void setUp();
+ void tearDown();
+
+ dht::crypto::Identity org1Id, org2Id;
+ dht::crypto::Identity aliceId, bobId;
+ dht::crypto::Identity aliceDevice1Id, bobDevice1Id;
+
+ std::unique_ptr<ConnectionHandler> alice;
+ std::unique_ptr<ConnectionHandler> bob;
+
+ std::mutex mtx;
+ std::shared_ptr<asio::io_context> ioContext;
+ std::shared_ptr<std::thread> ioContextRunner;
+ std::shared_ptr<Logger> logger = dht::log::getStdLogger();
+ std::shared_ptr<IceTransportFactory> factory;
+
+private:
+ std::unique_ptr<ConnectionHandler> setupHandler(const dht::crypto::Identity& id);
+ std::filesystem::path testDir_;
+
+ void testConnectDevice();
+ CPPUNIT_TEST_SUITE(PeerDiscoveryTest);
+ CPPUNIT_TEST(testConnectDevice);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(PeerDiscoveryTest, PeerDiscoveryTest::name());
+
+std::unique_ptr<ConnectionHandler>
+PeerDiscoveryTest::setupHandler(const dht::crypto::Identity& id)
+{
+ auto h = std::make_unique<ConnectionHandler>();
+ h->id = id;
+ h->logger = logger;
+ h->certStore = std::make_shared<tls::CertificateStore>(testDir_ / id.second->getName(), nullptr/*h->logger*/);
+ h->ioContext = ioContext;
+ h->ioContextRunner = ioContextRunner;
+
+ dht::DhtRunner::Config dhtConfig;
+ dhtConfig.dht_config.id = h->id;
+ dhtConfig.threaded = true;
+ dhtConfig.peer_discovery = true;
+ dhtConfig.peer_publish = true;
+
+ dht::DhtRunner::Context dhtContext;
+
+ dhtContext.certificateStore = [c = h->certStore](const dht::InfoHash& pk_id) {
+ std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
+ if (auto cert = c->getCertificate(pk_id.toString()))
+ ret.emplace_back(std::move(cert));
+ return ret;
+ };
+ dhtContext.logger = h->logger;
+
+ h->dht = std::make_shared<dht::DhtRunner>();
+ h->dht->run(dhtConfig, std::move(dhtContext));
+ auto config = std::make_shared<ConnectionManager::Config>();
+ config->dht = h->dht;
+ config->id = h->id;
+ config->ioContext = h->ioContext;
+ config->factory = factory;
+ config->certStore = h->certStore;
+ config->cachePath = testDir_ / id.second->getName() / "temp";
+
+ h->connectionManager = std::make_shared<ConnectionManager>(config);
+ h->connectionManager->onICERequest([](const DeviceId&) { return true; });
+ h->connectionManager->onDhtConnected(h->id.first->getPublicKey());
+
+ return h;
+}
+
+void
+PeerDiscoveryTest::setUp()
+{
+ if (not org1Id.first) {
+ org1Id = dht::crypto::generateIdentity("org1");
+ org2Id = dht::crypto::generateIdentity("org2");
+ aliceId = dht::crypto::generateIdentity("alice", org1Id, 2048, true);
+ bobId = dht::crypto::generateIdentity("bob", org2Id, 2048, true);
+ aliceDevice1Id = dht::crypto::generateIdentity("aliceDevice1", aliceId);
+ bobDevice1Id = dht::crypto::generateIdentity("bobDevice1", bobId);
+ }
+
+ ioContext = std::make_shared<asio::io_context>();
+ ioContextRunner = std::make_shared<std::thread>([context = ioContext]() {
+ try {
+ auto work = asio::make_work_guard(*context);
+ context->run();
+ } catch (const std::exception& ex) {
+ fmt::print("Exception in ioContextRunner: {}\n", ex.what());
+ }
+ });
+
+ factory = std::make_unique<IceTransportFactory>(/*logger*/);
+ alice = setupHandler(aliceDevice1Id);
+ bob = setupHandler(bobDevice1Id);
+}
+
+void
+PeerDiscoveryTest::tearDown()
+{
+ ioContext->stop();
+
+ if (ioContextRunner && ioContextRunner->joinable()) {
+ ioContextRunner->join();
+ }
+
+ alice.reset();
+ bob.reset();
+ factory.reset();
+ std::filesystem::remove_all(testDir_);
+}
+
+void PeerDiscoveryTest::testConnectDevice()
+{
+ std::condition_variable bobConVar;
+ bool isBobRecvChanlReq = false;
+ bob->connectionManager->onChannelRequest(
+ [&](const std::shared_ptr<dht::crypto::Certificate>&,
+ const std::string& name) {
+ std::lock_guard lock{mtx};
+ isBobRecvChanlReq = name == "dummyName";
+ bobConVar.notify_one();
+ return true;
+ });
+
+ std::condition_variable alicConVar;
+ bool isAlicConnected = false;
+ alice->connectionManager->connectDevice(bob->id.second, "dummyName", [&](std::shared_ptr<ChannelSocket> socket, const DeviceId&) {
+ std::lock_guard lock{mtx};
+ if (socket) {
+ isAlicConnected = true;
+ }
+ alicConVar.notify_one();
+ });
+
+ std::unique_lock lock{mtx};
+ CPPUNIT_ASSERT(bobConVar.wait_for(lock, 30s, [&] { return isBobRecvChanlReq; }));
+ CPPUNIT_ASSERT(alicConVar.wait_for(lock, 30s, [&] { return isAlicConnected; }));
+}
+
+}
+}
+JAMI_TEST_RUNNER(dhtnet::test::PeerDiscoveryTest::name())