-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 24f195c
Showing
9 changed files
with
963 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
build/ | ||
.vscode | ||
.cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |