From 3ed40ff31b663f60737497bfda3d168127a5a320 Mon Sep 17 00:00:00 2001 From: Franck R Date: Tue, 17 May 2022 18:18:45 +1000 Subject: [PATCH] 36/WAKU2-BINDINGS-API (#501) Co-authored-by: Richard Ramos --- content/docs/rfcs/36/README.md | 773 +++++++++++++++++++++++++++++++++ content/menu/index.md | 1 + 2 files changed, 774 insertions(+) create mode 100644 content/docs/rfcs/36/README.md diff --git a/content/docs/rfcs/36/README.md b/content/docs/rfcs/36/README.md new file mode 100644 index 00000000..cce25923 --- /dev/null +++ b/content/docs/rfcs/36/README.md @@ -0,0 +1,773 @@ +--- +slug: 36 +title: 36/WAKU2-BINDINGS-API +name: Waku v2 C Bindings API +status: draft +tags: waku-core +editor: Richard Ramos +contributors: +- Franck Royer +--- + +# Introduction + +Native applications that wish to integrate Waku may not be able to use nwaku and its JSON RPC API due to constraints +on packaging, performance or executables. + +An alternative is to link existing Waku implementation as a static or dynamic library in their application. + +This specification describes the C API that SHOULD be implemented by native Waku library and that SHOULD be used to +consume them. + +# Design requirements + +The API should be generic enough, so: + +- it can be implemented by both nwaku and go-waku C-Bindings, +- it can be consumed from a variety of languages such as C#, Kotlin, Swift, Rust, C++, etc. + +The selected format to pass data to and from the API is `JSON`. + +It has been selected due to its widespread usage and easiness of use. Other alternatives MAY replace it in the future (C +structure, protobuf) if it brings limitations that need to be lifted. + +# The API + +## General + +### `JsonResponse` type + +All the API functions return a `JsonResponse` unless specified otherwise. +`JsonResponse` is a `char *` whose format depends on whether the function was executed successfully or not. + +On failure: + +```ts +{ + error: string; +} +``` + +For example: + +```json +{ + "error": "the error message" +} +``` + +On success: + +```ts +{ + result: any +} +``` + +The type of the `result` object depends on the function it was returned by. + +### `JsonMessage` type + +A Waku Message in JSON Format: + +```ts +{ + payload: string; + contentTopic: string; + version: number; + timestamp: number; +} +``` + +Fields: + +- `payload`: base64 encoded payload, [`waku_utils_base64_encode`](#extern-char-waku_utils_base64_encodechar-data) can be used for this. +- `contentTopic`: The content topic to be set on the message. +- `version`: The Waku Message version number. +- `timestamp`: Unix timestamp in nanoseconds. + +### `DecodedPayload` type + +A payload once decoded, used when a received Waku Message is encrypted: + +```ts +interface DecodedPayload { + pubkey?: string; + signature?: string; + data: string; + padding: string; + } +``` + +Fields: + +- `pubkey`: Public key that signed the message (optional), hex encoded with `0x` prefix, +- `signature`: Message signature (optional), hex encoded with `0x` prefix, +- `data`: Decrypted message payload base64 encoded, +- `padding`: Padding base64 encoded. + +## Events + +Asynchronous events require a callback to be registered. +An example of an asynchronous event that might be emitted is receiving a message. +When an event is emitted, this callback will be triggered receiving a JSON string of type `JsonSignal`. + +### `JsonSignal` type + +```ts +{ + type: string; + event: any; +} +``` + +Fields: + +- `type`: Type of signal being emitted. Currently, only `message` is available. +- `event`: Format depends on the type of signal. + +For example: + +```json +{ + "type": "message", + "event": { + "subscriptionId": 1, + "pubsubTopic": "/waku/2/default-waku/proto", + "messageId": "0x6496491e40dbe0b6c3a2198c2426b16301688a2daebc4f57ad7706115eac3ad1", + "wakuMessage": { + "payload": "TODO", + "contentTopic": "/my-app/1/notification/proto", + "version": 1, + "timestamp": 1647826358000000000 + } + } +} +``` + +| `type` | `event` Type | +|:----------|--------------------| +| `message` | `JsonMessageEvent` | + +### `JsonMessageEvent` type + +Type of `event` field for a `message` event: + +```ts +{ + pubsubTopic: string; + messageId: string; + wakuMessage: JsonMessage; +} +``` + +- `pubsubTopic`: The pubsub topic on which the message was received. +- `messageId`: The message id. +- `wakuMessage`: The message in [`JsonMessage`](#jsonmessage-type) format. + +### `extern void waku_set_event_callback(void* cb)` + +Register callback to act as event handler and receive application signals, +which are used to react to asynchronous events in Waku. + +**Parameters** + +1. `void* cb`: callback that will be executed when an async event is emitted. + The function signature for the callback should be `void myCallback(char* jsonSignal)` + +## Node management + +### `JsonConfig` type + +Type holding a node configuration: + +```ts +interface JsonSignal { + host?: string; + port?: number; + advertiseAddr?: string; + nodeKey?: string; + keepAliveInterval?: number; + relay?: boolean; + minPeersToPublish?: number +} +``` + +Fields: + +All fields are optional. +If a key is `undefined`, or `null`, a default value will be set. + +- `host`: Listening IP address. + Default `0.0.0.0`. +- `port`: Libp2p TCP listening port. + Default `60000`. + Use `0` for random. +- `advertiseAddr`: External address to advertise to other nodes. + Can be ip4, ip6 or dns4, dns6. + If `null`, the multiaddress(es) generated from the ip and port specified in the config (or default ones) will be used. + Default: `null`. +- `nodeKey`: Secp256k1 private key in Hex format (`0x123...abc`). + Default random. +- `keepAliveInterval`: Interval in seconds for pinging peers to keep the connection alive. + Default `20`. +- `relay`: Enable relay protocol. + Default `true`. +- `minPeersToPublish`: The minimum number of peers required on a topic to allow broadcasting a message. + Default `0`. + +For example: +```json +{ + "host": "0.0.0.0", + "port": 60000, + "advertiseAddr": "1.2.3.4", + "nodeKey": "0x123...567", + "keepAliveInterval": 20, + "relay": true, + "minPeersToPublish": 0 +} +``` + +### `extern char* waku_new(char* jsonConfig)` + +Instantiates a Waku node. + +**Parameters** + +1. `char* jsonConfig`: JSON string containing the options used to initialize a go-waku node. + Type [`JsonConfig`](#jsonconfig-type). + It can be `NULL` to use defaults. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_start()` + +Start a Waku node mounting all the protocols that were enabled during the Waku node instantiation. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_stop()` + +Stops a Waku node. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_peerid()` + +Get the peer ID of the waku node. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the peer ID as a `string` (base58 encoded). + +For example: + +```json +{ + "result": "QmWjHKUrXDHPCwoWXpUZ77E8o6UbAoTTZwf1AD1tDC4KNP" +} +``` + +### `extern char* waku_listen_addresses()` + +Get the multiaddresses the Waku node is listening to. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains an array of multiaddresses. +The multiaddresses are `string`s. + +For example: + +```json +{ + "result": [ + "/ip4/127.0.0.1/tcp/30303", + "/ip4/1.2.3.4/tcp/30303", + "/dns4/waku.node.example/tcp/8000/wss" + ] +} +``` + +## Connecting to peers + +### `extern char* waku_add_peer(char* address, char* protocolId)` + +Add a node multiaddress and protocol to the waku node's peerstore. + +**Parameters** + +1. `char* address`: A multiaddress (with peer id) to reach the peer being added. +2. `char* protocolId`: A protocol we expect the peer to support. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the peer ID as a base58 `string` of the peer that was added. + +For example: + +```json +{ + "result": "QmWjHKUrXDHPCwoWXpUZ77E8o6UbAoTTZwf1AD1tDC4KNP" +} +``` + +### `extern char* waku_connect_peer(char* address, int timeoutMs)` + +Dial peer using a multiaddress. + +**Parameters** + +1. `char* address`: A multiaddress to reach the peer being dialed. +2. `int timeoutMs`: Timeout value in milliseconds to execute the call. + If the function execution takes longer than this value, + the execution will be canceled and an error returned. + Use `0` for no timeout. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_connect_peerid(char* peerId, int timeoutMs)` + +Dial peer using its peer ID. + +**Parameters** + +1`char* peerID`: Peer ID to dial. + The peer must be already known. + It must have been added before with [`waku_add_peer`](#extern-char-waku_add_peerchar-address-char-protocolid) + or previously dialed with [`waku_connect_peer`](#extern-char-waku_connect_peerchar-address-int-timeoutms). +2. `int timeoutMs`: Timeout value in milliseconds to execute the call. + If the function execution takes longer than this value, + the execution will be canceled and an error returned. + Use `0` for no timeout. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_disconnect_peer(char* peerId)` + +Disconnect a peer using its peerID + +**Parameters** + +1. `char* peerID`: Peer ID to disconnect. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_peer_count()` + +Get number of connected peers. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains an `integer` which represents the number of connected peers. + +For example: + +```json +{ + "result": 0 +} +``` + +### `extern char* waku_peers()` + +Retrieve the list of peers known by the Waku node. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type) containing a list of peers. +The list of peers has this format: + +```json +{ + "result": [ + { + "peerID": "16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47RedcBafeDCBA", + "protocols": [ + "/ipfs/id/1.0.0", + "/vac/waku/relay/2.0.0", + "/ipfs/ping/1.0.0" + ], + "addrs": [ + "/ip4/1.2.3.4/tcp/30303" + ], + "connected": true + } + ] +} +``` + +## Waku Relay + +### `extern char* waku_content_topic(char* applicationName, unsigned int applicationVersion, char* contentTopicName, char* encoding)` + +Create a content topic string according to [RFC 23](https://rfc.vac.dev/spec/23/). + +**Parameters** + +1. `char* applicationName` +2. `unsigned int applicationVersion` +3. `char* contentTopicName` +4. `char* encoding`: depending on the payload, use `proto`, `rlp` or `rfc26` + +**Returns** + +`char *` containing a content topic formatted according to [RFC 23](https://rfc.vac.dev/spec/23/). + +``` +/{application-name}/{version-of-the-application}/{content-topic-name}/{encoding} +``` + +### `extern char* waku_pubsub_topic(char* name, char* encoding)` + +Create a pubsub topic string according to [RFC 23](https://rfc.vac.dev/spec/23/). + +**Parameters** + +1. `char* name` +2. `char* encoding`: depending on the payload, use `proto`, `rlp` or `rfc26` + +**Returns** + +`char *` containing a content topic formatted according to [RFC 23](https://rfc.vac.dev/spec/23/). + +``` +/waku/2/{topic-name}/{encoding} +``` + +### `extern char* waku_default_pubsub_topic()` + +Returns the default pubsub topic used for exchanging waku messages defined in [RFC 10](https://rfc.vac.dev/spec/10/). + +**Returns** + +`char *` containing the default pubsub topic: + +``` +/waku/2/default-waku/proto +``` + +### `extern char* waku_relay_publish(char* messageJson, char* pubsubTopic, int timeoutMs)` + +Publish a message using Waku Relay. + +**Parameters** + +1. `char* messageJson`: JSON string containing the [Waku Message](https://rfc.vac.dev/spec/14/) as [`JsonMessage`](#jsonmessage-type). +2. `char* pubsubTopic`: pubsub topic on which to publish the message. + If `NULL`, it uses the default pubsub topic. +3. `int timeoutMs`: Timeout value in milliseconds to execute the call. + If the function execution takes longer than this value, + the execution will be canceled and an error returned. + Use `0` for no timeout. + +Note: `messageJson.version` is overwritten to `0`. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the message ID. + +### `extern char* waku_relay_publish_enc_asymmetric(char* messageJson, char* pubsubTopic, char* publicKey, char* optionalSigningKey, int timeoutMs)` + +Optionally sign, +encrypt using asymmetric encryption +and publish a message using Waku Relay. + +**Parameters** + +1. `char* messageJson`: JSON string containing the [Waku Message](https://rfc.vac.dev/spec/14/) as [`JsonMessage`](#jsonmessage-type). +2. `char* pubsubTopic`: pubsub topic on which to publish the message. + If `NULL`, it uses the default pubsub topic. +3. `char* publicKey`: hex encoded public key to be used for encryption. +4. `char* optionalSigningKey`: hex encoded private key to be used to sign the message. +5. `int timeoutMs`: Timeout value in milliseconds to execute the call. + If the function execution takes longer than this value, + the execution will be canceled and an error returned. + Use `0` for no timeout. + +Note: `messageJson.version` is overwritten to `1`. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the message ID. + +### `extern char* waku_relay_publish_enc_symmetric(char* messageJson, char* pubsubTopic, char* symmetricKey, char* optionalSigningKey, int timeoutMs)` + +Optionally sign, +encrypt using symmetric encryption +and publish a message using Waku Relay. + +**Parameters** + +1. `char* messageJson`: JSON string containing the [Waku Message](https://rfc.vac.dev/spec/14/) as [`JsonMessage`](#jsonmessage-type). +2. `char* pubsubTopic`: pubsub topic on which to publish the message. + If `NULL`, it uses the default pubsub topic. +3. `char* symmetricKey`: hex encoded secret key to be used for encryption. +4. `char* optionalSigningKey`: hex encoded private key to be used to sign the message. +5. `int timeoutMs`: Timeout value in milliseconds to execute the call. + If the function execution takes longer than this value, + the execution will be canceled and an error returned. + Use `0` for no timeout. + +Note: `messageJson.version` is overwritten to `1`. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the message ID. + +### `extern char* waku_relay_enough_peers(char* pubsubTopic)` + +Determine if there are enough peers to publish a message on a given pubsub topic. + +**Parameters** + +1. `char* pubsubTopic`: Pubsub topic to verify. + If `NULL`, it verifies the number of peers in the default pubsub topic. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains a `boolean` indicating whether there are enough peers. + +For example: + +```json +{ + "result": true +} +``` + +### `extern char* waku_relay_subscribe(char* topic)` + +Subscribe to a Waku Relay pubsub topic to receive messages. + +**Parameters** + +1. `char* topic`: Pubsub topic to subscribe to. + If `NULL`, it subscribes to the default pubsub topic. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + +**Events** + +When a message is received, a ``"message"` event` is emitted containing the message, pubsub topic, and node ID in which +the message was received. + +The `event` type is [`JsonMessageEvent`](#jsonmessageevent-type). + +For Example: + +```json +{ + "type": "message", + "event": { + "subscriptionID": 1, + "pubsubTopic": "/waku/2/default-waku/proto", + "messageID": "0x6496491e40dbe0b6c3a2198c2426b16301688a2daebc4f57ad7706115eac3ad1", + "wakuMessage": { + "payload": "TODO", + "contentTopic": "/my-app/1/notification/proto", + "version": 1, + "timestamp": 1647826358000000000 + } + } +} +``` + +### `extern char* waku_relay_unsubscribe(char* topic)` + +Closes the pubsub subscription to a pubsub topic. No more messages will be received +from this pubsub topic. + +**Parameters** + +1. `char* pusubTopic`: Pubsub topic to unsubscribe from. + If `NULL`, unsubscribes from the default pubsub topic. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field is set to `true`. + +For example: + +```json +{ + "result": true +} +``` + + +## Decrypting messages + +### `extern char* waku_decode_symmetric(char* messageJson, char* symmetricKey)` +Decrypt a message using a symmetric key + +**Parameters** + +1. `char* messageJson`: JSON string containing the [Waku Message](https://rfc.vac.dev/spec/14/) as [`JsonMessage`](#jsonmessage-type). +2. `char* symmetricKey`: 32 byte symmetric key hex encoded. + +Note: `messageJson.version` is expected to be `1`. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the decoded payload as a [`DecodedPayload`](#decodedpayload-type). +An `error` message otherwise. + +```json +{ + "result": { + "pubkey": "0x......", + "signature": "0x....", + "data": "...", + "padding": "..." + } +} +``` + +### `extern char* waku_decode_asymmetric(char* messageJson, char* privateKey)` +Decrypt a message using a secp256k1 private key + +**Parameters** + +1. `char* messageJson`: JSON string containing the [Waku Message](https://rfc.vac.dev/spec/14/) as [`JsonMessage`](#jsonmessage-type). +2. `char* privateKey`: secp256k1 private key hex encoded. + +Note: `messageJson.version` is expected to be `1`. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the decoded payload as a [`DecodedPayload`](#decodedpayload-type). +An `error` message otherwise. + +```json +{ + "result": { + "pubkey": "0x......", + "signature": "0x....", + "data": "...", + "padding": "..." + } +} +``` + +## Utils + +### `extern char* waku_utils_base64_encode(char* data)` + +Encode a byte array to base64. +Useful for creating the payload of a Waku Message in the format understood by [`waku_relay_publish`](#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms) + +**Parameters** + +1. `char* data`: Byte array to encode + +**Returns** + +A `char *` containing the base64 encoded byte array. + +### `extern char* waku_utils_base64_decode(char* data)` + +Decode a base64 string (useful for reading the payload from Waku Messages). + +**Parameters** + +1. `char* data`: base64 encoded byte array to decode. + +**Returns** + +A [`JsonResponse`](#jsonresponse-type). +If the execution is successful, the `result` field contains the decoded payload. + +# Copyright + +Copyright and related rights waived via +[CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/content/menu/index.md b/content/menu/index.md index 8f604d05..ded58d70 100644 --- a/content/menu/index.md +++ b/content/menu/index.md @@ -31,6 +31,7 @@ bookMenuLevels: 1 - [29/WAKU2-CONFIG]({{< relref "/docs/rfcs/29/README.md" >}}) - [30/ADAPTIVE-NODES]({{< relref "/docs/rfcs/30/README.md" >}}) - [33/WAKU2-DISCV5]({{< relref "/docs/rfcs/33/README.md" >}}) + - [36/WAKU2-BINDINGS-API]({{< relref "/docs/rfcs/36/README.md" >}}) - Stable - [2/MVDS]({{< relref "/docs/rfcs/2/README.md" >}}) - [6/WAKU1]({{< relref "/docs/rfcs/6/README.md" >}})