Skip to main content

alopex_chirps_core/
config.rs

1use std::net::SocketAddr;
2use std::path::PathBuf;
3use std::time::Duration;
4use thiserror::Error;
5
6#[derive(Error, Debug)]
7pub enum ConfigError {
8    #[error("Invalid certificate or key: {0}")]
9    Certificate(String),
10    #[error("I/O error: {0}")]
11    Io(#[from] std::io::Error),
12}
13
14/// Configuration for a alopex-chirps node.
15#[derive(Debug, Clone)]
16pub struct NodeConfig {
17    /// Address to bind for the transport.
18    pub bind_addr: SocketAddr,
19    /// A list of seed nodes to connect to for bootstrapping.
20    pub seeds: Vec<SocketAddr>,
21    /// Path to the TLS certificate file.
22    pub cert_path: Option<PathBuf>,
23    /// Path to the TLS private key file.
24    pub key_path: Option<PathBuf>,
25    /// DER-encoded TLS trust anchors accepted for peer certificates.
26    ///
27    /// Each node should include the cluster CA certificate or the public
28    /// certificates of the self-signed peers it is allowed to contact.
29    pub trusted_cert_paths: Vec<PathBuf>,
30    /// Timeout for a direct ping to a node.
31    pub ping_timeout: Duration,
32    /// Timeout for an indirect ping (via neighbors).
33    pub indirect_ping_timeout: Duration,
34    /// Timeout after which a suspected node is declared dead.
35    pub suspect_to_dead_timeout: Duration,
36    /// Interval for periodic gossip ticks.
37    pub gossip_interval: Duration,
38    /// Maximum accepted future skew for HLC-stamped gossip messages.
39    pub max_clock_skew: Duration,
40    /// Timeout for send/broadcast operations.
41    pub broadcast_timeout: Duration,
42    /// Maximum number of in-flight send/broadcast requests.
43    pub send_queue_capacity: usize,
44    /// Fanout for gossip messages. If None, it's calculated as `max(3, ceil(sqrt(N)))`.
45    pub fanout: Option<usize>,
46    /// Number of convergence rounds for gossip.
47    pub convergence_rounds: usize,
48    /// Path to the file where the node ID is persisted.
49    pub node_id_path: PathBuf,
50}
51
52impl Default for NodeConfig {
53    fn default() -> Self {
54        Self {
55            bind_addr: "127.0.0.1:0".parse().unwrap(),
56            seeds: Vec::new(),
57            cert_path: None,
58            key_path: None,
59            trusted_cert_paths: Vec::new(),
60            ping_timeout: Duration::from_secs(1),
61            indirect_ping_timeout: Duration::from_secs(3),
62            suspect_to_dead_timeout: Duration::from_secs(6),
63            gossip_interval: Duration::from_millis(200),
64            max_clock_skew: Duration::from_secs(1),
65            broadcast_timeout: Duration::from_millis(200),
66            send_queue_capacity: 1024,
67            fanout: None,
68            convergence_rounds: 3,
69            node_id_path: PathBuf::from(".chirps_node_id"),
70        }
71    }
72}
73
74impl NodeConfig {
75    /// Validates the configuration.
76    pub fn validate(&self) -> Result<(), ConfigError> {
77        match (&self.cert_path, &self.key_path) {
78            (Some(cert), Some(key)) => {
79                if !cert.exists() {
80                    return Err(ConfigError::Certificate(format!(
81                        "Certificate file not found: {}",
82                        cert.display()
83                    )));
84                }
85                if !key.exists() {
86                    return Err(ConfigError::Certificate(format!(
87                        "Key file not found: {}",
88                        key.display()
89                    )));
90                }
91            }
92            (Some(_), None) => {
93                return Err(ConfigError::Certificate(
94                    "Key file must be provided if certificate is provided".to_string(),
95                ));
96            }
97            (None, Some(_)) => {
98                return Err(ConfigError::Certificate(
99                    "Certificate file must be provided if key is provided".to_string(),
100                ));
101            }
102            (None, None) => {
103                // Self-signed certificates will be generated in this case.
104            }
105        }
106        for cert in &self.trusted_cert_paths {
107            if !cert.exists() {
108                return Err(ConfigError::Certificate(format!(
109                    "Trusted certificate file not found: {}",
110                    cert.display()
111                )));
112            }
113        }
114        Ok(())
115    }
116}