waku-bindings 0.6.0

Waku networking library
//! Waku node [configuration](https://rfc.vac.dev/spec/36/#jsonconfig-type) related items

use std::fmt::{Display, Formatter};
use std::str::FromStr;
// std
// crates
use crate::WakuPubSubTopic;
use multiaddr::Multiaddr;
use secp256k1::SecretKey;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
// internal

/// Waku node configuration
#[derive(Clone, SmartDefault, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WakuNodeConfig {
    /// Listening IP address. Default `0.0.0.0`
    #[default(Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))))]
    pub host: Option<std::net::IpAddr>,
    /// Libp2p TCP listening port. Default `60000`. Use `0` for **random**
    #[default(Some(60000))]
    pub port: Option<usize>,
    /// External address to advertise to other nodes. Can be ip4, ip6 or dns4, dns6.
    /// If null, the multiaddress(es) generated from the ip and port specified in the config (or default ones) will be used.
    /// Default: null
    pub advertise_addr: Option<Multiaddr>,
    /// Secp256k1 private key in Hex format (`0x123...abc`). Default random
    #[serde(with = "secret_key_serde")]
    pub node_key: Option<SecretKey>,
    /// Interval in seconds for pinging peers to keep the connection alive. Default `20`
    #[default(Some(20))]
    pub keep_alive_interval: Option<usize>,
    /// Enable relay protocol. Default `true`
    #[default(Some(true))]
    pub relay: Option<bool>,
    /// Enable store protocol to persist message history
    #[default(Some(false))]
    pub store: Option<bool>,
    /// Url connection string. Accepts SQLite and PostgreSQL connection strings
    #[default(Some("sqlite3://store.db".to_string()))]
    pub database_url: Option<String>,
    /// Max number of messages to store in the databas
    #[default(Some(1000))]
    pub store_retention_max_messages: Option<usize>,
    /// Max number of seconds that a message will be persisted in the database, default 1 day
    #[default(Some(86400))]
    pub store_retention_max_seconds: Option<usize>,
    pub relay_topics: Vec<WakuPubSubTopic>,
    /// The minimum number of peers required on a topic to allow broadcasting a message. Default `0`
    #[default(Some(0))]
    pub min_peers_to_publish: Option<usize>,
    /// Set the log level. Default `INFO`. Allowed values "DEBUG", "INFO", "WARN", "ERROR", "DPANIC", "PANIC", "FATAL"
    #[default(Some(WakuLogLevel::Info))]
    pub log_level: Option<WakuLogLevel>,
    /// Enable DiscoveryV5. Default `false`
    #[default(Some(false))]
    #[serde(rename = "discV5")]
    pub discv5: Option<bool>,
    /// Array of bootstrap nodes ENR.
    #[serde(rename = "discV5BootstrapNodes", default)]
    pub discv5_bootstrap_nodes: Vec<String>,
    /// UDP port for DiscoveryV5. Default `9000`.
    #[default(Some(9000))]
    #[serde(rename = "discV5UDPPort")]
    pub discv5_udp_port: Option<u16>,
    /// Array of DNS discovery URLs
    #[serde(rename = "dnsDiscoveryURLs", default)]
    pub dns_discovery_urls: Vec<String>,
    /// Use custom nameserver. Default `` (uses the OS nameserver)
    #[default(Some("".to_string()))]
    #[serde(rename = "dnsDiscoveryNameServer")]
    pub dns_discovery_nameserver: Option<String>,
    /// Gossipsub custom configuration.
    pub gossipsub_params: Option<GossipSubParams>,
    /// The domain name resolving to the node's public IPv4 address.
    #[serde(rename = "dns4DomainName")]
    pub dns4_domain_name: Option<String>,
    /// Custom websocket support parameters
    #[serde(rename = "websockets")]
    pub websocket_params: Option<WebsocketParams>,
}

#[derive(Clone, SmartDefault, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GossipSubParams {
    /// Sets the optimal degree for a GossipSub topic mesh. For example, if D == 6,
    /// each peer will want to have about six peers in their mesh for each topic they're subscribed to.
    /// `d` should be set somewhere between `dlo` and `dhi`.
    #[serde(rename = "d")]
    pub d: Option<i32>,
    /// Sets the lower bound on the number of peers we keep in a GossipSub topic mesh.
    /// If we have fewer than dlo peers, we will attempt to graft some more into the mesh at
    /// the next heartbeat.
    #[serde(rename = "d_low")]
    pub dlo: Option<i32>,
    /// Sets the upper bound on the number of peers we keep in a GossipSub topic mesh.
    /// If we have more than dhi peers, we will select some to prune from the mesh at the next heartbeat.
    #[serde(rename = "d_high")]
    pub dhi: Option<i32>,
    /// `dscore` affects how peers are selected when pruning a mesh due to over subscription.
    /// At least dscore of the retained peers will be high-scoring, while the remainder are
    /// chosen randomly.
    #[serde(rename = "d_score")]
    pub dscore: Option<i32>,
    /// Sets the quota for the number of outbound connections to maintain in a topic mesh.
    /// When the mesh is pruned due to over subscription, we make sure that we have outbound connections
    /// to at least dout of the survivor peers. This prevents sybil attackers from overwhelming
    /// our mesh with incoming connections.
    ///
    /// dout must be set below Dlo, and must not exceed D / 2.
    #[serde(rename = "d_out")]
    pub dout: Option<i32>,
    /// Controls the size of the message cache used for gossip.
    /// The message cache will remember messages for history_length heartbeats.
    pub history_length: Option<i32>,
    /// Controls how many cached message ids we will advertise in
    /// IHAVE gossip messages. When asked for our seen message IDs, we will return
    /// only those from the most recent history_gossip heartbeats. The slack between
    /// history_gossip and history_length allows us to avoid advertising messages
    /// that will be expired by the time they're requested.
    ///
    /// history_gossip must be less than or equal to history_length to
    /// avoid a runtime panic.
    pub history_gossip: Option<i32>,
    /// dlazy affects how many peers we will emit gossip to at each heartbeat.
    /// We will send gossip to at least dlazy peers outside our mesh. The actual
    /// number may be more, depending on gossip_factor and how many peers we're
    /// connected to.
    pub dlazy: Option<i32>,
    /// `gossip_factor` affects how many peers we will emit gossip to at each heartbeat.
    /// We will send gossip to gossip_factor * (total number of non-mesh peers), or
    /// Dlazy, whichever is greater.
    pub gossip_factor: Option<f64>,
    /// Controls how many times we will allow a peer to request
    /// the same message id through IWANT gossip before we start ignoring them. This is designed
    /// to prevent peers from spamming us with requests and wasting our resources.
    pub gossip_retransmission: Option<i32>,
    /// Short delay before the heartbeat timer begins
    /// after the router is initialized.
    pub heartbeat_initial_delay_ms: Option<i32>,
    /// Controls the time between heartbeats.
    pub heartbeat_interval_seconds: Option<i32>,
    /// Duration threshold for heartbeat processing before emitting
    /// a warning; this would be indicative of an overloaded peer.
    pub slow_heartbeat_warning: Option<f64>,
    /// Controls how long we keep track of the fanout state. If it's been
    /// fanout_ttl_seconds since we've published to a topic that we're not subscribed to,
    /// we'll delete the fanout map for that topic.
    pub fanout_ttl_seconds: Option<i32>,
    /// Controls the number of peers to include in prune Peer eXchange.
    /// When we prune a peer that's eligible for PX (has a good score, etc), we will try to
    /// send them signed peer records for up to prune_peers other peers that we
    /// know of.
    pub prune_peers: Option<i32>,
    /// Controls the backoff time for pruned peers. This is how long
    /// a peer must wait before attempting to graft into our mesh again after being pruned.
    /// When pruning a peer, we send them our value of PruneBackoff so they know
    /// the minimum time to wait. Peers running older versions may not send a backoff time,
    /// so if we receive a prune message without one, we will wait at least PruneBackoff
    /// before attempting to re-graft.
    pub prune_backoff_seconds: Option<i32>,
    /// Controls the backoff time to use when unsuscribing
    /// from a topic. A peer should not resubscribe to this topic before this
    /// duration.
    pub unsubscribe_backoff_seconds: Option<i32>,
    /// Controls the number of active connection attempts for peers obtained through PX.
    pub connectors: Option<i32>,
    /// Sets the maximum number of pending connections for peers attempted through px.
    pub max_pending_connections: Option<i32>,
    /// Controls the timeout for connection attempts.
    pub connection_timeout_seconds: Option<i32>,
    /// Number of heartbeat ticks for attempting to reconnect direct peers
    /// that are not currently connected.
    pub direct_connect_ticks: Option<u64>,
    /// Initial delay before opening connections to direct peers
    pub direct_connect_initial_delay_seconds: Option<i32>,
    /// Number of heartbeat ticks for attempting to improve the mesh
    /// with opportunistic grafting. Every opportunistic_graft_ticks we will attempt to select some
    /// high-scoring mesh peers to replace lower-scoring ones, if the median score of our mesh peers falls
    /// below a threshold (see https://godoc.org/github.com/libp2p/go-libp2p-pubsub#PeerScoreThresholds).
    pub opportunistic_graft_ticks: Option<u64>,
    /// Number of peers to opportunistically graft.
    pub opportunistic_graft_peers: Option<i32>,
    /// If a GRAFT comes before graft_flood_threshold_seconds has elapsed since the last PRUNE,
    /// then there is an extra score penalty applied to the peer through P7.
    pub graft_flood_threshold_seconds: Option<i32>,
    /// Maximum number of messages to include in an IHAVE message.
    /// Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a
    /// peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the
    /// default if your system is pushing more than 5000 messages in history_gossip heartbeats;
    /// with the defaults this is 1666 messages/s.
    #[serde(rename = "maxIHaveLength")]
    pub max_ihave_length: Option<i32>,
    /// Maximum number of IHAVE messages to accept from a peer within a heartbeat.
    #[serde(rename = "maxIHaveMessages")]
    pub max_ihave_messages: Option<i32>,
    /// Time to wait for a message requested through IWANT following an IHAVE advertisement.
    /// If the message is not received within this window, a broken promise is declared and
    /// the router may apply bahavioural penalties.
    #[serde(rename = "iwantFollowupTimeSeconds")]
    pub iwant_followup_time_seconds: Option<i32>,
    // Time until a previously seen message ID can be forgotten about.
    #[serde(rename = "seenMessagesTTLSeconds")]
    pub seen_messages_ttl_seconds: Option<i32>,
}

#[derive(Clone, SmartDefault, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WebsocketParams {
    /// Indicates if websockets support will be enabled
    #[default(Some(false))]
    pub enabled: Option<bool>,
    /// Listening address for websocket connections. Default `0.0.0.0`
    #[default(Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))))]
    pub host: Option<std::net::IpAddr>,
    /// TCP listening port for websocket connection. Use `0` for **random**. Default `60001`, if secure websockets support is enabled, the default is `6443“`
    pub port: Option<usize>,
    /// Enable secure websockets support
    #[default(Some(false))]
    pub secure: Option<bool>,
    /// Secure websocket certificate path. Mandatory if secure websockets support is enabled.
    pub cert_path: Option<String>,
    /// Secure websocket key path. Mandatory if secure websockets support is enabled.
    pub key_path: Option<String>,
}

#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub enum WakuLogLevel {
    #[default]
    Info,
    Debug,
    Warn,
    Error,
    DPanic,
    Panic,
    Fatal,
}

impl FromStr for WakuLogLevel {
    type Err = std::io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "info" => Ok(Self::Info),
            "debug" => Ok(Self::Debug),
            "warn" => Ok(Self::Warn),
            "error" => Ok(Self::Error),
            "dpanic" => Ok(Self::DPanic),
            "panic" => Ok(Self::Panic),
            "fatal" => Ok(Self::Fatal),
            _ => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("Unrecognized waku log level: {s}. Allowed values \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\", \"DPANIC\", \"PANIC\", \"FATAL\""),
            )),
        }
    }
}

impl Display for WakuLogLevel {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let tag = match self {
            WakuLogLevel::Info => "INFO",
            WakuLogLevel::Debug => "DEBUG",
            WakuLogLevel::Warn => "WARN",
            WakuLogLevel::Error => "ERROR",
            WakuLogLevel::DPanic => "DPANIC",
            WakuLogLevel::Panic => "PANIC",
            WakuLogLevel::Fatal => "FATAL",
        };
        write!(f, "{tag}")
    }
}

mod secret_key_serde {
    use secp256k1::SecretKey;
    use serde::de::Error;
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(key: &Option<SecretKey>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let as_string: Option<String> = key.as_ref().map(|key| hex::encode(key.secret_bytes()));
        as_string.serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<SecretKey>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let as_string: Option<String> = Option::<String>::deserialize(deserializer)?;
        match as_string {
            None => Ok(None),
            Some(s) => {
                let key_bytes = hex::decode(s).map_err(|e| D::Error::custom(format!("{e}")))?;
                Ok(Some(
                    SecretKey::from_slice(&key_bytes)
                        .map_err(|e| D::Error::custom(format!("{e}")))?,
                ))
            }
        }
    }
}