2022-10-07 13:15:13 +02:00

313 lines
9.7 KiB
Rust

//! 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::SecretKey;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use sscanf::{scanf, RegexRepresentation};
// internal
use crate::decrypt::{waku_decode_asymmetric, waku_decode_symmetric};
pub type WakuMessageVersion = usize;
/// Base58 encoded peer id
pub type PeerId = String;
pub type MessageId = String;
/// JsonResponse wrapper.
/// `go-waku` ffi returns this type as a `char *` as per the [specification](https://rfc.vac.dev/spec/36/#jsonresponse-type)
/// This is internal, as it is better to use rust plain `Result` type.
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum JsonResponse<T> {
Result(T),
Error(String),
}
/// Waku response, just a `Result` with an `String` error.
/// Convenient we can transform a [`JsonResponse`] into a [`std::result::Result`]
pub type Result<T> = std::result::Result<T, String>;
impl<T> From<JsonResponse<T>> for Result<T> {
fn from(response: JsonResponse<T>) -> Self {
match response {
JsonResponse::Result(t) => Ok(t),
JsonResponse::Error(e) => Err(e),
}
}
}
// 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]>,
/// The content topic to be set on the message
content_topic: WakuContentTopic,
/// The Waku Message version number
version: WakuMessageVersion,
/// Unix timestamp in nanoseconds
timestamp: usize,
}
impl WakuMessage {
/// 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)
}
}
// TODO: use proper types instead of base64 strings
/// 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>,
/// Message signature (optional), hex encoded with 0x prefix
signature: Option<String>,
/// Decrypted message payload base64 encoded
data: String,
/// Padding base64 encoded
padding: String,
}
/// The content topic of a Waku message
/// as per the [specification](https://rfc.vac.dev/spec/36/#contentfilter-type)
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContentFilter {
/// The content topic of a Waku message
content_topic: WakuContentTopic,
}
/// 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)]
#[serde(rename_all = "camelCase")]
pub struct FilterSubscription {
/// Array of [`ContentFilter`] being subscribed to / unsubscribed from
content_filters: Vec<ContentFilter>,
/// Optional pubsub topic
pubsub_topic: Option<WakuPubSubTopic>,
}
/// 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>,
/// Array of [`ContentFilter`] to query for historical messages
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>,
/// The inclusive upper bound on the timestamp of queried messages.
/// This field holds the Unix epoch time in nanoseconds
end_time: Option<usize>,
/// Paging information in [`PagingOptions`] format
paging_options: Option<PagingOptions>,
}
/// The response received after doing a query to a store node
#[derive(Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StoreResponse {
/// Array of retrieved historical messages in [`WakuMessage`] format
messages: Vec<WakuMessage>,
/// Paging information in [`PagingOptions`] format from which to resume further historical queries
paging_options: Option<PagingOptions>,
}
/// Paging information
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PagingOptions {
/// Number of messages to retrieve per page
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>,
/// `true` if paging forward, `false` if paging backward
forward: bool,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageIndex {
/// Hash of the message at this [`MessageIndex`]
digest: String,
/// UNIX timestamp in nanoseconds at which the message at this [`MessageIndex`] was received
receiver_time: usize,
/// UNIX timestamp in nanoseconds at which the message is generated by its sender
sender_time: usize,
/// The pubsub topic of the message at this [`MessageIndex`]
pubsub_topic: WakuPubSubTopic,
}
#[derive(Copy, Clone)]
pub enum Encoding {
Proto,
Rlp,
Rfc26,
}
impl Display for Encoding {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
Encoding::Proto => "proto",
Encoding::Rlp => "rlp",
Encoding::Rfc26 => "rfc26",
};
f.write_str(s)
}
}
impl FromStr for Encoding {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"proto" => Ok(Self::Proto),
"rlp" => Ok(Self::Rlp),
"rfc26" => Ok(Self::Rfc26),
encoding => Err(format!("Unrecognized encoding: {}", encoding)),
}
}
}
impl RegexRepresentation for Encoding {
const REGEX: &'static str = r"\w";
}
#[derive(Clone)]
pub struct WakuContentTopic {
application_name: String,
version: usize,
content_topic_name: String,
encoding: Encoding,
}
impl FromStr for WakuContentTopic {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if let Ok((application_name, version, content_topic_name, encoding)) =
scanf!(s, "/{}/{}/{}/{}", String, usize, String, Encoding)
{
Ok(WakuContentTopic {
application_name,
version,
content_topic_name,
encoding,
})
} else {
Err(
format!(
"Wrong pub-sub topic format. Should be `/{{application-name}}/{{version-of-the-application}}/{{content-topic-name}}/{{encoding}}`. Got: {}",
s
)
)
}
}
}
impl Display for WakuContentTopic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"/{}/{}/{}/{}",
self.application_name, self.version, self.content_topic_name, self.encoding
)
}
}
impl Serialize for WakuContentTopic {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for WakuContentTopic {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let as_string: String = String::deserialize(deserializer)?;
as_string
.parse::<WakuContentTopic>()
.map_err(D::Error::custom)
}
}
#[derive(Clone)]
pub struct WakuPubSubTopic {
topic_name: String,
encoding: Encoding,
}
impl FromStr for WakuPubSubTopic {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if let Ok((topic_name, encoding)) = scanf!(s, "/waku/v2/{}/{}", String, Encoding) {
Ok(WakuPubSubTopic {
topic_name,
encoding,
})
} else {
Err(
format!(
"Wrong pub-sub topic format. Should be `/waku/2/{{topic-name}}/{{encoding}}`. Got: {}",
s
)
)
}
}
}
impl Display for WakuPubSubTopic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "/waku/2/{}/{}", self.topic_name, self.encoding)
}
}
impl Serialize for WakuPubSubTopic {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for WakuPubSubTopic {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let as_string: String = String::deserialize(deserializer)?;
as_string
.parse::<WakuPubSubTopic>()
.map_err(D::Error::custom)
}
}