From 29614e2e52f9b4a312e3025311b276915b4c1be6 Mon Sep 17 00:00:00 2001 From: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> Date: Fri, 7 Jul 2023 10:53:00 +0200 Subject: [PATCH] Refactoring of libwaku to export only C types (#1845) * Simplifying libwaku.nim by extracting config parser to config.nim * Adding json_base_event.nim * Starting to control-version the libwaku.h We are creating this libwaku.h inspired by the one that is automatically generated by the nim compiler when `make libwaku` is invoked. Therefore, the self-generated header is then placed in: nimcache/release/libwaku/libwaku.h * Better waku_example.c organization * libwaku.nim: better memory management We need to create a 'cstring' internally from the 'const char*' passed from outside the library. We invoke 'allocShared' in order to create the internal 'cstring', and invoke 'deallocShared' in order to manually free the memory. --- .gitignore | 2 - Makefile | 2 +- examples/cbindings/nimbase.h | 602 ------------------ examples/cbindings/waku_example.c | 221 +++---- library/config.nim | 176 +++++ ...n_signal_event.nim => json_base_event.nim} | 4 +- library/events/json_error_event.nim | 5 +- library/events/json_message_event.nim | 4 +- library/libwaku.h | 61 ++ library/libwaku.nim | 390 ++++++------ 10 files changed, 570 insertions(+), 897 deletions(-) delete mode 100644 examples/cbindings/nimbase.h create mode 100644 library/config.nim rename library/events/{json_signal_event.nim => json_base_event.nim} (54%) create mode 100644 library/libwaku.h diff --git a/.gitignore b/.gitignore index 6e545958b..7caf5e114 100644 --- a/.gitignore +++ b/.gitignore @@ -53,5 +53,3 @@ nimbus-build-system.paths *.sqlite3-shm *.sqlite3-wal -# Ignore autogenerated C-bingings compilation files -/examples/cbindings/libwaku.h diff --git a/Makefile b/Makefile index 131718d24..529732972 100644 --- a/Makefile +++ b/Makefile @@ -301,9 +301,9 @@ endif cwaku_example: | build libwaku echo -e $(BUILD_MSG) "build/$@" && \ - cp nimcache/release/libwaku/libwaku.h ./examples/cbindings/ && \ cc -o "build/$@" \ ./examples/cbindings/waku_example.c \ + ./examples/cbindings/base64.c \ -lwaku -Lbuild/ \ -pthread -ldl -lm \ -lminiupnpc -Lvendor/nim-nat-traversal/vendor/miniupnp/miniupnpc/build/ \ diff --git a/examples/cbindings/nimbase.h b/examples/cbindings/nimbase.h deleted file mode 100644 index a83bd3006..000000000 --- a/examples/cbindings/nimbase.h +++ /dev/null @@ -1,602 +0,0 @@ -/* - - Nim's Runtime Library - (c) Copyright 2015 Andreas Rumpf - - See the file "copying.txt", included in this - distribution, for details about the copyright. -*/ - -/* compiler symbols: -__BORLANDC__ -_MSC_VER -__WATCOMC__ -__LCC__ -__GNUC__ -__DMC__ -__POCC__ -__TINYC__ -__clang__ -__AVR__ -*/ - - -#ifndef NIMBASE_H -#define NIMBASE_H - -/*------------ declaring a custom attribute to support using LLVM's Address Sanitizer ------------ */ - -/* - This definition exists to provide support for using the LLVM ASAN (Address SANitizer) tooling with Nim. This - should only be used to mark implementations of the GC system that raise false flags with the ASAN tooling, or - for functions that are hot and need to be disabled for performance reasons. Based on the official ASAN - documentation, both the clang and gcc compilers are supported. In addition to that, a check is performed to - verify that the necessary attribute is supported by the compiler. - - To flag a proc as ignored, append the following code pragma to the proc declaration: - {.codegenDecl: "CLANG_NO_SANITIZE_ADDRESS $# $#$#".} - - For further information, please refer to the official documentation: - https://github.com/google/sanitizers/wiki/AddressSanitizer - */ -#define CLANG_NO_SANITIZE_ADDRESS -#if defined(__clang__) -# if __has_attribute(no_sanitize_address) -# undef CLANG_NO_SANITIZE_ADDRESS -# define CLANG_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) -# endif -#endif - - -/* ------------ ignore typical warnings in Nim-generated files ------------- */ -#if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic ignored "-Wpragmas" -# pragma GCC diagnostic ignored "-Wwritable-strings" -# pragma GCC diagnostic ignored "-Winvalid-noreturn" -# pragma GCC diagnostic ignored "-Wformat" -# pragma GCC diagnostic ignored "-Wlogical-not-parentheses" -# pragma GCC diagnostic ignored "-Wlogical-op-parentheses" -# pragma GCC diagnostic ignored "-Wshadow" -# pragma GCC diagnostic ignored "-Wunused-function" -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Winvalid-offsetof" -# pragma GCC diagnostic ignored "-Wtautological-compare" -# pragma GCC diagnostic ignored "-Wswitch-bool" -# pragma GCC diagnostic ignored "-Wmacro-redefined" -# pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers" -# pragma GCC diagnostic ignored "-Wpointer-bool-conversion" -# pragma GCC diagnostic ignored "-Wconstant-conversion" -#endif - -#if defined(_MSC_VER) -# pragma warning(disable: 4005 4100 4101 4189 4191 4200 4244 4293 4296 4309) -# pragma warning(disable: 4310 4365 4456 4477 4514 4574 4611 4668 4702 4706) -# pragma warning(disable: 4710 4711 4774 4800 4809 4820 4996 4090 4297) -#endif -/* ------------------------------------------------------------------------- */ - -#if defined(__GNUC__) && !defined(__ZEPHYR__) -/* Zephyr does some magic in it's headers that override the GCC stdlib. This breaks that. */ -# define _GNU_SOURCE 1 -#endif - -#if defined(__TINYC__) -/*# define __GNUC__ 3 -# define GCC_MAJOR 4 -# define __GNUC_MINOR__ 4 -# define __GNUC_PATCHLEVEL__ 5 */ -# define __DECLSPEC_SUPPORTED 1 -#endif - -/* calling convention mess ----------------------------------------------- */ -#if defined(__GNUC__) || defined(__LCC__) || defined(__POCC__) \ - || defined(__TINYC__) - /* these should support C99's inline */ - /* the test for __POCC__ has to come before the test for _MSC_VER, - because PellesC defines _MSC_VER too. This is brain-dead. */ -# define N_INLINE(rettype, name) inline rettype name -#elif defined(__BORLANDC__) || defined(_MSC_VER) -/* Borland's compiler is really STRANGE here; note that the __fastcall - keyword cannot be before the return type, but __inline cannot be after - the return type, so we do not handle this mess in the code generator - but rather here. */ -# define N_INLINE(rettype, name) __inline rettype name -#elif defined(__DMC__) -# define N_INLINE(rettype, name) inline rettype name -#elif defined(__WATCOMC__) -# define N_INLINE(rettype, name) __inline rettype name -#else /* others are less picky: */ -# define N_INLINE(rettype, name) rettype __inline name -#endif - -#define N_INLINE_PTR(rettype, name) rettype (*name) - -#if defined(__POCC__) -# define NIM_CONST /* PCC is really picky with const modifiers */ -# undef _MSC_VER /* Yeah, right PCC defines _MSC_VER even if it is - not that compatible. Well done. */ -#elif defined(__cplusplus) -# define NIM_CONST /* C++ is picky with const modifiers */ -#else -# define NIM_CONST const -#endif - -/* - NIM_THREADVAR declaration based on - http://stackoverflow.com/questions/18298280/how-to-declare-a-variable-as-thread-local-portably -*/ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__ -# define NIM_THREADVAR _Thread_local -#elif defined _WIN32 && ( \ - defined _MSC_VER || \ - defined __ICL || \ - defined __DMC__ || \ - defined __BORLANDC__ ) -# define NIM_THREADVAR __declspec(thread) -#elif defined(__TINYC__) || defined(__GENODE__) -# define NIM_THREADVAR -/* note that ICC (linux) and Clang are covered by __GNUC__ */ -#elif defined __GNUC__ || \ - defined __SUNPRO_C || \ - defined __xlC__ -# define NIM_THREADVAR __thread -#else -# error "Cannot define NIM_THREADVAR" -#endif - -#if defined(__cplusplus) - #define NIM_THREAD_LOCAL thread_local -#endif - -/* --------------- how int64 constants should be declared: ----------- */ -#if defined(__GNUC__) || defined(__LCC__) || \ - defined(__POCC__) || defined(__DMC__) || defined(_MSC_VER) -# define IL64(x) x##LL -#else /* works only without LL */ -# define IL64(x) ((NI64)x) -#endif - -/* ---------------- casting without correct aliasing rules ----------- */ - -#if defined(__GNUC__) -# define NIM_CAST(type, ptr) (((union{type __x__;}*)(ptr))->__x__) -#else -# define NIM_CAST(type, ptr) ((type)(ptr)) -#endif - - -/* ------------------------------------------------------------------- */ -#ifdef __cplusplus -# define NIM_EXTERNC extern "C" -#else -# define NIM_EXTERNC -#endif - -#if defined(WIN32) || defined(_WIN32) /* only Windows has this mess... */ -# define N_LIB_PRIVATE -# define N_CDECL(rettype, name) rettype __cdecl name -# define N_STDCALL(rettype, name) rettype __stdcall name -# define N_SYSCALL(rettype, name) rettype __syscall name -# define N_FASTCALL(rettype, name) rettype __fastcall name -# define N_THISCALL(rettype, name) rettype __thiscall name -# define N_SAFECALL(rettype, name) rettype __stdcall name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (__cdecl *name) -# define N_STDCALL_PTR(rettype, name) rettype (__stdcall *name) -# define N_SYSCALL_PTR(rettype, name) rettype (__syscall *name) -# define N_FASTCALL_PTR(rettype, name) rettype (__fastcall *name) -# define N_THISCALL_PTR(rettype, name) rettype (__thiscall *name) -# define N_SAFECALL_PTR(rettype, name) rettype (__stdcall *name) - -# ifdef __cplusplus -# define N_LIB_EXPORT NIM_EXTERNC __declspec(dllexport) -# else -# define N_LIB_EXPORT NIM_EXTERNC __declspec(dllexport) -# endif -# define N_LIB_EXPORT_VAR __declspec(dllexport) -# define N_LIB_IMPORT extern __declspec(dllimport) -#else -# define N_LIB_PRIVATE __attribute__((visibility("hidden"))) -# if defined(__GNUC__) -# define N_CDECL(rettype, name) rettype name -# define N_STDCALL(rettype, name) rettype name -# define N_SYSCALL(rettype, name) rettype name -# define N_FASTCALL(rettype, name) __attribute__((fastcall)) rettype name -# define N_SAFECALL(rettype, name) rettype name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (*name) -# define N_STDCALL_PTR(rettype, name) rettype (*name) -# define N_SYSCALL_PTR(rettype, name) rettype (*name) -# define N_FASTCALL_PTR(rettype, name) __attribute__((fastcall)) rettype (*name) -# define N_SAFECALL_PTR(rettype, name) rettype (*name) -# else -# define N_CDECL(rettype, name) rettype name -# define N_STDCALL(rettype, name) rettype name -# define N_SYSCALL(rettype, name) rettype name -# define N_FASTCALL(rettype, name) rettype name -# define N_SAFECALL(rettype, name) rettype name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (*name) -# define N_STDCALL_PTR(rettype, name) rettype (*name) -# define N_SYSCALL_PTR(rettype, name) rettype (*name) -# define N_FASTCALL_PTR(rettype, name) rettype (*name) -# define N_SAFECALL_PTR(rettype, name) rettype (*name) -# endif -# define N_LIB_EXPORT NIM_EXTERNC __attribute__((visibility("default"))) -# define N_LIB_EXPORT_VAR __attribute__((visibility("default"))) -# define N_LIB_IMPORT extern -#endif - -#define N_NOCONV(rettype, name) rettype name -/* specify no calling convention */ -#define N_NOCONV_PTR(rettype, name) rettype (*name) - -#if defined(__GNUC__) || defined(__ICC__) -# define N_NOINLINE(rettype, name) rettype __attribute__((__noinline__)) name -#elif defined(_MSC_VER) -# define N_NOINLINE(rettype, name) __declspec(noinline) rettype name -#else -# define N_NOINLINE(rettype, name) rettype name -#endif - -#define N_NOINLINE_PTR(rettype, name) rettype (*name) - -#if defined(__BORLANDC__) || defined(__WATCOMC__) || \ - defined(__POCC__) || defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) -/* these compilers have a fastcall so use it: */ -# ifdef __TINYC__ -# define N_NIMCALL(rettype, name) rettype __attribute((__fastcall)) name -# define N_NIMCALL_PTR(rettype, name) rettype (__attribute((__fastcall)) *name) -# define N_RAW_NIMCALL __attribute((__fastcall)) -# else -# define N_NIMCALL(rettype, name) rettype __fastcall name -# define N_NIMCALL_PTR(rettype, name) rettype (__fastcall *name) -# define N_RAW_NIMCALL __fastcall -# endif -#else -# define N_NIMCALL(rettype, name) rettype name /* no modifier */ -# define N_NIMCALL_PTR(rettype, name) rettype (*name) -# define N_RAW_NIMCALL -#endif - -#define N_CLOSURE(rettype, name) N_NIMCALL(rettype, name) -#define N_CLOSURE_PTR(rettype, name) N_NIMCALL_PTR(rettype, name) - -/* ----------------------------------------------------------------------- */ - -#define COMMA , - -#include -#include - -// define NIM_STATIC_ASSERT -// example use case: CT sizeof for importc types verification -// where we have {.completeStruct.} (or lack of {.incompleteStruct.}) -#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) -#define NIM_STATIC_ASSERT(x, msg) _Static_assert((x), msg) -#elif defined(__cplusplus) -#define NIM_STATIC_ASSERT(x, msg) static_assert((x), msg) -#else -#define NIM_STATIC_ASSERT(x, msg) typedef int NIM_STATIC_ASSERT_AUX[(x) ? 1 : -1]; -// On failure, your C compiler will say something like: -// "error: 'NIM_STATIC_ASSERT_AUX' declared as an array with a negative size" -// we could use a better fallback to also show line number, using: -// http://www.pixelbeat.org/programming/gcc/static_assert.html -#endif - -/* C99 compiler? */ -#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)) -# define HAVE_STDINT_H -#endif - -/* Known compiler with stdint.h that doesn't fit the general pattern? */ -#if defined(__LCC__) || defined(__DMC__) || defined(__POCC__) || \ - defined(__AVR__) || (defined(__cplusplus) && (__cplusplus < 201103)) -# define HAVE_STDINT_H -#endif - -#if (!defined(HAVE_STDINT_H) && defined(__cplusplus) && (__cplusplus >= 201103)) -# define HAVE_CSTDINT -#endif - - -/* wrap all Nim typedefs into namespace Nim */ -#ifdef USE_NIM_NAMESPACE -#ifdef HAVE_CSTDINT -#include -#else -#include -#endif -namespace USE_NIM_NAMESPACE { -#endif - -// preexisting check, seems paranoid, maybe remove -#if defined(NIM_TRUE) || defined(NIM_FALSE) || defined(NIM_BOOL) -#error "nim reserved preprocessor macros clash" -#endif - -/* bool types (C++ has it): */ -#ifdef __cplusplus -#define NIM_BOOL bool -#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901) -// see #13798: to avoid conflicts for code emitting `#include ` -#define NIM_BOOL _Bool -#else -typedef unsigned char NIM_BOOL; // best effort -#endif - -NIM_STATIC_ASSERT(sizeof(NIM_BOOL) == 1, ""); // check whether really needed -NIM_STATIC_ASSERT(CHAR_BIT == 8, ""); - // fail fast for (rare) environments where this doesn't hold, as some implicit - // assumptions would need revisiting (e.g. `uint8` or https://github.com/nim-lang/Nim/pull/18505) - -#define NIM_TRUE true -#define NIM_FALSE false - -#ifdef __cplusplus -# if __cplusplus >= 201103L -# /* nullptr is more type safe (less implicit conversions than 0) */ -# define NIM_NIL nullptr -# else -# // both `((void*)0)` and `NULL` would cause codegen to emit -# // error: assigning to 'Foo *' from incompatible type 'void *' -# // but codegen could be fixed if need. See also potential caveat regarding -# // NULL. -# // However, `0` causes other issues, see #13798 -# define NIM_NIL 0 -# endif -#else -# include -# define NIM_NIL ((void*)0) /* C's NULL is fucked up in some C compilers, so - the generated code does not rely on it anymore */ -#endif - -#if defined(__BORLANDC__) || defined(__DMC__) \ - || defined(__WATCOMC__) || defined(_MSC_VER) -typedef signed char NI8; -typedef signed short int NI16; -typedef signed int NI32; -typedef __int64 NI64; -/* XXX: Float128? */ -typedef unsigned char NU8; -typedef unsigned short int NU16; -typedef unsigned int NU32; -typedef unsigned __int64 NU64; -#elif defined(HAVE_STDINT_H) -#ifndef USE_NIM_NAMESPACE -# include -#endif -typedef int8_t NI8; -typedef int16_t NI16; -typedef int32_t NI32; -typedef int64_t NI64; -typedef uint8_t NU8; -typedef uint16_t NU16; -typedef uint32_t NU32; -typedef uint64_t NU64; -#elif defined(HAVE_CSTDINT) -#ifndef USE_NIM_NAMESPACE -# include -#endif -typedef std::int8_t NI8; -typedef std::int16_t NI16; -typedef std::int32_t NI32; -typedef std::int64_t NI64; -typedef std::uint8_t NU8; -typedef std::uint16_t NU16; -typedef std::uint32_t NU32; -typedef std::uint64_t NU64; -#else -/* Unknown compiler/version, do our best */ -#ifdef __INT8_TYPE__ -typedef __INT8_TYPE__ NI8; -#else -typedef signed char NI8; -#endif -#ifdef __INT16_TYPE__ -typedef __INT16_TYPE__ NI16; -#else -typedef signed short int NI16; -#endif -#ifdef __INT32_TYPE__ -typedef __INT32_TYPE__ NI32; -#else -typedef signed int NI32; -#endif -#ifdef __INT64_TYPE__ -typedef __INT64_TYPE__ NI64; -#else -typedef long long int NI64; -#endif -/* XXX: Float128? */ -#ifdef __UINT8_TYPE__ -typedef __UINT8_TYPE__ NU8; -#else -typedef unsigned char NU8; -#endif -#ifdef __UINT16_TYPE__ -typedef __UINT16_TYPE__ NU16; -#else -typedef unsigned short int NU16; -#endif -#ifdef __UINT32_TYPE__ -typedef __UINT32_TYPE__ NU32; -#else -typedef unsigned int NU32; -#endif -#ifdef __UINT64_TYPE__ -typedef __UINT64_TYPE__ NU64; -#else -typedef unsigned long long int NU64; -#endif -#endif - -#ifdef NIM_INTBITS -# if NIM_INTBITS == 64 -typedef NI64 NI; -typedef NU64 NU; -# elif NIM_INTBITS == 32 -typedef NI32 NI; -typedef NU32 NU; -# elif NIM_INTBITS == 16 -typedef NI16 NI; -typedef NU16 NU; -# elif NIM_INTBITS == 8 -typedef NI8 NI; -typedef NU8 NU; -# else -# error "invalid bit width for int" -# endif -#endif - -// for now there isn't an easy way for C code to reach the program result -// when hot code reloading is ON - users will have to: -// load the nimhcr.dll, get the hcrGetGlobal proc from there and use it -#ifndef NIM_HOT_CODE_RELOADING -extern NI nim_program_result; -#endif - -typedef float NF32; -typedef double NF64; -typedef double NF; - -typedef char NIM_CHAR; -typedef char* NCSTRING; - -#ifdef NIM_BIG_ENDIAN -# define NIM_IMAN 1 -#else -# define NIM_IMAN 0 -#endif - -#define NIM_STRLIT_FLAG ((NU)(1) << ((NIM_INTBITS) - 2)) /* This has to be the same as system.strlitFlag! */ - -#define STRING_LITERAL(name, str, length) \ - static const struct { \ - TGenericSeq Sup; \ - NIM_CHAR data[(length) + 1]; \ - } name = {{length, (NI) ((NU)length | NIM_STRLIT_FLAG)}, str} - -/* declared size of a sequence/variable length array: */ -#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) -# define SEQ_DECL_SIZE /* empty is correct! */ -#else -# define SEQ_DECL_SIZE 1000000 -#endif - -#define ALLOC_0(size) calloc(1, size) -#define DL_ALLOC_0(size) dlcalloc(1, size) - -#define paramCount() cmdCount - -// NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0 -#ifndef NAN -# ifndef _HUGE_ENUF -# define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow -# endif -# define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) -# define NAN ((float)(NAN_INFINITY * 0.0F)) -#endif - -#ifndef INF -# ifdef INFINITY -# define INF INFINITY -# elif defined(HUGE_VAL) -# define INF HUGE_VAL -# elif defined(_MSC_VER) -# include -# define INF (DBL_MAX+DBL_MAX) -# else -# define INF (1.0 / 0.0) -# endif -#endif - -typedef struct TFrame_ TFrame; -struct TFrame_ { - TFrame* prev; - NCSTRING procname; - NI line; - NCSTRING filename; - NI16 len; - NI16 calldepth; - NI frameMsgLen; -}; - -#define NIM_POSIX_INIT __attribute__((constructor)) - -#ifdef __GNUC__ -# define NIM_LIKELY(x) __builtin_expect(x, 1) -# define NIM_UNLIKELY(x) __builtin_expect(x, 0) -/* We need the following for the posix wrapper. In particular it will give us - POSIX_SPAWN_USEVFORK: */ -# ifndef _GNU_SOURCE -# define _GNU_SOURCE -# endif -#else -# define NIM_LIKELY(x) (x) -# define NIM_UNLIKELY(x) (x) -#endif - -#if 0 // defined(__GNUC__) || defined(__clang__) -// not needed anymore because the stack marking cares about -// interior pointers now -static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); } -# define GC_GUARD __attribute__ ((cleanup(GCGuard))) -#else -# define GC_GUARD -#endif - -// Test to see if Nim and the C compiler agree on the size of a pointer. -NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, ""); - -#ifdef USE_NIM_NAMESPACE -} -#endif - -#if defined(_MSC_VER) -# define NIM_ALIGN(x) __declspec(align(x)) -# define NIM_ALIGNOF(x) __alignof(x) -#else -# define NIM_ALIGN(x) __attribute__((aligned(x))) -# define NIM_ALIGNOF(x) __alignof__(x) -#endif - -/* ---------------- platform specific includes ----------------------- */ - -/* VxWorks related includes */ -#if defined(__VXWORKS__) -# include -# include -# include -#elif defined(__FreeBSD__) -# include -#endif - -/* these exist to make the codegen logic simpler */ -#define nimModInt(a, b, res) (((*res) = (a) % (b)), 0) -#define nimModInt64(a, b, res) (((*res) = (a) % (b)), 0) - -#if (!defined(_MSC_VER) || defined(__clang__)) && !defined(NIM_EmulateOverflowChecks) - /* these exist because we cannot have .compilerProcs that are importc'ed - by a different name */ - - #define nimAddInt64(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res) - #define nimSubInt64(a, b, res) __builtin_ssubll_overflow(a, b, (long long int*)res) - #define nimMulInt64(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res) - - #if NIM_INTBITS == 32 - #define nimAddInt(a, b, res) __builtin_sadd_overflow(a, b, res) - #define nimSubInt(a, b, res) __builtin_ssub_overflow(a, b, res) - #define nimMulInt(a, b, res) __builtin_smul_overflow(a, b, res) - #else - /* map it to the 'long long' variant */ - #define nimAddInt(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res) - #define nimSubInt(a, b, res) __builtin_ssubll_overflow(a, b, (long long int*)res) - #define nimMulInt(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res) - #endif -#endif - -#define NIM_NOALIAS __restrict -/* __restrict is said to work for all the C(++) compilers out there that we support */ - -#endif /* NIMBASE_H */ diff --git a/examples/cbindings/waku_example.c b/examples/cbindings/waku_example.c index 6adb99b8b..e2dd834bd 100644 --- a/examples/cbindings/waku_example.c +++ b/examples/cbindings/waku_example.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -11,22 +10,26 @@ #include #include -#include "libwaku.h" +#include "base64.h" +#include "../../library/libwaku.h" -// Keep a global string to store the waku call responses -static NimStringDesc wakuString; -NimStringDesc* mResp = &wakuString; +#define WAKU_CALL(call) \ +do { \ + int ret = call; \ + if (ret != 0) { \ + printf("Failed the call to: %s. Returned code: %d\n", #call, ret); \ + exit(1); \ + } \ +} while (0) struct ConfigNode { - NCSTRING host; - NU port; - NCSTRING key; - NIM_BOOL relay; - NCSTRING peers; + char host[128]; + int port; + char key[128]; + int relay; + char peers[2048]; }; -static ConfigNode cfgNode; - // Arguments parsing static char doc[] = "\nC example that shows how to use the waku library."; static char args_doc[] = ""; @@ -46,19 +49,19 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { struct ConfigNode *cfgNode = state->input; switch (key) { case 'h': - cfgNode->host = arg; + snprintf(cfgNode->host, 128, "%s", arg); break; case 'p': cfgNode->port = atoi(arg); break; case 'k': - cfgNode->key = arg; + snprintf(cfgNode->key, 128, "%s", arg); break; case 'r': cfgNode->relay = atoi(arg); break; case 'a': - cfgNode->peers = arg; + snprintf(cfgNode->peers, 2048, "%s", arg); break; case ARGP_KEY_ARG: if (state->arg_num >= 1) /* Too many arguments. */ @@ -75,61 +78,77 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 }; -// 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; +char* contentTopic = NULL; +void handle_content_topic(char* msg, size_t len) { + if (contentTopic != NULL) { + free(contentTopic); + } - ret = inlen; - if (inlen % 3 != 0) - ret += 3 - (inlen % 3); - ret /= 3; - ret *= 4; - - return ret; + contentTopic = malloc(len * sizeof(char) + 1); + strcpy(contentTopic, msg); } -const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +char* publishResponse = NULL; +void handle_publish_ok(char* msg, size_t len) { + printf("Publish Ok: %s %lu\n", msg, len); -char *b64_encode(const unsigned char *in, size_t len) -{ - char *out; - size_t elen; - size_t i; - size_t j; - size_t v; + if (publishResponse != NULL) { + free(publishResponse); + } - if (in == NULL || len == 0) - return NULL; - - elen = b64_encoded_size(len); - out = malloc(elen+1); - out[elen] = '\0'; - - 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] = '='; - } - } - - return out; + publishResponse = malloc(len * sizeof(char) + 1); + strcpy(publishResponse, msg); } -// End of Base64 encoding +void handle_error(char* msg, size_t len) { + printf("Error: %s\n", msg); + exit(1); +} + +#define MAX_MSG_SIZE 65535 + +void publish_message(char* pubsubTopic, char* msg) { + char jsonWakuMsg[MAX_MSG_SIZE]; + char *msgPayload = b64_encode(msg, strlen(msg)); + + WAKU_CALL( waku_content_topic("appName", + 1, + "contentTopicName", + "encoding", + handle_content_topic) ); + + snprintf(jsonWakuMsg, + MAX_MSG_SIZE, + "{\"payload\":\"%s\",\"content_topic\":\"%s\"}", + msgPayload, contentTopic); + + free(msgPayload); + + WAKU_CALL( waku_relay_publish(pubsubTopic, + jsonWakuMsg, + 10000 /*timeout ms*/, + handle_publish_ok, + handle_error) ); + + printf("waku relay response [%s]\n", publishResponse); +} + +void show_help_and_exit() { + printf("Wrong parameters\n"); + exit(1); +} + +void event_handler(char* msg, size_t len) { + printf("Receiving message %s\n", msg); +} + +void print_default_pubsub_topic(char* msg, size_t len) { + printf("Default pubsub topic: %s\n", msg); +} + +void print_waku_version(char* msg, size_t len) { + printf("Git Version: %s\n", msg); +} // Beginning of UI program logic @@ -176,10 +195,10 @@ void handle_user_input() { set_scanf_to_block(); 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); + + WAKU_CALL( waku_relay_subscribe(pubsubTopic, + handle_error) ); + printf("The subscription went well\n"); set_scanf_to_not_block(); show_main_menu(); @@ -192,9 +211,7 @@ void handle_user_input() { set_scanf_to_block(); 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); - } + WAKU_CALL(waku_connect(peerAddr, 10000 /* timeoutMs */, handle_error)); set_scanf_to_not_block(); show_main_menu(); break; @@ -210,20 +227,8 @@ void handle_user_input() { char msg[1024]; scanf("%1023s", msg); - char jsonWakuMsg[1024]; - char *msgPayload = b64_encode(msg, strlen(msg)); + publish_message(pubsubTopic, msg); - 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); set_scanf_to_not_block(); show_main_menu(); } @@ -236,51 +241,53 @@ void handle_user_input() { // End of UI program logic -void show_help_and_exit() { - printf("Wrong parameters\n"); - exit(1); -} - -void event_handler(char* msg) { - printf("Receiving message [%s]\n", msg); -} - int main(int argc, char** argv) { + + waku_init_lib(); + + struct ConfigNode cfgNode; // default values - cfgNode.host = "0.0.0.0"; + snprintf(cfgNode.host, 128, "0.0.0.0"); cfgNode.port = 60000; cfgNode.relay = 1; - cfgNode.peers = NULL; 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"); + // To allow non-blocking 'reads' from stdin fcntl(0, F_SETFL, fcntl(0, F_GETFL) ^ O_NONBLOCK); - NimMain(); // initialize the Nim runtime - - waku_default_pubsub_topic(&mResp); - printf("Default pubsub topic: %s\n", mResp->data); - printf("Git Version: %s\n", waku_version()); + WAKU_CALL( waku_default_pubsub_topic(print_default_pubsub_topic) ); + WAKU_CALL( waku_version(print_waku_version) ); printf("Bind addr: %s:%u\n", cfgNode.host, cfgNode.port); printf("Waku Relay enabled: %s\n", cfgNode.relay == 1 ? "YES": "NO"); - if (!waku_new(&cfgNode, &mResp)) { - printf("Error creating WakuNode: %s\n", mResp->data); - exit(-1); - } + WAKU_CALL( waku_new(jsonConfig, handle_error) ); - waku_set_event_callback(event_handler); + waku_set_relay_callback(event_handler); waku_start(); printf("Establishing connection with: %s\n", cfgNode.peers); - if (!waku_connect(cfgNode.peers, 10000 /* timeoutMs */, &mResp)) { - printf("Couldn't connect to the remote peer: %s\n", mResp->data); - } + WAKU_CALL( waku_connect(cfgNode.peers, + 10000 /* timeoutMs */, + handle_error) ); + + WAKU_CALL( waku_relay_subscribe("/waku/2/default-waku/proto", + handle_error) ); show_main_menu(); while(1) { handle_user_input(); diff --git a/library/config.nim b/library/config.nim new file mode 100644 index 000000000..13e18212d --- /dev/null +++ b/library/config.nim @@ -0,0 +1,176 @@ + + +import + std/[json,strformat,options] +import + libp2p/crypto/crypto, + libp2p/crypto/secp, + stew/shims/net, + ../../waku/v2/waku_enr/capabilities, + ../../waku/common/utils/nat, + ../../waku/v2/node/waku_node, + ../../waku/v2/node/config, + ./events/[json_error_event,json_base_event] + +proc parsePrivateKey(jsonNode: JsonNode, + privateKey: var PrivateKey, + jsonResp: var JsonEvent): bool = + + if not jsonNode.contains("key"): + jsonResp = JsonErrorEvent.new("The node key is missing."); + return false + + if jsonNode["key"].kind != JsonNodeKind.JString: + jsonResp = JsonErrorEvent.new("The node key should be a string."); + return false + + let key = jsonNode["key"].getStr() + + try: + let skPrivKey = SkPrivateKey.init(crypto.fromHex(key)).tryGet() + privateKey = crypto.PrivateKey(scheme: Secp256k1, skkey: skPrivKey) + except CatchableError: + let msg = "Invalid node key: " & getCurrentExceptionMsg() + jsonResp = JsonErrorEvent.new(msg) + return false + + return true + +proc parseListenAddr(jsonNode: JsonNode, + listenAddr: var ValidIpAddress, + jsonResp: var JsonEvent): bool = + + if not jsonNode.contains("host"): + jsonResp = JsonErrorEvent.new("host attribute is required") + return false + + if jsonNode["host"].kind != JsonNodeKind.JString: + jsonResp = JsonErrorEvent.new("The node host should be a string."); + return false + + let host = jsonNode["host"].getStr() + + try: + listenAddr = ValidIpAddress.init(host) + except CatchableError: + let msg = "Invalid host IP address: " & getCurrentExceptionMsg() + jsonResp = JsonErrorEvent.new(msg) + return false + + return true + +proc parsePort(jsonNode: JsonNode, + port: var int, + jsonResp: var JsonEvent): bool = + + if not jsonNode.contains("port"): + jsonResp = JsonErrorEvent.new("port attribute is required") + return false + + if jsonNode["port"].kind != JsonNodeKind.JInt: + jsonResp = JsonErrorEvent.new("The node port should be an integer."); + return false + + port = jsonNode["port"].getInt() + + return true + +proc parseRelay(jsonNode: JsonNode, + relay: var bool, + jsonResp: var JsonEvent): bool = + + if not jsonNode.contains("relay"): + jsonResp = JsonErrorEvent.new("relay attribute is required") + return false + + if jsonNode["relay"].kind != JsonNodeKind.JBool: + jsonResp = JsonErrorEvent.new("The relay config param should be a boolean"); + return false + + relay = jsonNode["relay"].getBool() + + return true + +proc parseTopics(jsonNode: JsonNode, topics: var seq[string]) = + if jsonNode.contains("topics"): + for topic in jsonNode["topics"].items: + topics.add(topic.getStr()) + else: + topics = @["/waku/2/default-waku/proto"] + +proc parseConfig*(configNodeJson: string, + privateKey: var PrivateKey, + netConfig: var NetConfig, + relay: var bool, + topics: var seq[string], + jsonResp: var JsonEvent): bool = + + if configNodeJson.len == 0: + jsonResp = JsonErrorEvent.new("The configNodeJson is empty") + return false + + var jsonNode: JsonNode + + try: + jsonNode = parseJson(configNodeJson) + except JsonParsingError: + jsonResp = JsonErrorEvent.new("Exception: " & getCurrentExceptionMsg()) + return false + + # key + if not parsePrivateKey(jsonNode, privateKey, jsonResp): + return false + + # listenAddr + var listenAddr = ValidIpAddress.init("127.0.0.1") + if not parseListenAddr(jsonNode, listenAddr, jsonResp): + return false + + # port + var port = 0 + if not parsePort(jsonNode, port, jsonResp): + return false + + let natRes = setupNat("any", clientId, + Port(uint16(port)), + Port(uint16(port))) + if natRes.isErr(): + jsonResp = JsonErrorEvent.new(fmt"failed to setup NAT: {$natRes.error}") + return false + + let (extIp, extTcpPort, _) = natRes.get() + + let extPort = if extIp.isSome() and extTcpPort.isNone(): + some(Port(uint16(port))) + else: + extTcpPort + + # relay + if not parseRelay(jsonNode, relay, jsonResp): + return false + + # topics + parseTopics(jsonNode, topics) + + let wakuFlags = CapabilitiesBitfield.init( + lightpush = false, + filter = false, + store = false, + relay = relay + ) + + let netConfigRes = NetConfig.init( + bindIp = listenAddr, + bindPort = Port(uint16(port)), + extIp = extIp, + extPort = extPort, + wakuFlags = some(wakuFlags)) + + if netConfigRes.isErr(): + let msg = "Error creating NetConfig: " & $netConfigRes.error + jsonResp = JsonErrorEvent.new(msg) + return false + + netConfig = netConfigRes.value + + return true diff --git a/library/events/json_signal_event.nim b/library/events/json_base_event.nim similarity index 54% rename from library/events/json_signal_event.nim rename to library/events/json_base_event.nim index 9db241a64..97e4559e9 100644 --- a/library/events/json_signal_event.nim +++ b/library/events/json_base_event.nim @@ -1,8 +1,8 @@ -type JsonSignal* = ref object of RootObj +type JsonEvent* = ref object of RootObj # https://rfc.vac.dev/spec/36/#jsonsignal-type eventType* {.requiresInit.}: string -method `$`*(jsonSignal: JsonSignal): string {.base.} = discard +method `$`*(jsonEvent: JsonEvent): string {.base.} = discard # All events should implement this diff --git a/library/events/json_error_event.nim b/library/events/json_error_event.nim index fa2f1fc9a..3c672c189 100644 --- a/library/events/json_error_event.nim +++ b/library/events/json_error_event.nim @@ -2,13 +2,14 @@ import std/json import - json_signal_event + ./json_base_event -type JsonErrorEvent* = ref object of JsonSignal +type JsonErrorEvent* = ref object of JsonEvent message*: string proc new*(T: type JsonErrorEvent, message: string): T = + return JsonErrorEvent( eventType: "error", message: message) diff --git a/library/events/json_message_event.nim b/library/events/json_message_event.nim index e3dbee972..2f86521b9 100644 --- a/library/events/json_message_event.nim +++ b/library/events/json_message_event.nim @@ -3,7 +3,7 @@ import std/json import ../../waku/v2/waku_core/message/message, - json_signal_event + ./json_base_event type JsonMessage = ref object # https://rfc.vac.dev/spec/36/#jsonmessage-type @@ -12,7 +12,7 @@ type JsonMessage = ref object version: uint timestamp: int64 -type JsonMessageEvent* = ref object of JsonSignal +type JsonMessageEvent* = ref object of JsonEvent pubsubTopic*: string messageId*: string wakuMessage*: JsonMessage diff --git a/library/libwaku.h b/library/libwaku.h new file mode 100644 index 000000000..982d0b4f8 --- /dev/null +++ b/library/libwaku.h @@ -0,0 +1,61 @@ + +// Generated manually and inspired by the one generated by the Nim Compiler. +// In order to see the header file generated by Nim just run `make libwaku` +// from the root repo folder and the header should be created in +// nimcache/release/libwaku/libwaku.h +#ifndef __libwaku__ +#define __libwaku__ + +// The possible returned values for the functions that return int +#define RET_OK 0 +#define RET_ERR 1 +#define RET_MISSING_CALLBACK 2 + +typedef void (*WakuCallBack) (char* msg, size_t len_0); + +// This should only be called once. +// It initializes the nim runtime and GC. +void waku_init_lib(void); + +// Creates a new instance of the waku node. +// Sets up the waku node from the given configuration. +int waku_new(const char* configJson, WakuCallBack onErrCb); + +void waku_start(void); + +void waku_stop(void); + +int waku_version(WakuCallBack onOkCb); + +void waku_set_relay_callback(WakuCallBack callback); + +int waku_content_topic(const char* appName, + unsigned int appVersion, + const char* contentTopicName, + const char* encoding, + WakuCallBack onOkCb); + +int waku_pubsub_topic(const char* topicName, + WakuCallBack onOkCb); + +int waku_default_pubsub_topic(WakuCallBack onOkCb); + +int waku_relay_publish(const char* pubSubTopic, + const char* jsonWakuMessage, + unsigned int timeoutMs, + WakuCallBack onOkCb, + WakuCallBack onErrCb); + +int waku_relay_subscribe(const char* pubSubTopic, + WakuCallBack onErrCb); + +int waku_relay_unsubscribe(const char* pubSubTopic, + WakuCallBack onErrCb); + +int waku_connect(const char* peerMultiAddr, + unsigned int timeoutMs, + WakuCallBack onErrCb); + +void waku_poll(void); + +#endif /* __libwaku__ */ diff --git a/library/libwaku.nim b/library/libwaku.nim index c98a1c08b..cc5d80691 100644 --- a/library/libwaku.nim +++ b/library/libwaku.nim @@ -1,21 +1,17 @@ import - std/[sequtils,times,strformat,json,options], + std/[json,sequtils,times,strformat,options,atomics,strutils], strutils, os import chronicles, chronos, - libp2p/crypto/secp, stew/shims/net import - ../vendor/nim-libp2p/libp2p/crypto/crypto, ../../waku/common/enr/builder, - ../../waku/common/utils/nat, ../../waku/v2/waku_enr/capabilities, ../../waku/v2/waku_enr/multiaddr, ../../waku/v2/waku_enr/sharding, - ../../waku/v2/waku_core/message/codec, ../../waku/v2/waku_core/message/message, ../../waku/v2/waku_core/topics/pubsub_topic, ../../waku/v2/node/peer_manager/peer_manager, @@ -23,7 +19,8 @@ import ../../waku/v2/node/builder, ../../waku/v2/node/config, ../../waku/v2/waku_relay/protocol, - events/[json_error_event,json_message_event,json_signal_event] + ./events/[json_error_event,json_message_event,json_base_event], + ./config ################################################################################ ### Wrapper around the waku node @@ -31,13 +28,10 @@ import ################################################################################ ### Exported types -type - ConfigNode* {.exportc.} = object - # Struct exported to C - host*: cstring - port*: uint - key*: cstring - relay*: bool + +const RET_OK: cint = 0 +const RET_ERR: cint = 1 +const RET_MISSING_CALLBACK: cint = 2 ### End of exported types ################################################################################ @@ -45,113 +39,37 @@ type ################################################################################ ### Not-exported components -type - EventCallback = proc(signal: cstring) {.cdecl, gcsafe, raises: [Defect].} +proc alloc(str: cstring): cstring = + # Byte allocation from the given address. + # There should be the corresponding manual deallocation with deallocShared ! + let ret = cast[cstring](allocShared(len(str) + 1)) + copyMem(ret, str, len(str) + 1) + return ret -var eventCallback:EventCallback = nil +type + WakuCallBack = proc(msg: ptr cchar, len: csize_t) {.cdecl, gcsafe.} + +# May keep a reference to a callback defined externally +var extRelayEventCallback: WakuCallBack = nil proc relayEventCallback(pubsubTopic: string, msg: WakuMessage): Future[void] {.gcsafe, raises: [Defect].} = # Callback that hadles the Waku Relay events. i.e. messages or errors. - if not isNil(eventCallback): - let event = JsonMessageEvent.new(pubsubTopic, msg) + if not isNil(extRelayEventCallback): try: - eventCallback(cstring($event)) - except Exception: + let event = $JsonMessageEvent.new(pubsubTopic, msg) + extRelayEventCallback(unsafeAddr event[0], cast[csize_t](len(event))) + except Exception,CatchableError: error "Exception when calling 'eventCallBack': " & getCurrentExceptionMsg() else: - error "eventCallback is nil" + error "extRelayEventCallback is nil" var retFut = newFuture[void]() retFut.complete() return retFut -proc okResp(message: string): string = - $(%* { "result": message }) - -proc errResp(message: string): string = - # Convers an error message into an error-JsonResponse - # { - # error: string; - # } - return $(%* { "error": message }) - -proc parseConfig(config: ConfigNode, - privateKey: var PrivateKey, - netConfig: var NetConfig, - jsonResp: var string - ): bool = - if len(config.key) == 0: - jsonResp = errResp("The node key is missing."); - return false - - try: - let key = SkPrivateKey.init(crypto.fromHex($config.key)).tryGet() - privateKey = crypto.PrivateKey(scheme: Secp256k1, skkey: key) - except CatchableError: - let msg = string("Invalid node key: ") & getCurrentExceptionMsg() - jsonResp = errResp(msg) - return false - - if len(config.host) == 0: - jsonResp = errResp("host attribute is required") - return false - - var listenAddr = ValidIpAddress.init("127.0.0.1") - try: - listenAddr = ValidIpAddress.init($config.host) - except CatchableError: - let msg = string("Invalid host IP address: ") & getCurrentExceptionMsg() - jsonResp = errResp(msg) - return false - - if config.port == 0: - jsonResp = errResp("Please set a valid port number") - return false - - ## `udpPort` is only supplied to satisfy underlying APIs but is not - ## actually a supported transport for libp2p traffic. - let udpPort = config.port - - let natRes = setupNat("any", clientId, - Port(uint16(config.port)), - Port(uint16(udpPort))) - if natRes.isErr(): - jsonResp = errResp(fmt"failed to setup NAT: {$natRes.error}") - return false - - let (extIp, extTcpPort, _) = natRes.get() - - let extPort = if extIp.isSome() and extTcpPort.isNone(): - some(Port(uint16(config.port))) - else: - extTcpPort - - let wakuFlags = CapabilitiesBitfield.init( - lightpush = false, - filter = false, - store = false, - relay = config.relay - ) - - let netConfigRes = NetConfig.init( - bindIp = listenAddr, - bindPort = Port(uint16(config.port)), - extIp = extIp, - extPort = extPort, - wakuFlags = some(wakuFlags)) - - if netConfigRes.isErr(): - let msg = string("Error creating NetConfig: ") & $netConfigRes.error - jsonResp = errResp(msg) - return false - - netConfig = netConfigRes.value - - return true - # WakuNode instance var node {.threadvar.}: WakuNode @@ -161,17 +79,51 @@ var node {.threadvar.}: WakuNode ################################################################################ ### Exported procs -proc waku_new(config: ConfigNode, - jsonResp: var string): bool - {.dynlib, exportc.} = -# Creates a new instance of the WakuNode. -# Notice that the ConfigNode type is also exported and available for users. +# Every Nim library must have this function called - the name is derived from +# the `--nimMainPrefix` command line option +proc NimMain() {.importc.} + +var initialized: Atomic[bool] + +proc waku_init_lib() {.dynlib, exportc, cdecl.} = + if not initialized.exchange(true): + NimMain() # Every Nim library needs to call `NimMain` once exactly + when declared(setupForeignThreadGc): setupForeignThreadGc() + when declared(nimGC_setStackBottom): + var locals {.volatile, noinit.}: pointer + locals = addr(locals) + nimGC_setStackBottom(locals) + +proc waku_new(configJson: cstring, + onErrCb: WakuCallback): cint + {.dynlib, exportc, cdecl.} = + # Creates a new instance of the WakuNode. + # Notice that the ConfigNode type is also exported and available for users. + + if isNil(onErrCb): + return RET_MISSING_CALLBACK + var privateKey: PrivateKey - var netConfig = NetConfig.init(ValidIpAddress.init("127.0.0.1"), Port(60000'u16)).value - if not parseConfig(config, - privateKey, netConfig, + var netConfig = NetConfig.init(ValidIpAddress.init("127.0.0.1"), + Port(60000'u16)).value + var relay: bool + var topics = @[""] + var jsonResp: JsonEvent + + let cj = configJson.alloc() + + if not parseConfig($cj, + privateKey, + netConfig, + relay, + topics, jsonResp): - return false + deallocShared(cj) + let resp = $jsonResp + onErrCb(unsafeAddr resp[0], cast[csize_t](len(resp))) + return RET_ERR + + deallocShared(cj) var enrBuilder = EnrBuilder.init(privateKey) @@ -185,16 +137,19 @@ proc waku_new(config: ConfigNode, enrBuilder.withWakuCapabilities(netConfig.wakuFlags.get()) enrBuilder.withMultiaddrs(netConfig.enrMultiaddrs) - ## TODO: pass the topics from conf parameter. We will pass it from - ## json array in upcoming PRs. - let addShardedTopics = enrBuilder.withShardedTopics(@["/waku/2/default-waku/proto"]) + + let addShardedTopics = enrBuilder.withShardedTopics(topics) if addShardedTopics.isErr(): - return false + let resp = $addShardedTopics.error + onErrCb(unsafeAddr resp[0], cast[csize_t](len(resp))) + return RET_ERR let recordRes = enrBuilder.build() let record = if recordRes.isErr(): - return false + let resp = $recordRes.error + onErrCb(unsafeAddr resp[0], cast[csize_t](len(resp))) + return RET_ERR else: recordRes.get() var builder = WakuNodeBuilder.init() @@ -208,54 +163,103 @@ proc waku_new(config: ConfigNode, let wakuNodeRes = builder.build() if wakuNodeRes.isErr(): - let errorMsg = string("failed to create waku node instance: ") & wakuNodeRes.error - jsonResp = errResp(errorMsg) - return false + let errorMsg = "failed to create waku node instance: " & wakuNodeRes.error + let jsonErrEvent = $JsonErrorEvent.new(errorMsg) - node = wakuNodeRes.value + onErrCb(unsafeAddr jsonErrEvent[0], cast[csize_t](len(jsonErrEvent))) + return RET_ERR - if config.relay: + node = wakuNodeRes.get() + + if relay: waitFor node.mountRelay() node.peerManager.start() - return true + return RET_OK -proc waku_version(): cstring {.dynlib, exportc.} = - return WakuNodeVersionString +proc waku_version(onOkCb: WakuCallBack): cint {.dynlib, exportc.} = + if isNil(onOkCb): + return RET_MISSING_CALLBACK -proc waku_set_event_callback(callback: EventCallback) {.dynlib, exportc.} = - eventCallback = callback + onOkCb(cast[ptr cchar](WakuNodeVersionString), + cast[csize_t](len(WakuNodeVersionString))) + + return RET_OK + +proc waku_set_relay_callback(callback: WakuCallBack) {.dynlib, exportc.} = + extRelayEventCallback = callback proc waku_content_topic(appName: cstring, - appVersion: uint, + appVersion: cuint, contentTopicName: cstring, encoding: cstring, - outContentTopic: var string) {.dynlib, exportc.} = + onOkCb: WakuCallBack): cint {.dynlib, exportc.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_content_topicchar-applicationname-unsigned-int-applicationversion-char-contenttopicname-char-encoding - outContentTopic = fmt"{appName}/{appVersion}/{contentTopicName}/{encoding}" -proc waku_pubsub_topic(topicName: cstring, outPubsubTopic: var string) {.dynlib, exportc.} = + if isNil(onOkCb): + return RET_MISSING_CALLBACK + + let appStr = appName.alloc() + let ctnStr = contentTopicName.alloc() + let encodingStr = encoding.alloc() + + let contentTopic = fmt"/{$appStr}/{appVersion}/{$ctnStr}/{$encodingStr}" + onOkCb(unsafeAddr contentTopic[0], cast[csize_t](len(contentTopic))) + + deallocShared(appStr) + deallocShared(ctnStr) + deallocShared(encodingStr) + + return RET_OK + +proc waku_pubsub_topic(topicName: cstring, + onOkCb: WakuCallBack): cint {.dynlib, exportc, cdecl.} = + if isNil(onOkCb): + return RET_MISSING_CALLBACK + + let topicNameStr = topicName.alloc() + # https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding - outPubsubTopic = fmt"/waku/2/{topicName}" + let outPubsubTopic = fmt"/waku/2/{$topicNameStr}" + onOkCb(unsafeAddr outPubsubTopic[0], cast[csize_t](len(outPubsubTopic))) -proc waku_default_pubsub_topic(defPubsubTopic: var string) {.dynlib, exportc.} = + deallocShared(topicNameStr) + + return RET_OK + +proc waku_default_pubsub_topic(onOkCb: WakuCallBack): cint {.dynlib, exportc.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_default_pubsub_topic - defPubsubTopic = DefaultPubsubTopic + if isNil(onOkCb): + return RET_MISSING_CALLBACK + + onOkCb(cast[ptr cchar](DefaultPubsubTopic), + cast[csize_t](len(DefaultPubsubTopic))) + + return RET_OK proc waku_relay_publish(pubSubTopic: cstring, jsonWakuMessage: cstring, - timeoutMs: int, - jsonResp: var string): bool + timeoutMs: cuint, + onOkCb: WakuCallBack, + onErrCb: WakuCallBack): cint {.dynlib, exportc, cdecl.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms + if isNil(onOkCb) or isNil(onErrCb): + return RET_MISSING_CALLBACK + + let jwm = jsonWakuMessage.alloc() var jsonContent:JsonNode try: - jsonContent = parseJson($jsonWakuMessage) + jsonContent = parseJson($jwm) except JsonParsingError: - jsonResp = errResp (fmt"Problem parsing json message. {getCurrentExceptionMsg()}") - return false + deallocShared(jwm) + let msg = fmt"Error parsing json message: {getCurrentExceptionMsg()}" + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR + + deallocShared(jwm) var wakuMessage: WakuMessage try: @@ -272,17 +276,21 @@ proc waku_relay_publish(pubSubTopic: cstring, ephemeral: false ) except KeyError: - jsonResp = errResp(fmt"Problem building the WakuMessage. {getCurrentExceptionMsg()}") - return false + let msg = fmt"Problem building the WakuMessage: {getCurrentExceptionMsg()}" + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR - let targetPubSubTopic = if $pubSubTopic == "": + let pst = pubSubTopic.alloc() + + let targetPubSubTopic = if len(pst) == 0: DefaultPubsubTopic else: - $pubSubTopic + $pst if node.wakuRelay.isNil(): - jsonResp = errResp("Can't publish. WakuRelay is not enabled.") - return false + let msg = "Can't publish. WakuRelay is not enabled." + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR let pubMsgFut = node.wakuRelay.publish(targetPubSubTopic, wakuMessage) @@ -295,15 +303,19 @@ proc waku_relay_publish(pubSubTopic: cstring, if pubMsgFut.finished(): let numPeers = pubMsgFut.read() if numPeers == 0: - jsonResp = errResp("Message not sent because no peers found") + let msg = "Message not sent because no peers found." + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR elif numPeers > 0: # TODO: pending to return a valid message Id (response when all is correct) - jsonResp = okResp("hard-coded-message-id") + let msg = "hard-coded-message-id" + onOkCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_OK else: - jsonResp = errResp("Timeout expired") - - return true + let msg = "Timeout expired" + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR proc waku_start() {.dynlib, exportc.} = waitFor node.start() @@ -313,62 +325,82 @@ proc waku_stop() {.dynlib, exportc.} = proc waku_relay_subscribe( pubSubTopic: cstring, - jsonResp: var string): bool + onErrCb: WakuCallBack): cint {.dynlib, exportc.} = # @params # topic: Pubsub topic to subscribe to. If empty, it subscribes to the default pubsub topic. - if isNil(eventCallback): - jsonResp = errResp("""Cannot subscribe without a callback. -Kindly set it with the 'waku_set_event_callback' function""") - return false + if isNil(onErrCb): + return RET_MISSING_CALLBACK + + if isNil(extRelayEventCallback): + let msg = $"""Cannot subscribe without a callback. +# Kindly set it with the 'waku_set_relay_callback' function""" + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_MISSING_CALLBACK if node.wakuRelay.isNil(): - jsonResp = errResp("Cannot subscribe without Waku Relay enabled.") - return false + let msg = $"Cannot subscribe without Waku Relay enabled." + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR - node.wakuRelay.subscribe(PubsubTopic($pubSubTopic), + let pst = pubSubTopic.alloc() + node.wakuRelay.subscribe(PubsubTopic($pst), WakuRelayHandler(relayEventCallback)) + deallocShared(pst) - jsonResp = okResp("true") - return true + return RET_OK proc waku_relay_unsubscribe( pubSubTopic: cstring, - jsonResp: var string): bool + onErrCb: WakuCallBack): cint {.dynlib, exportc.} = # @params # topic: Pubsub topic to subscribe to. If empty, it unsubscribes to the default pubsub topic. - if isNil(eventCallback): - jsonResp = errResp("""Cannot unsubscribe without a callback. -Kindly set it with the 'waku_set_event_callback' function""") - return false + if isNil(onErrCb): + return RET_MISSING_CALLBACK + + if isNil(extRelayEventCallback): + let msg = """Cannot unsubscribe without a callback. +# Kindly set it with the 'waku_set_relay_callback' function""" + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_MISSING_CALLBACK if node.wakuRelay.isNil(): - jsonResp = errResp("Cannot unsubscribe without Waku Relay enabled.") - return false + let msg = "Cannot unsubscribe without Waku Relay enabled." + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR - node.wakuRelay.unsubscribe(PubsubTopic($pubSubTopic)) + let pst = pubSubTopic.alloc() + node.wakuRelay.unsubscribe(PubsubTopic($pst)) + deallocShared(pst) - jsonResp = okResp("true") - return true + return RET_OK proc waku_connect(peerMultiAddr: cstring, - timeoutMs: uint = 10000, - jsonResp: var string): bool + timeoutMs: cuint, + onErrCb: WakuCallBack): cint {.dynlib, exportc.} = # peerMultiAddr: comma-separated list of fully-qualified multiaddresses. - let peers = ($peerMultiAddr).split(",").mapIt(strip(it)) + # var ret = newString(len + 1) + # if len > 0: + # copyMem(addr ret[0], str, len + 1) + + let address = peerMultiAddr.alloc() + let peers = ($address).split(",").mapIt(strip(it)) # TODO: the timeoutMs is not being used at all! let connectFut = node.connectToNodes(peers, source="static") while not connectFut.finished(): poll() - if not connectFut.completed(): - jsonResp = errResp("Timeout expired.") - return false + deallocShared(address) - return true + if not connectFut.completed(): + let msg = "Timeout expired." + onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) + return RET_ERR + + return RET_OK proc waku_poll() {.dynlib, exportc, gcsafe.} = poll()