mirror of https://github.com/waku-org/nwaku.git
refactor(cbindings): libwaku - run waku node in a secondary working thread (#1865)
* Refactoring to have a working waku_thread
This commit is contained in:
parent
d2b6075bdc
commit
069c1ad2a5
|
@ -0,0 +1,58 @@
|
|||
|
||||
#include "base64.h"
|
||||
|
||||
// Base64 encoding
|
||||
// source: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/
|
||||
size_t b64_encoded_size(size_t inlen)
|
||||
{
|
||||
size_t ret;
|
||||
|
||||
ret = inlen;
|
||||
if (inlen % 3 != 0)
|
||||
ret += 3 - (inlen % 3);
|
||||
ret /= 3;
|
||||
ret *= 4;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
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<len; i+=3, j+=4) {
|
||||
v = in[i];
|
||||
v = i+1 < len ? v << 8 | in[i+1] : v << 8;
|
||||
v = i+2 < len ? v << 8 | in[i+2] : v << 8;
|
||||
|
||||
out[j] = b64chars[(v >> 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
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
#ifndef _BASE64_H_
|
||||
#define _BASE64_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
size_t b64_encoded_size(size_t inlen);
|
||||
|
||||
char *b64_encode(const unsigned char *in, size_t len);
|
||||
|
||||
#endif
|
|
@ -79,7 +79,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) {
|
|||
static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 };
|
||||
|
||||
char* contentTopic = NULL;
|
||||
void handle_content_topic(char* msg, size_t len) {
|
||||
void handle_content_topic(const char* msg, size_t len) {
|
||||
if (contentTopic != NULL) {
|
||||
free(contentTopic);
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ void handle_content_topic(char* msg, size_t len) {
|
|||
}
|
||||
|
||||
char* publishResponse = NULL;
|
||||
void handle_publish_ok(char* msg, size_t len) {
|
||||
void handle_publish_ok(const char* msg, size_t len) {
|
||||
printf("Publish Ok: %s %lu\n", msg, len);
|
||||
|
||||
if (publishResponse != NULL) {
|
||||
|
@ -100,14 +100,14 @@ void handle_publish_ok(char* msg, size_t len) {
|
|||
strcpy(publishResponse, msg);
|
||||
}
|
||||
|
||||
void handle_error(char* msg, size_t len) {
|
||||
void handle_error(const char* msg, size_t len) {
|
||||
printf("Error: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#define MAX_MSG_SIZE 65535
|
||||
|
||||
void publish_message(char* pubsubTopic, char* msg) {
|
||||
void publish_message(char* pubsubTopic, const char* msg) {
|
||||
char jsonWakuMsg[MAX_MSG_SIZE];
|
||||
char *msgPayload = b64_encode(msg, strlen(msg));
|
||||
|
||||
|
@ -138,15 +138,15 @@ void show_help_and_exit() {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
void event_handler(char* msg, size_t len) {
|
||||
void event_handler(const char* msg, size_t len) {
|
||||
printf("Receiving message %s\n", msg);
|
||||
}
|
||||
|
||||
void print_default_pubsub_topic(char* msg, size_t len) {
|
||||
void print_default_pubsub_topic(const char* msg, size_t len) {
|
||||
printf("Default pubsub topic: %s\n", msg);
|
||||
}
|
||||
|
||||
void print_waku_version(char* msg, size_t len) {
|
||||
void print_waku_version(const char* msg, size_t len) {
|
||||
printf("Git Version: %s\n", msg);
|
||||
}
|
||||
|
||||
|
@ -243,8 +243,6 @@ void handle_user_input() {
|
|||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
waku_init_lib();
|
||||
|
||||
struct ConfigNode cfgNode;
|
||||
// default values
|
||||
snprintf(cfgNode.host, 128, "0.0.0.0");
|
||||
|
@ -272,12 +270,13 @@ int main(int argc, char** argv) {
|
|||
|
||||
WAKU_CALL( waku_default_pubsub_topic(print_default_pubsub_topic) );
|
||||
WAKU_CALL( waku_version(print_waku_version) );
|
||||
|
||||
printf("Bind addr: %s:%u\n", cfgNode.host, cfgNode.port);
|
||||
printf("Waku Relay enabled: %s\n", cfgNode.relay == 1 ? "YES": "NO");
|
||||
|
||||
WAKU_CALL( waku_new(jsonConfig, handle_error) );
|
||||
|
||||
waku_set_relay_callback(event_handler);
|
||||
waku_set_event_callback(event_handler);
|
||||
waku_start();
|
||||
|
||||
printf("Establishing connection with: %s\n", cfgNode.peers);
|
||||
|
@ -291,6 +290,6 @@ int main(int argc, char** argv) {
|
|||
show_main_menu();
|
||||
while(1) {
|
||||
handle_user_input();
|
||||
waku_poll();
|
||||
// waku_poll();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
proc alloc*(str: cstring): cstring =
|
||||
# Byte allocation from the given address.
|
||||
# There should be the corresponding manual deallocation with deallocShared !
|
||||
let ret = cast[cstring](allocShared(len(str) + 1))
|
||||
copyMem(ret, str, len(str) + 1)
|
||||
return ret
|
|
@ -11,11 +11,7 @@
|
|||
#define RET_ERR 1
|
||||
#define RET_MISSING_CALLBACK 2
|
||||
|
||||
typedef void (*WakuCallBack) (char* msg, size_t len_0);
|
||||
|
||||
// This should only be called once.
|
||||
// It initializes the nim runtime and GC.
|
||||
void waku_init_lib(void);
|
||||
typedef void (*WakuCallBack) (const char* msg, size_t len_0);
|
||||
|
||||
// Creates a new instance of the waku node.
|
||||
// Sets up the waku node from the given configuration.
|
||||
|
@ -27,7 +23,7 @@ void waku_stop(void);
|
|||
|
||||
int waku_version(WakuCallBack onOkCb);
|
||||
|
||||
void waku_set_relay_callback(WakuCallBack callback);
|
||||
void waku_set_event_callback(WakuCallBack callback);
|
||||
|
||||
int waku_content_topic(const char* appName,
|
||||
unsigned int appVersion,
|
||||
|
@ -56,6 +52,4 @@ int waku_connect(const char* peerMultiAddr,
|
|||
unsigned int timeoutMs,
|
||||
WakuCallBack onErrCb);
|
||||
|
||||
void waku_poll(void);
|
||||
|
||||
#endif /* __libwaku__ */
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
|
||||
{.pragma: exported, exportc, cdecl, raises: [].}
|
||||
{.pragma: callback, cdecl, raises: [], gcsafe.}
|
||||
{.passc: "-fPIC".}
|
||||
|
||||
import
|
||||
std/[json,sequtils,times,strformat,options,atomics,strutils],
|
||||
strutils,
|
||||
os
|
||||
strutils
|
||||
import
|
||||
chronicles,
|
||||
chronos,
|
||||
stew/shims/net
|
||||
chronos
|
||||
import
|
||||
../../waku/common/enr/builder,
|
||||
../../waku/v2/waku_enr/capabilities,
|
||||
../../waku/v2/waku_enr/multiaddr,
|
||||
../../waku/v2/waku_enr/sharding,
|
||||
../../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_base_event],
|
||||
./config
|
||||
../../waku/v2/waku_core/topics/pubsub_topic,
|
||||
../../../waku/v2/waku_relay/protocol,
|
||||
./events/json_message_event,
|
||||
./waku_thread/waku_thread as waku_thread_module,
|
||||
./waku_thread/inter_thread_communication/node_lifecycle_request,
|
||||
./waku_thread/inter_thread_communication/peer_manager_request,
|
||||
./waku_thread/inter_thread_communication/protocols/relay_request,
|
||||
./alloc
|
||||
|
||||
################################################################################
|
||||
### Wrapper around the waku node
|
||||
|
@ -33,45 +32,30 @@ const RET_OK: cint = 0
|
|||
const RET_ERR: cint = 1
|
||||
const RET_MISSING_CALLBACK: cint = 2
|
||||
|
||||
type
|
||||
WakuCallBack* = proc(msg: ptr cchar, len: csize_t) {.cdecl, gcsafe.}
|
||||
|
||||
### End of exported types
|
||||
################################################################################
|
||||
|
||||
################################################################################
|
||||
### Not-exported components
|
||||
|
||||
proc alloc(str: cstring): cstring =
|
||||
# Byte allocation from the given address.
|
||||
# There should be the corresponding manual deallocation with deallocShared !
|
||||
let ret = cast[cstring](allocShared(len(str) + 1))
|
||||
copyMem(ret, str, len(str) + 1)
|
||||
return ret
|
||||
|
||||
type
|
||||
WakuCallBack = proc(msg: ptr cchar, len: csize_t) {.cdecl, gcsafe.}
|
||||
|
||||
# May keep a reference to a callback defined externally
|
||||
var extRelayEventCallback: WakuCallBack = nil
|
||||
var extEventCallback*: WakuCallBack = nil
|
||||
|
||||
proc relayEventCallback(pubsubTopic: string,
|
||||
msg: WakuMessage):
|
||||
Future[void] {.gcsafe, raises: [Defect].} =
|
||||
proc relayEventCallback(pubsubTopic: PubsubTopic,
|
||||
msg: WakuMessage): Future[void] {.async, gcsafe.} =
|
||||
# Callback that hadles the Waku Relay events. i.e. messages or errors.
|
||||
if not isNil(extRelayEventCallback):
|
||||
if not isNil(extEventCallback):
|
||||
try:
|
||||
let event = $JsonMessageEvent.new(pubsubTopic, msg)
|
||||
extRelayEventCallback(unsafeAddr event[0], cast[csize_t](len(event)))
|
||||
extEventCallback(unsafeAddr event[0], cast[csize_t](len(event)))
|
||||
except Exception,CatchableError:
|
||||
error "Exception when calling 'eventCallBack': " &
|
||||
getCurrentExceptionMsg()
|
||||
else:
|
||||
error "extRelayEventCallback is nil"
|
||||
|
||||
var retFut = newFuture[void]()
|
||||
retFut.complete()
|
||||
return retFut
|
||||
|
||||
# WakuNode instance
|
||||
var node {.threadvar.}: WakuNode
|
||||
error "extEventCallback is nil"
|
||||
|
||||
### End of not-exported components
|
||||
################################################################################
|
||||
|
@ -79,21 +63,6 @@ var node {.threadvar.}: WakuNode
|
|||
################################################################################
|
||||
### Exported procs
|
||||
|
||||
# Every Nim library must have this function called - the name is derived from
|
||||
# the `--nimMainPrefix` command line option
|
||||
proc NimMain() {.importc.}
|
||||
|
||||
var initialized: Atomic[bool]
|
||||
|
||||
proc waku_init_lib() {.dynlib, exportc, cdecl.} =
|
||||
if not initialized.exchange(true):
|
||||
NimMain() # Every Nim library needs to call `NimMain` once exactly
|
||||
when declared(setupForeignThreadGc): setupForeignThreadGc()
|
||||
when declared(nimGC_setStackBottom):
|
||||
var locals {.volatile, noinit.}: pointer
|
||||
locals = addr(locals)
|
||||
nimGC_setStackBottom(locals)
|
||||
|
||||
proc waku_new(configJson: cstring,
|
||||
onErrCb: WakuCallback): cint
|
||||
{.dynlib, exportc, cdecl.} =
|
||||
|
@ -103,78 +72,12 @@ proc waku_new(configJson: cstring,
|
|||
if isNil(onErrCb):
|
||||
return RET_MISSING_CALLBACK
|
||||
|
||||
var privateKey: PrivateKey
|
||||
var netConfig = NetConfig.init(ValidIpAddress.init("127.0.0.1"),
|
||||
Port(60000'u16)).value
|
||||
var relay: bool
|
||||
var topics = @[""]
|
||||
var jsonResp: JsonEvent
|
||||
|
||||
let cj = configJson.alloc()
|
||||
|
||||
if not parseConfig($cj,
|
||||
privateKey,
|
||||
netConfig,
|
||||
relay,
|
||||
topics,
|
||||
jsonResp):
|
||||
deallocShared(cj)
|
||||
let resp = $jsonResp
|
||||
onErrCb(unsafeAddr resp[0], cast[csize_t](len(resp)))
|
||||
let createThRes = waku_thread_module.createWakuThread(configJson)
|
||||
if createThRes.isErr():
|
||||
let msg = "Error in createWakuThread: " & $createThRes.error
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
deallocShared(cj)
|
||||
|
||||
var enrBuilder = EnrBuilder.init(privateKey)
|
||||
|
||||
enrBuilder.withIpAddressAndPorts(
|
||||
netConfig.enrIp,
|
||||
netConfig.enrPort,
|
||||
netConfig.discv5UdpPort
|
||||
)
|
||||
|
||||
if netConfig.wakuFlags.isSome():
|
||||
enrBuilder.withWakuCapabilities(netConfig.wakuFlags.get())
|
||||
|
||||
enrBuilder.withMultiaddrs(netConfig.enrMultiaddrs)
|
||||
|
||||
let addShardedTopics = enrBuilder.withShardedTopics(topics)
|
||||
if addShardedTopics.isErr():
|
||||
let resp = $addShardedTopics.error
|
||||
onErrCb(unsafeAddr resp[0], cast[csize_t](len(resp)))
|
||||
return RET_ERR
|
||||
|
||||
let recordRes = enrBuilder.build()
|
||||
let record =
|
||||
if recordRes.isErr():
|
||||
let resp = $recordRes.error
|
||||
onErrCb(unsafeAddr resp[0], cast[csize_t](len(resp)))
|
||||
return RET_ERR
|
||||
else: recordRes.get()
|
||||
|
||||
var builder = WakuNodeBuilder.init()
|
||||
builder.withRng(crypto.newRng())
|
||||
builder.withNodeKey(privateKey)
|
||||
builder.withRecord(record)
|
||||
builder.withNetworkConfiguration(netConfig)
|
||||
builder.withSwitchConfiguration(
|
||||
maxConnections = some(50.int)
|
||||
)
|
||||
|
||||
let wakuNodeRes = builder.build()
|
||||
if wakuNodeRes.isErr():
|
||||
let errorMsg = "failed to create waku node instance: " & wakuNodeRes.error
|
||||
let jsonErrEvent = $JsonErrorEvent.new(errorMsg)
|
||||
|
||||
onErrCb(unsafeAddr jsonErrEvent[0], cast[csize_t](len(jsonErrEvent)))
|
||||
return RET_ERR
|
||||
|
||||
node = wakuNodeRes.get()
|
||||
|
||||
if relay:
|
||||
waitFor node.mountRelay()
|
||||
node.peerManager.start()
|
||||
|
||||
return RET_OK
|
||||
|
||||
proc waku_version(onOkCb: WakuCallBack): cint {.dynlib, exportc.} =
|
||||
|
@ -186,8 +89,8 @@ proc waku_version(onOkCb: WakuCallBack): cint {.dynlib, exportc.} =
|
|||
|
||||
return RET_OK
|
||||
|
||||
proc waku_set_relay_callback(callback: WakuCallBack) {.dynlib, exportc.} =
|
||||
extRelayEventCallback = callback
|
||||
proc waku_set_event_callback(callback: WakuCallBack) {.dynlib, exportc.} =
|
||||
extEventCallback = callback
|
||||
|
||||
proc waku_content_topic(appName: cstring,
|
||||
appVersion: cuint,
|
||||
|
@ -287,123 +190,84 @@ proc waku_relay_publish(pubSubTopic: cstring,
|
|||
else:
|
||||
$pst
|
||||
|
||||
if node.wakuRelay.isNil():
|
||||
let msg = "Can't publish. WakuRelay is not enabled."
|
||||
let sendReqRes = waku_thread_module.sendRequestToWakuThread(
|
||||
RelayRequest.new(RelayMsgType.PUBLISH,
|
||||
PubsubTopic($pst),
|
||||
WakuRelayHandler(relayEventCallback),
|
||||
wakuMessage))
|
||||
deallocShared(pst)
|
||||
|
||||
if sendReqRes.isErr():
|
||||
let msg = $sendReqRes.error
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
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:
|
||||
let msg = "Message not sent because no peers found."
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
elif numPeers > 0:
|
||||
# TODO: pending to return a valid message Id (response when all is correct)
|
||||
let msg = "hard-coded-message-id"
|
||||
onOkCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_OK
|
||||
|
||||
else:
|
||||
let msg = "Timeout expired"
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
proc waku_start() {.dynlib, exportc.} =
|
||||
waitFor node.start()
|
||||
discard waku_thread_module.sendRequestToWakuThread(
|
||||
NodeLifecycleRequest.new(
|
||||
NodeLifecycleMsgType.START_NODE))
|
||||
|
||||
proc waku_stop() {.dynlib, exportc.} =
|
||||
waitFor node.stop()
|
||||
discard waku_thread_module.sendRequestToWakuThread(
|
||||
NodeLifecycleRequest.new(
|
||||
NodeLifecycleMsgType.STOP_NODE))
|
||||
|
||||
proc waku_relay_subscribe(
|
||||
pubSubTopic: cstring,
|
||||
onErrCb: WakuCallBack): cint
|
||||
{.dynlib, exportc.} =
|
||||
# @params
|
||||
# topic: Pubsub topic to subscribe to. If empty, it subscribes to the default pubsub topic.
|
||||
if isNil(onErrCb):
|
||||
return RET_MISSING_CALLBACK
|
||||
|
||||
if isNil(extRelayEventCallback):
|
||||
let msg = $"""Cannot subscribe without a callback.
|
||||
# Kindly set it with the 'waku_set_relay_callback' function"""
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_MISSING_CALLBACK
|
||||
|
||||
if node.wakuRelay.isNil():
|
||||
let msg = $"Cannot subscribe without Waku Relay enabled."
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
let pst = pubSubTopic.alloc()
|
||||
node.wakuRelay.subscribe(PubsubTopic($pst),
|
||||
WakuRelayHandler(relayEventCallback))
|
||||
let sendReqRes = waku_thread_module.sendRequestToWakuThread(
|
||||
RelayRequest.new(RelayMsgType.SUBSCRIBE,
|
||||
PubsubTopic($pst),
|
||||
WakuRelayHandler(relayEventCallback)))
|
||||
deallocShared(pst)
|
||||
|
||||
if sendReqRes.isErr():
|
||||
let msg = $sendReqRes.error
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
return RET_OK
|
||||
|
||||
proc waku_relay_unsubscribe(
|
||||
pubSubTopic: cstring,
|
||||
onErrCb: WakuCallBack): cint
|
||||
{.dynlib, exportc.} =
|
||||
# @params
|
||||
# topic: Pubsub topic to subscribe to. If empty, it unsubscribes to the default pubsub topic.
|
||||
if isNil(onErrCb):
|
||||
return RET_MISSING_CALLBACK
|
||||
|
||||
if isNil(extRelayEventCallback):
|
||||
let msg = """Cannot unsubscribe without a callback.
|
||||
# Kindly set it with the 'waku_set_relay_callback' function"""
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_MISSING_CALLBACK
|
||||
|
||||
if node.wakuRelay.isNil():
|
||||
let msg = "Cannot unsubscribe without Waku Relay enabled."
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
let pst = pubSubTopic.alloc()
|
||||
node.wakuRelay.unsubscribe(PubsubTopic($pst))
|
||||
let sendReqRes = waku_thread_module.sendRequestToWakuThread(
|
||||
RelayRequest.new(RelayMsgType.UNSUBSCRIBE,
|
||||
PubsubTopic($pst),
|
||||
WakuRelayHandler(relayEventCallback)))
|
||||
deallocShared(pst)
|
||||
|
||||
if sendReqRes.isErr():
|
||||
let msg = $sendReqRes.error
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
return RET_OK
|
||||
|
||||
proc waku_connect(peerMultiAddr: cstring,
|
||||
timeoutMs: cuint,
|
||||
onErrCb: WakuCallBack): cint
|
||||
{.dynlib, exportc.} =
|
||||
# peerMultiAddr: comma-separated list of fully-qualified multiaddresses.
|
||||
# var ret = newString(len + 1)
|
||||
# if len > 0:
|
||||
# copyMem(addr ret[0], str, len + 1)
|
||||
|
||||
let address = peerMultiAddr.alloc()
|
||||
let peers = ($address).split(",").mapIt(strip(it))
|
||||
|
||||
# TODO: the timeoutMs is not being used at all!
|
||||
let connectFut = node.connectToNodes(peers, source="static")
|
||||
while not connectFut.finished():
|
||||
poll()
|
||||
|
||||
deallocShared(address)
|
||||
|
||||
if not connectFut.completed():
|
||||
let msg = "Timeout expired."
|
||||
let connRes = waku_thread_module.sendRequestToWakuThread(
|
||||
PeerManagementRequest.new(
|
||||
PeerManagementMsgType.CONNECT_TO,
|
||||
$peerMultiAddr,
|
||||
chronos.milliseconds(timeoutMs)))
|
||||
if connRes.isErr():
|
||||
let msg = $connRes.error
|
||||
onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg)))
|
||||
return RET_ERR
|
||||
|
||||
return RET_OK
|
||||
|
||||
proc waku_poll() {.dynlib, exportc, gcsafe.} =
|
||||
poll()
|
||||
|
||||
### End of exported procs
|
||||
################################################################################
|
||||
|
|
|
@ -10,7 +10,7 @@ import
|
|||
../../waku/common/utils/nat,
|
||||
../../waku/v2/node/waku_node,
|
||||
../../waku/v2/node/config,
|
||||
./events/[json_error_event,json_base_event]
|
||||
../events/[json_error_event,json_base_event]
|
||||
|
||||
proc parsePrivateKey(jsonNode: JsonNode,
|
||||
privateKey: var PrivateKey,
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
import
|
||||
std/options
|
||||
import
|
||||
chronos,
|
||||
stew/results,
|
||||
stew/shims/net
|
||||
import
|
||||
../../../waku/v2/node/waku_node,
|
||||
./request
|
||||
|
||||
type
|
||||
NodeLifecycleMsgType* = enum
|
||||
START_NODE
|
||||
STOP_NODE
|
||||
|
||||
type
|
||||
NodeLifecycleRequest* = ref object of InterThreadRequest
|
||||
operation: NodeLifecycleMsgType
|
||||
|
||||
proc new*(T: type NodeLifecycleRequest,
|
||||
op: NodeLifecycleMsgType): T =
|
||||
|
||||
return NodeLifecycleRequest(operation: op)
|
||||
|
||||
method process*(self: NodeLifecycleRequest,
|
||||
node: WakuNode): Future[Result[string, string]] {.async.} =
|
||||
|
||||
case self.operation:
|
||||
|
||||
of START_NODE:
|
||||
waitFor node.start()
|
||||
|
||||
of STOP_NODE:
|
||||
waitFor node.stop()
|
||||
|
||||
return ok("")
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
import
|
||||
std/[options,sequtils,strutils]
|
||||
import
|
||||
chronicles,
|
||||
chronos,
|
||||
stew/results,
|
||||
stew/shims/net
|
||||
import
|
||||
../../../waku/v2/node/waku_node,
|
||||
./request
|
||||
|
||||
type
|
||||
PeerManagementMsgType* = enum
|
||||
CONNECT_TO
|
||||
|
||||
type
|
||||
PeerManagementRequest* = ref object of InterThreadRequest
|
||||
operation: PeerManagementMsgType
|
||||
peerMultiAddr: string
|
||||
dialTimeout: Duration
|
||||
|
||||
proc new*(T: type PeerManagementRequest,
|
||||
op: PeerManagementMsgType,
|
||||
peerMultiAddr: string,
|
||||
dialTimeout: Duration): T =
|
||||
|
||||
return PeerManagementRequest(operation: op,
|
||||
peerMultiAddr: peerMultiAddr,
|
||||
dialTimeout: dialTimeout)
|
||||
|
||||
proc connectTo(node: WakuNode,
|
||||
peerMultiAddr: string,
|
||||
dialTimeout: Duration): Result[void, string] =
|
||||
|
||||
let peers = (peerMultiAddr).split(",").mapIt(strip(it))
|
||||
|
||||
# TODO: the dialTimeout is not being used at all!
|
||||
let connectFut = node.connectToNodes(peers, source="static")
|
||||
while not connectFut.finished():
|
||||
poll()
|
||||
|
||||
if not connectFut.completed():
|
||||
let msg = "Timeout expired."
|
||||
return err(msg)
|
||||
|
||||
return ok()
|
||||
|
||||
method process*(self: PeerManagementRequest,
|
||||
node: WakuNode): Future[Result[string, string]] {.async.} =
|
||||
|
||||
case self.operation:
|
||||
|
||||
of CONNECT_TO:
|
||||
let ret = node.connectTo(self.peerMultiAddr, self.dialTimeout)
|
||||
if ret.isErr():
|
||||
return err(ret.error)
|
||||
|
||||
return ok("")
|
|
@ -0,0 +1,64 @@
|
|||
|
||||
import
|
||||
std/[options,sequtils,strutils]
|
||||
import
|
||||
chronicles,
|
||||
chronos,
|
||||
stew/results,
|
||||
stew/shims/net
|
||||
import
|
||||
../../../../waku/v2/waku_core/message/message,
|
||||
../../../../waku/v2/node/waku_node,
|
||||
../../../../waku/v2/waku_core/topics/pubsub_topic,
|
||||
../../../../waku/v2/waku_relay/protocol,
|
||||
../request
|
||||
|
||||
type
|
||||
RelayMsgType* = enum
|
||||
SUBSCRIBE
|
||||
UNSUBSCRIBE
|
||||
PUBLISH
|
||||
|
||||
type
|
||||
RelayRequest* = ref object of InterThreadRequest
|
||||
operation: RelayMsgType
|
||||
pubsubTopic: PubsubTopic
|
||||
relayEventCallback: WakuRelayHandler # not used in 'PUBLISH' requests
|
||||
message: WakuMessage # this field is only used in 'PUBLISH' requests
|
||||
|
||||
proc new*(T: type RelayRequest,
|
||||
op: RelayMsgType,
|
||||
pubsubTopic: PubsubTopic,
|
||||
relayEventCallback: WakuRelayHandler = nil,
|
||||
message = WakuMessage()): T =
|
||||
|
||||
return RelayRequest(operation: op,
|
||||
pubsubTopic: pubsubTopic,
|
||||
relayEventCallback: relayEventCallback,
|
||||
message: message)
|
||||
|
||||
method process*(self: RelayRequest,
|
||||
node: WakuNode): Future[Result[string, string]] {.async.} =
|
||||
|
||||
if node.wakuRelay.isNil():
|
||||
return err("Operation not supported without Waku Relay enabled.")
|
||||
|
||||
case self.operation:
|
||||
|
||||
of SUBSCRIBE:
|
||||
node.wakuRelay.subscribe(self.pubsubTopic, self.relayEventCallback)
|
||||
|
||||
of UNSUBSCRIBE:
|
||||
node.wakuRelay.unsubscribe(self.pubsubTopic)
|
||||
|
||||
of PUBLISH:
|
||||
let numPeers = await node.wakuRelay.publish(self.pubsubTopic,
|
||||
self.message)
|
||||
if numPeers == 0:
|
||||
return err("Message not sent because no peers found.")
|
||||
|
||||
elif numPeers > 0:
|
||||
# TODO: pending to return a valid message Id
|
||||
return ok("hard-coded-message-id")
|
||||
|
||||
return ok("")
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
# This file contains the base message request type that will be handled
|
||||
# by the Waku Node thread.
|
||||
|
||||
import
|
||||
std/json,
|
||||
stew/results
|
||||
import
|
||||
chronos
|
||||
import
|
||||
../../../waku/v2/node/waku_node,
|
||||
../waku_thread
|
||||
|
||||
type
|
||||
InterThreadRequest* = ref object of RootObj
|
||||
|
||||
method process*(self: InterThreadRequest, node: WakuNode):
|
||||
Future[Result[string, string]] {.base.} = discard
|
||||
|
||||
proc `$`*(self: InterThreadRequest): string =
|
||||
return $( %* self )
|
|
@ -0,0 +1,188 @@
|
|||
|
||||
{.pragma: exported, exportc, cdecl, raises: [].}
|
||||
{.pragma: callback, cdecl, raises: [], gcsafe.}
|
||||
{.passc: "-fPIC".}
|
||||
|
||||
import
|
||||
std/[json,sequtils,times,strformat,options,atomics,strutils,os]
|
||||
import
|
||||
chronicles,
|
||||
chronos,
|
||||
stew/results,
|
||||
stew/shims/net
|
||||
import
|
||||
../../../waku/common/enr/builder,
|
||||
../../../waku/v2/waku_enr/capabilities,
|
||||
../../../waku/v2/waku_enr/multiaddr,
|
||||
../../../waku/v2/waku_enr/sharding,
|
||||
../../../waku/v2/waku_core/message/message,
|
||||
../../../waku/v2/waku_core/topics/pubsub_topic,
|
||||
../../../waku/v2/node/peer_manager/peer_manager,
|
||||
../../../waku/v2/waku_core,
|
||||
../../../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_base_event],
|
||||
../alloc,
|
||||
./config,
|
||||
./inter_thread_communication/request
|
||||
|
||||
type
|
||||
Context* = object
|
||||
thread: Thread[(ptr Context)]
|
||||
reqChannel: Channel[InterThreadRequest]
|
||||
respChannel: Channel[Result[string, string]]
|
||||
node: WakuNode
|
||||
|
||||
var ctx {.threadvar.}: ptr Context
|
||||
|
||||
# To control when the thread is running
|
||||
var running: Atomic[bool]
|
||||
|
||||
# Every Nim library must have this function called - the name is derived from
|
||||
# the `--nimMainPrefix` command line option
|
||||
proc NimMain() {.importc.}
|
||||
var initialized: Atomic[bool]
|
||||
|
||||
proc waku_init() =
|
||||
if not initialized.exchange(true):
|
||||
NimMain() # Every Nim library needs to call `NimMain` once exactly
|
||||
when declared(setupForeignThreadGc): setupForeignThreadGc()
|
||||
when declared(nimGC_setStackBottom):
|
||||
var locals {.volatile, noinit.}: pointer
|
||||
locals = addr(locals)
|
||||
nimGC_setStackBottom(locals)
|
||||
|
||||
proc createNode(configJson: cstring): Result[WakuNode, string] =
|
||||
var privateKey: PrivateKey
|
||||
var netConfig = NetConfig.init(ValidIpAddress.init("127.0.0.1"),
|
||||
Port(60000'u16)).value
|
||||
var relay: bool
|
||||
var topics = @[""]
|
||||
var jsonResp: JsonEvent
|
||||
|
||||
let cj = configJson.alloc()
|
||||
|
||||
if not parseConfig($cj,
|
||||
privateKey,
|
||||
netConfig,
|
||||
relay,
|
||||
topics,
|
||||
jsonResp):
|
||||
deallocShared(cj)
|
||||
return err($jsonResp)
|
||||
|
||||
deallocShared(cj)
|
||||
|
||||
var enrBuilder = EnrBuilder.init(privateKey)
|
||||
|
||||
enrBuilder.withIpAddressAndPorts(
|
||||
netConfig.enrIp,
|
||||
netConfig.enrPort,
|
||||
netConfig.discv5UdpPort
|
||||
)
|
||||
|
||||
if netConfig.wakuFlags.isSome():
|
||||
enrBuilder.withWakuCapabilities(netConfig.wakuFlags.get())
|
||||
|
||||
enrBuilder.withMultiaddrs(netConfig.enrMultiaddrs)
|
||||
|
||||
let addShardedTopics = enrBuilder.withShardedTopics(topics)
|
||||
if addShardedTopics.isErr():
|
||||
let msg = "Error setting shared topics: " & $addShardedTopics.error
|
||||
return err($JsonErrorEvent.new(msg))
|
||||
|
||||
let recordRes = enrBuilder.build()
|
||||
let record =
|
||||
if recordRes.isErr():
|
||||
let msg = "Error building enr record: " & $recordRes.error
|
||||
return err($JsonErrorEvent.new(msg))
|
||||
|
||||
else: recordRes.get()
|
||||
|
||||
var builder = WakuNodeBuilder.init()
|
||||
builder.withRng(crypto.newRng())
|
||||
builder.withNodeKey(privateKey)
|
||||
builder.withRecord(record)
|
||||
builder.withNetworkConfiguration(netConfig)
|
||||
builder.withSwitchConfiguration(
|
||||
maxConnections = some(50.int)
|
||||
)
|
||||
|
||||
let wakuNodeRes = builder.build()
|
||||
if wakuNodeRes.isErr():
|
||||
let errorMsg = "failed to create waku node instance: " & wakuNodeRes.error
|
||||
return err($JsonErrorEvent.new(errorMsg))
|
||||
|
||||
var newNode = wakuNodeRes.get()
|
||||
|
||||
if relay:
|
||||
waitFor newNode.mountRelay()
|
||||
newNode.peerManager.start()
|
||||
|
||||
return ok(newNode)
|
||||
|
||||
proc run(ctx: ptr Context) {.thread.} =
|
||||
## This is the worker thread body. This thread runs the Waku node
|
||||
## and attends library user requests (stop, connect_to, etc.)
|
||||
|
||||
while running.load == true:
|
||||
## Trying to get a request from the libwaku main thread
|
||||
let req = ctx.reqChannel.tryRecv()
|
||||
if req[0] == true:
|
||||
let response = waitFor req[1].process(ctx.node)
|
||||
ctx.respChannel.send( response )
|
||||
|
||||
poll()
|
||||
|
||||
tearDownForeignThreadGc()
|
||||
|
||||
proc createWakuThread*(configJson: cstring): Result[void, string] =
|
||||
## This proc is called from the main thread and it creates
|
||||
## the Waku working thread.
|
||||
|
||||
waku_init()
|
||||
|
||||
ctx = createShared(Context, 1)
|
||||
ctx.reqChannel.open()
|
||||
ctx.respChannel.open()
|
||||
|
||||
let newNodeRes = createNode(configJson)
|
||||
if newNodeRes.isErr():
|
||||
return err(newNodeRes.error)
|
||||
|
||||
ctx.node = newNodeRes.get()
|
||||
|
||||
running.store(true)
|
||||
|
||||
try:
|
||||
createThread(ctx.thread, run, ctx)
|
||||
except ResourceExhaustedError:
|
||||
# and freeShared for typed allocations!
|
||||
freeShared(ctx)
|
||||
|
||||
return err("failed to create a thread: " & getCurrentExceptionMsg())
|
||||
|
||||
return ok()
|
||||
|
||||
proc stopWakuNodeThread*() =
|
||||
running.store(false)
|
||||
joinThread(ctx.thread)
|
||||
|
||||
ctx.reqChannel.close()
|
||||
ctx.respChannel.close()
|
||||
|
||||
freeShared(ctx)
|
||||
|
||||
proc sendRequestToWakuThread*(req: InterThreadRequest): Result[string, string] =
|
||||
|
||||
ctx.reqChannel.send(req)
|
||||
|
||||
var resp = ctx.respChannel.tryRecv()
|
||||
while resp[0] == false:
|
||||
resp = ctx.respChannel.tryRecv()
|
||||
os.sleep(1)
|
||||
|
||||
return resp[1]
|
||||
|
Loading…
Reference in New Issue