feat: c-bindings for waku relay (#212)

This commit is contained in:
Richard Ramos 2022-03-21 19:15:53 -04:00 committed by GitHub
parent 21b2e1d97c
commit df235db6b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1713 additions and 4 deletions

View File

@ -4,7 +4,30 @@ GO_HTML_COV := ./coverage.html
GO_TEST_OUTFILE := ./c.out
CC_PREFIX := github.com/status-im/go-waku
.PHONY: all build lint test coverage build-example
SHELL := bash # the shell used internally by Make
.PHONY: all build lint test coverage build-example static-library dynamic-library test-c test-c-template
ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10...
detected_OS := Windows
else
detected_OS := $(strip $(shell uname))
endif
ifeq ($(detected_OS),Darwin)
GOBIN_SHARED_LIB_EXT := dylib
ifeq ("$(shell sysctl -nq hw.optional.arm64)","1")
# Building on M1 is still not supported, so in the meantime we crosscompile to amd64
GOBIN_SHARED_LIB_CFLAGS=CGO_ENABLED=1 GOOS=darwin GOARCH=amd64
endif
else ifeq ($(detected_OS),Windows)
# on Windows need `--export-all-symbols` flag else expected symbols will not be found in libgowaku.dll
GOBIN_SHARED_LIB_CGO_LDFLAGS := CGO_LDFLAGS="-Wl,--export-all-symbols"
GOBIN_SHARED_LIB_EXT := dll
else
GOBIN_SHARED_LIB_EXT := so
GOBIN_SHARED_LIB_CGO_LDFLAGS := CGO_LDFLAGS="-Wl,-soname,libgowaku.so.0"
endif
all: build
@ -60,4 +83,30 @@ build-example-chat-2:
build-example-filter2:
cd examples/filter2 && $(MAKE)
build-example: build-example-basic2 build-example-chat-2 build-example-filter2
build-example: build-example-basic2 build-example-chat-2 build-example-filter2
static-library: ##@cross-compile Build go-waku as static library for current platform
mkdir -p ./build/lib
@echo "Building static library..."
go build \
-buildmode=c-archive \
-o ./build/lib/libgowaku.a \
./library/
@echo "Static library built:"
@ls -la ./build/lib/libgowaku.*
dynamic-library: ##@cross-compile Build status-go as shared library for current platform
mkdir -p ./build/lib
@echo "Building shared library..."
$(GOBIN_SHARED_LIB_CFLAGS) $(GOBIN_SHARED_LIB_CGO_LDFLAGS) go build \
-buildmode=c-shared \
-o ./build/lib/libgowaku.$(GOBIN_SHARED_LIB_EXT) \
./library/
ifeq ($(detected_OS),Linux)
cd ./build/lib && \
ls -lah . && \
mv ./libgowaku.$(GOBIN_SHARED_LIB_EXT) ./libgowaku.$(GOBIN_SHARED_LIB_EXT).0 && \
ln -s ./libgowaku.$(GOBIN_SHARED_LIB_EXT).0 ./libgowaku.$(GOBIN_SHARED_LIB_EXT)
endif
@echo "Shared library built:"
@ls -la ./build/lib/libgowaku.*

View File

@ -34,6 +34,12 @@ docker run go-waku:latest --help
go get github.com/status-im/go-waku
```
## C Bindings
```
make static-library
make dynamic-library
```
## Examples
Examples of usage of go-waku as a library can be found in the examples folder. There is a fully featured chat example.

View File

@ -0,0 +1,45 @@
SHELL := bash # the shell used internally by Make
.PHONY: all build run
ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10...
detected_OS := Windows
else
detected_OS := $(strip $(shell uname))
endif
all: build
ifeq ($(detected_OS),Linux)
PLATFORM_FLAGS_TEST_C ?= -ldl
else ifeq ($(detected_OS),macOS)
PLATFORM_FLAGS_TEST_C ?= -Wl,-headerpad_max_install_names
endif
build:
cd ../../ && $(MAKE) static-library # Building library
rm -rf build && \
echo "Compiling 'main.c'"
+ mkdir -p build
$(CC) \
-I../../build/lib/ \
main.c \
../../build/lib/libgowaku.a \
-lm \
-pthread \
$(PLATFORM_FLAGS_TEST_C) \
-o build/main
run:
echo "Executing './build/main.c'"
ifeq ($(detected_OS),macOS)
./build/main
else ifeq ($(detected_OS),Windows)
PATH="$(PATH_TEST)" \
./build/main
else
./build/main
endif

190
examples/c-bindings/main.c Normal file
View File

@ -0,0 +1,190 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "libgowaku.h"
#include "nxjson.c"
#include "main.h"
char *alicePrivKey = "0x4f012057e1a1458ce34189cb27daedbbe434f3df0825c1949475dec786e2c64e";
char *alicePubKey = "0x0440f05847c4c7166f57ae8ecaaf72d31bddcbca345e26713ca9e26c93fb8362ddcd5ae7f4533ee956428ad08a89cd18b234c2911a3b1c7fbd1c0047610d987302";
char *bobPrivKey = "0xb91d6b2df8fb6ef8b53b51b2b30a408c49d5e2b530502d58ac8f94e5c5de1453";
char *bobPubKey = "0x045eef61a98ba1cf44a2736fac91183ea2bd86e67de20fe4bff467a71249a8a0c05f795dd7f28ced7c15eaa69c89d4212cc4f526ca5e9a62e88008f506d850cccd";
int main(int argc, char *argv[])
{
char *response;
gowaku_set_event_callback(callBack);
char *configJSON = "{\"host\": \"0.0.0.0\", \"port\": 60000}";
response = gowaku_new(configJSON); // configJSON can be NULL too to use defaults
if (isError(response))
return 1;
int nodeID = getIntValue(response); // Obtain the nodeID from the response
response = gowaku_start(nodeID); // Start the node, enabling the waku protocols
if (isError(response))
return 1;
response = gowaku_id(nodeID); // Obtain the node peerID
if (isError(response))
return 1;
char *nodePeerID = getStrValue(response);
printf("PeerID: %s\n", nodePeerID);
/*
response = gowaku_dial_peer(nodeID, "/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS", 0); // Connect to a node
if (isError(response))
return 1;
*/
response = gowaku_relay_subscribe(nodeID, NULL);
if (isError(response))
return 1;
char *subscriptionID = getStrValue(response);
printf("SubscriptionID: %s\n", subscriptionID);
int i = 0;
int version = 1;
while (true){
i++;
response = gowaku_encode_data("Hello World!", ASYMMETRIC, bobPubKey, alicePrivKey, version); // Send a message encrypting it with Bob's PubK, and signing it with Alice PrivK
if (isError(response))
return 1;
char *encodedData = getStrValue(response);
char *contentTopic = getStrValue(gowaku_content_topic("example", 1, "default", "rfc26"));
char wakuMsg[1000];
sprintf(wakuMsg, "{\"payload\":\"%s\",\"contentTopic\":\"%s\",\"version\":%d,\"timestamp\":%d}", encodedData, contentTopic, version, i);
response = gowaku_relay_publish(nodeID, wakuMsg, NULL, 0); // Broadcast a message
if (isError(response))
return 1;
// char *messageID = getStrValue(response);
sleep(1);
}
response = gowaku_stop(nodeID);
if (isError(response))
return 1;
return 0;
}
void callBack(char *signal)
{
// This callback will be executed each time a new message is received
// Example signal:
/*{
"nodeId":1,
"type":"message",
"event":{
"messageID":"0x6496491e40dbe0b6c3a2198c2426b16301688a2daebc4f57ad7706115eac3ad1",
"pubsubTopic":"/waku/2/default-waku/proto",
"wakuMessage":{
"payload":"BPABASUqWgRkgp73aW/FHIyGtJDYnStvaQvCoX9MdaNsOH39Vet0em6ipZc3lZ7kK9uFFtbJgIWfRaqTxSRjiFOPx88gXt1JeSm2SUwGSz+1gh2xTy0am8tXkc8OWSSjamdkEbXuVgAueLxHOnV3xlGwYt7nx2G5DWYqUu1BXv4yWHPOoiH2yx3fxX0OajgKGBwiMbadRNUuAUFPRM90f+bzG2y22ssHctDV/U6sXOa9ljNgpAx703Q3WIFleSRozto7ByNAdRFwWR0RGGV4l0btJXM7JpnrYcVC24dB0tJ3HVWuD0ZcwOM1zTL0wwc0hTezLHvI+f6bHSzsFGcCWIlc03KSoMjK1XENNL4dtDmSFI1DQCGgq09c2Bc3Je3Ci6XJHu+FP1F1pTnRzevv2WP8FSBJiTXpmJXdm6evB7V1Xxj4QlzQDvmHLRpBOL6PSttxf1Dc0IwC6BfZRN5g0dNmItNlS2pcY1MtZLxD5zpj",
"contentTopic":"ABC",
"version":1,
"timestamp":1647826358000000000
}
}
}*/
const nx_json *json = nx_json_parse(signal, 0);
const char *type = nx_json_get(json, "type")->text_value;
if (strcmp(type,"message") == 0){
const char *encodedPayload = nx_json_get(nx_json_get(nx_json_get(json, "event"), "wakuMessage"), "payload")->text_value;
int version = nx_json_get(nx_json_get(nx_json_get(json, "event"), "wakuMessage"), "version")->int_value;
char *decodedData = gowaku_decode_data((char*)encodedPayload, ASYMMETRIC, bobPrivKey, version);
if(isError(decodedData)) return;
const nx_json *dataJson = nx_json_parse(decodedData, 0);
const char *pubkey = nx_json_get(nx_json_get(dataJson, "result"), "pubkey")->text_value;
const char *base64data = nx_json_get(nx_json_get(dataJson, "result"), "data")->text_value;
char *data = gowaku_utils_base64_decode((char*)base64data);
printf("Received \"%s\" from %s\n", getStrValue(data), pubkey);
fflush(stdout);
nx_json_free(dataJson);
}
nx_json_free(json);
}
bool isError(char *input)
{
char *jsonStr = malloc(strlen(input) + 1);
strcpy(jsonStr, input);
const nx_json *json = nx_json_parse(jsonStr, 0);
bool result = false;
if (json)
{
const char *errTxt = nx_json_get(json, "error")->text_value;
result = errTxt != NULL;
if (result)
{
printf("ERROR: %s\n", errTxt);
}
}
nx_json_free(json);
free(jsonStr);
return result;
}
int getIntValue(char *input)
{
char *jsonStr = malloc(strlen(input) + 1);
strcpy(jsonStr, input);
const nx_json *json = nx_json_parse(jsonStr, 0);
int result = -1;
if (json)
{
result = nx_json_get(json, "result")->int_value;
}
nx_json_free(json);
free(jsonStr);
return result;
}
char* getStrValue(char *input)
{
char *jsonStr = malloc(strlen(input) + 1);
strcpy(jsonStr, input);
const nx_json *json = nx_json_parse(jsonStr, 0);
char* result = "";
if (json)
{
const char* text_value = nx_json_get(json, "result")->text_value;
result = strdup(text_value);
}
nx_json_free(json);
free(jsonStr);
return result;
}

View File

@ -0,0 +1,14 @@
#ifndef MAIN_H
#define MAIN_H
#include <stdbool.h>
void callBack(char *signal);
bool isError(char *input);
int getIntValue(char *input);
char* getStrValue(char *input);
#endif /* MAIN_H */

View File

@ -0,0 +1,387 @@
/*
* Copyright (c) 2013 Yaroslav Stavnichiy <yarosla@gmail.com>
*
* This file is part of NXJSON.
*
* NXJSON is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* NXJSON is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with NXJSON. If not, see <http://www.gnu.org/licenses/>.
*/
// this file can be #included in your code
#ifndef NXJSON_C
#define NXJSON_C
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "nxjson.h"
// redefine NX_JSON_CALLOC & NX_JSON_FREE to use custom allocator
#ifndef NX_JSON_CALLOC
#define NX_JSON_CALLOC() calloc(1, sizeof(nx_json))
#define NX_JSON_FREE(json) free((void*)(json))
#endif
// redefine NX_JSON_REPORT_ERROR to use custom error reporting
#ifndef NX_JSON_REPORT_ERROR
#define NX_JSON_REPORT_ERROR(msg, p) fprintf(stderr, "NXJSON PARSE ERROR (%d): " msg " at %s\n", __LINE__, p)
#endif
#define IS_WHITESPACE(c) ((unsigned char)(c)<=(unsigned char)' ')
static const nx_json dummy={ NX_JSON_NULL };
static nx_json* create_json(nx_json_type type, const char* key, nx_json* parent) {
nx_json* js=NX_JSON_CALLOC();
assert(js);
js->type=type;
js->key=key;
if (!parent->last_child) {
parent->child=parent->last_child=js;
}
else {
parent->last_child->next=js;
parent->last_child=js;
}
parent->length++;
return js;
}
void nx_json_free(const nx_json* js) {
nx_json* p=js->child;
nx_json* p1;
while (p) {
p1=p->next;
nx_json_free(p);
p=p1;
}
NX_JSON_FREE(js);
}
static int unicode_to_utf8(unsigned int codepoint, char* p, char** endp) {
// code from http://stackoverflow.com/a/4609989/697313
if (codepoint<0x80) *p++=codepoint;
else if (codepoint<0x800) *p++=192+codepoint/64, *p++=128+codepoint%64;
else if (codepoint-0xd800u<0x800) return 0; // surrogate must have been treated earlier
else if (codepoint<0x10000) *p++=224+codepoint/4096, *p++=128+codepoint/64%64, *p++=128+codepoint%64;
else if (codepoint<0x110000) *p++=240+codepoint/262144, *p++=128+codepoint/4096%64, *p++=128+codepoint/64%64, *p++=128+codepoint%64;
else return 0; // error
*endp=p;
return 1;
}
nx_json_unicode_encoder nx_json_unicode_to_utf8=unicode_to_utf8;
static inline int hex_val(char c) {
if (c>='0' && c<='9') return c-'0';
if (c>='a' && c<='f') return c-'a'+10;
if (c>='A' && c<='F') return c-'A'+10;
return -1;
}
static char* unescape_string(char* s, char** end, nx_json_unicode_encoder encoder) {
char* p=s;
char* d=s;
char c;
while ((c=*p++)) {
if (c=='"') {
*d='\0';
*end=p;
return s;
}
else if (c=='\\') {
switch (*p) {
case '\\':
case '/':
case '"':
*d++=*p++;
break;
case 'b':
*d++='\b'; p++;
break;
case 'f':
*d++='\f'; p++;
break;
case 'n':
*d++='\n'; p++;
break;
case 'r':
*d++='\r'; p++;
break;
case 't':
*d++='\t'; p++;
break;
case 'u': // unicode
if (!encoder) {
// leave untouched
*d++=c;
break;
}
char* ps=p-1;
int h1, h2, h3, h4;
if ((h1=hex_val(p[1]))<0 || (h2=hex_val(p[2]))<0 || (h3=hex_val(p[3]))<0 || (h4=hex_val(p[4]))<0) {
NX_JSON_REPORT_ERROR("invalid unicode escape", p-1);
return 0;
}
unsigned int codepoint=h1<<12|h2<<8|h3<<4|h4;
if ((codepoint & 0xfc00)==0xd800) { // high surrogate; need one more unicode to succeed
p+=6;
if (p[-1]!='\\' || *p!='u' || (h1=hex_val(p[1]))<0 || (h2=hex_val(p[2]))<0 || (h3=hex_val(p[3]))<0 || (h4=hex_val(p[4]))<0) {
NX_JSON_REPORT_ERROR("invalid unicode surrogate", ps);
return 0;
}
unsigned int codepoint2=h1<<12|h2<<8|h3<<4|h4;
if ((codepoint2 & 0xfc00)!=0xdc00) {
NX_JSON_REPORT_ERROR("invalid unicode surrogate", ps);
return 0;
}
codepoint=0x10000+((codepoint-0xd800)<<10)+(codepoint2-0xdc00);
}
if (!encoder(codepoint, d, &d)) {
NX_JSON_REPORT_ERROR("invalid codepoint", ps);
return 0;
}
p+=5;
break;
default:
// leave untouched
*d++=c;
break;
}
}
else {
*d++=c;
}
}
NX_JSON_REPORT_ERROR("no closing quote for string", s);
return 0;
}
static char* skip_block_comment(char* p) {
// assume p[-2]=='/' && p[-1]=='*'
char* ps=p-2;
if (!*p) {
NX_JSON_REPORT_ERROR("endless comment", ps);
return 0;
}
REPEAT:
p=strchr(p+1, '/');
if (!p) {
NX_JSON_REPORT_ERROR("endless comment", ps);
return 0;
}
if (p[-1]!='*') goto REPEAT;
return p+1;
}
static char* parse_key(const char** key, char* p, nx_json_unicode_encoder encoder) {
// on '}' return with *p=='}'
char c;
while ((c=*p++)) {
if (c=='"') {
*key=unescape_string(p, &p, encoder);
if (!*key) return 0; // propagate error
while (*p && IS_WHITESPACE(*p)) p++;
if (*p==':') return p+1;
NX_JSON_REPORT_ERROR("unexpected chars", p);
return 0;
}
else if (IS_WHITESPACE(c) || c==',') {
// continue
}
else if (c=='}') {
return p-1;
}
else if (c=='/') {
if (*p=='/') { // line comment
char* ps=p-1;
p=strchr(p+1, '\n');
if (!p) {
NX_JSON_REPORT_ERROR("endless comment", ps);
return 0; // error
}
p++;
}
else if (*p=='*') { // block comment
p=skip_block_comment(p+1);
if (!p) return 0;
}
else {
NX_JSON_REPORT_ERROR("unexpected chars", p-1);
return 0; // error
}
}
else {
NX_JSON_REPORT_ERROR("unexpected chars", p-1);
return 0; // error
}
}
NX_JSON_REPORT_ERROR("unexpected chars", p-1);
return 0; // error
}
static char* parse_value(nx_json* parent, const char* key, char* p, nx_json_unicode_encoder encoder) {
nx_json* js;
while (1) {
switch (*p) {
case '\0':
NX_JSON_REPORT_ERROR("unexpected end of text", p);
return 0; // error
case ' ': case '\t': case '\n': case '\r':
case ',':
// skip
p++;
break;
case '{':
js=create_json(NX_JSON_OBJECT, key, parent);
p++;
while (1) {
const char* new_key;
p=parse_key(&new_key, p, encoder);
if (!p) return 0; // error
if (*p=='}') return p+1; // end of object
p=parse_value(js, new_key, p, encoder);
if (!p) return 0; // error
}
case '[':
js=create_json(NX_JSON_ARRAY, key, parent);
p++;
while (1) {
p=parse_value(js, 0, p, encoder);
if (!p) return 0; // error
if (*p==']') return p+1; // end of array
}
case ']':
return p;
case '"':
p++;
js=create_json(NX_JSON_STRING, key, parent);
js->text_value=unescape_string(p, &p, encoder);
if (!js->text_value) return 0; // propagate error
return p;
case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
{
js=create_json(NX_JSON_INTEGER, key, parent);
char* pe;
js->int_value=strtoll(p, &pe, 0);
if (pe==p || errno==ERANGE) {
NX_JSON_REPORT_ERROR("invalid number", p);
return 0; // error
}
if (*pe=='.' || *pe=='e' || *pe=='E') { // double value
js->type=NX_JSON_DOUBLE;
js->dbl_value=strtod(p, &pe);
if (pe==p || errno==ERANGE) {
NX_JSON_REPORT_ERROR("invalid number", p);
return 0; // error
}
}
else {
js->dbl_value=js->int_value;
}
return pe;
}
case 't':
if (!strncmp(p, "true", 4)) {
js=create_json(NX_JSON_BOOL, key, parent);
js->int_value=1;
return p+4;
}
NX_JSON_REPORT_ERROR("unexpected chars", p);
return 0; // error
case 'f':
if (!strncmp(p, "false", 5)) {
js=create_json(NX_JSON_BOOL, key, parent);
js->int_value=0;
return p+5;
}
NX_JSON_REPORT_ERROR("unexpected chars", p);
return 0; // error
case 'n':
if (!strncmp(p, "null", 4)) {
create_json(NX_JSON_NULL, key, parent);
return p+4;
}
NX_JSON_REPORT_ERROR("unexpected chars", p);
return 0; // error
case '/': // comment
if (p[1]=='/') { // line comment
char* ps=p;
p=strchr(p+2, '\n');
if (!p) {
NX_JSON_REPORT_ERROR("endless comment", ps);
return 0; // error
}
p++;
}
else if (p[1]=='*') { // block comment
p=skip_block_comment(p+2);
if (!p) return 0;
}
else {
NX_JSON_REPORT_ERROR("unexpected chars", p);
return 0; // error
}
break;
default:
NX_JSON_REPORT_ERROR("unexpected chars", p);
return 0; // error
}
}
}
const nx_json* nx_json_parse_utf8(char* text) {
return nx_json_parse(text, unicode_to_utf8);
}
const nx_json* nx_json_parse(char* text, nx_json_unicode_encoder encoder) {
nx_json js={0};
if (!parse_value(&js, 0, text, encoder)) {
if (js.child) nx_json_free(js.child);
return 0;
}
return js.child;
}
const nx_json* nx_json_get(const nx_json* json, const char* key) {
if (!json || !key) return &dummy; // never return null
nx_json* js;
for (js=json->child; js; js=js->next) {
if (js->key && !strcmp(js->key, key)) return js;
}
return &dummy; // never return null
}
const nx_json* nx_json_item(const nx_json* json, int idx) {
if (!json) return &dummy; // never return null
nx_json* js;
for (js=json->child; js; js=js->next) {
if (!idx--) return js;
}
return &dummy; // never return null
}
#ifdef __cplusplus
}
#endif
#endif /* NXJSON_C */

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2013 Yaroslav Stavnichiy <yarosla@gmail.com>
*
* This file is part of NXJSON.
*
* NXJSON is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* NXJSON is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with NXJSON. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NXJSON_H
#define NXJSON_H
#ifdef __cplusplus
extern "C" {
#endif
typedef enum nx_json_type {
NX_JSON_NULL, // this is null value
NX_JSON_OBJECT, // this is an object; properties can be found in child nodes
NX_JSON_ARRAY, // this is an array; items can be found in child nodes
NX_JSON_STRING, // this is a string; value can be found in text_value field
NX_JSON_INTEGER, // this is an integer; value can be found in int_value field
NX_JSON_DOUBLE, // this is a double; value can be found in dbl_value field
NX_JSON_BOOL // this is a boolean; value can be found in int_value field
} nx_json_type;
typedef struct nx_json {
nx_json_type type; // type of json node, see above
const char* key; // key of the property; for object's children only
const char* text_value; // text value of STRING node
long long int_value; // the value of INTEGER or BOOL node
double dbl_value; // the value of DOUBLE node
int length; // number of children of OBJECT or ARRAY
struct nx_json* child; // points to first child
struct nx_json* next; // points to next child
struct nx_json* last_child;
} nx_json;
typedef int (*nx_json_unicode_encoder)(unsigned int codepoint, char* p, char** endp);
extern nx_json_unicode_encoder nx_json_unicode_to_utf8;
const nx_json* nx_json_parse(char* text, nx_json_unicode_encoder encoder);
const nx_json* nx_json_parse_utf8(char* text);
void nx_json_free(const nx_json* js);
const nx_json* nx_json_get(const nx_json* json, const char* key); // get object's property by key
const nx_json* nx_json_item(const nx_json* json, int idx); // get array element by index
#ifdef __cplusplus
}
#endif
#endif /* NXJSON_H */

1
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/ethereum/go-ethereum v1.10.13
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/rpc v1.2.0
github.com/ipfs/go-ds-sql v0.2.0
github.com/ipfs/go-log v1.0.5

703
library/api.go Normal file
View File

@ -0,0 +1,703 @@
package main
/*
#include <stdlib.h>
#include <stddef.h>
typedef struct {
size_t len;
char* data;
} ByteArray;
#define SYMMETRIC "Symmetric"
#define ASYMMETRIC "Asymmetric"
#define NONE "None"
*/
import "C"
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"time"
"unsafe"
"sync"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/google/uuid"
"github.com/libp2p/go-libp2p-core/peer"
p2pproto "github.com/libp2p/go-libp2p-core/protocol"
"github.com/multiformats/go-multiaddr"
"github.com/status-im/go-waku/waku/v2/node"
"github.com/status-im/go-waku/waku/v2/protocol"
"github.com/status-im/go-waku/waku/v2/protocol/pb"
"github.com/status-im/go-waku/waku/v2/protocol/relay"
)
var nodes map[int]*node.WakuNode = make(map[int]*node.WakuNode)
var subscriptions map[string]*relay.Subscription = make(map[string]*relay.Subscription)
var mutex sync.Mutex
var ErrWakuNodeNotReady = errors.New("go-waku not initialized")
func randomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func main() {}
type WakuConfig struct {
Host *string `json:"host,omitempty"`
Port *int `json:"port,omitempty"`
AdvertiseAddress *string `json:"advertiseAddr,omitempty"`
NodeKey *string `json:"nodeKey,omitempty"`
KeepAliveInterval *int `json:"keepAliveInterval,omitempty"`
EnableRelay *bool `json:"relay"`
}
var DefaultHost = "0.0.0.0"
var DefaultPort = 60000
var DefaultKeepAliveInterval = 20
var DefaultEnableRelay = true
func getConfig(configJSON *C.char) (WakuConfig, error) {
var config WakuConfig
if configJSON != nil {
err := json.Unmarshal([]byte(C.GoString(configJSON)), &config)
if err != nil {
return WakuConfig{}, err
}
}
if config.Host == nil {
config.Host = &DefaultHost
}
if config.EnableRelay == nil {
config.EnableRelay = &DefaultEnableRelay
}
if config.Host == nil {
config.Host = &DefaultHost
}
if config.Port == nil {
config.Port = &DefaultPort
}
if config.KeepAliveInterval == nil {
config.KeepAliveInterval = &DefaultKeepAliveInterval
}
return config, nil
}
//export gowaku_new
// Initialize a waku node. Receives a JSON string containing the configuration
// for the node. It can be NULL. Example configuration:
// ```
// {"host": "0.0.0.0", "port": 60000, "advertiseAddr": "1.2.3.4", "nodeKey": "0x123...567", "keepAliveInterval": 20, "relay": true}
// ```
// All keys are optional. If not specified a default value will be set:
// - host: IP address. Default 0.0.0.0
// - port: TCP port to listen. Default 60000. Use 0 for random
// - advertiseAddr: External IP
// - nodeKey: secp256k1 private key. Default random
// - keepAliveInterval: interval in seconds to ping all peers
// - relay: Enable WakuRelay. Default `true`
// This function will return a nodeID which should be used in all calls from this API that require
// interacting with the node.
func gowaku_new(configJSON *C.char) *C.char {
config, err := getConfig(configJSON)
if err != nil {
return makeJSONResponse(err)
}
hostAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", *config.Host, *config.Port))
if err != nil {
return makeJSONResponse(err)
}
var prvKey *ecdsa.PrivateKey
if config.NodeKey != nil {
prvKey, err = crypto.HexToECDSA(*config.NodeKey)
if err != nil {
return makeJSONResponse(err)
}
} else {
key, err := randomHex(32)
if err != nil {
return makeJSONResponse(err)
}
prvKey, err = crypto.HexToECDSA(key)
if err != nil {
return makeJSONResponse(err)
}
}
opts := []node.WakuNodeOption{
node.WithPrivateKey(prvKey),
node.WithHostAddress(hostAddr),
node.WithKeepAlive(time.Duration(*config.KeepAliveInterval) * time.Second),
}
if *config.EnableRelay {
opts = append(opts, node.WithWakuRelay())
}
ctx := context.Background()
wakuNode, err := node.New(ctx, opts...)
if err != nil {
return makeJSONResponse(err)
}
mutex.Lock()
defer mutex.Unlock()
id := len(nodes) + 1
nodes[id] = wakuNode
return prepareJSONResponse(id, nil)
}
//export gowaku_start
// Starts the waku node
func gowaku_start(nodeID C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
if err := wakuNode.Start(); err != nil {
return makeJSONResponse(err)
}
return makeJSONResponse(nil)
}
//export gowaku_stop
// Stops a waku node
func gowaku_stop(nodeID C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
wakuNode.Stop()
nodes[int(nodeID)] = nil
return makeJSONResponse(nil)
}
//export gowaku_id
// Obtain the peer ID of the waku node
func gowaku_id(nodeID C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
return prepareJSONResponse(wakuNode.ID(), nil)
}
//export gowaku_listen_addresses
// Obtain the multiaddresses the wakunode is listening to
func gowaku_listen_addresses(nodeID C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
addrs, err := json.Marshal(wakuNode.ListenAddresses())
return prepareJSONResponse(addrs, err)
}
//export gowaku_add_peer
// Add node multiaddress and protocol to the wakunode peerstore
func gowaku_add_peer(nodeID C.int, address *C.char, protocolID *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
ma, err := multiaddr.NewMultiaddr(C.GoString(address))
if err != nil {
return makeJSONResponse(err)
}
peerID, err := wakuNode.AddPeer(ma, p2pproto.ID(C.GoString(protocolID)))
return prepareJSONResponse(peerID, err)
}
//export gowaku_dial_peer
// Dial peer at multiaddress. if ms > 0, cancel the function execution if it takes longer than N milliseconds
func gowaku_dial_peer(nodeID C.int, address *C.char, ms C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
var ctx context.Context
var cancel context.CancelFunc
if ms > 0 {
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
defer cancel()
} else {
ctx = context.Background()
}
err := wakuNode.DialPeer(ctx, C.GoString(address))
return makeJSONResponse(err)
}
//export gowaku_dial_peerid
// Dial known peer by peerID. if ms > 0, cancel the function execution if it takes longer than N milliseconds
func gowaku_dial_peerid(nodeID C.int, id *C.char, ms C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
var ctx context.Context
var cancel context.CancelFunc
peerID, err := peer.Decode(C.GoString(id))
if err != nil {
return makeJSONResponse(err)
}
if ms > 0 {
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
defer cancel()
} else {
ctx = context.Background()
}
err = wakuNode.DialPeerByID(ctx, peerID)
return makeJSONResponse(err)
}
//export gowaku_close_peer
// Close connection to peer at multiaddress
func gowaku_close_peer(nodeID C.int, address *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
err := wakuNode.ClosePeerByAddress(C.GoString(address))
return makeJSONResponse(err)
}
//export gowaku_close_peerid
// Close connection to a known peer by peerID
func gowaku_close_peerid(nodeID C.int, id *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
peerID, err := peer.Decode(C.GoString(id))
if err != nil {
return makeJSONResponse(err)
}
err = wakuNode.ClosePeerById(peerID)
return makeJSONResponse(err)
}
//export gowaku_peer_cnt
// Get number of connected peers
func gowaku_peer_cnt(nodeID C.int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
return prepareJSONResponse(wakuNode.PeerCount(), nil)
}
//export gowaku_content_topic
// Create a content topic string according to RFC 23
func gowaku_content_topic(applicationName *C.char, applicationVersion C.uint, contentTopicName *C.char, encoding *C.char) *C.char {
return prepareJSONResponse(protocol.NewContentTopic(C.GoString(applicationName), uint(applicationVersion), C.GoString(contentTopicName), C.GoString(encoding)).String(), nil)
}
//export gowaku_pubsub_topic
// Create a pubsub topic string according to RFC 23
func gowaku_pubsub_topic(name *C.char, encoding *C.char) *C.char {
return prepareJSONResponse(protocol.NewPubsubTopic(C.GoString(name), C.GoString(encoding)).String(), nil)
}
//export gowaku_default_pubsub_topic
// Get the default pubsub topic used in waku2: /waku/2/default-waku/proto
func gowaku_default_pubsub_topic() *C.char {
return prepareJSONResponse(protocol.DefaultPubsubTopic().String(), nil)
}
func publish(nodeID int, message string, pubsubTopic string, ms int) (string, error) {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[nodeID]
if !ok || wakuNode == nil {
return "", ErrWakuNodeNotReady
}
var msg pb.WakuMessage
err := json.Unmarshal([]byte(message), &msg)
if err != nil {
return "", err
}
var ctx context.Context
var cancel context.CancelFunc
if ms > 0 {
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
defer cancel()
} else {
ctx = context.Background()
}
hash, err := wakuNode.Relay().PublishToTopic(ctx, &msg, pubsubTopic)
return hexutil.Encode(hash), err
}
//export gowaku_relay_publish
// Publish a message using waku relay. Use NULL for topic to use the default pubsub topic
// If ms is greater than 0, the broadcast of the message must happen before the timeout
// (in milliseconds) is reached, or an error will be returned
func gowaku_relay_publish(nodeID C.int, messageJSON *C.char, topic *C.char, ms C.int) *C.char {
topicToPublish := ""
if topic != nil {
topicToPublish = C.GoString(topic)
} else {
topicToPublish = protocol.DefaultPubsubTopic().String()
}
hash, err := publish(int(nodeID), C.GoString(messageJSON), topicToPublish, int(ms))
return prepareJSONResponse(hash, err)
}
//export gowaku_enough_peers
// Determine if there are enough peers to publish a message on a topic. Use NULL
// to verify the number of peers in the default pubsub topic
func gowaku_enough_peers(nodeID C.int, topic *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
topicToCheck := protocol.DefaultPubsubTopic().String()
if topic != nil {
topicToCheck = C.GoString(topic)
}
return prepareJSONResponse(wakuNode.Relay().EnoughPeersToPublishToTopic(topicToCheck), nil)
}
//export gowaku_set_event_callback
// Register callback to act as signal handler and receive application signal
// (in JSON) which are used o react to asyncronous events in waku. The function
// signature for the callback should be `void myCallback(char* signalJSON)`
func gowaku_set_event_callback(cb unsafe.Pointer) {
setEventCallback(cb)
}
type SubscriptionMsg struct {
MessageID string `json:"messageID"`
PubsubTopic string `json:"pubsubTopic"`
Message *pb.WakuMessage `json:"wakuMessage"`
}
func toSubscriptionMessage(msg *protocol.Envelope) *SubscriptionMsg {
return &SubscriptionMsg{
MessageID: hexutil.Encode(msg.Hash()),
PubsubTopic: msg.PubsubTopic(),
Message: msg.Message(),
}
}
//export gowaku_relay_subscribe
// Subscribe to a WakuRelay topic. Set the topic to NULL to subscribe
// to the default topic. Returns a json response containing the subscription ID
// or an error message. When a message is received, a "message" is emitted containing
// the message, pubsub topic, and nodeID in which the message was received
func gowaku_relay_subscribe(nodeID int, topic *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
topicToSubscribe := protocol.DefaultPubsubTopic().String()
if topic != nil {
topicToSubscribe = C.GoString(topic)
}
subscription, err := wakuNode.Relay().SubscribeToTopic(context.Background(), topicToSubscribe)
if err != nil {
return makeJSONResponse(err)
}
subsID := uuid.New().String()
subscriptions[subsID] = subscription
go func() {
for envelope := range subscription.C {
send(nodeID, "message", toSubscriptionMessage(envelope))
}
}()
return prepareJSONResponse(subsID, nil)
}
//export gowaku_relay_unsubscribe_from_topic
// Closes the pubsub subscription to a pubsub topic. Existing subscriptions
// will not be closed, but they will stop receiving messages
func gowaku_relay_unsubscribe_from_topic(nodeID int, topic *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
topicToUnsubscribe := protocol.DefaultPubsubTopic().String()
if topic != nil {
topicToUnsubscribe = C.GoString(topic)
}
err := wakuNode.Relay().Unsubscribe(context.Background(), topicToUnsubscribe)
if err != nil {
return makeJSONResponse(err)
}
return makeJSONResponse(nil)
}
//export gowaku_relay_close_subscription
// Closes a waku relay subscription
func gowaku_relay_close_subscription(nodeID int, subsID *C.char) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
subscription, ok := subscriptions[C.GoString(subsID)]
if !ok {
return makeJSONResponse(errors.New("Subscription does not exist"))
}
subscription.Unsubscribe()
delete(subscriptions, C.GoString(subsID))
return makeJSONResponse(nil)
}
//export gowaku_peers
// Retrieve the list of peers connected to the waku node
func gowaku_peers(nodeID int) *C.char {
mutex.Lock()
defer mutex.Unlock()
wakuNode, ok := nodes[int(nodeID)]
if !ok || wakuNode == nil {
return makeJSONResponse(ErrWakuNodeNotReady)
}
peers, err := wakuNode.Peers()
return prepareJSONResponse(peers, err)
}
func unmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) {
x, y := elliptic.Unmarshal(secp256k1.S256(), pub)
if x == nil {
return nil, errors.New("invalid public key")
}
return &ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}, nil
}
//export gowaku_encode_data
// Encode a byte array. `keyType` defines the type of key to use: `NONE`,
// `ASYMMETRIC` and `SYMMETRIC`. `version` is used to define the type of
// payload encryption:
// When `version` is 0
// - No encryption is used
// When `version` is 1
// - If using `ASYMMETRIC` encoding, `key` must contain a secp256k1 public key
// to encrypt the data with,
// - If using `SYMMETRIC` encoding, `key` must contain a 32 bytes symmetric key.
// The `signingKey` can contain an optional secp256k1 private key to sign the
// encoded message, otherwise NULL can be used.
func gowaku_encode_data(data *C.char, keyType *C.char, key *C.char, signingKey *C.char, version C.int) *C.char {
keyInfo := &node.KeyInfo{
Kind: node.KeyKind(C.GoString(keyType)),
}
keyBytes, err := hexutil.Decode(C.GoString(key))
if err != nil {
return makeJSONResponse(err)
}
if signingKey != nil {
signingKeyBytes, err := hexutil.Decode(C.GoString(signingKey))
if err != nil {
return makeJSONResponse(err)
}
privK, err := crypto.ToECDSA(signingKeyBytes)
if err != nil {
return makeJSONResponse(err)
}
keyInfo.PrivKey = privK
}
switch keyInfo.Kind {
case node.Symmetric:
keyInfo.SymKey = keyBytes
case node.Asymmetric:
pubK, err := unmarshalPubkey(keyBytes)
if err != nil {
return makeJSONResponse(err)
}
keyInfo.PubKey = *pubK
}
payload := node.Payload{
Data: []byte(C.GoString(data)),
Key: keyInfo,
}
response, err := payload.Encode(uint32(version))
return prepareJSONResponse(response, err)
}
//export gowaku_decode_data
// Decode a byte array. `keyType` defines the type of key used: `NONE`,
// `ASYMMETRIC` and `SYMMETRIC`. `version` is used to define the type of
// encryption that was used in the payload:
// When `version` is 0
// - No encryption was used. It will return the original message payload
// When `version` is 1
// - If using `ASYMMETRIC` encoding, `key` must contain a secp256k1 public key
// to decrypt the data with,
// - If using `SYMMETRIC` encoding, `key` must contain a 32 bytes symmetric key.
func gowaku_decode_data(data *C.char, keyType *C.char, key *C.char, version C.int) *C.char {
b, err := base64.StdEncoding.DecodeString(C.GoString(data))
if err != nil {
return makeJSONResponse(err)
}
keyInfo := &node.KeyInfo{
Kind: node.KeyKind(C.GoString(keyType)),
}
keyBytes, err := hexutil.Decode(C.GoString(key))
if err != nil {
return makeJSONResponse(err)
}
switch keyInfo.Kind {
case node.Symmetric:
keyInfo.SymKey = keyBytes
case node.Asymmetric:
privK, err := crypto.ToECDSA(keyBytes)
if err != nil {
return makeJSONResponse(err)
}
keyInfo.PrivKey = privK
}
msg := pb.WakuMessage{
Payload: b,
Version: uint32(version),
}
payload, err := node.DecodePayload(&msg, keyInfo)
if err != nil {
return makeJSONResponse(err)
}
response := struct {
PubKey string `json:"pubkey"`
Signature string `json:"signature"`
Data []byte `json:"data"`
Padding []byte `json:"padding"`
}{
PubKey: hexutil.Encode(crypto.FromECDSAPub(payload.PubKey)),
Signature: hexutil.Encode(payload.Signature),
Data: payload.Data,
Padding: payload.Padding,
}
return prepareJSONResponse(response, err)
}
//export gowaku_utils_base64_decode
// Decode a base64 string (useful for reading the payload from waku messages)
func gowaku_utils_base64_decode(data *C.char) *C.char {
b, err := base64.StdEncoding.DecodeString(C.GoString(data))
if err != nil {
return makeJSONResponse(err)
}
return prepareJSONResponse(string(b), nil)
}
//export gowaku_utils_base64_encode
// Encode data to base64 (useful for creating the payload of a waku message in the
// format understood by gowaku_relay_publish)
func gowaku_utils_base64_encode(data *C.char) *C.char {
str := base64.StdEncoding.EncodeToString([]byte(C.GoString(data)))
return prepareJSONResponse(str, nil)
}
// TODO:
// connected/disconnected
// dns discovery
// func gowaku_relay_publish_msg(msg C.WakuMessage, pubsubTopic *C.char, ms C.int) *C.char
// getFastestPeer(protocol)
// getRandomPeer(protocol)
// func (wakuLP *WakuLightPush) PublishToTopic(ctx context.Context, message *pb.WakuMessage, topic string, peer, requestId nil) ([]byte, error) {
// func (wakuLP *WakuLightPush) Publish(ctx context.Context, message *pb.WakuMessage, peer, requestId nil) ([]byte, error) {
// func (query)

12
library/ios.go Normal file
View File

@ -0,0 +1,12 @@
// +build darwin,cgo
package main
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#include <stddef.h>
#include <stdbool.h>
extern bool StatusServiceSignalEvent( const char *jsonEvent );
*/
import "C"

70
library/response.go Normal file
View File

@ -0,0 +1,70 @@
package main
import "C"
import (
"encoding/json"
)
const (
codeUnknown int = iota
// special codes
codeFailedParseResponse
// codeFailedParseParams
)
var errToCodeMap = map[error]int{
//transactions.ErrInvalidTxSender: codeErrInvalidTxSender,
}
type jsonrpcSuccessfulResponse struct {
Result interface{} `json:"result"`
}
type jsonrpcErrorResponse struct {
Error jsonError `json:"error"`
}
type jsonError struct {
Code int `json:"code,omitempty"`
Message string `json:"message"`
}
func prepareJSONResponse(result interface{}, err error) *C.char {
code := codeUnknown
if c, ok := errToCodeMap[err]; ok {
code = c
}
return prepareJSONResponseWithCode(result, err, code)
}
func prepareJSONResponseWithCode(result interface{}, err error, code int) *C.char {
if err != nil {
errResponse := jsonrpcErrorResponse{
Error: jsonError{Code: code, Message: err.Error()},
}
response, _ := json.Marshal(&errResponse)
return C.CString(string(response))
}
data, err := json.Marshal(jsonrpcSuccessfulResponse{result})
if err != nil {
return prepareJSONResponseWithCode(nil, err, codeFailedParseResponse)
}
return C.CString(string(data))
}
func makeJSONResponse(err error) *C.char {
var errString *string = nil
if err != nil {
errStr := err.Error()
errString = &errStr
}
out := APIResponse{
Error: errString,
}
outBytes, _ := json.Marshal(out)
return C.CString(string(outBytes))
}

23
library/signals.c Normal file
View File

@ -0,0 +1,23 @@
// ======================================================================================
// cgo compilation (for desktop platforms and local tests)
// ======================================================================================
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include "_cgo_export.h"
typedef void (*callback)(const char *jsonEvent);
callback gCallback = 0;
bool StatusServiceSignalEvent(const char *jsonEvent) {
if (gCallback) {
gCallback(jsonEvent);
}
return true;
}
void SetEventCallback(void *cb) {
gCallback = (callback)cb;
}

71
library/signals.go Normal file
View File

@ -0,0 +1,71 @@
package main
/*
#include <stdbool.h>
#include <stdlib.h>
extern bool StatusServiceSignalEvent(const char *jsonEvent);
extern void SetEventCallback(void *cb);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
// SignalHandler is a simple callback function that gets called when any signal is received
type MobileSignalHandler func([]byte)
// storing the current mobile signal handler here
var mobileSignalHandler MobileSignalHandler
// SignalEnvelope is a general signal sent upward from node to app
type SignalEnvelope struct {
NodeID int `json:"nodeId"`
Type string `json:"type"`
Event interface{} `json:"event"`
}
// NewEnvelope creates new envlope of given type and event payload.
func NewEnvelope(nodeId int, typ string, event interface{}) *SignalEnvelope {
return &SignalEnvelope{
NodeID: nodeId,
Type: typ,
Event: event,
}
}
// send sends application signal (in JSON) upwards to application (via default notification handler)
func send(node int, typ string, event interface{}) {
signal := NewEnvelope(node, typ, event)
data, err := json.Marshal(&signal)
if err != nil {
fmt.Println("marshal signal error", err)
return
}
// If a Go implementation of signal handler is set, let's use it.
if mobileSignalHandler != nil {
mobileSignalHandler(data)
} else {
// ...and fallback to C implementation otherwise.
str := C.CString(string(data))
C.StatusServiceSignalEvent(str)
C.free(unsafe.Pointer(str))
}
}
// SetMobileSignalHandler setup geth callback to notify about new signal
// used for gomobile builds
//nolint
func SetMobileSignalHandler(handler SignalHandler) {
mobileSignalHandler = func(data []byte) {
if len(data) > 0 {
handler.HandleSignal(string(data))
}
}
}
func setEventCallback(cb unsafe.Pointer) {
C.SetEventCallback(cb)
}

74
library/types.go Normal file
View File

@ -0,0 +1,74 @@
package main
import (
"bytes"
"fmt"
"strings"
)
// APIResponse generic response from API.
type APIResponse struct {
Error *string `json:"error"`
}
// APIDetailedResponse represents a generic response
// with possible errors.
//nolint
type APIDetailedResponse struct {
Status bool `json:"status"`
Message string `json:"message,omitempty"`
FieldErrors []APIFieldError `json:"field_errors,omitempty"`
}
// Error string representation of APIDetailedResponse.
//nolint
func (r APIDetailedResponse) Error() string {
buf := bytes.NewBufferString("")
for _, err := range r.FieldErrors {
buf.WriteString(err.Error() + "\n") // nolint: gas
}
return strings.TrimSpace(buf.String())
}
// APIFieldError represents a set of errors
// related to a parameter.
//nolint
type APIFieldError struct {
Parameter string `json:"parameter,omitempty"`
Errors []APIError `json:"errors"`
}
// Error string representation of APIFieldError.
func (e APIFieldError) Error() string {
if len(e.Errors) == 0 {
return ""
}
buf := bytes.NewBufferString(fmt.Sprintf("Parameter: %s\n", e.Parameter))
for _, err := range e.Errors {
buf.WriteString(err.Error() + "\n") // nolint: gas
}
return strings.TrimSpace(buf.String())
}
// APIError represents a single error.
//nolint
type APIError struct {
Message string `json:"message"`
}
// Error string representation of APIError.
func (e APIError) Error() string {
return fmt.Sprintf("message=%s", e.Message)
}
// SignalHandler defines a minimal interface
// a signal handler needs to implement.
//nolint
type SignalHandler interface {
HandleSignal(string)
}

View File

View File

@ -45,7 +45,6 @@ type KeyInfo struct {
SymKey []byte // If the encryption is Symmetric, a Symmetric key must be specified
PubKey ecdsa.PublicKey // If the encryption is Asymmetric, the public key of the message receptor must be specified
PrivKey *ecdsa.PrivateKey // Set a privkey if the message requires a signature
}
// Encode encodes a payload depending on the version parameter.

View File

@ -27,7 +27,7 @@ import (
const clientId string = "Go Waku v2 node"
// Default minRelayPeersToPublish
const defaultMinRelayPeersToPublish = 1
const defaultMinRelayPeersToPublish = 0
type WakuNodeParameters struct {
hostAddr *net.TCPAddr