diff --git a/.gitignore b/.gitignore index 6c56261df..21f372df6 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ nimbus-build-system.paths *.sqlite3 *.sqlite3-shm *.sqlite3-wal + +# Ignore autogenerated C-bingings compilation files +/examples/cbindings/libwaku.h diff --git a/Makefile b/Makefile index 122be8e73..6be1d3401 100644 --- a/Makefile +++ b/Makefile @@ -280,24 +280,31 @@ docker-push: docker push $(DOCKER_IMAGE_NAME) -############## -## Wrappers ## -############## -# TODO: Remove unused target -libwaku.so: | build deps deps2 +################ +## C Bindings ## +################ +.PHONY: cbindings cwaku_example libwaku.a + +libwaku.a: | build deps echo -e $(BUILD_MSG) "build/$@" && \ - $(ENV_SCRIPT) nim c --app:lib --noMain --nimcache:nimcache/libwaku $(NIM_PARAMS) -o:build/$@.0 wrappers/libwaku.nim && \ - rm -f build/$@ && \ - ln -s $@.0 build/$@ + $(ENV_SCRIPT) nim libwaku $(NIM_PARAMS) $(EXPERIMENTAL_PARAMS) waku.nims -# libraries for dynamic linking of non-Nim objects -EXTRA_LIBS_DYNAMIC := -L"$(CURDIR)/build" -lwaku -lm +libwaku.so: | build deps +# TODO: pending to enhance this part. Kindly use the static approach. + echo -e $(BUILD_MSG) "build/$@" && \ + $(ENV_SCRIPT) nim c --app:lib --opt:size --noMain --header -o:build/$@ library/cwakunode.nim -# TODO: Remove unused target -wrappers: | build deps librln libwaku.so - echo -e $(BUILD_MSG) "build/C_wrapper_example" && \ - $(CC) wrappers/wrapper_example.c -Wl,-rpath,'$$ORIGIN' $(EXTRA_LIBS_DYNAMIC) -g -o build/C_wrapper_example - echo -e $(BUILD_MSG) "build/go_wrapper_example" && \ - go build -ldflags "-linkmode external -extldflags '$(EXTRA_LIBS_DYNAMIC)'" -o build/go_wrapper_example wrappers/wrapper_example.go #wrappers/cfuncs.go +cbindings: | build libwaku.a + +cwaku_example: | build cbindings + echo -e $(BUILD_MSG) "build/$@" && \ + cp nimcache/release/libwaku/libwaku.h ./examples/cbindings/ && \ + cc -o "build/$@" \ + ./examples/cbindings/waku_example.c \ + -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 endif # "variables.mk" was not included diff --git a/examples/cbindings/nimbase.h b/examples/cbindings/nimbase.h new file mode 100644 index 000000000..a83bd3006 --- /dev/null +++ b/examples/cbindings/nimbase.h @@ -0,0 +1,602 @@ +/* + + 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 new file mode 100644 index 000000000..6adb99b8b --- /dev/null +++ b/examples/cbindings/waku_example.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "libwaku.h" + +// Keep a global string to store the waku call responses +static NimStringDesc wakuString; +NimStringDesc* mResp = &wakuString; + +struct ConfigNode { + NCSTRING host; + NU port; + NCSTRING key; + NIM_BOOL relay; + NCSTRING peers; +}; + +static ConfigNode cfgNode; + +// 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 = state->input; + switch (key) { + case 'h': + cfgNode->host = arg; + break; + case 'p': + cfgNode->port = atoi(arg); + break; + case 'k': + cfgNode->key = arg; + break; + case 'r': + cfgNode->relay = atoi(arg); + break; + case 'a': + cfgNode->peers = 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 }; + +// 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+/"; + +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 (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; +} + +// End of Base64 encoding + +// 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 set_scanf_to_not_block() { + fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); +} + +void set_scanf_to_block() { + fcntl(0, F_SETFL, fcntl(0, F_GETFL) ^ O_NONBLOCK); +} + +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"); + 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); + + set_scanf_to_not_block(); + 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"); + 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); + } + set_scanf_to_not_block(); + show_main_menu(); + break; + + case PUBLISH_MESSAGE_MENU: + { + set_scanf_to_block(); + 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]; + char *msgPayload = b64_encode(msg, strlen(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(); + } + break; + + case MAIN_MENU: + break; + } +} + +// 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) { + // default values + cfgNode.host = "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(); + } + + // 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()); + 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_set_event_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); + } + + show_main_menu(); + while(1) { + handle_user_input(); + waku_poll(); + } +} diff --git a/library/events/json_error_event.nim b/library/events/json_error_event.nim new file mode 100644 index 000000000..fa2f1fc9a --- /dev/null +++ b/library/events/json_error_event.nim @@ -0,0 +1,17 @@ + +import + std/json +import + json_signal_event + +type JsonErrorEvent* = ref object of JsonSignal + message*: string + +proc new*(T: type JsonErrorEvent, + message: string): T = + return JsonErrorEvent( + eventType: "error", + message: message) + +method `$`*(jsonError: JsonErrorEvent): string = + $( %* jsonError ) \ No newline at end of file diff --git a/library/events/json_message_event.nim b/library/events/json_message_event.nim new file mode 100644 index 000000000..e3dbee972 --- /dev/null +++ b/library/events/json_message_event.nim @@ -0,0 +1,42 @@ + +import + std/json +import + ../../waku/v2/waku_core/message/message, + json_signal_event + +type JsonMessage = ref object + # https://rfc.vac.dev/spec/36/#jsonmessage-type + payload: string + contentTopic: string + version: uint + timestamp: int64 + +type JsonMessageEvent* = ref object of JsonSignal + pubsubTopic*: string + messageId*: string + wakuMessage*: JsonMessage + +proc new*(T: type JsonMessageEvent, + pubSubTopic: string, + msg: WakuMessage): T = + # Returns a WakuMessage event as indicated in + # https://rfc.vac.dev/spec/36/#jsonmessageevent-type + + var payload = newString(len(msg.payload)) + copyMem(addr payload[0], unsafeAddr msg.payload[0], len(msg.payload)) + + return JsonMessageEvent( + eventType: "message", + pubSubTopic: pubSubTopic, + messageId: "TODO", + wakuMessage: JsonMessage( + payload: payload, + contentTopic: msg.contentTopic, + version: msg.version, + timestamp: int64(msg.timestamp) + ) + ) + +method `$`*(jsonMessage: JsonMessageEvent): string = + $( %* jsonMessage ) diff --git a/library/events/json_signal_event.nim b/library/events/json_signal_event.nim new file mode 100644 index 000000000..9db241a64 --- /dev/null +++ b/library/events/json_signal_event.nim @@ -0,0 +1,8 @@ + +type JsonSignal* = ref object of RootObj + # https://rfc.vac.dev/spec/36/#jsonsignal-type + eventType* {.requiresInit.}: string + +method `$`*(jsonSignal: JsonSignal): string {.base.} = discard + # All events should implement this + diff --git a/library/libwaku.nim b/library/libwaku.nim new file mode 100644 index 000000000..6227225ee --- /dev/null +++ b/library/libwaku.nim @@ -0,0 +1,400 @@ + +import + std/[sequtils,times,strformat,json,options], + strutils, + os +import + chronicles, + chronos, + libp2p/crypto/secp, + stew/shims/net, + eth/net/nat as todo_delete_this_module +import + ../vendor/nim-libp2p/libp2p/crypto/crypto, + ../../waku/common/utils/nat, + ../../waku/v2/waku_enr/capabilities, + ../../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, + ../../waku/v2/node/waku_node, + ../../waku/v2/node/builder, + ../../waku/v2/node/config, + ../../waku/v2/waku_relay/protocol, + events/[json_error_event,json_message_event,json_signal_event] + +################################################################################ +### Wrapper around the waku node +################################################################################ + +################################################################################ +### Exported types +type + ConfigNode* {.exportc.} = object + # Struct exported to C + host*: cstring + port*: uint + key*: cstring + relay*: bool + +### End of exported types +################################################################################ + +################################################################################ +### Not-exported components + +type + EventCallback = proc(signal: cstring) {.cdecl, gcsafe, raises: [Defect].} + +var eventCallback:EventCallback = nil + +proc relayEventCallback(pubsubTopic: string, data: seq[byte]): Future[void] {.gcsafe, raises: [Defect].} = + # Callback that hadles the Waku Relay events. i.e. messages or errors. + if not isNil(eventCallback): + let msg = WakuMessage.decode(data) + var event: JsonSignal + if msg.isOk(): + event = JsonMessageEvent.new(pubsubTopic, msg.value) + else: + let errorMsg = string("Error decoding message.") & $msg.error + event = JsonErrorEvent.new(errorMsg) + + try: + eventCallback(cstring($event)) + except Exception: + error "Exception when calling 'eventCallBack': " & + getCurrentExceptionMsg() + else: + error "eventCallback 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 setupNat(natConf, clientId: string, tcpPort, udpPort: Port): + Result[tuple[ip: Option[ValidIpAddress], tcpPort: Option[Port], udpPort: Option[Port]], string] {.gcsafe.} = + # TODO reorganize all setupNat calls and use only one commom proc + + let strategy = case natConf.toLowerAscii(): + of "any": NatAny + of "none": NatNone + of "upnp": NatUpnp + of "pmp": NatPmp + else: NatNone + + var endpoint: tuple[ip: Option[ValidIpAddress], tcpPort: Option[Port], udpPort: Option[Port]] + + if strategy != NatNone: + let extIp = getExternalIP(strategy) + if extIP.isSome(): + endpoint.ip = some(ValidIpAddress.init(extIp.get())) + # RedirectPorts in considered a gcsafety violation + # because it obtains the address of a non-gcsafe proc? + var extPorts: Option[(Port, Port)] + try: + extPorts = ({.gcsafe.}: redirectPorts(tcpPort = tcpPort, + udpPort = udpPort, + description = clientId)) + except CatchableError: + # TODO: nat.nim Error: can raise an unlisted exception: Exception. Isolate here for now. + error "unable to determine external ports" + extPorts = none((Port, Port)) + + if extPorts.isSome(): + let (extTcpPort, extUdpPort) = extPorts.get() + endpoint.tcpPort = some(extTcpPort) + endpoint.udpPort = some(extUdpPort) + + else: # NatNone + if not natConf.startsWith("extip:"): + return err("not a valid NAT mechanism: " & $natConf) + + try: + # any required port redirection is assumed to be done by hand + endpoint.ip = some(ValidIpAddress.init(natConf[6..^1])) + except ValueError: + return err("not a valid IP address: " & $natConf[6..^1]) + + return ok(endpoint) + +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 + +### End of not-exported components +################################################################################ + +################################################################################ +### 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. + var privateKey: PrivateKey + var netConfig = NetConfig.init(ValidIpAddress.init("127.0.0.1"), Port(60000'u16)).value + if not parseConfig(config, + privateKey, netConfig, + jsonResp): + return false + + var builder = WakuNodeBuilder.init() + builder.withRng(crypto.newRng()) + builder.withNodeKey(privateKey) + builder.withNetworkConfiguration(netConfig) + builder.withSwitchConfiguration( + maxConnections = some(50.int) + ) + + let wakuNodeRes = builder.build() + if wakuNodeRes.isErr(): + let errorMsg = string("failed to create waku node instance: ") & wakuNodeRes.error + jsonResp = errResp(errorMsg) + return false + + node = wakuNodeRes.value + + if config.relay: + waitFor node.mountRelay() + node.peerManager.start() + + return true + +proc waku_version(): cstring {.dynlib, exportc.} = + return WakuNodeVersionString + +proc waku_set_event_callback(callback: EventCallback) {.dynlib, exportc.} = + eventCallback = callback + +proc waku_content_topic(appName: cstring, + appVersion: uint, + contentTopicName: cstring, + encoding: cstring, + outContentTopic: var string) {.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.} = + # https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding + outPubsubTopic = fmt"/waku/2/{topicName}" + +proc waku_default_pubsub_topic(defPubsubTopic: var string) {.dynlib, exportc.} = + # https://rfc.vac.dev/spec/36/#extern-char-waku_default_pubsub_topic + defPubsubTopic = DefaultPubsubTopic + +proc waku_relay_publish(pubSubTopic: cstring, + jsonWakuMessage: cstring, + timeoutMs: int, + jsonResp: var string): bool + + {.dynlib, exportc, cdecl.} = + # https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms + + var jsonContent:JsonNode + try: + jsonContent = parseJson($jsonWakuMessage) + except JsonParsingError: + jsonResp = errResp (fmt"Problem parsing json message. {getCurrentExceptionMsg()}") + return false + + var wakuMessage: WakuMessage + try: + var version = 0'u32 + if jsonContent.hasKey("version"): + version = (uint32) jsonContent["version"].getInt() + + wakuMessage = WakuMessage( + # Visit https://rfc.vac.dev/spec/14/ for further details + payload: jsonContent["payload"].getStr().toSeq().mapIt(byte (it)), + contentTopic: $jsonContent["content_topic"].getStr(), + version: version, + timestamp: getTime().toUnix(), + ephemeral: false + ) + except KeyError: + jsonResp = errResp(fmt"Problem building the WakuMessage. {getCurrentExceptionMsg()}") + return false + + let targetPubSubTopic = if $pubSubTopic == "": + DefaultPubsubTopic + else: + $pubSubTopic + + if node.wakuRelay.isNil(): + jsonResp = errResp("Can't publish. WakuRelay is not enabled.") + return false + + let pubMsgFut = node.wakuRelay.publish(targetPubSubTopic, wakuMessage) + + # With the next loop we convert an asynchronous call into a synchronous one + for i in 0 .. timeoutMs: + if pubMsgFut.finished(): + break + sleep(1) + + if pubMsgFut.finished(): + let numPeers = pubMsgFut.read() + if numPeers == 0: + jsonResp = errResp("Message not sent because no peers found") + elif numPeers > 0: + # TODO: pending to return a valid message Id (response when all is correct) + jsonResp = okResp("hard-coded-message-id") + + else: + jsonResp = errResp("Timeout expired") + + return true + +proc waku_start() {.dynlib, exportc.} = + waitFor node.start() + +proc waku_stop() {.dynlib, exportc.} = + waitFor node.stop() + +proc waku_relay_subscribe( + pubSubTopic: cstring, + jsonResp: var string): bool + {.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 node.wakuRelay.isNil(): + jsonResp = errResp("Cannot subscribe without Waku Relay enabled.") + return false + + node.wakuRelay.subscribe(PubsubTopic($pubSubTopic), PubsubRawHandler(relayEventCallback)) + + jsonResp = okResp("true") + return true + +proc waku_relay_unsubscribe( + pubSubTopic: cstring, + jsonResp: var string): bool + {.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 node.wakuRelay.isNil(): + jsonResp = errResp("Cannot unsubscribe without Waku Relay enabled.") + return false + + node.wakuRelay.unsubscribeAll(PubsubTopic($pubSubTopic)) + + jsonResp = okResp("true") + return true + +proc waku_connect(peerMultiAddr: cstring, + timeoutMs: uint = 10000, + jsonResp: var string): bool + {.dynlib, exportc.} = + # peerMultiAddr: comma-separated list of fully-qualified multiaddresses. + let peers = ($peerMultiAddr).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 + + return true + +proc waku_poll() {.dynlib, exportc, gcsafe.} = + poll() + +### End of exported procs +################################################################################ \ No newline at end of file diff --git a/waku.nimble b/waku.nimble index 19d1c1461..27add8d6a 100644 --- a/waku.nimble +++ b/waku.nimble @@ -34,6 +34,18 @@ proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") = extra_params &= " " & paramStr(i) exec "nim " & lang & " --out:build/" & name & " " & extra_params & " " & srcDir & name & ".nim" +proc buildLibrary(name: string, srcDir = "./", params = "", lang = "c", isStatic = true) = + if not dirExists "build": + mkDir "build" + # allow something like "nim nimbus --verbosity:0 --hints:off nimbus.nims" + var extra_params = params + for i in 2.. NIM_PARAMS="-d:chronicles_log_level=INFO" make test2` # I expect compiler flag to be overridden, however it stays with whatever is @@ -41,13 +53,10 @@ proc test(name: string, params = "-d:chronicles_log_level=DEBUG", lang = "c") = buildBinary name, "tests/", params exec "build/" & name - ### Waku common tasks task testcommon, "Build & run common tests": test "all_tests_common", "-d:chronicles_log_level=WARN -d:chronosStrictException" - - ### Waku v2 tasks task wakunode2, "Build Waku v2 cli node": let name = "wakunode2" @@ -65,7 +74,6 @@ task networkmonitor, "Build network monitor tool": let name = "networkmonitor" buildBinary name, "apps/networkmonitor/", "-d:chronicles_log_level=TRACE" - task test2, "Build & run Waku v2 tests": test "all_tests_v2" @@ -75,7 +83,6 @@ task testwakunode2, "Build & run wakunode2 app tests": task testbridge, "Build & run wakubridge tests": test "all_tests_wakubridge" - task example2, "Build Waku v2 example": buildBinary "publisher", "examples/v2/" buildBinary "subscriber", "examples/v2/" @@ -92,6 +99,10 @@ task chat2bridge, "Build chat2bridge": let name = "chat2bridge" buildBinary name, "apps/chat2bridge/", "-d:chronicles_log_level=TRACE" +### C Bindings +task libwaku, "Build the cbindings waku node library": + let name = "libwaku" + buildLibrary name, "library/", "-d:chronicles_log_level=ERROR" ### Legacy: Whisper & Waku v1 tasks task testwhisper, "Build & run Whisper tests": diff --git a/waku/common/protobuf.nim b/waku/common/protobuf.nim index db66198be..9e012e0a9 100644 --- a/waku/common/protobuf.nim +++ b/waku/common/protobuf.nim @@ -67,3 +67,27 @@ proc finish3*(proto: var ProtoBuffer) = proc `==`*(a: zint64, b: zint64): bool = int64(a) == int64(b) + +proc `$`*(err: ProtobufError): string = + case err.kind: + of DecodeFailure: + case err.error: + of VarintDecode: + return "VarintDecode" + of MessageIncomplete: + return "MessageIncomplete" + of BufferOverflow: + return "BufferOverflow" + of MessageTooBig: + return "MessageTooBig" + of BadWireType: + return "BadWireType" + of IncorrectBlob: + return "IncorrectBlob" + of RequiredFieldMissing: + return "RequiredFieldMissing" + of MissingRequiredField: + return "MissingRequiredField " & err.field + of InvalidLengthField: + return "InvalidLengthField " & err.field + diff --git a/waku/v2/node/waku_node.nim b/waku/v2/node/waku_node.nim index ffb0ef663..b30b39dcd 100644 --- a/waku/v2/node/waku_node.nim +++ b/waku/v2/node/waku_node.nim @@ -60,7 +60,6 @@ declarePublicGauge waku_px_peers, "number of peers (in the node's peerManager) s logScope: topics = "waku node" - # TODO: Move to application instance (e.g., `WakuNode2`) # Git version in git describe format (defined compile time) const git_version* {.strdefine.} = "n/a" @@ -71,6 +70,7 @@ const clientId* = "Nimbus Waku v2 node" # Default Waku Filter Timeout const WakuFilterTimeout: Duration = 1.days +const WakuNodeVersionString* = "version / git commit hash: " & git_version # key and crypto modules different type diff --git a/wrappers/README.md b/wrappers/README.md deleted file mode 100644 index 4ecd2ff3f..000000000 --- a/wrappers/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# libwaku - -Exposes a C API that can be used by other environments other than C. - -## Running - -``` -make wrappers -``` diff --git a/wrappers/libwaku.h b/wrappers/libwaku.h deleted file mode 100644 index dce2deaa3..000000000 --- a/wrappers/libwaku.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __LIBWAKU_H__ -#define __LIBWAKU_H__ - -#include -#include - -// Initialize Nim -void NimMain(); - -char* info(const char* wakuNode); - -void echo(); - -#endif //__LIBWAKU_H__ diff --git a/wrappers/libwaku.nim b/wrappers/libwaku.nim deleted file mode 100644 index 776ac291c..000000000 --- a/wrappers/libwaku.nim +++ /dev/null @@ -1,38 +0,0 @@ -# libwaku -# -# Exposes a C API that can be used by other environment than C. - -# TODO Start a node -# TODO Mock info call -# TODO Write header file -# TODO Write example C code file -# TODO Wrap info call -# TODO Init a node - -# proc info*(node: WakuNode): WakuInfo = -proc info(foo: cstring): cstring {.exportc, dynlib.} = - echo "info about node" - echo foo - return foo - -proc echo() {.exportc.} = - echo "echo" - -# TODO Here at the moment, start the node -# Then do info call -# WIP -#proc main() {.async.} = -# let -# rng = crypto.newRng() -# conf = WakuNodeConf.load() -# (extIp, extTcpPort, extUdpPort) = setupNat(conf.nat, clientId, -# Port(uint16(conf.tcpPort) + conf.portsShift), -# Port(uint16(conf.udpPort) + conf.portsShift)) -# node = WakuNode.new(conf.nodeKey, conf.listenAddress, -# Port(uint16(conf.tcpPort) + conf.portsShift), extIp, extTcpPort) -# -# await node.start() -# -#main() - - # When main done stuff diff --git a/wrappers/wrapper_example.c b/wrappers/wrapper_example.c deleted file mode 100644 index 33bec5db3..000000000 --- a/wrappers/wrapper_example.c +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -#include "libwaku.h" - -void NimMain(); - -int main(int argc, char* argv[]) { - char* string; - NimMain(); - //echo(); - string = info("hello there"); - printf("Info: %s", string); -} diff --git a/wrappers/wrapper_example.go b/wrappers/wrapper_example.go deleted file mode 100644 index 4291e2dd0..000000000 --- a/wrappers/wrapper_example.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "fmt" - "runtime" -) - -/* -#include - -// Passing "-lwaku" to the Go linker through "-extldflags" is not enough. We need it in here, for some reason. -#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR}/../build -lwaku -#include "libwaku.h" - -*/ -import "C" - -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() -} - -func Start() { - C.NimMain() - - messageC := C.CString("Calling info") - fmt.Println("Start nim-waku") - var str = C.info(messageC) - fmt.Println("Info", str) -} - -func main() { - fmt.Println("Hi main") - Start() -}