Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
marty1885 committed Oct 20, 2022
0 parents commit 24f195c
Show file tree
Hide file tree
Showing 9 changed files with 963 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build/
.vscode
.cache
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.15)
project(gnunetpp)

add_subdirectory(gnunetpp)

add_subdirectory(examples)
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# GNUNet++ - A C++ wrapper for GNUNet

## Why GNUNet++?

IMO GNUNet is a cool project which shows what decentralized systems could look like. However the [time to first hello world][ttfhw] is way too long, API is not well documented and everything is written GNU style C. Which are all factors against it's adoption. GNUNet++ wraps all the C-ness into a (more or less) modern C++ style API. Provides proper lifetime management and an easier learning path. Hopefully leading to a better development experience.

[ttfhw]: https://www.moesif.com/blog/technical/api-product-management/What-is-TTFHW/

## Show me some code!

The DHT class provides easy access to GNUnet's DHT. It's API is designed to look like [OpenDHT][opendht]'s for more familiarity to potential users. Note that need GNUNet running on your system before running the samples.

```cpp
// Max 32 simultaneous operations
auto dht = std::make_shared<DHT>(cfg, 32);

...
dht->put("Answer", "42");
dht->get("some_super_secret", [](const std::string_view data) {
std::cout << "super secret info: " << data << std::endl;
return true; // keep on searching after the current one.
// return false to cancel futher lookups.
}, std::chrono::seconds(30)); // Max searching for 30 seconds

```
[opendht]: https://github.com/savoirfairelinux/opendht
2 changes: 2 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_executable(gunetpp-dht dht/main.cpp)
target_link_libraries(gunetpp-dht gnunetdht gnunetcore gnunetutil gnunetpp)
21 changes: 21 additions & 0 deletions examples/dht/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <gnunetpp.hpp>
#include <iostream>

std::shared_ptr<gnunetpp::DHT> dht;
void service(const GNUNET_CONFIGURATION_Handle* cfg)
{
dht = std::make_shared<gnunetpp::DHT>(cfg);
gnunetpp::runLater(std::chrono::seconds(1), []{
gnunetpp::shutdown();
});
dht->get("hello", [](const std::string_view payload) -> bool {
std::cout << "Found " << payload << "!" << std::endl;
return false;
}, std::chrono::seconds(10));
gnunetpp::shutdown();
}

int main()
{
gnunetpp::run(service);
}
2 changes: 2 additions & 0 deletions gnunetpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_library(gnunetpp gnunetpp.cpp)
target_include_directories(gnunetpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
Empty file added gnunetpp/gnunetpp.cpp
Empty file.
241 changes: 241 additions & 0 deletions gnunetpp/gnunetpp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#pragma once

#include "gnunet/platform.h"
#include <gnunet/gnunet_core_service.h>
#include "gnunet/gnunet_dht_service.h"

#include <chrono>
#include <stdexcept>
#include <functional>
#include <cassert>
#include <memory>
#include <mutex>
#include <set>
#include <iostream>
#include <vector>

namespace gnunetpp
{

struct Service
{
virtual ~Service() = default;
Service(const Service&) = delete;
Service& operator=(const Service&) = delete;
Service(Service&&) = delete;
Service& operator=(Service&&) = delete;

virtual void shutdown() = 0;
protected:
Service() = default;
};

std::mutex g_mtx_services;
std::set<Service*> g_services;

inline void registerService(Service* service)
{
std::lock_guard<std::mutex> m(g_mtx_services);
g_services.insert(service);
}

inline void removeService(Service* service)
{
std::lock_guard<std::mutex> m(g_mtx_services);
service->shutdown();
g_services.erase(service);
}

inline void removeAllServices()
{
std::lock_guard<std::mutex> m(g_mtx_services);
for (auto& service : g_services)
service->shutdown();
g_services.clear();
}

inline GNUNET_SCHEDULER_Task* runLater(std::chrono::duration<double> delay, std::function<void()> fn)
{
using namespace std::chrono;
const uint64_t usec = duration_cast<microseconds>(delay).count();
GNUNET_TIME_Relative time{usec};
return GNUNET_SCHEDULER_add_delayed(time, [] (void *cls) {
auto ptr = std::unique_ptr<std::function<void()>>(static_cast<std::function<void()>*>(cls));
(*ptr)();
}, new std::function<void()>(fn));
}

struct DHT : public Service
{
using PutCallbackFunctor = std::function<void()>;
using GetCallbackFunctor = std::function<bool(std::string_view)>;

struct GetCallbackPack
{
DHT* self;
GNUNET_DHT_GetHandle* handle;
GNUNET_SCHEDULER_Task* timer_task;
GetCallbackFunctor callback;
};

DHT(const GNUNET_CONFIGURATION_Handle* cfg, unsigned int ht_len = 32)
{
dht_handle = GNUNET_DHT_connect(cfg, ht_len);
if(dht_handle == NULL)
throw std::runtime_error("Failed to connect to GNUNet DHT service");
registerService(this);
}

~DHT()
{
shutdown();
removeService(this);
}

void shutdown() override
{
if(dht_handle != NULL)
{
for(auto pack : get_handles)
GNUNET_DHT_get_stop(pack->handle);
get_handles.clear();

GNUNET_DHT_disconnect(dht_handle);
dht_handle = NULL;
}
}

GNUNET_DHT_PutHandle* put(const std::string_view key, const std::string_view data
, PutCallbackFunctor completedCallback
, std::chrono::duration<double> expiration = std::chrono::hours(1)
, unsigned int replication = 5
, GNUNET_BLOCK_Type data_type = GNUNET_BLOCK_TYPE_TEST
, GNUNET_DHT_RouteOption routing_options = GNUNET_DHT_RO_NONE)
{
if(dht_handle == NULL)
throw std::runtime_error("DHT not connected");
GNUNET_HashCode key_hash;
GNUNET_CRYPTO_hash(key.data(), key.size(), &key_hash);

// XXX: This only works because internally GNUNet uses msec. If they ever change this, this will break.
const size_t num_usecs = std::chrono::duration_cast<std::chrono::microseconds>(expiration).count();
GNUNET_TIME_Relative gnunet_expiration{num_usecs};

PutCallbackFunctor* functor = new PutCallbackFunctor(std::move(completedCallback));

// No need to copy data to ensure lifetime ourselves, GNUNet does it for us
GNUNET_DHT_PutHandle* handle = GNUNET_DHT_put(dht_handle, &key_hash, replication, routing_options, data_type, data.size()
, data.data(), GNUNET_TIME_relative_to_absolute(gnunet_expiration), &DHT::putCallback, functor);
if(handle == NULL)
throw std::runtime_error("Failed to put data into GNUNet DHT");
return handle;
}

GNUNET_DHT_GetHandle* get(const std::string_view key, GetCallbackFunctor completedCallback
, std::chrono::duration<double> search_timeout = std::chrono::seconds(10)
, GNUNET_BLOCK_Type data_type = GNUNET_BLOCK_TYPE_TEST
, unsigned int replication = 5
, GNUNET_DHT_RouteOption routing_options = GNUNET_DHT_RO_NONE)
{
if(dht_handle == NULL)
throw std::runtime_error("DHT not connected");
GNUNET_HashCode key_hash;
GNUNET_CRYPTO_hash(key.data(), key.size(), &key_hash);
auto data = new GetCallbackPack;
data->callback = std::move(completedCallback);
data->self = this;

GNUNET_DHT_GetHandle* handle = GNUNET_DHT_get_start(dht_handle, data_type, &key_hash, replication, routing_options
, NULL, 0, &DHT::getCallback, data);
data->handle = handle;
if(handle == NULL)
throw std::runtime_error("Failed to get data from GNUNet DHT");
get_handles.insert(data);
data->timer_task = runLater(search_timeout, [data, this] () {
if(get_handles.find(data) != get_handles.end())
{
GNUNET_DHT_get_stop(data->handle);
delete data;
get_handles.erase(data);
}
});
return handle;
}

void cancle(GNUNET_DHT_PutHandle* handle)
{
GNUNET_DHT_put_cancel(handle);
}

void cancle(GNUNET_DHT_GetHandle* handle)
{
GNUNET_DHT_get_stop(handle);
}

protected:
static void putCallback(void* cls)
{
// ensure functor is deleted even if it throws
std::unique_ptr<PutCallbackFunctor> functor{static_cast<PutCallbackFunctor*>(cls)};
assert(functor != nullptr);
(*functor)();
}

static void getCallback(void *cls,
struct GNUNET_TIME_Absolute exp,
const struct GNUNET_HashCode *query_hash,
const struct GNUNET_PeerIdentity *trunc_peer,
const struct GNUNET_DHT_PathElement *get_path,
unsigned int get_path_length,
const struct GNUNET_DHT_PathElement *put_path,
unsigned int put_path_length,
enum GNUNET_BLOCK_Type type,
size_t size,
const void *data)
{
auto pack = reinterpret_cast<GetCallbackPack*>(cls);
assert(pack != nullptr);
std::string_view data_view{reinterpret_cast<const char*>(data), size};
bool keep_running = pack->callback(data_view);
if(keep_running == false)
{
pack->self->get_handles.erase(pack);
GNUNET_DHT_get_stop(pack->handle);
GNUNET_SCHEDULER_cancel(pack->timer_task);
delete pack;
}
}

struct GNUNET_DHT_Handle *dht_handle = nullptr;
std::set<GetCallbackPack*> get_handles;
};

void run(std::function<void(const GNUNET_CONFIGURATION_Handle*)> f)
{
using CallbackType = std::function<void(const GNUNET_CONFIGURATION_Handle* c)>;

const char* args_dummy = "gnunetpp";
CallbackType* functor = new CallbackType(std::move(f));
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_OPTION_END
};
auto r = GNUNET_PROGRAM_run(1, const_cast<char**>(&args_dummy), "gnunetpp", "no help", options
, [](void *cls, char *const *args, const char *cfgfile
, const GNUNET_CONFIGURATION_Handle* c) {
std::unique_ptr<CallbackType> functor{static_cast<CallbackType*>(cls)};
assert(functor != nullptr);
(*functor)(c);
}
, functor);
if(r != GNUNET_OK)
throw std::runtime_error("GNUNet program run failed");
}

void shutdown()
{
GNUNET_SCHEDULER_add_now([] (void* d) {
removeAllServices();
}, NULL);
}

}

0 comments on commit 24f195c

Please sign in to comment.