| /* |
| * 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()) |