From 8d66a5485d5e67281da781e7f90450606b47d5b3 Mon Sep 17 00:00:00 2001 From: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:19:16 +0100 Subject: [PATCH] feat: examples/golang/waku.go add new example (#2559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- examples/golang/waku.go | 533 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 examples/golang/waku.go diff --git a/examples/golang/waku.go b/examples/golang/waku.go new file mode 100644 index 0000000000..587aa9f6ec --- /dev/null +++ b/examples/golang/waku.go @@ -0,0 +1,533 @@ +package main + +/* + #cgo LDFLAGS: -L../../build/ -lwaku -Wl,--allow-multiple-definition + + #include "../../library/libwaku.h" + #include + #include + + 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 + } +}