feat: kotlin android example (#226)

* feat: kotlin android example
* Adding lightpush and store to kotlin example
This commit is contained in:
Richard Ramos 2022-04-12 08:12:14 -04:00 committed by GitHub
parent 4d0e4ca4da
commit f1f6cb04f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 2501 additions and 684 deletions

3
.gitignore vendored
View File

@ -7,9 +7,10 @@ nodekey
*.dll
*.so
*.dylib
*.aar
*.jar
# output binaries
main
go-waku
# Test binary, built with `go test -c`

View File

@ -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

15
examples/android-kotlin/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1 @@
Waku

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/activity_main.xml" value="0.35625" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="VisualizationToolProject">
<option name="state">
<ProjectState>
<option name="scale" value="0.35625" />
</ProjectState>
</option>
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

View File

@ -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

View File

@ -0,0 +1 @@
/build

View File

@ -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'
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.waku">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Waku">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission-sdk-23 android:name="android.permission.INTERNET" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission-sdk-23 android:name="android.permission.CHANGE_WIFI_STATE" />
</manifest>

View File

@ -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
)

View File

@ -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<T>(val error: String? = null, val result: T? = null)
inline fun <reified T> handleResponse(response: String): T {
val jsonResult = Json.decodeFromString<JsonResult<T>>(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<JsonResult<String>>(response)
if (jsonResult.error != null)
throw Exception(jsonResult.error)
}

View File

@ -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<TextView>(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()
}
}

View File

@ -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<BaseEvent>(signalJson)
when (evt.type) {
EventType.Message -> {
try {
val msgEvt = Json.decodeFromString<MessageEvent>(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<String>(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<Int>(response)
}
/**
* Obtain the multiaddresses the wakunode is listening to
* @return List of multiaddresses
*/
fun Node.listenAddresses(): List<String> {
val response = Gowaku.listenAddresses()
return handleResponse<List<String>>(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<String>(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<String>(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<String>(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<String>(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<String>(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<String>(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<String>(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<Boolean>(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<Peer> {
val response = Gowaku.peers()
return handleResponse<List<Peer>>(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<StoreResponse>(response)
}

View File

@ -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<String>,
val addrs: List<String>,
)

View File

@ -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)
}

View File

@ -0,0 +1,6 @@
package com.example.waku.events
import kotlinx.serialization.Serializable
@Serializable
data class BaseEvent(override val type: EventType) : Event

View File

@ -0,0 +1,5 @@
package com.example.waku.events
interface Event {
val type: EventType
}

View File

@ -0,0 +1,5 @@
package com.example.waku.events
interface EventHandler {
fun handleEvent(evt: Event)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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?
)

View File

@ -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<DecodedPayload>(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<DecodedPayload>(response)
}

View File

@ -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<ByteArray> {
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)
}
}

View File

@ -0,0 +1,6 @@
package com.example.waku.store
import kotlinx.serialization.Serializable
@Serializable
data class ContentFilter(val contentTopic: String)

View File

@ -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
)

View File

@ -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)

View File

@ -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<ContentFilter>?,
var pagingOptions: PagingOptions?
)

View File

@ -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<Message>, val pagingOptions: PagingOptions?)

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/lbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Waku" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Waku</string>
</resources>

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Waku" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -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)
}
}

View File

@ -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 {
}

View File

@ -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

View File

@ -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

185
examples/android-kotlin/gradlew vendored Executable file
View File

@ -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" "$@"

89
examples/android-kotlin/gradlew.bat vendored Normal file
View File

@ -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

View File

@ -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'

2
examples/c-bindings/build/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -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);

View File

@ -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();
/// <summary>
/// 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.
/// </summary>
public void Start()
{
if(_running) {
return
}
IntPtr ptr = waku_start();
Response.HandleResponse(ptr);
@ -212,6 +213,10 @@ namespace Waku
/// </summary>
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);
/// <summary>
/// Publish a message using waku lightpush.
/// </summary>
/// <param name="msg">Message to broadcast</param>
/// <param name="topic">Pubsub topic. Set to `null` to use the default pubsub topic</param>
/// <param name="peerID">ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node</param>
/// <param name="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</param>
/// <returns></returns>
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);
/// <summary>
/// Publish a message encrypted with an secp256k1 public key using waku lightpush.
/// </summary>
/// <param name="msg">Message to broadcast</param>
/// <param name="publicKey">Secp256k1 public key</param>
/// <param name="optionalSigningKey">Optional secp256k1 private key for signing the message</param>
/// <param name="topic">Pubsub topic. Set to `null` to use the default pubsub topic</param>
/// <param name="peerID">ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node</param>
/// <param name="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</param>
/// <returns></returns>
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);
/// <summary>
/// Publish a message encrypted with a 32 bytes symmetric key using waku lightpush.
/// </summary>
/// <param name="msg">Message to broadcast</param>
/// <param name="symmetricKey">32 byte hex string containing a symmetric key</param>
/// <param name="optionalSigningKey">Optional secp256k1 private key for signing the message</param>
/// <param name="topic">Pubsub topic. Set to `null` to use the default pubsub topic</param>
/// <param name="peerID">ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node</param>
/// <param name="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</param>
/// <returns></returns>
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
/// <param name="msg">Message to decode</param>
/// <param name="symmetricKey">Symmetric key used to decode the message</param>
/// <returns>DecodedPayload containing the decrypted message, padding, public key and signature (if available)</returns>
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
/// <param name="msg">Message to decode</param>
/// <param name="privateKey">Secp256k1 private key used to decode the message</param>
/// <returns>DecodedPayload containing the decrypted message, padding, public key and signature (if available)</returns>
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);

View File

@ -43,6 +43,7 @@ namespace Waku
return result;
}
internal static T HandleResponse<T>(IntPtr ptr, string errNoValue) where T : struct
{
string strResponse = PtrToStringUtf8(ptr);

1
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

37
library/api_utils.go Normal file
View File

@ -0,0 +1,37 @@
package main
/*
#include <stdlib.h>
#include <stddef.h>
*/
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))
}

View File

@ -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)

31
mobile/README.md Normal file
View File

@ -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.

404
mobile/api.go Normal file
View File

@ -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)
}

74
mobile/api_lightpush.go Normal file
View File

@ -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)
}

138
mobile/api_relay.go Normal file
View File

@ -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)
}

99
mobile/api_store.go Normal file
View File

@ -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)
}

View File

@ -1,4 +1,4 @@
package main
package gowaku
import (
"encoding/json"

View File

@ -1,6 +1,7 @@
//go:build darwin && cgo
// +build darwin,cgo
package main
package gowaku
/*
#cgo CFLAGS: -x objective-c

41
mobile/response.go Normal file
View File

@ -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)
}

View File

@ -1,13 +1,14 @@
package main
package gowaku
/*
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
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)
}