{.pragma: exported, exportc, cdecl, raises: [].} {.pragma: callback, cdecl, raises: [], gcsafe.} {.passc: "-fPIC".} import std/[json,sequtils,times,strformat,options,atomics,strutils], strutils import chronicles, chronos import ../../waku/waku_core/message/message, ../../waku/node/waku_node, ../../waku/waku_core/topics/pubsub_topic, ../../../waku/waku_relay/protocol, ./events/json_message_event, ./waku_thread/waku_thread, ./waku_thread/inter_thread_communication/requests/node_lifecycle_request, ./waku_thread/inter_thread_communication/requests/peer_manager_request, ./waku_thread/inter_thread_communication/requests/protocols/relay_request, ./waku_thread/inter_thread_communication/waku_thread_request, ./alloc ################################################################################ ### Wrapper around the waku node ################################################################################ ################################################################################ ### Exported types 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 # May keep a reference to a callback defined externally var extEventCallback*: WakuCallBack = nil 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(extEventCallback): try: let event = $JsonMessageEvent.new(pubsubTopic, msg) extEventCallback(unsafeAddr event[0], cast[csize_t](len(event))) except Exception,CatchableError: error "Exception when calling 'eventCallBack': " & getCurrentExceptionMsg() else: error "extEventCallback is nil" ### End of not-exported components ################################################################################ ################################################################################ ### Exported procs proc waku_new(configJson: cstring, onErrCb: WakuCallback): cint {.dynlib, exportc, cdecl.} = # Creates a new instance of the WakuNode. # Notice that the ConfigNode type is also exported and available for users. if isNil(onErrCb): return RET_MISSING_CALLBACK ## Create the Waku thread that will keep waiting for req from the main thread. waku_thread.createWakuThread().isOkOr: let msg = "Error in createWakuThread: " & $error onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) return RET_ERR let sendReqRes = waku_thread.sendRequestToWakuThread( RequestType.LIFECYCLE, NodeLifecycleRequest.createShared( NodeLifecycleMsgType.CREATE_NODE, configJson)) if sendReqRes.isErr(): let msg = $sendReqRes.error onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) return RET_ERR return RET_OK proc waku_version(onOkCb: WakuCallBack): cint {.dynlib, exportc.} = if isNil(onOkCb): return RET_MISSING_CALLBACK onOkCb(cast[ptr cchar](WakuNodeVersionString), cast[csize_t](len(WakuNodeVersionString))) return RET_OK proc waku_set_event_callback(callback: WakuCallBack) {.dynlib, exportc.} = extEventCallback = callback proc waku_content_topic(appName: cstring, appVersion: cuint, contentTopicName: cstring, encoding: cstring, onOkCb: WakuCallBack): cint {.dynlib, exportc.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_content_topicchar-applicationname-unsigned-int-applicationversion-char-contenttopicname-char-encoding if isNil(onOkCb): return RET_MISSING_CALLBACK let appStr = appName.alloc() let ctnStr = contentTopicName.alloc() let encodingStr = encoding.alloc() let contentTopic = fmt"/{$appStr}/{appVersion}/{$ctnStr}/{$encodingStr}" onOkCb(unsafeAddr contentTopic[0], cast[csize_t](len(contentTopic))) deallocShared(appStr) deallocShared(ctnStr) deallocShared(encodingStr) return RET_OK proc waku_pubsub_topic(topicName: cstring, onOkCb: WakuCallBack): cint {.dynlib, exportc, cdecl.} = if isNil(onOkCb): return RET_MISSING_CALLBACK let topicNameStr = topicName.alloc() # https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding let outPubsubTopic = fmt"/waku/2/{$topicNameStr}" onOkCb(unsafeAddr outPubsubTopic[0], cast[csize_t](len(outPubsubTopic))) deallocShared(topicNameStr) return RET_OK proc waku_default_pubsub_topic(onOkCb: WakuCallBack): cint {.dynlib, exportc.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_default_pubsub_topic if isNil(onOkCb): return RET_MISSING_CALLBACK onOkCb(cast[ptr cchar](DefaultPubsubTopic), cast[csize_t](len(DefaultPubsubTopic))) return RET_OK proc waku_relay_publish(pubSubTopic: cstring, jsonWakuMessage: cstring, timeoutMs: cuint, onErrCb: WakuCallBack): cint {.dynlib, exportc, cdecl.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms if isNil(onErrCb): return RET_MISSING_CALLBACK let jwm = jsonWakuMessage.alloc() var jsonContent:JsonNode try: jsonContent = parseJson($jwm) except JsonParsingError: deallocShared(jwm) let msg = fmt"Error parsing json message: {getCurrentExceptionMsg()}" onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) return RET_ERR deallocShared(jwm) var wakuMessage: WakuMessage try: 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: let msg = fmt"Problem building the WakuMessage: {getCurrentExceptionMsg()}" onErrCb(unsafeAddr msg[0], cast[csize_t](len(msg))) return RET_ERR let pst = pubSubTopic.alloc() let targetPubSubTopic = if len(pst) == 0: DefaultPubsubTopic else: $pst let sendReqRes = waku_thread.sendRequestToWakuThread( RequestType.RELAY, RelayRequest.createShared(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 return RET_OK proc waku_start() {.dynlib, exportc.} = discard waku_thread.sendRequestToWakuThread( RequestType.LIFECYCLE, NodeLifecycleRequest.createShared( NodeLifecycleMsgType.START_NODE)) proc waku_stop() {.dynlib, exportc.} = discard waku_thread.sendRequestToWakuThread( RequestType.LIFECYCLE, NodeLifecycleRequest.createShared( NodeLifecycleMsgType.STOP_NODE)) proc waku_relay_subscribe( pubSubTopic: cstring, onErrCb: WakuCallBack): cint {.dynlib, exportc.} = let pst = pubSubTopic.alloc() let sendReqRes = waku_thread.sendRequestToWakuThread( RequestType.RELAY, RelayRequest.createShared(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.} = let pst = pubSubTopic.alloc() let sendReqRes = waku_thread.sendRequestToWakuThread( RequestType.RELAY, RelayRequest.createShared(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_connect(peerMultiAddr: cstring, timeoutMs: cuint, onErrCb: WakuCallBack): cint {.dynlib, exportc.} = let connRes = waku_thread.sendRequestToWakuThread( RequestType.PEER_MANAGER, PeerManagementRequest.createShared( 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 ### End of exported procs ################################################################################