feat: examples/golang/waku.go add new example (#2559)

* examples/golang/waku.go: add new example
* waku.go: Richard recommendations
https://github.com/waku-org/nwaku/pull/2559#pullrequestreview-1963210599
Not addressing points 3 and 9 in this commit.
* waku.go: allow setting separate callback methods per WakuNode instance
---------
Co-authored-by: richΛrd <info@richardramos.me>
This commit is contained in:
Ivan FB 2024-03-28 11:19:16 +01:00 committed by GitHub
parent 2aa835e3bf
commit 8d66a5485d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 533 additions and 0 deletions

533
examples/golang/waku.go Normal file
View File

@ -0,0 +1,533 @@
package main
/*
#cgo LDFLAGS: -L../../build/ -lwaku -Wl,--allow-multiple-definition
#include "../../library/libwaku.h"
#include <stdio.h>
#include <stdlib.h>
extern void globalEventCallback(int ret, char* msg, size_t len, void* userData);
typedef struct {
int ret;
char* msg;
size_t len;
} Resp;
void* allocResp() {
return calloc(1, sizeof(Resp));
}
void freeResp(void* resp) {
if (resp != NULL) {
free(resp);
}
}
char* getMyCharPtr(void* resp) {
if (resp == NULL) {
return NULL;
}
Resp* m = (Resp*) resp;
return m->msg;
}
size_t getMyCharLen(void* resp) {
if (resp == NULL) {
return 0;
}
Resp* m = (Resp*) resp;
return m->len;
}
int getRet(void* resp) {
if (resp == NULL) {
return 0;
}
Resp* m = (Resp*) resp;
return m->ret;
}
// resp must be set != NULL in case interest on retrieving data from the callback
void callback(int ret, char* msg, size_t len, void* resp) {
if (resp != NULL) {
Resp* m = (Resp*) resp;
m->ret = ret;
m->msg = msg;
m->len = len;
}
}
#define WAKU_CALL(call) \
do { \
int ret = call; \
if (ret != 0) { \
printf("Failed the call to: %s. Returned code: %d\n", #call, ret); \
exit(1); \
} \
} while (0)
void* cGoWakuNew(const char* configJson, void* resp) {
// We pass NULL because we are not interested in retrieving data from this callback
return waku_new(configJson, (WakuCallBack) callback, resp);
}
void cGoWakuStart(void* ctx, void* resp) {
WAKU_CALL(waku_start(ctx, (WakuCallBack) callback, resp));
}
void cGoWakuStop(void* ctx, void* resp) {
WAKU_CALL(waku_stop(ctx, (WakuCallBack) callback, resp));
}
void cGoWakuDestroy(void* ctx, void* resp) {
WAKU_CALL(waku_destroy(ctx, (WakuCallBack) callback, resp));
}
void cGoWakuVersion(void* ctx, void* resp) {
WAKU_CALL(waku_version(ctx, (WakuCallBack) callback, resp));
}
void cGoWakuSetEventCallback(void* ctx) {
// The 'globalEventCallback' Go function is shared amongst all possible WakuNode instances.
// Given that the 'globalEventCallback' is shared, we pass again the
// ctx instance but in this case is needed to pick up the correct method
// that will handle the event.
// In other words, for every call the libwaku makes to globalEventCallback,
// the 'userData' parameter will bring the context of the node that registered
// that globalEventCallback.
// This technique is needed because cgo only allows to export Go functions and not methods.
waku_set_event_callback(ctx, (WakuCallBack) globalEventCallback, ctx);
}
void cGoWakuContentTopic(void* ctx,
char* appName,
int appVersion,
char* contentTopicName,
char* encoding,
void* resp) {
WAKU_CALL( waku_content_topic(ctx,
appName,
appVersion,
contentTopicName,
encoding,
(WakuCallBack) callback,
resp) );
}
void cGoWakuPubsubTopic(void* ctx, char* topicName, void* resp) {
WAKU_CALL( waku_pubsub_topic(ctx, topicName, (WakuCallBack) callback, resp) );
}
void cGoWakuDefaultPubsubTopic(void* ctx, void* resp) {
WAKU_CALL (waku_default_pubsub_topic(ctx, (WakuCallBack) callback, resp));
}
void cGoWakuRelayPublish(void* ctx,
const char* pubSubTopic,
const char* jsonWakuMessage,
int timeoutMs,
void* resp) {
WAKU_CALL (waku_relay_publish(ctx,
pubSubTopic,
jsonWakuMessage,
timeoutMs,
(WakuCallBack) callback,
resp));
}
void cGoWakuRelaySubscribe(void* ctx, char* pubSubTopic, void* resp) {
WAKU_CALL ( waku_relay_subscribe(ctx,
pubSubTopic,
(WakuCallBack) callback,
resp) );
}
void cGoWakuRelayUnsubscribe(void* ctx, char* pubSubTopic, void* resp) {
WAKU_CALL ( waku_relay_unsubscribe(ctx,
pubSubTopic,
(WakuCallBack) callback,
resp) );
}
void cGoWakuConnect(void* ctx, char* peerMultiAddr, int timeoutMs, void* resp) {
WAKU_CALL( waku_connect(ctx,
peerMultiAddr,
timeoutMs,
(WakuCallBack) callback,
resp) );
}
void cGoWakuListenAddresses(void* ctx, void* resp) {
WAKU_CALL (waku_listen_addresses(ctx, (WakuCallBack) callback, resp) );
}
*/
import "C"
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"unsafe"
)
type WakuMessageHash = string
type WakuPubsubTopic = string
type WakuContentTopic = string
type WakuConfig struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
NodeKey string `json:"key,omitempty"`
EnableRelay bool `json:"relay"`
}
type WakuNode struct {
ctx unsafe.Pointer
}
func WakuNew(config WakuConfig) (*WakuNode, error) {
jsonConfig, err := json.Marshal(config)
if err != nil {
return nil, err
}
var cJsonConfig = C.CString(string(jsonConfig))
var resp = C.allocResp()
defer C.free(unsafe.Pointer(cJsonConfig))
defer C.freeResp(resp)
ctx := C.cGoWakuNew(cJsonConfig, resp)
if C.getRet(resp) == C.RET_OK {
return &WakuNode{ctx: ctx}, nil
}
errMsg := "error WakuNew: " + C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return nil, errors.New(errMsg)
}
func (self *WakuNode) WakuStart() error {
var resp = C.allocResp()
defer C.freeResp(resp)
C.cGoWakuStart(self.ctx, resp)
if C.getRet(resp) == C.RET_OK {
return nil
}
errMsg := "error WakuStart: " + C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return errors.New(errMsg)
}
func (self *WakuNode) WakuStop() error {
var resp = C.allocResp()
defer C.freeResp(resp)
C.cGoWakuStop(self.ctx, resp)
if C.getRet(resp) == C.RET_OK {
return nil
}
errMsg := "error WakuStop: " + C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return errors.New(errMsg)
}
func (self *WakuNode) WakuDestroy() error {
var resp = C.allocResp()
defer C.freeResp(resp)
C.cGoWakuDestroy(self.ctx, resp)
if C.getRet(resp) == C.RET_OK {
return nil
}
errMsg := "error WakuDestroy: " + C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return errors.New(errMsg)
}
func (self *WakuNode) WakuVersion() (string, error) {
var resp = C.allocResp()
defer C.freeResp(resp)
C.cGoWakuVersion(self.ctx, resp)
if C.getRet(resp) == C.RET_OK {
var version = C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return version, nil
}
errMsg := "error WakuVersion: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return "", errors.New(errMsg)
}
//export globalEventCallback
func globalEventCallback(callerRet C.int, msg *C.char, len C.size_t, userData unsafe.Pointer) {
// This is shared among all Golang instances
self := WakuNode{ctx: userData}
self.MyEventCallback(callerRet, msg, len)
}
func (self *WakuNode) MyEventCallback(callerRet C.int, msg *C.char, len C.size_t) {
fmt.Println("Event received:", C.GoStringN(msg, C.int(len)))
}
func (self *WakuNode) WakuSetEventCallback() {
// Notice that the events for self node are handled by the 'MyEventCallback' method
C.cGoWakuSetEventCallback(self.ctx)
}
func (self *WakuNode) FormatContentTopic(
appName string,
appVersion int,
contentTopicName string,
encoding string) (WakuContentTopic, error) {
var cAppName = C.CString(appName)
var cContentTopicName = C.CString(contentTopicName)
var cEncoding = C.CString(encoding)
var resp = C.allocResp()
defer C.free(unsafe.Pointer(cAppName))
defer C.free(unsafe.Pointer(cContentTopicName))
defer C.free(unsafe.Pointer(cEncoding))
defer C.freeResp(resp)
C.cGoWakuContentTopic(self.ctx,
cAppName,
C.int(appVersion),
cContentTopicName,
cEncoding,
resp)
if C.getRet(resp) == C.RET_OK {
var contentTopic = C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return contentTopic, nil
}
errMsg := "error FormatContentTopic: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return "", errors.New(errMsg)
}
func (self *WakuNode) FormatPubsubTopic(topicName string) (WakuPubsubTopic, error) {
var cTopicName = C.CString(topicName)
var resp = C.allocResp()
defer C.free(unsafe.Pointer(cTopicName))
defer C.freeResp(resp)
C.cGoWakuPubsubTopic(self.ctx, cTopicName, resp)
if C.getRet(resp) == C.RET_OK {
var pubsubTopic = C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return pubsubTopic, nil
}
errMsg := "error FormatPubsubTopic: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return "", errors.New(errMsg)
}
func (self *WakuNode) WakuDefaultPubsubTopic() (WakuPubsubTopic, error) {
var resp = C.allocResp()
defer C.freeResp(resp)
C.cGoWakuDefaultPubsubTopic(self.ctx, resp)
if C.getRet(resp) == C.RET_OK {
var defaultPubsubTopic = C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return defaultPubsubTopic, nil
}
errMsg := "error WakuDefaultPubsubTopic: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return "", errors.New(errMsg)
}
func (self *WakuNode) WakuRelayPublish(
pubsubTopic string,
message string,
timeoutMs int) (WakuMessageHash, error) {
var cPubsubTopic = C.CString(pubsubTopic)
var msg = C.CString(message)
var resp = C.allocResp()
defer C.freeResp(resp)
defer C.free(unsafe.Pointer(cPubsubTopic))
defer C.free(unsafe.Pointer(msg))
C.cGoWakuRelayPublish(self.ctx, cPubsubTopic, msg, C.int(timeoutMs), resp)
if C.getRet(resp) == C.RET_OK {
msgHash := C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return msgHash, nil
}
errMsg := "error WakuRelayPublish: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return "", errors.New(errMsg)
}
func (self *WakuNode) WakuRelaySubscribe(pubsubTopic string) error {
var resp = C.allocResp()
var cPubsubTopic = C.CString(pubsubTopic)
defer C.freeResp(resp)
defer C.free(unsafe.Pointer(cPubsubTopic))
C.cGoWakuRelaySubscribe(self.ctx, cPubsubTopic, resp)
if C.getRet(resp) == C.RET_OK {
return nil
}
errMsg := "error WakuRelaySubscribe: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return errors.New(errMsg)
}
func (self *WakuNode) WakuRelayUnsubscribe(pubsubTopic string) error {
var resp = C.allocResp()
var cPubsubTopic = C.CString(pubsubTopic)
defer C.freeResp(resp)
defer C.free(unsafe.Pointer(cPubsubTopic))
C.cGoWakuRelayUnsubscribe(self.ctx, cPubsubTopic, resp)
if C.getRet(resp) == C.RET_OK {
return nil
}
errMsg := "error WakuRelayUnsubscribe: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return errors.New(errMsg)
}
func (self *WakuNode) WakuConnect(peerMultiAddr string, timeoutMs int) error {
var resp = C.allocResp()
var cPeerMultiAddr = C.CString(peerMultiAddr)
defer C.freeResp(resp)
defer C.free(unsafe.Pointer(cPeerMultiAddr))
C.cGoWakuConnect(self.ctx, cPeerMultiAddr, C.int(timeoutMs), resp)
if C.getRet(resp) == C.RET_OK {
return nil
}
errMsg := "error WakuConnect: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return errors.New(errMsg)
}
func (self *WakuNode) WakuListenAddresses() (string, error) {
var resp = C.allocResp()
defer C.freeResp(resp)
C.cGoWakuListenAddresses(self.ctx, resp)
if C.getRet(resp) == C.RET_OK {
var listenAddresses = C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return listenAddresses, nil
}
errMsg := "error WakuListenAddresses: " +
C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp)))
return "", errors.New(errMsg)
}
func main() {
config := WakuConfig{
Host: "0.0.0.0",
Port: 30304,
NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710",
EnableRelay: true,
}
node, err := WakuNew(config)
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
node.WakuSetEventCallback()
defaultPubsubTopic, err := node.WakuDefaultPubsubTopic()
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
err = node.WakuRelaySubscribe(defaultPubsubTopic)
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
err = node.WakuConnect(
// tries to connect to a localhost node with key: 0d714a1fada214dead6dc9c7274585eca0ff292451866e7d6d677dc818e8ccd2
"/ip4/0.0.0.0/tcp/60000/p2p/16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN",
10000)
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
err = node.WakuStart()
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
version, err := node.WakuVersion()
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
formattedContentTopic, err := node.FormatContentTopic("appName", 1, "cTopicName", "enc")
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
formattedPubsubTopic, err := node.FormatPubsubTopic("my-ctopic")
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
listenAddresses, err := node.WakuListenAddresses()
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
fmt.Println("Version:", version)
fmt.Println("Custom content topic:", formattedContentTopic)
fmt.Println("Custom pubsub topic:", formattedPubsubTopic)
fmt.Println("Default pubsub topic:", defaultPubsubTopic)
fmt.Println("Listen addresses:", listenAddresses)
// Wait for a SIGINT or SIGTERM signal
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
err = node.WakuStop()
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
err = node.WakuDestroy()
if err != nil {
fmt.Println("Error happened:", err.Error())
return
}
}