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