diff --git a/CMakeLists.txt b/CMakeLists.txt index 64c1b6d3bae478cc8548b2b3a5ea16e60a5a67c1..0939b6924dd9015980dfae3688073a0214f87578 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ option(BUILD_DEPENDENCIES "Build dependencies" ON) option(DNC_SYSTEMD_UNIT_FILE_LOCATION "Where to install systemd unit file") option(DNC_SYSTEMD "Enable dnc systemd integration" ON) option(CODE_COVERAGE "Enable coverage reporting" OFF) +option(BUILD_EXAMPLE "Build example" ON) # Check if testing is enabled if(BUILD_TESTING) @@ -426,4 +427,17 @@ if (BUILD_TESTING AND NOT MSVC) #target_link_libraries(tests_stringutils PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit) #add_test(NAME tests_stringutils COMMAND tests_stringutils) +endif() +if (BUILD_EXAMPLES AND NOT MSVC) + add_executable(server + example/server.cpp) + target_link_libraries(server PRIVATE dhtnet) + target_include_directories(server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/example) + install(TARGETS server RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(client + example/client.cpp) + target_link_libraries(client PRIVATE dhtnet) + target_include_directories(client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/example) + install(TARGETS client RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() \ No newline at end of file diff --git a/README.md b/README.md index ab329ddbbad41ac525644432ffd4d382633dba0d..475dde2518d853737e724bb8d0f045a58bbdf8b1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DHTNet - Lightweight Peer-to-Peer Communication Library - +![DHTNet Logo]() DHTNet is a C++17 library designed to serve as a network overlay that provides an IP network abstraction. Its main objective is to establish secure peer-to-peer connections using public-key authentication. @@ -31,58 +31,9 @@ Get started with DHTNet by building and installing the library: - [Build and Install Instructions](BUILD.md) ## Usage Example - -```cpp -#include "connectionmanager.h" -#include <opendht/log.h> -#include <opendht/utils.h> -#include <opendht/thread_pool.h> -#include <fmt/core.h> - -int main() { - // Create identities for CA (Certificate Authority), client, and server - auto ca = dht::crypto::generateIdentity("ca"); - auto id_client = dht::crypto::generateIdentity("client", ca); - auto id_server = dht::crypto::generateIdentity("server", ca); - - // Create client and server ConnectionManager instances - auto client = std::make_shared<ConnectionManager>(id_client); - auto server = std::make_shared<ConnectionManager>(id_server); - - // Launch dht nodes - client->onDhtConnected(id_client.first->getPublicKey()); - server->onDhtConnected(id_server.first->getPublicKey()); - - // Connect the client to the server's device via a channel named "channelName" - client->connectDevice(id_server.second->getId(), "channelName", [&](std::shared_ptr<dhtnet::ChannelSocket> socket, - const dht::InfoHash&) { - if (socket) { - // Send a message (example: "Hello") to the server - std::error_code ec; - std::string data = "hello"; - socket->write(data.data(), data.size(), ec); - } - }); - - // Define a callback function for when the server's connection is ready - server->onConnectionReady([&](const DeviceId& device, const std::string& name, std::shared_ptr<ChannelSocket> socket) { - if (socket) { - // Server: Connection succeeded - fmt::print("Server: Connection succeeded\n"); - - // Set a callback for receiving messages - socket->setOnRecv([&](const uint8_t* data, size_t size) { - fmt::print("Message received: {}\n", std::string_view(data, data + size)); // Print received message - }); - } else { - // Server: Connection failed - fmt::print("Server: Connection failed\n"); - } - }); - - return 0; -} -``` +In the example repository, there is a client-server application where the client connects to the server and sends a "hello" message. +You can build the example using the project's [Build and Install Instructions](BUILD.md) with `-BUILS_EXAMPLE=ON`. + ## Dependencies diff --git a/example/client-server_dhtnet.png b/example/client-server_dhtnet.png new file mode 100644 index 0000000000000000000000000000000000000000..de6ea40a3210752455551800e986217ab5496ac5 Binary files /dev/null and b/example/client-server_dhtnet.png differ diff --git a/example/client.cpp b/example/client.cpp new file mode 100644 index 0000000000000000000000000000000000000000..39073b65ed09012ad9e9cde226495ba4035ffb99 --- /dev/null +++ b/example/client.cpp @@ -0,0 +1,74 @@ +#include "certstore.h" +#include "connectionmanager.h" +#include "fileutils.h" + +#include <opendht/crypto.h> + +#include <string> +#include <vector> + +namespace dhtnet { +void +client(dht::crypto::Identity id_client, dht::InfoHash id_server) +{ + fmt::print("Start client\n"); + fmt::print("Client identity: {}\n", id_client.second->getId()); + + // Create client ConnectionManager instance + auto client = std::make_shared<ConnectionManager>(id_client); + + // Launch dht node + client->onDhtConnected(id_client.first->getPublicKey()); + + // Connect the client to the server's device via a channel named "channelName" + client->connectDevice(id_server, + "channelName", + [&](std::shared_ptr<ChannelSocket> socket, const dht::InfoHash&) { + fmt::print("Client: Sending request\n"); + if (socket) { + // Send a message (example: "Hello") to the server + std::error_code ec; + std::string msg = "hello"; + fmt::print("Client: Sending message: {}\n", msg); + std::vector<unsigned char> data(msg.begin(), msg.end()); + + socket->write(data.data(), data.size(), ec); + // For continuous data transmission, refer to the readFromPipe + // function in tools/common.cpp + if (ec) { + fmt::print("Client: Error writing to socket: {}\n", + ec.message()); + } else { + fmt::print("Client: Message sent\n"); + } + } else { + fmt::print("Client: Connection failed\n"); + return; + } + }); + + // keep the client running + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} +} // namespace dhtnet + +int +main(int argc, char** argv) +{ + // Set the log level to 0 to avoids pj logs + pj_log_set_level(0); + + // This is the root certificate that will be used to sign other certificates + auto ca = dht::crypto::generateIdentity("ca_client"); + + auto id_client = dht::crypto::generateIdentity("client", ca); + + auto id_server = dht::InfoHash(argv[1]); + + dhtnet::client(id_client, id_server); + + + return 0; +} \ No newline at end of file diff --git a/example/server.cpp b/example/server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ace5fe7878688d327acf493dc95793c66779a6c --- /dev/null +++ b/example/server.cpp @@ -0,0 +1,85 @@ + +#include "connectionmanager.h" +#include "fileutils.h" + +#include <opendht/log.h> +#include <opendht/crypto.h> + +#include <fcntl.h> +#include <unistd.h> + +#include <string> +#include <string_view> +namespace dhtnet { +void +server(dht::crypto::Identity id_server) +{ + fmt::print("Server identity: {}\n", id_server.second->getId()); + // Create an instance of ConnectionManager for the server + auto server = std::make_shared<ConnectionManager>(id_server); + + fmt::print("Start server\n"); + // Start the DHT node for the server + server->onDhtConnected(id_server.first->getPublicKey()); + + // Handle ICE connection requests from devices + // This callback is triggered when a device requests an ICE connection. + // The callback should decide whether to accept or decline the request. + server->onICERequest([id_server](const DeviceId& device) { + // Optional: Add logic to validate the device's certificate + // Example: Check if the device's certificate is signed by a trusted authority + // In this example, all devices are allowed to connect + fmt::print("Server: ICE request received from {}\n", device.toString()); + return true; + }); + + // Handle requests for establishing a communication channel + // The callback checks if the channel should be opened based on the name or device's certificate. + server->onChannelRequest( + [&](const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name) { + // Optional: Add logic to validate the channel name or certificate + // Example: Allow the connection if the channel name is "channelName" + fmt::print("Server: Channel request received from {}\n", cert->getLongId()); + return name == "channelName"; + }); + + // Define a callback when the connection is established + server->onConnectionReady([&](const DeviceId& device, + const std::string& name, + std::shared_ptr<ChannelSocket> socket) { + if (socket) { + fmt::print("Server: Connection succeeded\n"); + // Set up a callback to handle incoming messages on this connection + socket->setOnRecv([socket](const uint8_t* data, size_t size) { + fmt::print("Server: Received message: {}\n", std::string((const char*) data, size)); + return size; + }); + } else { + // The connection failed + fmt::print("Server: Connection failed\n"); + } + }); + + // Keep the server running indefinitely + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} + +} // namespace dhtnet + +int +main() +{ + // Set the log level to 0 to avoids pj logs + pj_log_set_level(0); + + // This is the root certificate that will be used to sign other certificates + auto ca = dht::crypto::generateIdentity("ca"); + + auto id_server = dht::crypto::generateIdentity("server", ca); + + dhtnet::server(id_server); + + return 0; +} \ No newline at end of file