diff --git a/CMakeLists.txt b/CMakeLists.txt index deda8b71ef5b0c643652c91640a2f130f0a3bbde..55bd01c833addf7d76321b49a925ed228320d747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,10 @@ if (BUILD_TESTING AND NOT MSVC) 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 0000000000000000000000000000000000000000..fae03878456cf8677aa35519101d7d4b1cd75ebb --- /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())