From 8455b8dd1764d7e82d51e6020ac613fb17bfbe6e Mon Sep 17 00:00:00 2001 From: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:10:42 +0200 Subject: [PATCH] chore(cbindings): Adding cpp example that integrates the 'libwaku' (#2079) * Adding cpp example that integrates the `libwaku` --------- Co-authored-by: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com> --- Makefile | 12 ++ examples/cpp/base64.cpp | 53 ++++++++ examples/cpp/base64.h | 11 ++ examples/cpp/waku.cpp | 271 ++++++++++++++++++++++++++++++++++++++++ library/libwaku.h | 8 ++ 5 files changed, 355 insertions(+) create mode 100644 examples/cpp/base64.cpp create mode 100644 examples/cpp/base64.h create mode 100644 examples/cpp/waku.cpp diff --git a/Makefile b/Makefile index 8fe940880d..97d96adbed 100644 --- a/Makefile +++ b/Makefile @@ -273,6 +273,18 @@ cwaku_example: | build libwaku vendor/nim-libbacktrace/libbacktrace_wrapper.o \ vendor/nim-libbacktrace/install/usr/lib/libbacktrace.a +cppwaku_example: | build libwaku + echo -e $(BUILD_MSG) "build/$@" && \ + g++ -o "build/$@" \ + ./examples/cpp/waku.cpp \ + ./examples/cpp/base64.cpp \ + -lwaku -Lbuild/ \ + -pthread -ldl -lm \ + -lminiupnpc -Lvendor/nim-nat-traversal/vendor/miniupnp/miniupnpc/build/ \ + -lnatpmp -Lvendor/nim-nat-traversal/vendor/libnatpmp-upstream/ \ + vendor/nim-libbacktrace/libbacktrace_wrapper.o \ + vendor/nim-libbacktrace/install/usr/lib/libbacktrace.a + nodejswaku: | build deps echo -e $(BUILD_MSG) "build/$@" && \ node-gyp build --directory=examples/nodejs/ diff --git a/examples/cpp/base64.cpp b/examples/cpp/base64.cpp new file mode 100644 index 0000000000..517e4b2a56 --- /dev/null +++ b/examples/cpp/base64.cpp @@ -0,0 +1,53 @@ + +#include +#include "base64.h" + +// Base64 encoding +// source: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/ +size_t b64_encoded_size(size_t inlen) +{ + size_t ret; + + ret = inlen; + if (inlen % 3 != 0) + ret += 3 - (inlen % 3); + ret /= 3; + ret *= 4; + + return ret; +} + +const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void b64_encode(char* in, size_t len, std::vector& out) +{ + size_t elen; + size_t i; + size_t j; + size_t v; + + if (in == NULL || len == 0) + return; + + elen = b64_encoded_size(len); + out.reserve(elen+1); + + for (i=0, j=0; i> 18) & 0x3F]; + out[j+1] = b64chars[(v >> 12) & 0x3F]; + if (i+1 < len) { + out[j+2] = b64chars[(v >> 6) & 0x3F]; + } else { + out[j+2] = '='; + } + if (i+2 < len) { + out[j+3] = b64chars[v & 0x3F]; + } else { + out[j+3] = '='; + } + } +} diff --git a/examples/cpp/base64.h b/examples/cpp/base64.h new file mode 100644 index 0000000000..0994a0b693 --- /dev/null +++ b/examples/cpp/base64.h @@ -0,0 +1,11 @@ + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#include + +size_t b64_encoded_size(size_t inlen); + +void b64_encode(char* in, size_t len, std::vector& out); + +#endif diff --git a/examples/cpp/waku.cpp b/examples/cpp/waku.cpp new file mode 100644 index 0000000000..ec8346520b --- /dev/null +++ b/examples/cpp/waku.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "base64.h" +#include "../../library/libwaku.h" + +#define WAKU_CALL(call) \ +do { \ + int ret = call; \ + if (ret != 0) { \ + std::cout << "Failed the call to: " << #call << ". Code: " << ret << "\n"; \ + } \ +} while (0) + +struct ConfigNode { + char host[128]; + int port; + char key[128]; + int relay; + char peers[2048]; +}; + +// Arguments parsing +static char doc[] = "\nC example that shows how to use the waku library."; +static char args_doc[] = ""; + +static struct argp_option options[] = { + { "host", 'h', "HOST", 0, "IP to listen for for LibP2P traffic. (default: \"0.0.0.0\")"}, + { "port", 'p', "PORT", 0, "TCP listening port. (default: \"60000\")"}, + { "key", 'k', "KEY", 0, "P2P node private key as 64 char hex string."}, + { "relay", 'r', "RELAY", 0, "Enable relay protocol: 1 or 0. (default: 1)"}, + { "peers", 'a', "PEERS", 0, "Comma-separated list of peer-multiaddress to connect\ + to. (default: \"\") e.g. \"/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN\""}, + { 0 } +}; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + + struct ConfigNode *cfgNode = (ConfigNode *) state->input; + switch (key) { + case 'h': + snprintf(cfgNode->host, 128, "%s", arg); + break; + case 'p': + cfgNode->port = atoi(arg); + break; + case 'k': + snprintf(cfgNode->key, 128, "%s", arg); + break; + case 'r': + cfgNode->relay = atoi(arg); + break; + case 'a': + snprintf(cfgNode->peers, 2048, "%s", arg); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) /* Too many arguments. */ + argp_usage(state); + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 }; + +// Beginning of UI program logic + +enum PROGRAM_STATE { + MAIN_MENU, + SUBSCRIBE_TOPIC_MENU, + CONNECT_TO_OTHER_NODE_MENU, + PUBLISH_MESSAGE_MENU +}; + +enum PROGRAM_STATE current_state = MAIN_MENU; + +void show_main_menu() { + printf("\nPlease, select an option:\n"); + printf("\t1.) Subscribe to topic\n"); + printf("\t2.) Connect to other node\n"); + printf("\t3.) Publish a message\n"); +} + +void handle_user_input() { + char cmd[1024]; + memset(cmd, 0, 1024); + int numRead = read(0, cmd, 1024); + if (numRead <= 0) { + return; + } + + int c; + while ( (c = getchar()) != '\n' && c != EOF ) { } + + switch (atoi(cmd)) + { + case SUBSCRIBE_TOPIC_MENU: + { + printf("Indicate the Pubsubtopic to subscribe:\n"); + char pubsubTopic[128]; + scanf("%127s", pubsubTopic); + // if (!waku_relay_subscribe(pubsubTopic, &mResp)) { + // printf("Error subscribing to PubsubTopic: %s\n", mResp->data); + // } + // printf("Waku Relay subscription response: %s\n", mResp->data); + + show_main_menu(); + } + break; + + case CONNECT_TO_OTHER_NODE_MENU: + printf("Connecting to a node. Please indicate the peer Multiaddress:\n"); + printf("e.g.: /ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN\n"); + char peerAddr[512]; + scanf("%511s", peerAddr); + // if (!waku_connect(peerAddr, 10000 /* timeoutMs */, &mResp)) { + // printf("Couldn't connect to the remote peer: %s\n", mResp->data); + // } + show_main_menu(); + break; + + case PUBLISH_MESSAGE_MENU: + { + printf("Indicate the Pubsubtopic:\n"); + char pubsubTopic[128]; + scanf("%127s", pubsubTopic); + + printf("Type the message tp publish:\n"); + char msg[1024]; + scanf("%1023s", msg); + + char jsonWakuMsg[1024]; + std::vector msgPayload; + b64_encode(msg, strlen(msg), msgPayload); + + // waku_content_topic("appName", + // 1, + // "contentTopicName", + // "encoding", + // &mResp); + + // snprintf(jsonWakuMsg, + // 1024, + // "{\"payload\":\"%s\",\"content_topic\":\"%s\"}", + // msgPayload, mResp->data); + + // free(msgPayload); + + // waku_relay_publish(pubsubTopic, jsonWakuMsg, 10000 /*timeout ms*/, &mResp); + // printf("waku relay response [%s]\n", mResp->data); + show_main_menu(); + } + break; + + case MAIN_MENU: + break; + } +} + +// End of UI program logic + +void show_help_and_exit() { + printf("Wrong parameters\n"); + exit(1); +} + +void event_handler(const char* msg, size_t len) { + printf("Receiving message %s\n", msg); +} + +void handle_error(const char* msg, size_t len) { + printf("Error: %s\n", msg); + exit(1); +} + +template +auto cify(F&& f) { + static F fn = std::forward(f); + return [](const char* msg, size_t len) { + return fn(msg, len); + }; +} + +int main(int argc, char** argv) { + + struct ConfigNode cfgNode; + // default values + snprintf(cfgNode.host, 128, "0.0.0.0"); + snprintf(cfgNode.key, 128, + "364d111d729a6eb6d2e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9"); + cfgNode.port = 60000; + cfgNode.relay = 1; + + if (argp_parse(&argp, argc, argv, 0, 0, &cfgNode) + == ARGP_ERR_UNKNOWN) { + show_help_and_exit(); + } + + char jsonConfig[1024]; + snprintf(jsonConfig, 1024, "{ \ + \"host\": \"%s\", \ + \"port\": %d, \ + \"key\": \"%s\", \ + \"relay\": %s, \ + }", cfgNode.host, + cfgNode.port, + cfgNode.key, + cfgNode.relay ? "true":"false"); + + WAKU_CALL(waku_new(jsonConfig, cify([](const char* msg, size_t len) { + std::cout << "Error: " << msg << std::endl; + exit(1); + }))); + + // example on how to retrieve a value from the `libwaku` callback. + std::string defaultPubsubTopic; + WAKU_CALL(waku_default_pubsub_topic(cify([&defaultPubsubTopic](const char* msg, size_t len) { + defaultPubsubTopic = msg; + }))); + + std::cout << "Default pubsub topic: " << defaultPubsubTopic << std::endl; + + WAKU_CALL(waku_version(cify([&](const char* msg, size_t len) { + std::cout << "Git Version: " << msg << std::endl; + }))); + + printf("Bind addr: %s:%u\n", cfgNode.host, cfgNode.port); + printf("Waku Relay enabled: %s\n", cfgNode.relay == 1 ? "YES": "NO"); + + std::string pubsubTopic; + WAKU_CALL(waku_pubsub_topic("example", cify([&](const char* msg, size_t len) { + pubsubTopic = msg; + }))); + + std::cout << "Custom pubsub topic: " << pubsubTopic << std::endl; + + waku_set_event_callback(event_handler); + waku_start(); + + WAKU_CALL( waku_connect(cfgNode.peers, + 10000 /* timeoutMs */, + handle_error) ); + + WAKU_CALL( waku_relay_subscribe(defaultPubsubTopic.c_str(), + handle_error) ); + + std::cout << "Establishing connection with: " << cfgNode.peers << std::endl; + WAKU_CALL(waku_connect(cfgNode.peers, 10000 /* timeoutMs */, handle_error)); + + show_main_menu(); + while(1) { + handle_user_input(); + } +} diff --git a/library/libwaku.h b/library/libwaku.h index 1c383ef5cb..d2fe5cd1e4 100644 --- a/library/libwaku.h +++ b/library/libwaku.h @@ -11,6 +11,10 @@ #define RET_ERR 1 #define RET_MISSING_CALLBACK 2 +#ifdef __cplusplus +extern "C" { +#endif + typedef void (*WakuCallBack) (const char* msg, size_t len_0); // Creates a new instance of the waku node. @@ -51,4 +55,8 @@ int waku_connect(const char* peerMultiAddr, unsigned int timeoutMs, WakuCallBack onErrCb); +#ifdef __cplusplus +} +#endif + #endif /* __libwaku__ */