blob: a9590ca92866d3533d1853e871b25cafb41398e6 [file] [log] [blame]
/*
* 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 "certstore.h"
#include "string_utils.h"
#include <opendht/log.h>
#include <opendht/utils.h>
#include <opendht/thread_pool.h>
#include <asio/executor_work_guard.hpp>
#include <asio/io_context.hpp>
#include <readline/readline.h>
#include <readline/history.h>
#include <condition_variable>
#include<fmt/chrono.h>
namespace dhtnet {
using namespace std::literals::chrono_literals;
using clock = std::chrono::high_resolution_clock;
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;
};
std::unique_ptr<ConnectionHandler>
setupHandler(const std::string& name,
std::shared_ptr<asio::io_context> ioContext,
std::shared_ptr<std::thread> ioContextRunner,
std::shared_ptr<IceTransportFactory> factory,
std::shared_ptr<Logger> logger)
{
auto h = std::make_unique<ConnectionHandler>();
auto ca = dht::crypto::generateIdentity("ca");
h->id = dht::crypto::generateIdentity(name, ca);
h->logger = logger;
h->certStore = std::make_shared<tls::CertificateStore>(name, h->logger);
h->ioContext = std::make_shared<asio::io_context>();
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.identityAnnouncedCb = [](bool ok) {
fmt::print("{} Identity announced {}\n", clock::now().time_since_epoch().count(), ok);
};
dhtContext.publicAddressChangedCb = [](std::vector<dht::SockAddr> addr) {
if (addr.size() != 0)
fmt::print("{} Public address changed\n", clock::now().time_since_epoch().count());
};
dhtContext.statusChangedCallback = [](dht::NodeStatus status4, dht::NodeStatus status6) {
fmt::print("{} Connectivity changed: IPv4: {}, IPv6: {}\n", clock::now().time_since_epoch().count(), dht::statusToStr(status4), dht::statusToStr(status6));
};
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->logger = logger;
config->certStore = h->certStore;
config->cachePath = std::filesystem::current_path() / "temp";
h->connectionManager = std::make_shared<ConnectionManager>(config);
h->connectionManager->onICERequest([](const DeviceId&) { return true; });
fmt::print("Identity:{}\n", h->id.second->getId());
return h;
}
void
print_help()
{
fmt::print("Commands:\n");
fmt::print(" help, h, ? - print this help\n");
fmt::print(" quit, exit, q, x - exit the program\n");
fmt::print(" connect <peer_id> - connect to a peer\n");
fmt::print(" cc - connectivity changed\n");
}
} // namespace dhtnet
static void
setSipLogLevel()
{
int level = 0;
if (char* envvar = getenv("SIPLOGLEVEL")) {
// From 0 (min) to 6 (max)
level = std::clamp(std::stoi(envvar), 0, 6);
}
pj_log_set_level(level);
pj_log_set_log_func([](int level, const char* data, int /*len*/) {});
}
using namespace std::literals::chrono_literals;
int
main(int argc, char** argv)
{
setSipLogLevel();
std::shared_ptr<dhtnet::Logger> logger; // = dht::log::getStdLogger();
auto factory = std::make_shared<dhtnet::IceTransportFactory>(logger);
auto ioContext = std::make_shared<asio::io_context>();
auto 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(stderr, "Exception: {}\n", ex.what());
}
});
// Create a new DHTNet node
auto dhtnet = setupHandler("DHT", ioContext, ioContextRunner, factory, logger);
dhtnet->connectionManager->onDhtConnected(dhtnet->id.first->getPublicKey());
// Set up a handler for incoming channel requests
dhtnet->connectionManager->onChannelRequest(
[](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& name) {
fmt::print("Channel request received: {}\n", name);
return true;
});
while (true) {
char* l = readline("> ");
if (not l)
break;
std::string_view line {l};
if (line.empty())
continue;
add_history(l);
auto args = dhtnet::split_string(line, ' ');
auto command = args[0];
if (command == "quit" || command == "exit" || command == "q" || command == "x")
break;
else if (command == "help" || command == "h" || command == "?") {
dhtnet::print_help();
} else if (command == "connect") {
if (args.size() < 2) {
fmt::print("Usage: connect <peer_id>\n");
continue;
}
std::condition_variable cv;
std::mutex mtx;
std::unique_lock lock {mtx};
bool ret = false;
dht::InfoHash peer_id(args[1]);
dhtnet->connectionManager
->connectDevice(peer_id,
"channelName",
[&](std::shared_ptr<dhtnet::ChannelSocket> socket,
const dht::InfoHash&) {
if (socket) {
ret = true;
cv.notify_one();
}
});
if (cv.wait_for(lock, 10s, [&] { return ret; })) {
fmt::print("Connected to {}\n", peer_id);
} else {
fmt::print("Failed to connect to {}\n", peer_id);
}
} else if (command == "cc") {
dhtnet->dht->connectivityChanged();
} else {
fmt::print("Unknown command: {}\n", command);
}
}
fmt::print("Stopping…\n");
ioContext->stop();
ioContextRunner->join();
}