diff --git a/.gitignore b/.gitignore
index 52b16964..9da01168 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,9 +7,10 @@ nodekey
*.dll
*.so
*.dylib
+*.aar
+*.jar
# output binaries
-main
go-waku
# Test binary, built with `go test -c`
diff --git a/Makefile b/Makefile
index 1ff2c5e1..862cb8d4 100644
--- a/Makefile
+++ b/Makefile
@@ -88,7 +88,7 @@ build-example-c-bindings:
build-example: build-example-basic2 build-example-chat-2 build-example-filter2 build-example-c-bindings
-static-library: ##@cross-compile Build go-waku as static library for current platform
+static-library:
@echo "Building static library..."
go build \
-buildmode=c-archive \
@@ -97,7 +97,7 @@ static-library: ##@cross-compile Build go-waku as static library for current pla
@echo "Static library built:"
@ls -la ./build/lib/libgowaku.*
-dynamic-library: ##@cross-compile Build status-go as shared library for current platform
+dynamic-library:
@echo "Building shared library..."
$(GOBIN_SHARED_LIB_CFLAGS) $(GOBIN_SHARED_LIB_CGO_LDFLAGS) go build \
-buildmode=c-shared \
@@ -111,3 +111,9 @@ ifeq ($(detected_OS),Linux)
endif
@echo "Shared library built:"
@ls -la ./build/lib/libgowaku.*
+
+mobile-android:
+ gomobile init && \
+ gomobile bind -target=android -ldflags="-s -w" -o ./build/lib/gowaku.aar ./mobile
+ @echo "Android library built:"
+ @ls -la ./build/lib/*.aar ./build/lib/*.jar
\ No newline at end of file
diff --git a/examples/android-kotlin/.gitignore b/examples/android-kotlin/.gitignore
new file mode 100644
index 00000000..aa724b77
--- /dev/null
+++ b/examples/android-kotlin/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/examples/android-kotlin/.idea/.gitignore b/examples/android-kotlin/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/examples/android-kotlin/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/examples/android-kotlin/.idea/.name b/examples/android-kotlin/.idea/.name
new file mode 100644
index 00000000..9082f0d4
--- /dev/null
+++ b/examples/android-kotlin/.idea/.name
@@ -0,0 +1 @@
+Waku
\ No newline at end of file
diff --git a/examples/android-kotlin/.idea/compiler.xml b/examples/android-kotlin/.idea/compiler.xml
new file mode 100644
index 00000000..fb7f4a8a
--- /dev/null
+++ b/examples/android-kotlin/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/.idea/gradle.xml b/examples/android-kotlin/.idea/gradle.xml
new file mode 100644
index 00000000..526b4c25
--- /dev/null
+++ b/examples/android-kotlin/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/.idea/misc.xml b/examples/android-kotlin/.idea/misc.xml
new file mode 100644
index 00000000..a4bc1d33
--- /dev/null
+++ b/examples/android-kotlin/.idea/misc.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/.idea/vcs.xml b/examples/android-kotlin/.idea/vcs.xml
new file mode 100644
index 00000000..b2bdec2d
--- /dev/null
+++ b/examples/android-kotlin/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/README.md b/examples/android-kotlin/README.md
new file mode 100644
index 00000000..2412b8fb
--- /dev/null
+++ b/examples/android-kotlin/README.md
@@ -0,0 +1,36 @@
+# Android Kotlin Example
+
+
+## Requirements
+- Android Studio
+
+
+## Running this example
+These instructions should be executed in the terminal:
+```bash
+# Clone the repository
+git clone https://github.com/status-im/go-waku.git
+cd go-waku
+
+# Set required env variables
+export ANDROID_NDK_HOME=/path/to/android/ndk
+export ANDROID_HOME=/path/to/android/sdk/
+
+# Build the .jar
+make mobile-android
+
+# Copy the jar into `libs/` folder
+cp ./build/lib/gowaku.jar ./examples/android-kotlin/app/libs/.
+```
+
+Open the project in Android Studio and run the example app.
+
+
+## Help wanted!
+- Is it possible to build go-waku automatically by executing `make mobile-android` and copying the .jar automatically into `libs/` in Android Studio?
+- Permissions should be requested on runtime
+- Determine the required permission to fix this:
+```
+2022-04-07 19:29:27.542 20042-20068/com.example.waku E/GoLog: 2022-04-07T23:29:27.542Z ERROR basichost basic/basic_host.go:327 failed to resolve local interface addresses {"error": "route ip+net: netlinkrib: permission denied"}
+```
+- The example app blocks the main thread and code in general could be improved
diff --git a/examples/android-kotlin/app/.gitignore b/examples/android-kotlin/app/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/examples/android-kotlin/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/examples/android-kotlin/app/build.gradle b/examples/android-kotlin/app/build.gradle
new file mode 100644
index 00000000..f8900606
--- /dev/null
+++ b/examples/android-kotlin/app/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'org.jetbrains.kotlin.plugin.serialization'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "com.example.waku"
+ minSdk 26
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.aar'], dir: 'libs')
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+ implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/examples/android-kotlin/app/proguard-rules.pro b/examples/android-kotlin/app/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/examples/android-kotlin/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/androidTest/java/com/example/waku/ExampleInstrumentedTest.kt b/examples/android-kotlin/app/src/androidTest/java/com/example/waku/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..5bb7afe0
--- /dev/null
+++ b/examples/android-kotlin/app/src/androidTest/java/com/example/waku/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.waku
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.waku", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/AndroidManifest.xml b/examples/android-kotlin/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..8d4d2132
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/Config.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/Config.kt
new file mode 100644
index 00000000..813b14ee
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/Config.kt
@@ -0,0 +1,14 @@
+package com.example.waku
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Config(
+ var host: String? = null,
+ var result: Int? = null,
+ var advertiseAddr: String? = null,
+ var nodeKey: String? = null,
+ var keepAliveInterval: Int? = null,
+ var relay: Boolean? = null,
+ var minPeersToPublish: Int? = null
+)
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/JsonResult.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/JsonResult.kt
new file mode 100644
index 00000000..4da3a951
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/JsonResult.kt
@@ -0,0 +1,27 @@
+package com.example.waku
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+
+@Serializable
+data class JsonResult(val error: String? = null, val result: T? = null)
+
+inline fun handleResponse(response: String): T {
+ val jsonResult = Json.decodeFromString>(response)
+
+ if (jsonResult.error != null)
+ throw Exception(jsonResult.error)
+
+ if (jsonResult.result == null)
+ throw Exception("no result in response")
+
+ return jsonResult.result
+}
+
+inline fun handleResponse(response: String) {
+ val jsonResult = Json.decodeFromString>(response)
+
+ if (jsonResult.error != null)
+ throw Exception(jsonResult.error)
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/MainActivity.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/MainActivity.kt
new file mode 100644
index 00000000..f8ead47a
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/MainActivity.kt
@@ -0,0 +1,98 @@
+package com.example.waku
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.example.waku.events.Event
+import com.example.waku.events.EventHandler
+import com.example.waku.events.EventType
+import com.example.waku.events.MessageEvent
+import com.example.waku.messages.Message
+import com.example.waku.messages.decodeAsymmetric
+import gowaku.Gowaku.defaultPubsubTopic
+
+val alicePrivKey: String = "0x4f012057e1a1458ce34189cb27daedbbe434f3df0825c1949475dec786e2c64e"
+val alicePubKey: String =
+ "0x0440f05847c4c7166f57ae8ecaaf72d31bddcbca345e26713ca9e26c93fb8362ddcd5ae7f4533ee956428ad08a89cd18b234c2911a3b1c7fbd1c0047610d987302"
+val bobPrivKey: String = "0xb91d6b2df8fb6ef8b53b51b2b30a408c49d5e2b530502d58ac8f94e5c5de1453"
+val bobPubKey: String =
+ "0x045eef61a98ba1cf44a2736fac91183ea2bd86e67de20fe4bff467a71249a8a0c05f795dd7f28ced7c15eaa69c89d4212cc4f526ca5e9a62e88008f506d850cccd"
+
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val lbl = findViewById(R.id.lbl)
+
+ // This configuration and its attributes are optional
+ var c = Config()
+ c.relay = true
+
+ var node = Node(c)
+
+ // A callback must be registered to receive events
+ class MyEventHandler(var lbl: TextView) : EventHandler {
+ override fun handleEvent(evt: Event) {
+ lbl.text =
+ (lbl.text.toString() + ">>> Received a signal: " + evt.type.toString() + "\n")
+ if (evt.type == EventType.Message) {
+ val m = evt as MessageEvent
+ val decodedPayload = m.event.wakuMessage.decodeAsymmetric(bobPrivKey)
+ lbl.text =
+ (lbl.text.toString() + ">>> Message: " + decodedPayload.data.toString(
+ Charsets.UTF_8
+ ) + "\n")
+ }
+ }
+ }
+ node.setEventHandler(MyEventHandler(lbl))
+
+ node.start()
+
+ lbl.text = (lbl.text.toString() + ">>> The node peer ID is " + node.peerID() + "\n")
+
+ node.listenAddresses().forEach {
+ lbl.text = (lbl.text.toString() + ">>> Listening on " + it + "\n")
+ }
+
+ lbl.text = (lbl.text.toString() + ">>> Default pubsub topic: " + defaultPubsubTopic() + "\n");
+
+ try {
+ node.connect("/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS")
+ lbl.text = (lbl.text.toString() + ">>> Connected to Peer" + "\n")
+
+ node.peers().forEach {
+ lbl.text = (lbl.text.toString() + ">>> Peer: " + it.peerID + "\n")
+ lbl.text =
+ (lbl.text.toString() + ">>> Protocols: " + it.protocols.joinToString(",") + "\n")
+ lbl.text =
+ (lbl.text.toString() + ">>> Addresses: " + it.addrs.joinToString(",") + "\n")
+ }
+
+ /*var q = StoreQuery();
+ q.pubsubTopic = defaultPubsubTopic();
+ q.pagingOptions = new(3, null, false);
+ val response = node.StoreQuery(q);
+ println(">>> Retrieved " + response.messages.Count + " messages from store");*/
+
+ } catch (ex: Exception) {
+ lbl.text = (lbl.text.toString() + ">>> Could not connect to peer: " + ex.message)
+ }
+
+ node.relaySubscribe()
+
+ for (i in 1..5) {
+ val payload = ("Hello world! - " + i).toByteArray(Charsets.UTF_8)
+ val timestamp = System.currentTimeMillis() * 1000000
+ val contentTopic = ContentTopic("example", 1, "example", "rfc26")
+ val msg = Message(payload, contentTopic, timestamp = timestamp)
+ val messageID = node.relayPublishEncodeAsymmetric(msg, bobPubKey, alicePrivKey)
+ Thread.sleep(1_000)
+ }
+
+ node.relayUnsubscribe()
+
+ node.stop()
+ }
+}
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/Node.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/Node.kt
new file mode 100644
index 00000000..bd9170f8
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/Node.kt
@@ -0,0 +1,326 @@
+package com.example.waku
+
+import com.example.waku.events.BaseEvent
+import com.example.waku.events.EventHandler
+import com.example.waku.events.EventType
+import com.example.waku.events.MessageEvent
+import com.example.waku.messages.Message
+import com.example.waku.store.StoreQuery
+import com.example.waku.store.StoreResponse
+import gowaku.Gowaku
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+
+/**
+ * @param c Config containing the options used to initialize a node. It can be `null` to use
+ * defaults. All the keys from the configuration are optional
+ */
+class Node(c: Config? = null) {
+ var running: Boolean = false
+ lateinit var signalHandler: gowaku.SignalHandler
+ lateinit var eventHandler: EventHandler
+
+ init {
+ val configJson = Json.encodeToString(c)
+ val response = Gowaku.newNode(configJson)
+ handleResponse(response)
+
+ signalHandler = DefaultEventHandler()
+ Gowaku.setMobileSignalHandler(signalHandler)
+ }
+
+ inner class DefaultEventHandler : gowaku.SignalHandler {
+ override fun handleSignal(signalJson: String) {
+ if (eventHandler != null) {
+ val evt = Json {
+ ignoreUnknownKeys = true; coerceInputValues = true
+ }.decodeFromString(signalJson)
+ when (evt.type) {
+ EventType.Message -> {
+ try {
+ val msgEvt = Json.decodeFromString(signalJson)
+ eventHandler.handleEvent(msgEvt)
+ } catch (e: Exception) {
+ // TODO: do something
+ }
+ }
+ else -> {
+ // TODO: do something with invalid message type
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Register callback to act as event handler and receive application signals which are used to
+ * react to asyncronous events in waku.
+ * @param handler event handler
+ */
+fun Node.setEventHandler(handler: EventHandler) {
+ eventHandler = handler
+}
+
+/**
+ * Initialize a node mounting all the protocols that were enabled during the node instantiation.
+ */
+fun Node.start() {
+ if (running) {
+ return
+ }
+
+ val response = Gowaku.start()
+ handleResponse(response)
+ running = true
+}
+
+/**
+ * Stops a node
+ */
+fun Node.stop() {
+ if (!running) {
+ return
+ }
+
+ val response = Gowaku.stop()
+ handleResponse(response)
+ running = false
+}
+
+/**
+ * Obtain the peer ID of the go-waku node.
+ * @return The base58 encoded peer Id
+ */
+fun Node.peerID(): String {
+ val response = Gowaku.peerID()
+ return handleResponse(response)
+}
+
+/**
+ * Obtain number of connected peers
+ * @return The number of peers connected to this node
+ */
+fun Node.peerCnt(): Int {
+ val response = Gowaku.peerCnt()
+ return handleResponse(response)
+}
+
+/**
+ * Obtain the multiaddresses the wakunode is listening to
+ * @return List of multiaddresses
+ */
+fun Node.listenAddresses(): List {
+ val response = Gowaku.listenAddresses()
+ return handleResponse>(response)
+}
+
+/**
+ * Add node multiaddress and protocol to the wakunode peerstore
+ * @param address multiaddress of the peer being added
+ * @param protocolID protocol supported by the peer
+ * @return Base58 encoded peer Id
+ */
+fun Node.addPeer(address: String, protocolID: String): String {
+ val response = Gowaku.addPeer(address, protocolID)
+ return handleResponse(response)
+}
+
+/**
+ * Connect to peer at multiaddress
+ * @param address multiaddress of the peer being dialed
+ * @param ms max duration in milliseconds this function might take to execute. If the function
+ * execution takes longer than this value, the execution will be canceled and an error
+ * returned. Use 0 for unlimited duration
+ */
+fun Node.connect(address: String, ms: Long = 0) {
+ val response = Gowaku.connect(address, ms)
+ handleResponse(response)
+}
+
+/**
+ * Close connection to a known peer by peerID
+ * @param peerID Base58 encoded peer ID to disconnect
+ */
+fun Node.disconnect(peerID: String) {
+ val response = Gowaku.disconnect(peerID)
+ handleResponse(response)
+}
+
+/**
+ * Subscribe to a WakuRelay topic to receive messages
+ * @param topic Pubsub topic to subscribe to. Use NULL for subscribing to the default pubsub topic
+ */
+fun Node.relaySubscribe(topic: String? = null) {
+ val response = Gowaku.relaySubscribe(topic)
+ handleResponse(response)
+}
+
+/**
+ * Publish a message using waku relay
+ * @param msg Message to broadcast
+ * @param topic Pubsub topic. Set to `null` to use the default pubsub topic
+ * @param ms 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
+ * @return message id
+ */
+fun Node.relayPublish(msg: Message, topic: String? = null, ms: Long = 0): String {
+ val jsonMsg = Json.encodeToString(msg)
+ val response = Gowaku.relayPublish(jsonMsg, topic, ms)
+ return handleResponse(response)
+}
+
+/**
+ * Publish a message using waku lightpush
+ * @param msg Message to broadcast
+ * @param topic Pubsub topic. Set to `null` to use the default pubsub topic
+ * @param peerID ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
+ * @param ms 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
+ * @return message id
+ */
+fun Node.lightpushPublish(msg: Message, topic: String? = null, peerID: String? = null, ms: Long = 0): String {
+ val jsonMsg = Json.encodeToString(msg)
+ val response = Gowaku.lightpushPublish(jsonMsg, topic, peerID, ms)
+ return handleResponse(response)
+}
+
+/**
+ * Publish a message encrypted with an secp256k1 public key using waku relay
+ * @param msg Message to broadcast
+ * @param publicKey Secp256k1 public key
+ * @param optionalSigningKey Optional secp256k1 private key for signing the message
+ * @param topic Pubsub topic. Set to `null` to use the default pubsub topic
+ * @param ms 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
+ * @return message id
+ */
+fun Node.relayPublishEncodeAsymmetric(
+ msg: Message,
+ publicKey: String,
+ optionalSigningKey: String? = null,
+ topic: String? = null,
+ ms: Long = 0
+): String {
+ val jsonMsg = Json.encodeToString(msg)
+ val response =
+ Gowaku.relayPublishEncodeAsymmetric(jsonMsg, topic, publicKey, optionalSigningKey, ms)
+ return handleResponse(response)
+}
+
+/**
+ * Publish a message encrypted with an secp256k1 public key using waku lightpush
+ * @param msg Message to broadcast
+ * @param publicKey Secp256k1 public key
+ * @param optionalSigningKey Optional secp256k1 private key for signing the message
+ * @param topic Pubsub topic. Set to `null` to use the default pubsub topic
+ * @param peerID ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
+ * @param ms 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
+ * @return message id
+ */
+fun Node.lightpushPublishEncodeAsymmetric(
+ msg: Message,
+ publicKey: String,
+ optionalSigningKey: String? = null,
+ topic: String? = null,
+ peerID: String? = null,
+ ms: Long = 0
+): String {
+ val jsonMsg = Json.encodeToString(msg)
+ val response =
+ Gowaku.lightpushPublishEncodeAsymmetric(jsonMsg, topic, peerID, publicKey, optionalSigningKey, ms)
+ return handleResponse(response)
+}
+
+/**
+ * Publish a message encrypted with a 32 byte symmetric key using waku relay
+ * @param msg Message to broadcast
+ * @param symmetricKey 32 byte hex string containing a symmetric key
+ * @param optionalSigningKey Optional secp256k1 private key for signing the message
+ * @param topic Pubsub topic. Set to `null` to use the default pubsub topic
+ * @param ms 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
+ * @return message id
+ */
+fun Node.relayPublishEncodeSymmetric(
+ msg: Message,
+ symmetricKey: String,
+ optionalSigningKey: String? = null,
+ topic: String? = null,
+ ms: Long = 0
+): String {
+ val jsonMsg = Json.encodeToString(msg)
+ val response =
+ Gowaku.relayPublishEncodeSymmetric(jsonMsg, topic, symmetricKey, optionalSigningKey, ms)
+ return handleResponse(response)
+}
+
+/**
+ * Publish a message encrypted with a 32 byte symmetric key using waku lightpush
+ * @param msg Message to broadcast
+ * @param symmetricKey 32 byte hex string containing a symmetric key
+ * @param optionalSigningKey Optional secp256k1 private key for signing the message
+ * @param topic Pubsub topic. Set to `null` to use the default pubsub topic
+ * @param peerID ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
+ * @param ms 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
+ * @return message id
+ */
+fun Node.lightpushPublishEncodeSymmetric(
+ msg: Message,
+ symmetricKey: String,
+ optionalSigningKey: String? = null,
+ topic: String? = null,
+ peerID: String? = null,
+ ms: Long = 0
+): String {
+ val jsonMsg = Json.encodeToString(msg)
+ val response =
+ Gowaku.lightpushPublishEncodeSymmetric(jsonMsg, topic, peerID, symmetricKey, optionalSigningKey, ms)
+ return handleResponse(response)
+}
+
+/**
+ * Determine if there are enough peers to publish a message on a topic
+ * @param topic pubsub topic to verify. Use NULL to verify the number of peers in the default pubsub topic
+ * @return boolean indicating if there are enough peers or not
+ */
+fun Node.relayEnoughPeers(topic: String? = null): Boolean {
+ val response = Gowaku.relayEnoughPeers(topic)
+ return handleResponse(response)
+}
+
+/**
+ * Closes the pubsub subscription to a pubsub topic
+ * @param topic Pubsub topic to unsubscribe. Use NULL for unsubscribe from the default pubsub topic
+ */
+fun Node.relayUnsubscribe(topic: String? = null) {
+ val response = Gowaku.relayUnsubscribe(topic)
+ handleResponse(response)
+}
+
+/**
+ * Get peers
+ * @return Retrieve list of peers and their supported protocols
+ */
+fun Node.peers(): List {
+ val response = Gowaku.peers()
+ return handleResponse>(response)
+}
+
+/**
+ * Query message history
+ * @param query Query
+ * @param peerID PeerID to ask the history from. Use NULL to automatically select a peer
+ * @param ms 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
+ * @return Response containing the messages and cursor for pagination. Use the cursor in further queries to retrieve more results
+ */
+fun Node.storeQuery(query: StoreQuery, peerID: String?, ms: Long = 0): StoreResponse{
+ val queryJSON = Json.encodeToString(query)
+ val response = Gowaku.storeQuery(queryJSON, peerID, ms)
+ return handleResponse(response)
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/Peer.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/Peer.kt
new file mode 100644
index 00000000..e9c34514
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/Peer.kt
@@ -0,0 +1,11 @@
+package com.example.waku
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Peer(
+ val peerID: String?,
+ val connected: Boolean,
+ val protocols: List,
+ val addrs: List,
+)
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/Utils.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/Utils.kt
new file mode 100644
index 00000000..57422596
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/Utils.kt
@@ -0,0 +1,33 @@
+package com.example.waku
+
+import gowaku.Gowaku
+
+/**
+ * Get default pubsub topic
+ * @return Default pubsub topic used for exchanging waku messages defined in RFC 10
+ */
+fun DefaultPubsubTopic(): String {
+ return Gowaku.defaultPubsubTopic()
+}
+
+/**
+ * Create a content topic string
+ * @param applicationName
+ * @param applicationVersion
+ * @param contentTopicName
+ * @param encoding
+ * @return Content topic string according to RFC 23
+ */
+fun ContentTopic(applicationName: String, applicationVersion: Long, contentTopicName: String, encoding: String): String{
+ return Gowaku.contentTopic(applicationName, applicationVersion, contentTopicName, encoding)
+}
+
+/**
+ * Create a pubsub topic string
+ * @param name
+ * @param encoding
+ * @return Pubsub topic string according to RFC 23
+ */
+fun PubsubTopic(name: String, encoding: String): String {
+ return Gowaku.pubsubTopic(name, encoding)
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/events/BaseEvent.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/events/BaseEvent.kt
new file mode 100644
index 00000000..a29dafb9
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/events/BaseEvent.kt
@@ -0,0 +1,6 @@
+package com.example.waku.events
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BaseEvent(override val type: EventType) : Event
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/events/Event.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/events/Event.kt
new file mode 100644
index 00000000..610324e5
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/events/Event.kt
@@ -0,0 +1,5 @@
+package com.example.waku.events
+
+interface Event {
+ val type: EventType
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/events/EventHandler.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/events/EventHandler.kt
new file mode 100644
index 00000000..dfb83a3a
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/events/EventHandler.kt
@@ -0,0 +1,5 @@
+package com.example.waku.events
+
+interface EventHandler {
+ fun handleEvent(evt: Event)
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/events/EventType.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/events/EventType.kt
new file mode 100644
index 00000000..132d470b
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/events/EventType.kt
@@ -0,0 +1,12 @@
+package com.example.waku.events
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+enum class EventType {
+ @SerialName("unknown")
+ Unknown,
+ @SerialName("message")
+ Message
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/events/MessageEvent.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/events/MessageEvent.kt
new file mode 100644
index 00000000..998cecbb
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/events/MessageEvent.kt
@@ -0,0 +1,6 @@
+package com.example.waku.events
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MessageEvent(override val type: EventType, val event: MessageEventData) : Event
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/events/MessageEventData.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/events/MessageEventData.kt
new file mode 100644
index 00000000..d2739ec2
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/events/MessageEventData.kt
@@ -0,0 +1,7 @@
+package com.example.waku.events
+
+import com.example.waku.messages.Message
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MessageEventData(val messageID: String, val pubsubTopic: String, val wakuMessage: Message)
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/messages/DecodedPayload.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/messages/DecodedPayload.kt
new file mode 100644
index 00000000..09281c01
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/messages/DecodedPayload.kt
@@ -0,0 +1,12 @@
+package com.example.waku.messages
+
+import com.example.waku.serializers.ByteArrayBase64Serializer
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class DecodedPayload(
+ val pubkey: String?,
+ val signature: String?,
+ @Serializable(with = ByteArrayBase64Serializer::class) val data: ByteArray,
+ val padding: String?
+)
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/messages/Message.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/messages/Message.kt
new file mode 100644
index 00000000..c328723f
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/messages/Message.kt
@@ -0,0 +1,40 @@
+package com.example.waku.messages
+
+import com.example.waku.handleResponse
+import com.example.waku.serializers.ByteArrayBase64Serializer
+import gowaku.Gowaku
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+@Serializable
+data class Message(
+ @Serializable(with = ByteArrayBase64Serializer::class) val payload: ByteArray,
+ val contentTopic: String? = "",
+ val version: Int? = 0,
+ val timestamp: Long? = null
+)
+
+/**
+ * Decode a waku message using an asymmetric key
+ * @param msg Message to decode
+ * @param privateKey Secp256k1 private key used to decode the message
+ * @return DecodedPayload containing the decrypted message, padding, public key and signature (if available)
+ */
+fun Message.decodeAsymmetric(privateKey: String): DecodedPayload {
+ val jsonMsg = Json.encodeToString(this)
+ val response = Gowaku.decodeAsymmetric(jsonMsg, privateKey)
+ return handleResponse(response)
+}
+
+/**
+ * Decode a waku message using a symmetric key
+ * @param msg Message to decode
+ * @param privateKey Symmetric key used to decode the message
+ * @return DecodedPayload containing the decrypted message, padding, public key and signature (if available)
+ */
+fun Message.decodeSymmetric(symmetricKey: String): DecodedPayload {
+ val jsonMsg = Json.encodeToString(this)
+ val response = Gowaku.decodeSymmetric(jsonMsg, symmetricKey)
+ return handleResponse(response)
+}
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/serializers/ByteArrayBase64Serializer.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/serializers/ByteArrayBase64Serializer.kt
new file mode 100644
index 00000000..cc08dc39
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/serializers/ByteArrayBase64Serializer.kt
@@ -0,0 +1,25 @@
+package com.example.waku.serializers
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import java.util.*
+
+@Serializer(forClass = ByteArray::class)
+object ByteArrayBase64Serializer : KSerializer {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("ByteArrayBase64Serializer", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: ByteArray) {
+ encoder.encodeString(Base64.getEncoder().encodeToString(value))
+ }
+
+ override fun deserialize(decoder: Decoder): ByteArray {
+ val text = decoder.decodeString()
+ return Base64.getDecoder().decode(text)
+ }
+}
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/store/ContentFilter.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/store/ContentFilter.kt
new file mode 100644
index 00000000..1eb1c1f5
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/store/ContentFilter.kt
@@ -0,0 +1,6 @@
+package com.example.waku.store
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ContentFilter(val contentTopic: String)
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/store/Cursor.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/store/Cursor.kt
new file mode 100644
index 00000000..5def9390
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/store/Cursor.kt
@@ -0,0 +1,13 @@
+package com.example.waku.store
+
+import com.example.waku.DefaultPubsubTopic
+import com.example.waku.serializers.ByteArrayBase64Serializer
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Cursor(
+ @Serializable(with = ByteArrayBase64Serializer::class) val digest: ByteArray,
+ val pubsubTopic: String = DefaultPubsubTopic(),
+ val receiverTime: Long = 0,
+ val senderTime: Long = 0
+)
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/store/PagingOptions.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/store/PagingOptions.kt
new file mode 100644
index 00000000..a660cc1c
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/store/PagingOptions.kt
@@ -0,0 +1,6 @@
+package com.example.waku.store
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PagingOptions(val pageSize: Int, val cursor: Cursor?, val forward: Boolean)
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/store/StoreQuery.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/store/StoreQuery.kt
new file mode 100644
index 00000000..3f181c31
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/store/StoreQuery.kt
@@ -0,0 +1,13 @@
+package com.example.waku.store
+
+import com.example.waku.DefaultPubsubTopic
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class StoreQuery(
+ var pubsubTopic: String? = DefaultPubsubTopic(),
+ var startTime: Long? = null,
+ var endTime: Long? = null,
+ var contentFilter: List?,
+ var pagingOptions: PagingOptions?
+)
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/java/com/example/waku/store/StoreResponse.kt b/examples/android-kotlin/app/src/main/java/com/example/waku/store/StoreResponse.kt
new file mode 100644
index 00000000..9b0cc114
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/java/com/example/waku/store/StoreResponse.kt
@@ -0,0 +1,7 @@
+package com.example.waku.store
+
+import com.example.waku.messages.Message
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class StoreResponse(val messages: List, val pagingOptions: PagingOptions?)
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/examples/android-kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..2b068d11
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/drawable/ic_launcher_background.xml b/examples/android-kotlin/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..07d5da9c
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/android-kotlin/app/src/main/res/layout/activity_main.xml b/examples/android-kotlin/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..08636e07
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/examples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/examples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/examples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/examples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/examples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/examples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/examples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/examples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/examples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/examples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
Binary files /dev/null and b/examples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/examples/android-kotlin/app/src/main/res/values-night/themes.xml b/examples/android-kotlin/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..709ac1f9
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/values/colors.xml b/examples/android-kotlin/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/values/strings.xml b/examples/android-kotlin/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..f1c856d7
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Waku
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/main/res/values/themes.xml b/examples/android-kotlin/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..e3898535
--- /dev/null
+++ b/examples/android-kotlin/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/android-kotlin/app/src/test/java/com/example/waku/ExampleUnitTest.kt b/examples/android-kotlin/app/src/test/java/com/example/waku/ExampleUnitTest.kt
new file mode 100644
index 00000000..7fd1768a
--- /dev/null
+++ b/examples/android-kotlin/app/src/test/java/com/example/waku/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.waku
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/examples/android-kotlin/build.gradle b/examples/android-kotlin/build.gradle
new file mode 100644
index 00000000..635ff36a
--- /dev/null
+++ b/examples/android-kotlin/build.gradle
@@ -0,0 +1,14 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.1.0' apply false
+ id 'com.android.library' version '7.1.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
+ id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.20'
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+dependencies {
+}
\ No newline at end of file
diff --git a/examples/android-kotlin/gradle.properties b/examples/android-kotlin/gradle.properties
new file mode 100644
index 00000000..cd0519bb
--- /dev/null
+++ b/examples/android-kotlin/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/examples/android-kotlin/gradle/wrapper/gradle-wrapper.properties b/examples/android-kotlin/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..e580f25c
--- /dev/null
+++ b/examples/android-kotlin/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Apr 05 10:08:00 AST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/examples/android-kotlin/gradlew b/examples/android-kotlin/gradlew
new file mode 100755
index 00000000..4f906e0c
--- /dev/null
+++ b/examples/android-kotlin/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/examples/android-kotlin/gradlew.bat b/examples/android-kotlin/gradlew.bat
new file mode 100644
index 00000000..ac1b06f9
--- /dev/null
+++ b/examples/android-kotlin/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/examples/android-kotlin/settings.gradle b/examples/android-kotlin/settings.gradle
new file mode 100644
index 00000000..6e74f040
--- /dev/null
+++ b/examples/android-kotlin/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "Waku"
+include ':app'
diff --git a/examples/c-bindings/build/.gitignore b/examples/c-bindings/build/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/examples/c-bindings/build/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/examples/waku-csharp/waku-csharp/Program.cs b/examples/waku-csharp/waku-csharp/Program.cs
index 9674c261..b192217b 100644
--- a/examples/waku-csharp/waku-csharp/Program.cs
+++ b/examples/waku-csharp/waku-csharp/Program.cs
@@ -23,7 +23,7 @@ void SignalHandler(Waku.Event evt)
if (evt.type == Waku.EventType.Message)
{
Waku.MessageEvent msgEvt = (Waku.MessageEvent)evt; // Downcast to specific event type to access the event data
- Waku.DecodedPayload decodedPayload = node.RelayPublishDecodeAsymmetric(msgEvt.data.wakuMessage, bobPrivKey);
+ Waku.DecodedPayload decodedPayload = node.DecodeAsymmetric(msgEvt.data.wakuMessage, bobPrivKey);
string message = Encoding.UTF8.GetString(decodedPayload.data);
Console.WriteLine(">>> Message: " + message + " from: " + decodedPayload.pubkey);
diff --git a/examples/waku-csharp/waku-csharp/Waku.Node.cs b/examples/waku-csharp/waku-csharp/Waku.Node.cs
index c6c6f42e..329c99e6 100644
--- a/examples/waku-csharp/waku-csharp/Waku.Node.cs
+++ b/examples/waku-csharp/waku-csharp/Waku.Node.cs
@@ -12,6 +12,7 @@ namespace Waku
public string? nodeKey { get; set; }
public int? keepAliveInterval { get; set; }
public bool? relay { get; set; }
+ public int? minPeersToPublish {get; set; }
}
public enum EventType
@@ -45,8 +46,6 @@ namespace Waku
public class Event
{
- public int nodeId { get; set; }
-
public EventType type { get; set; } = EventType.Unknown;
}
@@ -54,8 +53,6 @@ namespace Waku
{
public string messageID { get; set; } = "";
- public string subscriptionID { get; set; } = "";
-
public string pubsubTopic { get; set; } = Utils.DefaultPubsubTopic();
public Message wakuMessage { get; set; } = new Message();
@@ -194,10 +191,14 @@ namespace Waku
internal static extern IntPtr waku_start();
///
- /// Initialize a go-waku node mounting all the protocols that were enabled during the waku node initialization.
+ /// Initialize a go-waku node mounting all the protocols that were enabled during the waku node instantiation.
///
public void Start()
{
+ if(_running) {
+ return
+ }
+
IntPtr ptr = waku_start();
Response.HandleResponse(ptr);
@@ -212,6 +213,10 @@ namespace Waku
///
public void Stop()
{
+ if(!_running) {
+ return
+ }
+
IntPtr ptr = waku_stop();
Response.HandleResponse(ptr);
@@ -331,6 +336,23 @@ namespace Waku
return Response.HandleResponse(ptr, "could not obtain the message id");
}
+ [DllImport(Constants.dllName)]
+ internal static extern IntPtr waku_lightpush_publish(string messageJSON, string? topic, string? peerID, int ms);
+
+ ///
+ /// Publish a message using waku lightpush.
+ ///
+ /// Message to broadcast
+ /// Pubsub topic. Set to `null` to use the default pubsub topic
+ /// ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
+ /// 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
+ ///
+ public string LightpushPublish(Message msg, string? topic = null, string? peerID = null, int ms = 0)
+ {
+ string jsonMsg = JsonSerializer.Serialize(msg);
+ IntPtr ptr = waku_lightpush_publish(jsonMsg, topic, peerID, ms);
+ return Response.HandleResponse(ptr, "could not obtain the message id");
+ }
[DllImport(Constants.dllName)]
internal static extern IntPtr waku_relay_publish_enc_asymmetric(string messageJSON, string? topic, string publicKey, string? optionalSigningKey, int ms);
@@ -351,6 +373,26 @@ namespace Waku
return Response.HandleResponse(ptr, "could not obtain the message id");
}
+ [DllImport(Constants.dllName)]
+ internal static extern IntPtr waku_lightpush_publish_enc_asymmetric(string messageJSON, string? topic, string? peerID, string publicKey, string? optionalSigningKey, int ms);
+
+ ///
+ /// Publish a message encrypted with an secp256k1 public key using waku lightpush.
+ ///
+ /// Message to broadcast
+ /// Secp256k1 public key
+ /// Optional secp256k1 private key for signing the message
+ /// Pubsub topic. Set to `null` to use the default pubsub topic
+ /// ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
+ /// 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
+ ///
+ public string LightpushPublishEncodeAsymmetric(Message msg, string publicKey, string? optionalSigningKey = null, string? topic = null, string? peerID = null, int ms = 0)
+ {
+ string jsonMsg = JsonSerializer.Serialize(msg);
+ IntPtr ptr = waku_lightpush_publish_enc_asymmetric(jsonMsg, topic, peerID, publicKey, optionalSigningKey, ms);
+ return Response.HandleResponse(ptr, "could not obtain the message id");
+ }
+
[DllImport(Constants.dllName)]
internal static extern IntPtr waku_relay_publish_enc_symmetric(string messageJSON, string? topic, string symmetricKey, string? optionalSigningKey, int ms);
@@ -370,6 +412,26 @@ namespace Waku
return Response.HandleResponse(ptr, "could not obtain the message id");
}
+ [DllImport(Constants.dllName)]
+ internal static extern IntPtr waku_lightpush_publish_enc_symmetric(string messageJSON, string? topic, string? peerID, string symmetricKey, string? optionalSigningKey, int ms);
+
+ ///
+ /// Publish a message encrypted with a 32 bytes symmetric key using waku lightpush.
+ ///
+ /// Message to broadcast
+ /// 32 byte hex string containing a symmetric key
+ /// Optional secp256k1 private key for signing the message
+ /// Pubsub topic. Set to `null` to use the default pubsub topic
+ /// ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
+ /// 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
+ ///
+ public string RelayPublishEncodeSymmetric(Message msg, string symmetricKey, string? optionalSigningKey = null, string? topic = null, string? peerID = null, int ms = 0)
+ {
+ string jsonMsg = JsonSerializer.Serialize(msg);
+ IntPtr ptr = waku_lightpush_publish_enc_symmetric(jsonMsg, topic, peerID, symmetricKey, optionalSigningKey, ms);
+ return Response.HandleResponse(ptr, "could not obtain the message id");
+ }
+
[DllImport(Constants.dllName)]
internal static extern IntPtr waku_decode_symmetric(string messageJSON, string symmetricKey);
@@ -379,7 +441,7 @@ namespace Waku
/// Message to decode
/// Symmetric key used to decode the message
/// DecodedPayload containing the decrypted message, padding, public key and signature (if available)
- public DecodedPayload RelayPublishDecodeSymmetric(Message msg, string symmetricKey)
+ public DecodedPayload DecodeSymmetric(Message msg, string symmetricKey)
{
string jsonMsg = JsonSerializer.Serialize(msg);
IntPtr ptr = waku_decode_symmetric(jsonMsg, symmetricKey);
@@ -395,7 +457,7 @@ namespace Waku
/// Message to decode
/// Secp256k1 private key used to decode the message
/// DecodedPayload containing the decrypted message, padding, public key and signature (if available)
- public DecodedPayload RelayPublishDecodeAsymmetric(Message msg, string privateKey)
+ public DecodedPayload DecodeAsymmetric(Message msg, string privateKey)
{
string jsonMsg = JsonSerializer.Serialize(msg);
IntPtr ptr = waku_decode_asymmetric(jsonMsg, privateKey);
diff --git a/examples/waku-csharp/waku-csharp/Waku.Response.cs b/examples/waku-csharp/waku-csharp/Waku.Response.cs
index 9387ef72..f2a6f729 100644
--- a/examples/waku-csharp/waku-csharp/Waku.Response.cs
+++ b/examples/waku-csharp/waku-csharp/Waku.Response.cs
@@ -43,6 +43,7 @@ namespace Waku
return result;
}
+
internal static T HandleResponse(IntPtr ptr, string errNoValue) where T : struct
{
string strResponse = PtrToStringUtf8(ptr);
diff --git a/go.sum b/go.sum
index b06574a7..4f138de7 100644
--- a/go.sum
+++ b/go.sum
@@ -1136,6 +1136,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
diff --git a/library/api.go b/library/api.go
index be174c9b..33a2db1a 100644
--- a/library/api.go
+++ b/library/api.go
@@ -6,96 +6,14 @@ package main
*/
import "C"
import (
- "context"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "encoding/base64"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "net"
- "time"
"unsafe"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/secp256k1"
- "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"
+ mobile "github.com/status-im/go-waku/mobile"
"github.com/status-im/go-waku/waku/v2/protocol"
- "github.com/status-im/go-waku/waku/v2/protocol/pb"
)
-var wakuNode *node.WakuNode
-
-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"`
- MinPeersToPublish *int `json:"minPeersToPublish"`
-}
-
-var DefaultHost = "0.0.0.0"
-var DefaultPort = 60000
-var DefaultKeepAliveInterval = 20
-var DefaultEnableRelay = true
-var DefaultMinPeersToPublish = 0
-
-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
- }
-
- if config.MinPeersToPublish == nil {
- config.MinPeersToPublish = &DefaultMinPeersToPublish
- }
-
- return config, nil
-}
-
//export waku_new
// Initialize a waku node. Receives a JSON string containing the configuration
// for the node. It can be NULL. Example configuration:
@@ -112,198 +30,71 @@ func getConfig(configJSON *C.char) (WakuConfig, error) {
// This function will return a nodeID which should be used in all calls from this API that require
// interacting with the node.
func waku_new(configJSON *C.char) *C.char {
- if wakuNode != nil {
- return makeJSONResponse(errors.New("go-waku already initialized. stop it first"))
- }
-
- 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.WithWakuRelayAndMinPeers(*config.MinPeersToPublish))
- }
-
- ctx := context.Background()
- w, err := node.New(ctx, opts...)
-
- if err != nil {
- return makeJSONResponse(err)
- }
-
- wakuNode = w
-
- return makeJSONResponse(nil)
+ response := mobile.NewNode(C.GoString(configJSON))
+ return C.CString(response)
}
//export waku_start
// Starts the waku node
func waku_start() *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- if err := wakuNode.Start(); err != nil {
- return makeJSONResponse(err)
- }
-
- return makeJSONResponse(nil)
+ response := mobile.Start()
+ return C.CString(response)
}
//export waku_stop
// Stops a waku node
func waku_stop() *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- wakuNode.Stop()
- wakuNode = nil
-
- return makeJSONResponse(nil)
+ response := mobile.Stop()
+ return C.CString(response)
}
//export waku_peerid
// Obtain the peer ID of the waku node
func waku_peerid() *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- return prepareJSONResponse(wakuNode.ID(), nil)
+ response := mobile.PeerID()
+ return C.CString(response)
}
//export waku_listen_addresses
// Obtain the multiaddresses the wakunode is listening to
func waku_listen_addresses() *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- var addresses []string
- for _, addr := range wakuNode.ListenAddresses() {
- addresses = append(addresses, addr.String())
- }
-
- return prepareJSONResponse(addresses, nil)
+ response := mobile.ListenAddresses()
+ return C.CString(response)
}
//export waku_add_peer
// Add node multiaddress and protocol to the wakunode peerstore
func waku_add_peer(address *C.char, protocolID *C.char) *C.char {
- if 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)
+ response := mobile.AddPeer(C.GoString(address), C.GoString(protocolID))
+ return C.CString(response)
}
//export waku_connect
// Connect to peer at multiaddress. if ms > 0, cancel the function execution if it takes longer than N milliseconds
func waku_connect(address *C.char, ms C.int) *C.char {
- if 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)
+ response := mobile.Connect(C.GoString(address), int(ms))
+ return C.CString(response)
}
//export waku_connect_peerid
// Connect to known peer by peerID. if ms > 0, cancel the function execution if it takes longer than N milliseconds
func waku_connect_peerid(peerID *C.char, ms C.int) *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- var ctx context.Context
- var cancel context.CancelFunc
-
- pID, err := peer.Decode(C.GoString(peerID))
- 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, pID)
- return makeJSONResponse(err)
+ response := mobile.Connect(C.GoString(peerID), int(ms))
+ return C.CString(response)
}
//export waku_disconnect
// Close connection to a known peer by peerID
func waku_disconnect(peerID *C.char) *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- pID, err := peer.Decode(C.GoString(peerID))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- err = wakuNode.ClosePeerById(pID)
- return makeJSONResponse(err)
+ response := mobile.Disconnect(C.GoString(peerID))
+ return C.CString(response)
}
//export waku_peer_cnt
// Get number of connected peers
func waku_peer_cnt() *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- return prepareJSONResponse(wakuNode.PeerCount(), nil)
+ response := mobile.PeerCnt()
+ return C.CString(response)
}
//export waku_content_topic
@@ -315,23 +106,13 @@ func waku_content_topic(applicationName *C.char, applicationVersion C.uint, cont
//export waku_pubsub_topic
// Create a pubsub topic string according to RFC 23
func waku_pubsub_topic(name *C.char, encoding *C.char) *C.char {
- return prepareJSONResponse(protocol.NewPubsubTopic(C.GoString(name), C.GoString(encoding)).String(), nil)
+ return C.CString(mobile.PubsubTopic(C.GoString(name), C.GoString(encoding)))
}
//export waku_default_pubsub_topic
// Get the default pubsub topic used in waku2: /waku/2/default-waku/proto
func waku_default_pubsub_topic() *C.char {
- return C.CString(protocol.DefaultPubsubTopic().String())
-}
-
-func getTopic(topic *C.char) string {
- result := ""
- if topic != nil {
- result = C.GoString(topic)
- } else {
- result = protocol.DefaultPubsubTopic().String()
- }
- return result
+ return C.CString(mobile.DefaultPubsubTopic())
}
//export waku_set_event_callback
@@ -339,160 +120,26 @@ func getTopic(topic *C.char) string {
// (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 waku_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(),
- }
+ mobile.SetEventCallback(cb)
}
//export waku_peers
// Retrieve the list of peers known by the waku node
func waku_peers() *C.char {
- if 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 ecdsa.PublicKey{}, errors.New("invalid public key")
- }
- return ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}, nil
+ response := mobile.Peers()
+ return C.CString(response)
}
//export waku_decode_symmetric
// Decode a waku message using a 32 bytes symmetric key. The key must be a hex encoded string with "0x" prefix
func waku_decode_symmetric(messageJSON *C.char, symmetricKey *C.char) *C.char {
- var msg pb.WakuMessage
- err := json.Unmarshal([]byte(C.GoString(messageJSON)), &msg)
- if err != nil {
- return makeJSONResponse(err)
- }
-
- if msg.Version == 0 {
- return prepareJSONResponse(msg.Payload, nil)
- } else if msg.Version > 1 {
- return makeJSONResponse(errors.New("unsupported wakumessage version"))
- }
-
- keyInfo := &node.KeyInfo{
- Kind: node.Symmetric,
- }
-
- keyInfo.SymKey, err = hexutil.Decode(C.GoString(symmetricKey))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- 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)
+ response := mobile.DecodeSymmetric(C.GoString(messageJSON), C.GoString(symmetricKey))
+ return C.CString(response)
}
//export waku_decode_asymmetric
// Decode a waku message using a secp256k1 private key. The key must be a hex encoded string with "0x" prefix
func waku_decode_asymmetric(messageJSON *C.char, privateKey *C.char) *C.char {
- var msg pb.WakuMessage
- err := json.Unmarshal([]byte(C.GoString(messageJSON)), &msg)
- if err != nil {
- return makeJSONResponse(err)
- }
-
- if msg.Version == 0 {
- return prepareJSONResponse(msg.Payload, nil)
- } else if msg.Version > 1 {
- return makeJSONResponse(errors.New("unsupported wakumessage version"))
- }
-
- keyInfo := &node.KeyInfo{
- Kind: node.Asymmetric,
- }
-
- keyBytes, err := hexutil.Decode(C.GoString(privateKey))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- keyInfo.PrivKey, err = crypto.ToECDSA(keyBytes)
- if err != nil {
- return makeJSONResponse(err)
- }
-
- 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)
+ response := mobile.DecodeAsymmetric(C.GoString(messageJSON), C.GoString(privateKey))
+ return C.CString(response)
}
-
-//export waku_utils_base64_decode
-// Decode a base64 string (useful for reading the payload from waku messages)
-func waku_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 waku_utils_base64_encode
-// Encode data to base64 (useful for creating the payload of a waku message in the
-// format understood by waku_relay_publish)
-func waku_utils_base64_encode(data *C.char) *C.char {
- str := base64.StdEncoding.EncodeToString([]byte(C.GoString(data)))
- return C.CString(string(str))
-
-}
-
-//export waku_utils_free
-// Frees a char* since all strings returned by gowaku are allocated in the C heap using malloc.
-func waku_utils_free(data *C.char) {
- C.free(unsafe.Pointer(data))
-}
-
-// TODO:
-// connected/disconnected
diff --git a/library/api_lightpush.go b/library/api_lightpush.go
index 1f31f0a7..0401b70b 100644
--- a/library/api_lightpush.go
+++ b/library/api_lightpush.go
@@ -3,46 +3,8 @@ package main
import (
"C"
- "github.com/status-im/go-waku/waku/v2/protocol/pb"
+ mobile "github.com/status-im/go-waku/mobile"
)
-import (
- "context"
- "time"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/libp2p/go-libp2p-core/peer"
- "github.com/status-im/go-waku/waku/v2/protocol/lightpush"
-)
-
-func lightpushPublish(msg pb.WakuMessage, pubsubTopic string, peerID string, ms int) (string, error) {
- if wakuNode == nil {
- return "", 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()
- }
-
- var lpOptions []lightpush.LightPushOption
- if peerID != "" {
- p, err := peer.Decode(peerID)
- if err != nil {
- return "", err
- }
- lpOptions = append(lpOptions, lightpush.WithPeer(p))
- } else {
- lpOptions = append(lpOptions, lightpush.WithAutomaticPeerSelection(wakuNode.Host()))
- }
-
- hash, err := wakuNode.Lightpush().PublishToTopic(ctx, &msg, pubsubTopic, lpOptions...)
- return hexutil.Encode(hash), err
-}
//export waku_lightpush_publish
// Publish a message using waku lightpush. Use NULL for topic to use the default pubsub topic..
@@ -50,13 +12,8 @@ func lightpushPublish(msg pb.WakuMessage, pubsubTopic string, peerID string, ms
// 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 waku_lightpush_publish(messageJSON *C.char, topic *C.char, peerID *C.char, ms C.int) *C.char {
- msg, err := wakuMessage(C.GoString(messageJSON))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- hash, err := lightpushPublish(msg, getTopic(topic), C.GoString(peerID), int(ms))
- return prepareJSONResponse(hash, err)
+ response := mobile.LightpushPublish(C.GoString(messageJSON), C.GoString(topic), C.GoString(peerID), int(ms))
+ return C.CString(response)
}
//export waku_lightpush_publish_enc_asymmetric
@@ -67,14 +24,8 @@ func waku_lightpush_publish(messageJSON *C.char, topic *C.char, peerID *C.char,
// 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 waku_lightpush_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, peerID *C.char, publicKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
- msg, err := wakuMessageAsymmetricEncoding(C.GoString(messageJSON), C.GoString(publicKey), C.GoString(optionalSigningKey))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- hash, err := lightpushPublish(msg, getTopic(topic), C.GoString(peerID), int(ms))
-
- return prepareJSONResponse(hash, err)
+ response := mobile.LightpushPublishEncodeAsymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(peerID), C.GoString(publicKey), C.GoString(optionalSigningKey), int(ms))
+ return C.CString(response)
}
//export waku_lightpush_publish_enc_symmetric
@@ -85,12 +36,6 @@ func waku_lightpush_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, p
// 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 waku_lightpush_publish_enc_symmetric(messageJSON *C.char, topic *C.char, peerID *C.char, symmetricKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
- msg, err := wakuMessageSymmetricEncoding(C.GoString(messageJSON), C.GoString(symmetricKey), C.GoString(optionalSigningKey))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- hash, err := lightpushPublish(msg, getTopic(topic), C.GoString(peerID), int(ms))
-
- return prepareJSONResponse(hash, err)
+ response := mobile.LightpushPublishEncodeSymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(peerID), C.GoString(symmetricKey), C.GoString(optionalSigningKey), int(ms))
+ return C.CString(response)
}
diff --git a/library/api_relay.go b/library/api_relay.go
index cbf67edb..36ef38bc 100644
--- a/library/api_relay.go
+++ b/library/api_relay.go
@@ -2,55 +2,16 @@ package main
import (
"C"
- "context"
- "time"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/status-im/go-waku/waku/v2/protocol"
- "github.com/status-im/go-waku/waku/v2/protocol/pb"
+ mobile "github.com/status-im/go-waku/mobile"
)
-import (
- "sync"
-
- "github.com/status-im/go-waku/waku/v2/protocol/relay"
-)
-
-var subscriptions map[string]*relay.Subscription = make(map[string]*relay.Subscription)
-var mutex sync.Mutex
//export waku_relay_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 waku_relay_enough_peers(topic *C.char) *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- topicToCheck := protocol.DefaultPubsubTopic().String()
- if topic != nil {
- topicToCheck = C.GoString(topic)
- }
-
- return prepareJSONResponse(wakuNode.Relay().EnoughPeersToPublishToTopic(topicToCheck), nil)
-}
-
-func relayPublish(msg pb.WakuMessage, pubsubTopic string, ms int) (string, error) {
- if wakuNode == nil {
- return "", 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()
- }
-
- hash, err := wakuNode.Relay().PublishToTopic(ctx, &msg, pubsubTopic)
- return hexutil.Encode(hash), err
+ response := mobile.RelayEnoughPeers(C.GoString(topic))
+ return C.CString(response)
}
//export waku_relay_publish
@@ -58,13 +19,8 @@ func relayPublish(msg pb.WakuMessage, pubsubTopic string, ms int) (string, error
// 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 waku_relay_publish(messageJSON *C.char, topic *C.char, ms C.int) *C.char {
- msg, err := wakuMessage(C.GoString(messageJSON))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- hash, err := relayPublish(msg, getTopic(topic), int(ms))
- return prepareJSONResponse(hash, err)
+ response := mobile.RelayPublish(C.GoString(messageJSON), C.GoString(topic), int(ms))
+ return C.CString(response)
}
//export waku_relay_publish_enc_asymmetric
@@ -74,14 +30,8 @@ func waku_relay_publish(messageJSON *C.char, topic *C.char, ms C.int) *C.char {
// 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 waku_relay_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, publicKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
- msg, err := wakuMessageAsymmetricEncoding(C.GoString(messageJSON), C.GoString(publicKey), C.GoString(optionalSigningKey))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- hash, err := relayPublish(msg, getTopic(topic), int(ms))
-
- return prepareJSONResponse(hash, err)
+ response := mobile.RelayPublishEncodeAsymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(publicKey), C.GoString(optionalSigningKey), int(ms))
+ return C.CString(response)
}
//export waku_relay_publish_enc_symmetric
@@ -91,14 +41,8 @@ func waku_relay_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, publi
// 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 waku_relay_publish_enc_symmetric(messageJSON *C.char, topic *C.char, symmetricKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
- msg, err := wakuMessageSymmetricEncoding(C.GoString(messageJSON), C.GoString(symmetricKey), C.GoString(optionalSigningKey))
- if err != nil {
- return makeJSONResponse(err)
- }
-
- hash, err := relayPublish(msg, getTopic(topic), int(ms))
-
- return prepareJSONResponse(hash, err)
+ response := mobile.RelayPublishEncodeSymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(symmetricKey), C.GoString(optionalSigningKey), int(ms))
+ return C.CString(response)
}
//export waku_relay_subscribe
@@ -107,68 +51,14 @@ func waku_relay_publish_enc_symmetric(messageJSON *C.char, topic *C.char, symmet
// 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 waku_relay_subscribe(topic *C.char) *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- topicToSubscribe := protocol.DefaultPubsubTopic().String()
- if topic != nil {
- topicToSubscribe = C.GoString(topic)
- }
-
- mutex.Lock()
- defer mutex.Unlock()
-
- subscription, ok := subscriptions[topicToSubscribe]
- if ok {
- return makeJSONResponse(nil)
- }
-
- subscription, err := wakuNode.Relay().SubscribeToTopic(context.Background(), topicToSubscribe)
- if err != nil {
- return makeJSONResponse(err)
- }
-
- subscriptions[topicToSubscribe] = subscription
-
- go func() {
- for envelope := range subscription.C {
- send("message", toSubscriptionMessage(envelope))
- }
- }()
-
- return makeJSONResponse(nil)
+ response := mobile.RelaySubscribe(C.GoString(topic))
+ return C.CString(response)
}
//export waku_relay_unsubscribe
// Closes the pubsub subscription to a pubsub topic. Existing subscriptions
// will not be closed, but they will stop receiving messages
func waku_relay_unsubscribe(topic *C.char) *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- topicToUnsubscribe := protocol.DefaultPubsubTopic().String()
- if topic != nil {
- topicToUnsubscribe = C.GoString(topic)
- }
-
- mutex.Lock()
- defer mutex.Unlock()
-
- subscription, ok := subscriptions[topicToUnsubscribe]
- if ok {
- return makeJSONResponse(nil)
- }
-
- subscription.Unsubscribe()
-
- delete(subscriptions, topicToUnsubscribe)
-
- err := wakuNode.Relay().Unsubscribe(context.Background(), topicToUnsubscribe)
- if err != nil {
- return makeJSONResponse(err)
- }
-
- return makeJSONResponse(nil)
+ response := mobile.RelayUnsubscribe(C.GoString(topic))
+ return C.CString(response)
}
diff --git a/library/api_store.go b/library/api_store.go
index 9afa8884..09071632 100644
--- a/library/api_store.go
+++ b/library/api_store.go
@@ -2,37 +2,9 @@ package main
import (
"C"
- "encoding/json"
- "github.com/status-im/go-waku/waku/v2/protocol/pb"
- "github.com/status-im/go-waku/waku/v2/protocol/store"
+ mobile "github.com/status-im/go-waku/mobile"
)
-import (
- "context"
- "time"
-
- "github.com/libp2p/go-libp2p-core/peer"
-)
-
-type StorePagingOptions struct {
- PageSize uint64 `json:"pageSize,omitempty"`
- Cursor *pb.Index `json:"cursor,omitempty"`
- Forward bool `json:"forward,omitempty"`
-}
-
-type StoreMessagesArgs struct {
- Topic string `json:"pubsubTopic,omitempty"`
- ContentFilters []string `json:"contentFilters,omitempty"`
- StartTime int64 `json:"startTime,omitempty"`
- EndTime int64 `json:"endTime,omitempty"`
- PagingOptions StorePagingOptions `json:"pagingOptions,omitempty"`
-}
-
-type StoreMessagesReply struct {
- Messages []*pb.WakuMessage `json:"messages,omitempty"`
- PagingInfo StorePagingOptions `json:"pagingInfo,omitempty"`
- Error string `json:"error,omitempty"`
-}
//export waku_store_query
// Query historic messages using waku store protocol.
@@ -61,66 +33,6 @@ type StoreMessagesReply struct {
// 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 waku_store_query(queryJSON *C.char, peerID *C.char, ms C.int) *C.char {
- if wakuNode == nil {
- return makeJSONResponse(ErrWakuNodeNotReady)
- }
-
- var args StoreMessagesArgs
- err := json.Unmarshal([]byte(C.GoString(queryJSON)), &args)
- if err != nil {
- return makeJSONResponse(err)
- }
-
- options := []store.HistoryRequestOption{
- store.WithAutomaticRequestId(),
- store.WithPaging(args.PagingOptions.Forward, args.PagingOptions.PageSize),
- store.WithCursor(args.PagingOptions.Cursor),
- }
-
- pid := C.GoString(peerID)
- if pid != "" {
- p, err := peer.Decode(pid)
- if err != nil {
- return makeJSONResponse(err)
- }
- options = append(options, store.WithPeer(p))
- } else {
- options = append(options, store.WithAutomaticPeerSelection())
- }
-
- reply := StoreMessagesReply{}
-
- 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()
- }
-
- res, err := wakuNode.Store().Query(
- ctx,
- store.Query{
- Topic: args.Topic,
- ContentTopics: args.ContentFilters,
- StartTime: args.StartTime,
- EndTime: args.EndTime,
- },
- options...,
- )
-
- if err != nil {
- reply.Error = err.Error()
- return prepareJSONResponse(reply, nil)
- }
- reply.Messages = res.Messages
- reply.PagingInfo = StorePagingOptions{
- PageSize: args.PagingOptions.PageSize,
- Cursor: res.Cursor(),
- Forward: args.PagingOptions.Forward,
- }
-
- return prepareJSONResponse(reply, nil)
+ response := mobile.StoreQuery(C.GoString(queryJSON), C.GoString(peerID), int(ms))
+ return C.CString(response)
}
diff --git a/library/api_utils.go b/library/api_utils.go
new file mode 100644
index 00000000..5db1618d
--- /dev/null
+++ b/library/api_utils.go
@@ -0,0 +1,37 @@
+package main
+
+/*
+#include
+#include
+*/
+import "C"
+import (
+ "encoding/base64"
+ "unsafe"
+)
+
+//export waku_utils_base64_decode
+// Decode a base64 string (useful for reading the payload from waku messages)
+func waku_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 waku_utils_base64_encode
+// Encode data to base64 (useful for creating the payload of a waku message in the
+// format understood by waku_relay_publish)
+func waku_utils_base64_encode(data *C.char) *C.char {
+ str := base64.StdEncoding.EncodeToString([]byte(C.GoString(data)))
+ return C.CString(string(str))
+
+}
+
+//export waku_utils_free
+// Frees a char* since all strings returned by gowaku are allocated in the C heap using malloc.
+func waku_utils_free(data *C.char) {
+ C.free(unsafe.Pointer(data))
+}
diff --git a/library/response.go b/library/response.go
index 1d538cb9..9fc13613 100644
--- a/library/response.go
+++ b/library/response.go
@@ -5,7 +5,7 @@ import (
"encoding/json"
)
-type JSONResponse struct {
+type jsonResponse struct {
Error *string `json:"error,omitempty"`
Result interface{} `json:"result"`
}
@@ -14,14 +14,14 @@ func prepareJSONResponse(result interface{}, err error) *C.char {
if err != nil {
errStr := err.Error()
- errResponse := JSONResponse{
+ errResponse := jsonResponse{
Error: &errStr,
}
response, _ := json.Marshal(&errResponse)
return C.CString(string(response))
}
- data, err := json.Marshal(JSONResponse{Result: result})
+ data, err := json.Marshal(jsonResponse{Result: result})
if err != nil {
return prepareJSONResponse(nil, err)
}
@@ -35,7 +35,7 @@ func makeJSONResponse(err error) *C.char {
errString = &errStr
}
- out := JSONResponse{
+ out := jsonResponse{
Error: errString,
}
outBytes, _ := json.Marshal(out)
diff --git a/mobile/README.md b/mobile/README.md
new file mode 100644
index 00000000..510031a5
--- /dev/null
+++ b/mobile/README.md
@@ -0,0 +1,31 @@
+# Mobile
+
+Package mobile implements [gomobile](https://github.com/golang/mobile) bindings for go-waku.
+
+## Usage
+
+For properly using this package, please refer to Makefile in the root of `go-waku` directory.
+
+To manually build library, run following commands:
+
+### iOS
+
+```
+gomobile init
+gomobile bind -v -target=ios -ldflags="-s -w" github.com/status-im/go-waku/mobile
+```
+This will produce `gowaku.framework` file in the current directory, which can be used in iOS project.
+
+### Android
+
+```
+export ANDROID_NDK_HOME=/path/to/android/ndk
+export ANDROID_HOME=/path/to/android/sdk/
+gomobile init
+gomobile bind -v -target=android -ldflags="-s -w" github.com/status-im/go-waku/mobile
+```
+This will generate `gowaku.aar` file in the current dir.
+
+## Notes
+
+See [https://github.com/golang/go/wiki/Mobile](https://github.com/golang/go/wiki/Mobile) for more information on `gomobile` usage.
diff --git a/mobile/api.go b/mobile/api.go
new file mode 100644
index 00000000..0237ffbb
--- /dev/null
+++ b/mobile/api.go
@@ -0,0 +1,404 @@
+package gowaku
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/crypto/secp256k1"
+ "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"
+)
+
+var wakuNode *node.WakuNode
+
+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
+}
+
+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"`
+ MinPeersToPublish *int `json:"minPeersToPublish"`
+}
+
+var defaultHost = "0.0.0.0"
+var defaultPort = 60000
+var defaultKeepAliveInterval = 20
+var defaultEnableRelay = true
+var defaultMinPeersToPublish = 0
+
+func getConfig(configJSON string) (wakuConfig, error) {
+ var config wakuConfig
+ if configJSON != "" {
+ err := json.Unmarshal([]byte(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
+ }
+
+ if config.MinPeersToPublish == nil {
+ config.MinPeersToPublish = &defaultMinPeersToPublish
+ }
+
+ return config, nil
+}
+
+func NewNode(configJSON string) string {
+ if wakuNode != nil {
+ return makeJSONResponse(errors.New("go-waku already initialized. stop it first"))
+ }
+
+ 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.WithWakuRelayAndMinPeers(*config.MinPeersToPublish))
+ }
+
+ ctx := context.Background()
+ w, err := node.New(ctx, opts...)
+
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ wakuNode = w
+
+ return makeJSONResponse(nil)
+}
+
+func Start() string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ if err := wakuNode.Start(); err != nil {
+ return makeJSONResponse(err)
+ }
+
+ return makeJSONResponse(nil)
+}
+
+func Stop() string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ wakuNode.Stop()
+ wakuNode = nil
+
+ return makeJSONResponse(nil)
+}
+
+func PeerID() string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ return prepareJSONResponse(wakuNode.ID(), nil)
+}
+
+func ListenAddresses() string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ var addresses []string
+ for _, addr := range wakuNode.ListenAddresses() {
+ addresses = append(addresses, addr.String())
+ }
+
+ return prepareJSONResponse(addresses, nil)
+}
+
+func AddPeer(address string, protocolID string) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ ma, err := multiaddr.NewMultiaddr(address)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ peerID, err := wakuNode.AddPeer(ma, p2pproto.ID(protocolID))
+ return prepareJSONResponse(peerID, err)
+}
+
+func Connect(address string, ms int) string {
+ if 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, address)
+ return makeJSONResponse(err)
+}
+
+func ConnectPeerID(peerID string, ms int) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ var ctx context.Context
+ var cancel context.CancelFunc
+
+ pID, err := peer.Decode(peerID)
+ 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, pID)
+ return makeJSONResponse(err)
+}
+
+func Disconnect(peerID string) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ pID, err := peer.Decode(peerID)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ err = wakuNode.ClosePeerById(pID)
+ return makeJSONResponse(err)
+}
+
+func PeerCnt() string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ return prepareJSONResponse(wakuNode.PeerCount(), nil)
+}
+
+func ContentTopic(applicationName string, applicationVersion int, contentTopicName string, encoding string) string {
+ return protocol.NewContentTopic(applicationName, uint(applicationVersion), contentTopicName, encoding).String()
+}
+
+func PubsubTopic(name string, encoding string) string {
+ return protocol.NewPubsubTopic(name, encoding).String()
+}
+
+func DefaultPubsubTopic() string {
+ return protocol.DefaultPubsubTopic().String()
+}
+
+func getTopic(topic string) string {
+ if topic == "" {
+ return protocol.DefaultPubsubTopic().String()
+ }
+ return topic
+}
+
+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(),
+ }
+}
+
+func Peers() string {
+ if 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 ecdsa.PublicKey{}, errors.New("invalid public key")
+ }
+ return ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}, nil
+}
+
+func DecodeSymmetric(messageJSON string, symmetricKey string) string {
+ var msg pb.WakuMessage
+ err := json.Unmarshal([]byte(messageJSON), &msg)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ if msg.Version == 0 {
+ return prepareJSONResponse(msg.Payload, nil)
+ } else if msg.Version > 1 {
+ return makeJSONResponse(errors.New("unsupported wakumessage version"))
+ }
+
+ keyInfo := &node.KeyInfo{
+ Kind: node.Symmetric,
+ }
+
+ keyInfo.SymKey, err = hexutil.Decode(symmetricKey)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ 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)
+}
+
+func DecodeAsymmetric(messageJSON string, privateKey string) string {
+ var msg pb.WakuMessage
+ err := json.Unmarshal([]byte(messageJSON), &msg)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ if msg.Version == 0 {
+ return prepareJSONResponse(msg.Payload, nil)
+ } else if msg.Version > 1 {
+ return makeJSONResponse(errors.New("unsupported wakumessage version"))
+ }
+
+ keyInfo := &node.KeyInfo{
+ Kind: node.Asymmetric,
+ }
+
+ keyBytes, err := hexutil.Decode(privateKey)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ keyInfo.PrivKey, err = crypto.ToECDSA(keyBytes)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ 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)
+}
diff --git a/mobile/api_lightpush.go b/mobile/api_lightpush.go
new file mode 100644
index 00000000..b36c036f
--- /dev/null
+++ b/mobile/api_lightpush.go
@@ -0,0 +1,74 @@
+package gowaku
+
+import (
+ "context"
+ "time"
+
+ "github.com/status-im/go-waku/waku/v2/protocol/pb"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/libp2p/go-libp2p-core/peer"
+ "github.com/status-im/go-waku/waku/v2/protocol/lightpush"
+)
+
+func lightpushPublish(msg pb.WakuMessage, pubsubTopic string, peerID string, ms int) (string, error) {
+ if wakuNode == nil {
+ return "", 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()
+ }
+
+ var lpOptions []lightpush.LightPushOption
+ if peerID != "" {
+ p, err := peer.Decode(peerID)
+ if err != nil {
+ return "", err
+ }
+ lpOptions = append(lpOptions, lightpush.WithPeer(p))
+ } else {
+ lpOptions = append(lpOptions, lightpush.WithAutomaticPeerSelection(wakuNode.Host()))
+ }
+
+ hash, err := wakuNode.Lightpush().PublishToTopic(ctx, &msg, pubsubTopic, lpOptions...)
+ return hexutil.Encode(hash), err
+}
+
+func LightpushPublish(messageJSON string, topic string, peerID string, ms int) string {
+ msg, err := wakuMessage(messageJSON)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ hash, err := lightpushPublish(msg, getTopic(topic), peerID, ms)
+ return prepareJSONResponse(hash, err)
+}
+
+func LightpushPublishEncodeAsymmetric(messageJSON string, topic string, peerID string, publicKey string, optionalSigningKey string, ms int) string {
+ msg, err := wakuMessageAsymmetricEncoding(messageJSON, publicKey, optionalSigningKey)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ hash, err := lightpushPublish(msg, getTopic(topic), peerID, ms)
+
+ return prepareJSONResponse(hash, err)
+}
+
+func LightpushPublishEncodeSymmetric(messageJSON string, topic string, peerID string, symmetricKey string, optionalSigningKey string, ms int) string {
+ msg, err := wakuMessageSymmetricEncoding(messageJSON, symmetricKey, optionalSigningKey)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ hash, err := lightpushPublish(msg, getTopic(topic), peerID, ms)
+
+ return prepareJSONResponse(hash, err)
+}
diff --git a/mobile/api_relay.go b/mobile/api_relay.go
new file mode 100644
index 00000000..4f7b3802
--- /dev/null
+++ b/mobile/api_relay.go
@@ -0,0 +1,138 @@
+package gowaku
+
+import (
+ "context"
+ "time"
+
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "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 subscriptions map[string]*relay.Subscription = make(map[string]*relay.Subscription)
+var mutex sync.Mutex
+
+func RelayEnoughPeers(topic string) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ topicToCheck := protocol.DefaultPubsubTopic().String()
+ if topic != "" {
+ topicToCheck = topic
+ }
+
+ return prepareJSONResponse(wakuNode.Relay().EnoughPeersToPublishToTopic(topicToCheck), nil)
+}
+
+func relayPublish(msg pb.WakuMessage, pubsubTopic string, ms int) (string, error) {
+ if wakuNode == nil {
+ return "", 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()
+ }
+
+ hash, err := wakuNode.Relay().PublishToTopic(ctx, &msg, pubsubTopic)
+ return hexutil.Encode(hash), err
+}
+
+func RelayPublish(messageJSON string, topic string, ms int) string {
+ msg, err := wakuMessage(messageJSON)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ hash, err := relayPublish(msg, getTopic(topic), int(ms))
+ return prepareJSONResponse(hash, err)
+}
+
+func RelayPublishEncodeAsymmetric(messageJSON string, topic string, publicKey string, optionalSigningKey string, ms int) string {
+ msg, err := wakuMessageAsymmetricEncoding(messageJSON, publicKey, optionalSigningKey)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ hash, err := relayPublish(msg, getTopic(topic), int(ms))
+
+ return prepareJSONResponse(hash, err)
+}
+
+func RelayPublishEncodeSymmetric(messageJSON string, topic string, symmetricKey string, optionalSigningKey string, ms int) string {
+ msg, err := wakuMessageSymmetricEncoding(messageJSON, symmetricKey, optionalSigningKey)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ hash, err := relayPublish(msg, getTopic(topic), int(ms))
+
+ return prepareJSONResponse(hash, err)
+}
+
+func RelaySubscribe(topic string) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ topicToSubscribe := getTopic(topic)
+
+ mutex.Lock()
+ defer mutex.Unlock()
+
+ subscription, ok := subscriptions[topicToSubscribe]
+ if ok {
+ return makeJSONResponse(nil)
+ }
+
+ subscription, err := wakuNode.Relay().SubscribeToTopic(context.Background(), topicToSubscribe)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ subscriptions[topicToSubscribe] = subscription
+
+ go func() {
+ for envelope := range subscription.C {
+ send("message", toSubscriptionMessage(envelope))
+ }
+ }()
+
+ return makeJSONResponse(nil)
+}
+
+func RelayUnsubscribe(topic string) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ topicToUnsubscribe := getTopic(topic)
+
+ mutex.Lock()
+ defer mutex.Unlock()
+
+ subscription, ok := subscriptions[topicToUnsubscribe]
+ if ok {
+ return makeJSONResponse(nil)
+ }
+
+ subscription.Unsubscribe()
+
+ delete(subscriptions, topicToUnsubscribe)
+
+ err := wakuNode.Relay().Unsubscribe(context.Background(), topicToUnsubscribe)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ return makeJSONResponse(nil)
+}
diff --git a/mobile/api_store.go b/mobile/api_store.go
new file mode 100644
index 00000000..47999c85
--- /dev/null
+++ b/mobile/api_store.go
@@ -0,0 +1,99 @@
+package gowaku
+
+import (
+ "C"
+ "encoding/json"
+
+ "github.com/status-im/go-waku/waku/v2/protocol/pb"
+ "github.com/status-im/go-waku/waku/v2/protocol/store"
+)
+import (
+ "context"
+ "time"
+
+ "github.com/libp2p/go-libp2p-core/peer"
+)
+
+type storePagingOptions struct {
+ PageSize uint64 `json:"pageSize,omitempty"`
+ Cursor *pb.Index `json:"cursor,omitempty"`
+ Forward bool `json:"forward,omitempty"`
+}
+
+type storeMessagesArgs struct {
+ Topic string `json:"pubsubTopic,omitempty"`
+ ContentFilters []string `json:"contentFilters,omitempty"`
+ StartTime int64 `json:"startTime,omitempty"`
+ EndTime int64 `json:"endTime,omitempty"`
+ PagingOptions storePagingOptions `json:"pagingOptions,omitempty"`
+}
+
+type storeMessagesReply struct {
+ Messages []*pb.WakuMessage `json:"messages,omitempty"`
+ PagingInfo storePagingOptions `json:"pagingInfo,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+func StoreQuery(queryJSON string, peerID string, ms int) string {
+ if wakuNode == nil {
+ return makeJSONResponse(errWakuNodeNotReady)
+ }
+
+ var args storeMessagesArgs
+ err := json.Unmarshal([]byte(queryJSON), &args)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+
+ options := []store.HistoryRequestOption{
+ store.WithAutomaticRequestId(),
+ store.WithPaging(args.PagingOptions.Forward, args.PagingOptions.PageSize),
+ store.WithCursor(args.PagingOptions.Cursor),
+ }
+
+ if peerID != "" {
+ p, err := peer.Decode(peerID)
+ if err != nil {
+ return makeJSONResponse(err)
+ }
+ options = append(options, store.WithPeer(p))
+ } else {
+ options = append(options, store.WithAutomaticPeerSelection())
+ }
+
+ reply := storeMessagesReply{}
+
+ 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()
+ }
+
+ res, err := wakuNode.Store().Query(
+ ctx,
+ store.Query{
+ Topic: args.Topic,
+ ContentTopics: args.ContentFilters,
+ StartTime: args.StartTime,
+ EndTime: args.EndTime,
+ },
+ options...,
+ )
+
+ if err != nil {
+ reply.Error = err.Error()
+ return prepareJSONResponse(reply, nil)
+ }
+ reply.Messages = res.Messages
+ reply.PagingInfo = storePagingOptions{
+ PageSize: args.PagingOptions.PageSize,
+ Cursor: res.Cursor(),
+ Forward: args.PagingOptions.Forward,
+ }
+
+ return prepareJSONResponse(reply, nil)
+}
diff --git a/library/encoding.go b/mobile/encoding.go
similarity index 99%
rename from library/encoding.go
rename to mobile/encoding.go
index d91d41e2..e9e8b03d 100644
--- a/library/encoding.go
+++ b/mobile/encoding.go
@@ -1,4 +1,4 @@
-package main
+package gowaku
import (
"encoding/json"
diff --git a/library/ios.go b/mobile/ios.go
similarity index 83%
rename from library/ios.go
rename to mobile/ios.go
index 25c4d2ad..f1a61e71 100644
--- a/library/ios.go
+++ b/mobile/ios.go
@@ -1,6 +1,7 @@
+//go:build darwin && cgo
// +build darwin,cgo
-package main
+package gowaku
/*
#cgo CFLAGS: -x objective-c
diff --git a/mobile/response.go b/mobile/response.go
new file mode 100644
index 00000000..cf799baf
--- /dev/null
+++ b/mobile/response.go
@@ -0,0 +1,41 @@
+package gowaku
+
+import "encoding/json"
+
+type jsonResponse struct {
+ Error *string `json:"error,omitempty"`
+ Result interface{} `json:"result"`
+}
+
+func prepareJSONResponse(result interface{}, err error) string {
+
+ if err != nil {
+ errStr := err.Error()
+ errResponse := jsonResponse{
+ Error: &errStr,
+ }
+ response, _ := json.Marshal(&errResponse)
+ return string(response)
+ }
+
+ data, err := json.Marshal(jsonResponse{Result: result})
+ if err != nil {
+ return prepareJSONResponse(nil, err)
+ }
+ return string(data)
+}
+
+func makeJSONResponse(err error) string {
+ var errString *string = nil
+ if err != nil {
+ errStr := err.Error()
+ errString = &errStr
+ }
+
+ out := jsonResponse{
+ Error: errString,
+ }
+ outBytes, _ := json.Marshal(out)
+
+ return string(outBytes)
+}
diff --git a/library/signals.c b/mobile/signals.c
similarity index 100%
rename from library/signals.c
rename to mobile/signals.c
diff --git a/library/signals.go b/mobile/signals.go
similarity index 84%
rename from library/signals.go
rename to mobile/signals.go
index 38ca6ef2..3e0a7957 100644
--- a/library/signals.go
+++ b/mobile/signals.go
@@ -1,13 +1,14 @@
-package main
+package gowaku
/*
+#include
#include
#include
-
extern bool StatusServiceSignalEvent(const char *jsonEvent);
extern void SetEventCallback(void *cb);
*/
import "C"
+
import (
"encoding/json"
"fmt"
@@ -27,15 +28,15 @@ 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 {
+// signalEnvelope is a general signal sent upward from node to app
+type signalEnvelope struct {
Type string `json:"type"`
Event interface{} `json:"event"`
}
// NewEnvelope creates new envlope of given type and event payload.
-func NewEnvelope(signalType string, event interface{}) *SignalEnvelope {
- return &SignalEnvelope{
+func newEnvelope(signalType string, event interface{}) *signalEnvelope {
+ return &signalEnvelope{
Type: signalType,
Event: event,
}
@@ -43,7 +44,8 @@ func NewEnvelope(signalType string, event interface{}) *SignalEnvelope {
// send sends application signal (in JSON) upwards to application (via default notification handler)
func send(signalType string, event interface{}) {
- signal := NewEnvelope(signalType, event)
+
+ signal := newEnvelope(signalType, event)
data, err := json.Marshal(&signal)
if err != nil {
fmt.Println("marshal signal error", err)
@@ -71,6 +73,6 @@ func SetMobileSignalHandler(handler SignalHandler) {
}
}
-func setEventCallback(cb unsafe.Pointer) {
+func SetEventCallback(cb unsafe.Pointer) {
C.SetEventCallback(cb)
}