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:
Daniel Sanchez 2022-10-09 16:50:40 +02:00 committed by GitHub
parent aefe45ad65
commit 94e643a250
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 709 additions and 37 deletions

1
Cargo.lock generated
View File

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

View File

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

65
waku/src/decrypt.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

72
waku/src/node/filter.rs Normal file
View File

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

148
waku/src/node/lightpush.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

42
waku/src/node/store.rs Normal file
View File

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