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"
dependencies = [
"aes-gcm",
"base64",
"hex",
"libsecp256k1",
"multiaddr",

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
aes-gcm = { version = "0.10", features = ["aes"] }
base64 = "0.13"
hex = "0.4"
libsecp256k1 = "0.7"
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
use std::ffi::{c_char, CStr};
use std::ops::Deref;
use std::sync::RwLock;
use std::sync::Mutex;
// crates
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
// internal
use crate::general::{WakuMessage, WakuPubSubTopic};
/// Event signal
#[derive(Serialize, Deserialize)]
pub struct Signal {
/// Type of signal being emitted. Currently, only message is available
#[serde(alias = "type")]
_type: String,
/// Format depends on the type of signal
event: Event,
}
impl Signal {
pub fn event(&self) -> &Event {
&self.event
}
}
/// Waku event
/// For now just WakuMessage is supported
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
#[serde(tag = "untagged", rename_all = "camelCase")]
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"
#[allow(clippy::type_complexity)]
static CALLBACK: Lazy<RwLock<Box<dyn FnMut(Signal) + Send + Sync>>> =
Lazy::new(|| RwLock::new(Box::new(|_| {})));
static CALLBACK: Lazy<Mutex<Box<dyn FnMut(Signal) + Send + Sync>>> =
Lazy::new(|| Mutex::new(Box::new(|_| {})));
/// Register global callback
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`]
@ -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");
(CALLBACK
.deref()
.write()
.lock()
.expect("Access to the shared callback")
.as_mut())(data)
}

View File

@ -1,14 +1,21 @@
//! Waku [general](https://rfc.vac.dev/spec/36/#general) types
// std
use std::fmt::{Display, Formatter};
use std::str::FromStr;
// crates
use aes_gcm::{Aes256Gcm, Key};
use libsecp256k1::{PublicKey, SecretKey, Signature};
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use sscanf::{scanf, RegexRepresentation};
// internal
use crate::decrypt::{waku_decode_asymmetric, waku_decode_symmetric};
/// Waku message version
pub type WakuMessageVersion = usize;
/// Base58 encoded peer id
pub type PeerId = String;
/// Waku message id, hex encoded sha256 digest of the message
pub type MessageId = String;
/// 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)
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WakuMessage {
payload: Box<[u8]>,
#[serde(with = "base64_serde")]
payload: Vec<u8>,
/// The content topic to be set on the message
content_topic: WakuContentTopic,
/// The Waku Message version number
@ -48,16 +57,87 @@ pub struct WakuMessage {
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
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DecodedPayload {
/// 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
signature: Option<String>,
#[serde(deserialize_with = "deserialize_optional_signature")]
signature: Option<Signature>,
/// Decrypted message payload base64 encoded
data: String,
#[serde(with = "base64_serde")]
data: Vec<u8>,
/// 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
@ -69,6 +149,16 @@ pub struct ContentFilter {
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
/// as per the [specification](https://rfc.vac.dev/spec/36/#filtersubscription-type)
#[derive(Clone, Serialize, Deserialize)]
@ -80,22 +170,32 @@ pub struct FilterSubscription {
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
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StoreQuery {
/// 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
content_filters: Vec<ContentFilter>,
pub content_filters: Vec<ContentFilter>,
/// The inclusive lower bound on the timestamp of queried messages.
/// 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.
/// This field holds the Unix epoch time in nanoseconds
end_time: Option<usize>,
pub end_time: Option<usize>,
/// 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
@ -108,33 +208,45 @@ pub struct StoreResponse {
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
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PagingOptions {
/// Number of messages to retrieve per page
page_size: usize,
pub page_size: usize,
/// 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 `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
forward: bool,
pub forward: bool,
}
/// Pagination index type
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageIndex {
/// Hash of the message at this [`MessageIndex`]
digest: String,
/// Hash of the message at this [``MessageIndex`]
pub digest: String,
/// 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
sender_time: usize,
pub sender_time: usize,
/// The pubsub topic of the message at this [`MessageIndex`]
pubsub_topic: WakuPubSubTopic,
pub pubsub_topic: WakuPubSubTopic,
}
/// WakuMessage encoding scheme
#[derive(Copy, Clone)]
pub enum Encoding {
Proto,
@ -170,12 +282,13 @@ impl RegexRepresentation for Encoding {
const REGEX: &'static str = r"\w";
}
/// A waku content topic `/{application_name}/{version}/{content_topic_name}/{encdoing}`
#[derive(Clone)]
pub struct WakuContentTopic {
application_name: String,
version: usize,
content_topic_name: String,
encoding: Encoding,
pub application_name: String,
pub version: usize,
pub content_topic_name: String,
pub encoding: Encoding,
}
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)]
pub struct WakuPubSubTopic {
topic_name: String,
encoding: Encoding,
pub topic_name: String,
pub encoding: Encoding,
}
impl WakuPubSubTopic {
pub fn new(topic_name: String, encoding: Encoding) -> Self {
Self {
topic_name,
encoding,
}
}
}
impl FromStr for WakuPubSubTopic {
@ -285,3 +408,53 @@ impl<'de> Deserialize<'de> for WakuPubSubTopic {
.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 general;
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)]
mod tests {
use std::ffi::CStr;

View File

@ -1,3 +1,5 @@
//! Waku node [configuration](https://rfc.vac.dev/spec/36/#jsonconfig-type) related items
// std
// crates
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
use multiaddr::Multiaddr;
use std::ffi::{CStr, CString};

View File

@ -1,7 +1,12 @@
//! Waku node implementation
mod config;
mod filter;
mod lightpush;
mod management;
mod peers;
mod relay;
mod store;
// std
use aes_gcm::{Aes256Gcm, Key};
@ -12,7 +17,10 @@ use std::sync::Mutex;
use std::time::Duration;
// crates
// 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 peers::{Protocol, WakuPeerData, WakuPeers};
@ -33,6 +41,11 @@ pub struct Running;
impl WakuNodeState for Initialized {}
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>);
/// 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,
pubsub_topic: Option<WakuPubSubTopic>,
public_key: &PublicKey,
signing_key: &SecretKey,
signing_key: Option<&SecretKey>,
timeout: Duration,
) -> Result<MessageId> {
relay::waku_relay_publish_encrypt_asymmetric(
@ -179,7 +192,7 @@ impl WakuNodeHandle<Running> {
message: &WakuMessage,
pubsub_topic: Option<WakuPubSubTopic>,
symmetric_key: &Key<Aes256Gcm>,
signing_key: &SecretKey,
signing_key: Option<&SecretKey>,
timeout: Duration,
) -> Result<MessageId> {
relay::waku_relay_publish_encrypt_symmetric(
@ -211,6 +224,92 @@ impl WakuNodeHandle<Running> {
pub fn relay_unsubscribe(&self, pubsub_topic: Option<WakuPubSubTopic>) -> Result<()> {
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)

View File

@ -1,3 +1,5 @@
//! Waku [peer handling and connection](https://rfc.vac.dev/spec/36/#connecting-to-peers) methods
// std
use std::ffi::{CStr, CString};
use std::time::Duration;
@ -111,6 +113,12 @@ pub fn waku_peer_count() -> Result<usize> {
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;
/// Peer data from known/connected waku nodes
@ -129,6 +137,24 @@ pub struct WakuPeerData {
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
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
use std::ffi::{CStr, CString};
use std::time::Duration;
@ -107,11 +109,13 @@ pub fn waku_relay_publish_encrypt_asymmetric(
message: &WakuMessage,
pubsub_topic: Option<WakuPubSubTopic>,
public_key: &PublicKey,
signing_key: &SecretKey,
signing_key: Option<&SecretKey>,
timeout: Duration,
) -> Result<MessageId> {
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
.unwrap_or_else(waku_dafault_pubsub_topic)
.to_string();
@ -151,11 +155,13 @@ pub fn waku_relay_publish_encrypt_symmetric(
message: &WakuMessage,
pubsub_topic: Option<WakuPubSubTopic>,
symmetric_key: &Key<Aes256Gcm>,
signing_key: &SecretKey,
signing_key: Option<&SecretKey>,
timeout: Duration,
) -> Result<MessageId> {
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
.unwrap_or_else(waku_dafault_pubsub_topic)
.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()
}