Implement remaning stack (#9)
* Wrapped lightpush * Optional signing key on relay a/symmetric publish * Added lightpush docs * Implemented filter methods wrappers * Implemented store methods wrappers * WakuHandle docs * Plumbing filter, lightpush and store into node * Callback RwLock -> Mutex * Removed wrong todo * Docs modules titles and links * Missing link * Implemented message decoding * Decrypt docs header * Added message and payload todos * Added missing structs fields exposures and constructors * Payload as base64 * Deserialize base64 encoded strings Use proper types on payload * Added MessageIndex type doc header * Added missing documentation * Added main lib header doc
This commit is contained in:
parent
aefe45ad65
commit
94e643a250
|
@ -977,6 +977,7 @@ name = "waku"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
"base64",
|
||||||
"hex",
|
"hex",
|
||||||
"libsecp256k1",
|
"libsecp256k1",
|
||||||
"multiaddr",
|
"multiaddr",
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-gcm = { version = "0.10", features = ["aes"] }
|
aes-gcm = { version = "0.10", features = ["aes"] }
|
||||||
|
base64 = "0.13"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
libsecp256k1 = "0.7"
|
libsecp256k1 = "0.7"
|
||||||
multiaddr = "0.14"
|
multiaddr = "0.14"
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
//! Symmetric and asymmetric waku messages [decrypting](https://rfc.vac.dev/spec/36/#decrypting-messages) methods
|
||||||
|
|
||||||
|
// std
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
// crates
|
||||||
|
use aes_gcm::{Aes256Gcm, Key};
|
||||||
|
use libsecp256k1::SecretKey;
|
||||||
|
// internal
|
||||||
|
use crate::general::{DecodedPayload, JsonResponse, Result, WakuMessage};
|
||||||
|
|
||||||
|
/// Decrypt a message using a symmetric key
|
||||||
|
///
|
||||||
|
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_decode_symmetricchar-messagejson-char-symmetrickey)
|
||||||
|
pub fn waku_decode_symmetric(
|
||||||
|
message: &WakuMessage,
|
||||||
|
symmetric_key: &Key<Aes256Gcm>,
|
||||||
|
) -> Result<DecodedPayload> {
|
||||||
|
let symk = hex::encode(symmetric_key.as_slice());
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_decode_symmetric(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(&message)
|
||||||
|
.expect("WakuMessages should always be able to success serializing"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized waku message")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(symk)
|
||||||
|
.expect("CString should build properly from hex encoded symmetric key")
|
||||||
|
.into_raw(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str");
|
||||||
|
let response: JsonResponse<DecodedPayload> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
response.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt a message using a symmetric key
|
||||||
|
///
|
||||||
|
/// As per the [specification](extern char* waku_decode_asymmetric(char* messageJson, char* privateKey))
|
||||||
|
pub fn waku_decode_asymmetric(
|
||||||
|
message: &WakuMessage,
|
||||||
|
asymmetric_key: &SecretKey,
|
||||||
|
) -> Result<DecodedPayload> {
|
||||||
|
let sk = hex::encode(asymmetric_key.serialize());
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_decode_asymmetric(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(&message)
|
||||||
|
.expect("WakuMessages should always be able to success serializing"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized waku message")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(sk)
|
||||||
|
.expect("CString should build properly from hex encoded symmetric key")
|
||||||
|
.into_raw(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str");
|
||||||
|
let response: JsonResponse<DecodedPayload> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
response.into()
|
||||||
|
}
|
|
@ -1,20 +1,38 @@
|
||||||
|
//! Waku message [event](https://rfc.vac.dev/spec/36/#events) related items
|
||||||
|
//!
|
||||||
|
//! 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 [`Signal`]
|
||||||
|
|
||||||
// std
|
// std
|
||||||
use std::ffi::{c_char, CStr};
|
use std::ffi::{c_char, CStr};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::RwLock;
|
use std::sync::Mutex;
|
||||||
// crates
|
// crates
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
// internal
|
// internal
|
||||||
use crate::general::{WakuMessage, WakuPubSubTopic};
|
use crate::general::{WakuMessage, WakuPubSubTopic};
|
||||||
|
|
||||||
|
/// Event signal
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Signal {
|
pub struct Signal {
|
||||||
|
/// Type of signal being emitted. Currently, only message is available
|
||||||
#[serde(alias = "type")]
|
#[serde(alias = "type")]
|
||||||
_type: String,
|
_type: String,
|
||||||
|
/// Format depends on the type of signal
|
||||||
event: Event,
|
event: Event,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Signal {
|
||||||
|
pub fn event(&self) -> &Event {
|
||||||
|
&self.event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waku event
|
||||||
|
/// For now just WakuMessage is supported
|
||||||
|
#[non_exhaustive]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(tag = "untagged", rename_all = "camelCase")]
|
#[serde(tag = "untagged", rename_all = "camelCase")]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
@ -49,12 +67,12 @@ impl WakuMessageEvent {
|
||||||
|
|
||||||
/// Shared callback slot. Callbacks are registered here so they can be accessed by the extern "C"
|
/// Shared callback slot. Callbacks are registered here so they can be accessed by the extern "C"
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
static CALLBACK: Lazy<RwLock<Box<dyn FnMut(Signal) + Send + Sync>>> =
|
static CALLBACK: Lazy<Mutex<Box<dyn FnMut(Signal) + Send + Sync>>> =
|
||||||
Lazy::new(|| RwLock::new(Box::new(|_| {})));
|
Lazy::new(|| Mutex::new(Box::new(|_| {})));
|
||||||
|
|
||||||
/// Register global callback
|
/// Register global callback
|
||||||
fn set_callback<F: FnMut(Signal) + Send + Sync + 'static>(f: F) {
|
fn set_callback<F: FnMut(Signal) + Send + Sync + 'static>(f: F) {
|
||||||
*CALLBACK.write().unwrap() = Box::new(f);
|
*CALLBACK.lock().unwrap() = Box::new(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper callback, it transformst the `*const c_char` into a [`Signal`]
|
/// Wrapper callback, it transformst the `*const c_char` into a [`Signal`]
|
||||||
|
@ -66,7 +84,7 @@ extern "C" fn callback(data: *const c_char) {
|
||||||
let data: Signal = serde_json::from_str(raw_response).expect("Parsing signal to succeed");
|
let data: Signal = serde_json::from_str(raw_response).expect("Parsing signal to succeed");
|
||||||
(CALLBACK
|
(CALLBACK
|
||||||
.deref()
|
.deref()
|
||||||
.write()
|
.lock()
|
||||||
.expect("Access to the shared callback")
|
.expect("Access to the shared callback")
|
||||||
.as_mut())(data)
|
.as_mut())(data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
|
//! Waku [general](https://rfc.vac.dev/spec/36/#general) types
|
||||||
|
|
||||||
// std
|
// std
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
// crates
|
// crates
|
||||||
|
use aes_gcm::{Aes256Gcm, Key};
|
||||||
|
use libsecp256k1::{PublicKey, SecretKey, Signature};
|
||||||
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use sscanf::{scanf, RegexRepresentation};
|
use sscanf::{scanf, RegexRepresentation};
|
||||||
// internal
|
// internal
|
||||||
|
use crate::decrypt::{waku_decode_asymmetric, waku_decode_symmetric};
|
||||||
|
|
||||||
|
/// Waku message version
|
||||||
pub type WakuMessageVersion = usize;
|
pub type WakuMessageVersion = usize;
|
||||||
/// Base58 encoded peer id
|
/// Base58 encoded peer id
|
||||||
pub type PeerId = String;
|
pub type PeerId = String;
|
||||||
|
/// Waku message id, hex encoded sha256 digest of the message
|
||||||
pub type MessageId = String;
|
pub type MessageId = String;
|
||||||
|
|
||||||
/// JsonResponse wrapper.
|
/// JsonResponse wrapper.
|
||||||
|
@ -34,12 +41,14 @@ impl<T> From<JsonResponse<T>> for Result<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JsonMessage, Waku message in JSON format.
|
// TODO: Properly type and deserialize payload form base64 encoded string
|
||||||
|
/// Waku message in JSON format.
|
||||||
/// as per the [specification](https://rfc.vac.dev/spec/36/#jsonmessage-type)
|
/// as per the [specification](https://rfc.vac.dev/spec/36/#jsonmessage-type)
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct WakuMessage {
|
pub struct WakuMessage {
|
||||||
payload: Box<[u8]>,
|
#[serde(with = "base64_serde")]
|
||||||
|
payload: Vec<u8>,
|
||||||
/// The content topic to be set on the message
|
/// The content topic to be set on the message
|
||||||
content_topic: WakuContentTopic,
|
content_topic: WakuContentTopic,
|
||||||
/// The Waku Message version number
|
/// The Waku Message version number
|
||||||
|
@ -48,16 +57,87 @@ pub struct WakuMessage {
|
||||||
timestamp: usize,
|
timestamp: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WakuMessage {
|
||||||
|
pub fn new<PAYLOAD: AsRef<[u8]>>(
|
||||||
|
payload: PAYLOAD,
|
||||||
|
content_topic: WakuContentTopic,
|
||||||
|
version: WakuMessageVersion,
|
||||||
|
timestamp: usize,
|
||||||
|
) -> Self {
|
||||||
|
let payload = payload.as_ref().to_vec();
|
||||||
|
Self {
|
||||||
|
payload,
|
||||||
|
content_topic,
|
||||||
|
version,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(&self) -> &[u8] {
|
||||||
|
&self.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_topic(&self) -> &WakuContentTopic {
|
||||||
|
&self.content_topic
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> WakuMessageVersion {
|
||||||
|
self.version
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timestamp(&self) -> usize {
|
||||||
|
self.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try decode the message with an expected symmetric key
|
||||||
|
///
|
||||||
|
/// wrapper around [`crate::decrypt::waku_decode_symmetric`]
|
||||||
|
pub fn try_decode_symmetric(&self, symmetric_key: &Key<Aes256Gcm>) -> Result<DecodedPayload> {
|
||||||
|
waku_decode_symmetric(self, symmetric_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try decode the message with an expected asymmetric key
|
||||||
|
///
|
||||||
|
/// wrapper around [`crate::decrypt::waku_decode_asymmetric`]
|
||||||
|
pub fn try_decode_asymmentric(&self, asymmetric_key: &SecretKey) -> Result<DecodedPayload> {
|
||||||
|
waku_decode_asymmetric(self, asymmetric_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A payload once decoded, used when a received Waku Message is encrypted
|
/// A payload once decoded, used when a received Waku Message is encrypted
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DecodedPayload {
|
pub struct DecodedPayload {
|
||||||
/// Public key that signed the message (optional), hex encoded with 0x prefix
|
/// Public key that signed the message (optional), hex encoded with 0x prefix
|
||||||
public_key: Option<String>,
|
#[serde(deserialize_with = "deserialize_optional_pk")]
|
||||||
|
public_key: Option<PublicKey>,
|
||||||
/// Message signature (optional), hex encoded with 0x prefix
|
/// Message signature (optional), hex encoded with 0x prefix
|
||||||
signature: Option<String>,
|
#[serde(deserialize_with = "deserialize_optional_signature")]
|
||||||
|
signature: Option<Signature>,
|
||||||
/// Decrypted message payload base64 encoded
|
/// Decrypted message payload base64 encoded
|
||||||
data: String,
|
#[serde(with = "base64_serde")]
|
||||||
|
data: Vec<u8>,
|
||||||
/// Padding base64 encoded
|
/// Padding base64 encoded
|
||||||
padding: String,
|
#[serde(with = "base64_serde")]
|
||||||
|
padding: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodedPayload {
|
||||||
|
pub fn public_key(&self) -> Option<&PublicKey> {
|
||||||
|
self.public_key.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signature(&self) -> Option<&Signature> {
|
||||||
|
self.signature.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(&self) -> &[u8] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn padding(&self) -> &[u8] {
|
||||||
|
&self.padding
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The content topic of a Waku message
|
/// The content topic of a Waku message
|
||||||
|
@ -69,6 +149,16 @@ pub struct ContentFilter {
|
||||||
content_topic: WakuContentTopic,
|
content_topic: WakuContentTopic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContentFilter {
|
||||||
|
pub fn new(content_topic: WakuContentTopic) -> Self {
|
||||||
|
Self { content_topic }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_topic(&self) -> &WakuContentTopic {
|
||||||
|
&self.content_topic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The criteria to create subscription to a light node in JSON Format
|
/// The criteria to create subscription to a light node in JSON Format
|
||||||
/// as per the [specification](https://rfc.vac.dev/spec/36/#filtersubscription-type)
|
/// as per the [specification](https://rfc.vac.dev/spec/36/#filtersubscription-type)
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
@ -80,22 +170,32 @@ pub struct FilterSubscription {
|
||||||
pubsub_topic: Option<WakuPubSubTopic>,
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FilterSubscription {
|
||||||
|
pub fn content_filters(&self) -> &[ContentFilter] {
|
||||||
|
&self.content_filters
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pubsub_topic(&self) -> Option<&WakuPubSubTopic> {
|
||||||
|
self.pubsub_topic.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Criteria used to retrieve historical messages
|
/// Criteria used to retrieve historical messages
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct StoreQuery {
|
pub struct StoreQuery {
|
||||||
/// The pubsub topic on which messages are published
|
/// The pubsub topic on which messages are published
|
||||||
pubsub_topic: Option<WakuPubSubTopic>,
|
pub pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
/// Array of [`ContentFilter`] to query for historical messages
|
/// Array of [`ContentFilter`] to query for historical messages
|
||||||
content_filters: Vec<ContentFilter>,
|
pub content_filters: Vec<ContentFilter>,
|
||||||
/// The inclusive lower bound on the timestamp of queried messages.
|
/// The inclusive lower bound on the timestamp of queried messages.
|
||||||
/// This field holds the Unix epoch time in nanoseconds
|
/// This field holds the Unix epoch time in nanoseconds
|
||||||
start_time: Option<usize>,
|
pub start_time: Option<usize>,
|
||||||
/// The inclusive upper bound on the timestamp of queried messages.
|
/// The inclusive upper bound on the timestamp of queried messages.
|
||||||
/// This field holds the Unix epoch time in nanoseconds
|
/// This field holds the Unix epoch time in nanoseconds
|
||||||
end_time: Option<usize>,
|
pub end_time: Option<usize>,
|
||||||
/// Paging information in [`PagingOptions`] format
|
/// Paging information in [`PagingOptions`] format
|
||||||
paging_options: Option<PagingOptions>,
|
pub paging_options: Option<PagingOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The response received after doing a query to a store node
|
/// The response received after doing a query to a store node
|
||||||
|
@ -108,33 +208,45 @@ pub struct StoreResponse {
|
||||||
paging_options: Option<PagingOptions>,
|
paging_options: Option<PagingOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StoreResponse {
|
||||||
|
pub fn messages(&self) -> &[WakuMessage] {
|
||||||
|
&self.messages
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paging_options(&self) -> Option<&PagingOptions> {
|
||||||
|
self.paging_options.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Paging information
|
/// Paging information
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PagingOptions {
|
pub struct PagingOptions {
|
||||||
/// Number of messages to retrieve per page
|
/// Number of messages to retrieve per page
|
||||||
page_size: usize,
|
pub page_size: usize,
|
||||||
/// Message Index from which to perform pagination.
|
/// Message Index from which to perform pagination.
|
||||||
/// If not included and forward is set to `true`, paging will be performed from the beginning of the list.
|
/// If not included and forward is set to `true`, paging will be performed from the beginning of the list.
|
||||||
/// If not included and forward is set to `false`, paging will be performed from the end of the list
|
/// If not included and forward is set to `false`, paging will be performed from the end of the list
|
||||||
cursor: Option<MessageIndex>,
|
pub cursor: Option<MessageIndex>,
|
||||||
/// `true` if paging forward, `false` if paging backward
|
/// `true` if paging forward, `false` if paging backward
|
||||||
forward: bool,
|
pub forward: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pagination index type
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MessageIndex {
|
pub struct MessageIndex {
|
||||||
/// Hash of the message at this [`MessageIndex`]
|
/// Hash of the message at this [``MessageIndex`]
|
||||||
digest: String,
|
pub digest: String,
|
||||||
/// UNIX timestamp in nanoseconds at which the message at this [`MessageIndex`] was received
|
/// UNIX timestamp in nanoseconds at which the message at this [`MessageIndex`] was received
|
||||||
receiver_time: usize,
|
pub receiver_time: usize,
|
||||||
/// UNIX timestamp in nanoseconds at which the message is generated by its sender
|
/// UNIX timestamp in nanoseconds at which the message is generated by its sender
|
||||||
sender_time: usize,
|
pub sender_time: usize,
|
||||||
/// The pubsub topic of the message at this [`MessageIndex`]
|
/// The pubsub topic of the message at this [`MessageIndex`]
|
||||||
pubsub_topic: WakuPubSubTopic,
|
pub pubsub_topic: WakuPubSubTopic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// WakuMessage encoding scheme
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum Encoding {
|
pub enum Encoding {
|
||||||
Proto,
|
Proto,
|
||||||
|
@ -170,12 +282,13 @@ impl RegexRepresentation for Encoding {
|
||||||
const REGEX: &'static str = r"\w";
|
const REGEX: &'static str = r"\w";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A waku content topic `/{application_name}/{version}/{content_topic_name}/{encdoing}`
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WakuContentTopic {
|
pub struct WakuContentTopic {
|
||||||
application_name: String,
|
pub application_name: String,
|
||||||
version: usize,
|
pub version: usize,
|
||||||
content_topic_name: String,
|
pub content_topic_name: String,
|
||||||
encoding: Encoding,
|
pub encoding: Encoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for WakuContentTopic {
|
impl FromStr for WakuContentTopic {
|
||||||
|
@ -233,10 +346,20 @@ impl<'de> Deserialize<'de> for WakuContentTopic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A waku pubsub topic in the form of `/waku/v2/{topic_name}/{encoding}`
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WakuPubSubTopic {
|
pub struct WakuPubSubTopic {
|
||||||
topic_name: String,
|
pub topic_name: String,
|
||||||
encoding: Encoding,
|
pub encoding: Encoding,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WakuPubSubTopic {
|
||||||
|
pub fn new(topic_name: String, encoding: Encoding) -> Self {
|
||||||
|
Self {
|
||||||
|
topic_name,
|
||||||
|
encoding,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for WakuPubSubTopic {
|
impl FromStr for WakuPubSubTopic {
|
||||||
|
@ -285,3 +408,53 @@ impl<'de> Deserialize<'de> for WakuPubSubTopic {
|
||||||
.map_err(D::Error::custom)
|
.map_err(D::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod base64_serde {
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(value: &[u8], serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
base64::encode(value).serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<Vec<u8>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let base64_str: String = String::deserialize(deserializer)?;
|
||||||
|
base64::decode(base64_str).map_err(D::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_optional_pk<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Option<PublicKey>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let base64_str: Option<String> = Option::<String>::deserialize(deserializer)?;
|
||||||
|
base64_str
|
||||||
|
.map(|base64_str| {
|
||||||
|
let raw_bytes = base64::decode(base64_str).map_err(D::Error::custom)?;
|
||||||
|
PublicKey::parse_slice(&raw_bytes, None).map_err(D::Error::custom)
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_optional_signature<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Option<Signature>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let base64_str: Option<String> = Option::<String>::deserialize(deserializer)?;
|
||||||
|
base64_str
|
||||||
|
.map(|base64_str| {
|
||||||
|
let raw_bytes = base64::decode(base64_str).map_err(D::Error::custom)?;
|
||||||
|
Signature::parse_der(&raw_bytes).map_err(D::Error::custom)
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,24 @@
|
||||||
|
//! # Waku
|
||||||
|
//!
|
||||||
|
//! Implementation on top of [`waku-bindings`](https://rfc.vac.dev/spec/36/)
|
||||||
|
mod decrypt;
|
||||||
mod events;
|
mod events;
|
||||||
mod general;
|
mod general;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
||||||
|
pub use node::{
|
||||||
|
waku_create_content_topic, waku_create_pubsub_topic, waku_dafault_pubsub_topic, waku_new,
|
||||||
|
Initialized, Protocol, Running, WakuNodeConfig, WakuNodeHandle, WakuPeerData, WakuPeers,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use general::{
|
||||||
|
ContentFilter, DecodedPayload, Encoding, FilterSubscription, MessageId, MessageIndex,
|
||||||
|
PagingOptions, PeerId, StoreQuery, StoreResponse, WakuContentTopic, WakuMessage,
|
||||||
|
WakuMessageVersion, WakuPubSubTopic,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use events::{waku_set_event_callback, Event, Signal, WakuMessageEvent};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Waku node [configuration](https://rfc.vac.dev/spec/36/#jsonconfig-type) related items
|
||||||
|
|
||||||
// std
|
// std
|
||||||
// crates
|
// crates
|
||||||
use libsecp256k1::SecretKey;
|
use libsecp256k1::SecretKey;
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
//! Waku [filter](https://rfc.vac.dev/spec/36/#waku-filter) protocol related methods
|
||||||
|
|
||||||
|
// std
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::time::Duration;
|
||||||
|
// crates
|
||||||
|
|
||||||
|
// internal
|
||||||
|
use crate::general::Result;
|
||||||
|
use crate::general::{FilterSubscription, JsonResponse, PeerId};
|
||||||
|
|
||||||
|
/// Creates a subscription in a lightnode for messages that matches a content filter and optionally a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`)
|
||||||
|
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_filter_subscribechar-filterjson-char-peerid-int-timeoutms)
|
||||||
|
pub fn waku_filter_subscribe(
|
||||||
|
filter_subscription: &FilterSubscription,
|
||||||
|
peer_id: PeerId,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<()> {
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_filter_subscribe(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(filter_subscription)
|
||||||
|
.expect("FilterSubscription should always be able to be serialized"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized filter subscription")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(peer_id)
|
||||||
|
.expect("CString should build properly from peer id")
|
||||||
|
.into_raw(),
|
||||||
|
timeout
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.expect("Duration as milliseconds should fit in a i32"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str");
|
||||||
|
|
||||||
|
let response: JsonResponse<bool> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
|
||||||
|
Result::from(response).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes subscriptions in a light node matching a content filter and, optionally, a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`)
|
||||||
|
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_filter_unsubscribechar-filterjson-int-timeoutms)
|
||||||
|
pub fn waku_filter_unsubscribe(
|
||||||
|
filter_subscription: &FilterSubscription,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<()> {
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_filter_unsubscribe(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(filter_subscription)
|
||||||
|
.expect("FilterSubscription should always be able to be serialized"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized filter subscription")
|
||||||
|
.into_raw(),
|
||||||
|
timeout
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.expect("Duration as milliseconds should fit in a i32"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str");
|
||||||
|
|
||||||
|
let response: JsonResponse<bool> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
|
||||||
|
Result::from(response).map(|_| ())
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
//! Waku [lightpush](https://rfc.vac.dev/spec/36/#waku-lightpush) protocol related methods
|
||||||
|
|
||||||
|
// std
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::time::Duration;
|
||||||
|
// crates
|
||||||
|
use aes_gcm::{Aes256Gcm, Key};
|
||||||
|
use libsecp256k1::{PublicKey, SecretKey};
|
||||||
|
// internal
|
||||||
|
use crate::general::{JsonResponse, MessageId, PeerId, Result, WakuMessage, WakuPubSubTopic};
|
||||||
|
use crate::node::waku_dafault_pubsub_topic;
|
||||||
|
|
||||||
|
/// Publish a message using Waku Lightpush
|
||||||
|
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_lightpush_publishchar-messagejson-char-topic-char-peerid-int-timeoutms)
|
||||||
|
pub fn waku_lightpush_publish(
|
||||||
|
message: &WakuMessage,
|
||||||
|
pubsub_topic: WakuPubSubTopic,
|
||||||
|
peer_id: PeerId,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<MessageId> {
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_lightpush_publish(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(&message)
|
||||||
|
.expect("WakuMessages should always be able to success serializing"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized waku message")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(pubsub_topic.to_string())
|
||||||
|
.expect("CString should build properly from pubsub topic")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(peer_id)
|
||||||
|
.expect("CString should build properly from peer id")
|
||||||
|
.into_raw(),
|
||||||
|
timeout
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.expect("Duration as milliseconds should fit in a i32"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str");
|
||||||
|
|
||||||
|
let response: JsonResponse<MessageId> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
|
||||||
|
response.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Lightpush
|
||||||
|
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_lightpush_publish_enc_asymmetricchar-messagejson-char-pubsubtopic-char-peerid-char-publickey-char-optionalsigningkey-int-timeoutms)
|
||||||
|
pub fn waku_lightpush_publish_encrypt_asymmetric(
|
||||||
|
message: &WakuMessage,
|
||||||
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
|
peer_id: PeerId,
|
||||||
|
public_key: &PublicKey,
|
||||||
|
signing_key: Option<&SecretKey>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<MessageId> {
|
||||||
|
let pk = hex::encode(public_key.serialize());
|
||||||
|
let sk = signing_key
|
||||||
|
.map(|signing_key| hex::encode(signing_key.serialize()))
|
||||||
|
.unwrap_or_else(String::new);
|
||||||
|
let pubsub_topic = pubsub_topic
|
||||||
|
.unwrap_or_else(waku_dafault_pubsub_topic)
|
||||||
|
.to_string();
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_lightpush_publish_enc_asymmetric(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(&message)
|
||||||
|
.expect("WakuMessages should always be able to success serializing"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized waku message")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(pubsub_topic)
|
||||||
|
.expect("CString should build properly from pubsub topic")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(peer_id)
|
||||||
|
.expect("CString should build properly from peer id")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(pk)
|
||||||
|
.expect("CString should build properly from hex encoded public key")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(sk)
|
||||||
|
.expect("CString should build properly from hex encoded signing key")
|
||||||
|
.into_raw(),
|
||||||
|
timeout
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.expect("Duration as milliseconds should fit in a i32"),
|
||||||
|
))
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str")
|
||||||
|
};
|
||||||
|
let message_id: JsonResponse<MessageId> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
message_id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally sign, encrypt using symmetric encryption and publish a message using Waku Lightpush
|
||||||
|
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_lightpush_publish_enc_symmetricchar-messagejson-char-pubsubtopic-char-peerid-char-symmetrickey-char-optionalsigningkey-int-timeoutms)
|
||||||
|
pub fn waku_lightpush_publish_encrypt_symmetric(
|
||||||
|
message: &WakuMessage,
|
||||||
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
|
peer_id: PeerId,
|
||||||
|
symmetric_key: &Key<Aes256Gcm>,
|
||||||
|
signing_key: Option<&SecretKey>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<MessageId> {
|
||||||
|
let symk = hex::encode(symmetric_key.as_slice());
|
||||||
|
let sk = signing_key
|
||||||
|
.map(|signing_key| hex::encode(signing_key.serialize()))
|
||||||
|
.unwrap_or_else(String::new);
|
||||||
|
let pubsub_topic = pubsub_topic
|
||||||
|
.unwrap_or_else(waku_dafault_pubsub_topic)
|
||||||
|
.to_string();
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_lightpush_publish_enc_symmetric(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(&message)
|
||||||
|
.expect("WakuMessages should always be able to success serializing"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized waku message")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(pubsub_topic)
|
||||||
|
.expect("CString should build properly from pubsub topic")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(peer_id)
|
||||||
|
.expect("CString should build properly from peer id")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(symk)
|
||||||
|
.expect("CString should build properly from hex encoded symmetric key")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(sk)
|
||||||
|
.expect("CString should build properly from hex encoded signing key")
|
||||||
|
.into_raw(),
|
||||||
|
timeout
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.expect("Duration as milliseconds should fit in a i32"),
|
||||||
|
))
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str")
|
||||||
|
};
|
||||||
|
let message_id: JsonResponse<MessageId> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
message_id.into()
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Node lifcycle [mangement](https://rfc.vac.dev/spec/36/#node-management) related methods
|
||||||
|
|
||||||
// std
|
// std
|
||||||
use multiaddr::Multiaddr;
|
use multiaddr::Multiaddr;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
//! Waku node implementation
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod filter;
|
||||||
|
mod lightpush;
|
||||||
mod management;
|
mod management;
|
||||||
mod peers;
|
mod peers;
|
||||||
mod relay;
|
mod relay;
|
||||||
|
mod store;
|
||||||
|
|
||||||
// std
|
// std
|
||||||
use aes_gcm::{Aes256Gcm, Key};
|
use aes_gcm::{Aes256Gcm, Key};
|
||||||
|
@ -12,7 +17,10 @@ use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
// crates
|
// crates
|
||||||
// internal
|
// internal
|
||||||
use crate::general::{MessageId, PeerId, Result, WakuMessage, WakuPubSubTopic};
|
use crate::general::{
|
||||||
|
FilterSubscription, MessageId, PeerId, Result, StoreQuery, StoreResponse, WakuMessage,
|
||||||
|
WakuPubSubTopic,
|
||||||
|
};
|
||||||
|
|
||||||
pub use config::WakuNodeConfig;
|
pub use config::WakuNodeConfig;
|
||||||
pub use peers::{Protocol, WakuPeerData, WakuPeers};
|
pub use peers::{Protocol, WakuPeerData, WakuPeers};
|
||||||
|
@ -33,6 +41,11 @@ pub struct Running;
|
||||||
impl WakuNodeState for Initialized {}
|
impl WakuNodeState for Initialized {}
|
||||||
impl WakuNodeState for Running {}
|
impl WakuNodeState for Running {}
|
||||||
|
|
||||||
|
/// Handle to the underliying waku node
|
||||||
|
/// Safe to sendt to/through threads.
|
||||||
|
/// Only a waku node can be running at a time.
|
||||||
|
/// Referenes (`&`) to the handle can call queries and perform operations in a thread safe way.
|
||||||
|
/// Only an owned version of the handle can `start` or `stop` the node.
|
||||||
pub struct WakuNodeHandle<State: WakuNodeState>(PhantomData<State>);
|
pub struct WakuNodeHandle<State: WakuNodeState>(PhantomData<State>);
|
||||||
|
|
||||||
/// We do not have any inner state, so the handle should be safe to be send among threads.
|
/// We do not have any inner state, so the handle should be safe to be send among threads.
|
||||||
|
@ -159,7 +172,7 @@ impl WakuNodeHandle<Running> {
|
||||||
message: &WakuMessage,
|
message: &WakuMessage,
|
||||||
pubsub_topic: Option<WakuPubSubTopic>,
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
public_key: &PublicKey,
|
public_key: &PublicKey,
|
||||||
signing_key: &SecretKey,
|
signing_key: Option<&SecretKey>,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<MessageId> {
|
) -> Result<MessageId> {
|
||||||
relay::waku_relay_publish_encrypt_asymmetric(
|
relay::waku_relay_publish_encrypt_asymmetric(
|
||||||
|
@ -179,7 +192,7 @@ impl WakuNodeHandle<Running> {
|
||||||
message: &WakuMessage,
|
message: &WakuMessage,
|
||||||
pubsub_topic: Option<WakuPubSubTopic>,
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
symmetric_key: &Key<Aes256Gcm>,
|
symmetric_key: &Key<Aes256Gcm>,
|
||||||
signing_key: &SecretKey,
|
signing_key: Option<&SecretKey>,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<MessageId> {
|
) -> Result<MessageId> {
|
||||||
relay::waku_relay_publish_encrypt_symmetric(
|
relay::waku_relay_publish_encrypt_symmetric(
|
||||||
|
@ -211,6 +224,92 @@ impl WakuNodeHandle<Running> {
|
||||||
pub fn relay_unsubscribe(&self, pubsub_topic: Option<WakuPubSubTopic>) -> Result<()> {
|
pub fn relay_unsubscribe(&self, pubsub_topic: Option<WakuPubSubTopic>) -> Result<()> {
|
||||||
relay::waku_relay_unsubscribe(pubsub_topic)
|
relay::waku_relay_unsubscribe(pubsub_topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves historical messages on specific content topics
|
||||||
|
///
|
||||||
|
/// wrapper around [`store::waku_store_query`]
|
||||||
|
pub fn store_query(
|
||||||
|
query: &StoreQuery,
|
||||||
|
peer_id: PeerId,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<StoreResponse> {
|
||||||
|
store::waku_store_query(query, peer_id, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publish a message using Waku Lightpush
|
||||||
|
///
|
||||||
|
/// wrapper around [`lightpush::waku_lightpush_publish`]
|
||||||
|
pub fn lightpush_publish(
|
||||||
|
message: &WakuMessage,
|
||||||
|
pubsub_topic: WakuPubSubTopic,
|
||||||
|
peer_id: PeerId,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<MessageId> {
|
||||||
|
lightpush::waku_lightpush_publish(message, pubsub_topic, peer_id, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Lightpush
|
||||||
|
///
|
||||||
|
/// wrapper around [`lightpush::waku_lightpush_publish_encrypt_asymmetric`]
|
||||||
|
pub fn lightpush_publish_encrypt_asymmetric(
|
||||||
|
message: &WakuMessage,
|
||||||
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
|
peer_id: PeerId,
|
||||||
|
public_key: &PublicKey,
|
||||||
|
signing_key: Option<&SecretKey>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<MessageId> {
|
||||||
|
lightpush::waku_lightpush_publish_encrypt_asymmetric(
|
||||||
|
message,
|
||||||
|
pubsub_topic,
|
||||||
|
peer_id,
|
||||||
|
public_key,
|
||||||
|
signing_key,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally sign, encrypt using symmetric encryption and publish a message using Waku Lightpush
|
||||||
|
///
|
||||||
|
/// wrapper around [`lightpush::waku_lightpush_publish_encrypt_symmetric`]
|
||||||
|
pub fn lightpush_publish_encrypt_symmetric(
|
||||||
|
message: &WakuMessage,
|
||||||
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
|
peer_id: PeerId,
|
||||||
|
symmetric_key: &Key<Aes256Gcm>,
|
||||||
|
signing_key: Option<&SecretKey>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<MessageId> {
|
||||||
|
lightpush::waku_lightpush_publish_encrypt_symmetric(
|
||||||
|
message,
|
||||||
|
pubsub_topic,
|
||||||
|
peer_id,
|
||||||
|
symmetric_key,
|
||||||
|
signing_key,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a subscription in a lightnode for messages that matches a content filter and optionally a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`)
|
||||||
|
///
|
||||||
|
/// wrapper around [`filter::waku_filter_subscribe`]
|
||||||
|
pub fn filter_subscribe(
|
||||||
|
filter_subscription: &FilterSubscription,
|
||||||
|
peer_id: PeerId,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<()> {
|
||||||
|
filter::waku_filter_subscribe(filter_subscription, peer_id, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes subscriptions in a light node matching a content filter and, optionally, a [`WakuPubSubTopic`](`crate::general::WakuPubSubTopic`)
|
||||||
|
///
|
||||||
|
/// wrapper around [`filter::waku_filter_unsubscribe`]
|
||||||
|
pub fn filter_unsubscribe(
|
||||||
|
filter_subscription: &FilterSubscription,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<()> {
|
||||||
|
filter::waku_filter_unsubscribe(filter_subscription, timeout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a new Waku node with the givent configuration (default configuration if `None` provided)
|
/// Spawn a new Waku node with the givent configuration (default configuration if `None` provided)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Waku [peer handling and connection](https://rfc.vac.dev/spec/36/#connecting-to-peers) methods
|
||||||
|
|
||||||
// std
|
// std
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -111,6 +113,12 @@ pub fn waku_peer_count() -> Result<usize> {
|
||||||
result.into()
|
result.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Waku peer supported protocol
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// `"/ipfs/id/1.0.0"`
|
||||||
|
/// `"/vac/waku/relay/2.0.0"`
|
||||||
|
/// `"/ipfs/ping/1.0.0"`
|
||||||
pub type Protocol = String;
|
pub type Protocol = String;
|
||||||
|
|
||||||
/// Peer data from known/connected waku nodes
|
/// Peer data from known/connected waku nodes
|
||||||
|
@ -129,6 +137,24 @@ pub struct WakuPeerData {
|
||||||
connected: bool,
|
connected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WakuPeerData {
|
||||||
|
pub fn peer_id(&self) -> &PeerId {
|
||||||
|
&self.peer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn protocols(&self) -> &[Protocol] {
|
||||||
|
&self.protocols
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addresses(&self) -> &[Multiaddr] {
|
||||||
|
&self.addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connected(&self) -> bool {
|
||||||
|
self.connected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// List of [`WakuPeerData`], return value from [`waku_peers`] funtion
|
/// List of [`WakuPeerData`], return value from [`waku_peers`] funtion
|
||||||
pub type WakuPeers = Vec<WakuPeerData>;
|
pub type WakuPeers = Vec<WakuPeerData>;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Waku [relay](https://rfc.vac.dev/spec/36/#waku-relay) protocol related methods
|
||||||
|
|
||||||
// std
|
// std
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -107,11 +109,13 @@ pub fn waku_relay_publish_encrypt_asymmetric(
|
||||||
message: &WakuMessage,
|
message: &WakuMessage,
|
||||||
pubsub_topic: Option<WakuPubSubTopic>,
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
public_key: &PublicKey,
|
public_key: &PublicKey,
|
||||||
signing_key: &SecretKey,
|
signing_key: Option<&SecretKey>,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<MessageId> {
|
) -> Result<MessageId> {
|
||||||
let pk = hex::encode(public_key.serialize());
|
let pk = hex::encode(public_key.serialize());
|
||||||
let sk = hex::encode(signing_key.serialize());
|
let sk = signing_key
|
||||||
|
.map(|signing_key| hex::encode(signing_key.serialize()))
|
||||||
|
.unwrap_or_else(String::new);
|
||||||
let pubsub_topic = pubsub_topic
|
let pubsub_topic = pubsub_topic
|
||||||
.unwrap_or_else(waku_dafault_pubsub_topic)
|
.unwrap_or_else(waku_dafault_pubsub_topic)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
@ -151,11 +155,13 @@ pub fn waku_relay_publish_encrypt_symmetric(
|
||||||
message: &WakuMessage,
|
message: &WakuMessage,
|
||||||
pubsub_topic: Option<WakuPubSubTopic>,
|
pubsub_topic: Option<WakuPubSubTopic>,
|
||||||
symmetric_key: &Key<Aes256Gcm>,
|
symmetric_key: &Key<Aes256Gcm>,
|
||||||
signing_key: &SecretKey,
|
signing_key: Option<&SecretKey>,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<MessageId> {
|
) -> Result<MessageId> {
|
||||||
let symk = hex::encode(symmetric_key.as_slice());
|
let symk = hex::encode(symmetric_key.as_slice());
|
||||||
let sk = hex::encode(signing_key.serialize());
|
let sk = signing_key
|
||||||
|
.map(|signing_key| hex::encode(signing_key.serialize()))
|
||||||
|
.unwrap_or_else(String::new);
|
||||||
let pubsub_topic = pubsub_topic
|
let pubsub_topic = pubsub_topic
|
||||||
.unwrap_or_else(waku_dafault_pubsub_topic)
|
.unwrap_or_else(waku_dafault_pubsub_topic)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
//! Waku [store](https://rfc.vac.dev/spec/36/#waku-store) handling methods
|
||||||
|
|
||||||
|
// std
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::time::Duration;
|
||||||
|
// crates
|
||||||
|
// internal
|
||||||
|
use crate::general::{JsonResponse, PeerId, Result, StoreQuery, StoreResponse};
|
||||||
|
|
||||||
|
/// Retrieves historical messages on specific content topics. This method may be called with [`PagingOptions`](`crate::general::PagingOptions`),
|
||||||
|
/// to retrieve historical messages on a per-page basis. If the request included [`PagingOptions`](`crate::general::PagingOptions`),
|
||||||
|
/// the node must return messages on a per-page basis and include [`PagingOptions`](`crate::general::PagingOptions`) in the response.
|
||||||
|
/// These [`PagingOptions`](`crate::general::PagingOptions`) must contain a cursor pointing to the Index from which a new page can be requested
|
||||||
|
pub fn waku_store_query(
|
||||||
|
query: &StoreQuery,
|
||||||
|
peer_id: PeerId,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<StoreResponse> {
|
||||||
|
let result = unsafe {
|
||||||
|
CStr::from_ptr(waku_sys::waku_store_query(
|
||||||
|
CString::new(
|
||||||
|
serde_json::to_string(query)
|
||||||
|
.expect("StoreQuery should always be able to be serialized"),
|
||||||
|
)
|
||||||
|
.expect("CString should build properly from the serialized filter subscription")
|
||||||
|
.into_raw(),
|
||||||
|
CString::new(peer_id)
|
||||||
|
.expect("CString should build properly from peer id")
|
||||||
|
.into_raw(),
|
||||||
|
timeout
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.expect("Duration as milliseconds should fit in a i32"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.expect("Response should always succeed to load to a &str");
|
||||||
|
|
||||||
|
let response: JsonResponse<StoreResponse> =
|
||||||
|
serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize");
|
||||||
|
response.into()
|
||||||
|
}
|
Loading…
Reference in New Issue