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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR Commercial
//! P2P configuration with CLI flag parsing and validation.
use std::net::SocketAddr;
use std::path::PathBuf;
/// Configuration for the P2P subsystem.
#[derive(Debug, Clone)]
pub struct P2pConfig {
/// Whether P2P is enabled (`--p2p` flag).
pub enabled: bool,
/// QUIC listen port (`--p2p-port`, default 19091).
pub port: u16,
/// Network isolation seed (`--p2p-seed`). Nodes with different seeds reject each other.
pub seed: Option<String>,
/// Manually specified peer addresses (`--p2p-peer`, repeatable).
pub manual_peers: Vec<SocketAddr>,
/// Enable mDNS discovery (`--p2p-mdns`).
pub mdns: bool,
/// Gossip interval in milliseconds (default 5000).
pub gossip_interval_ms: u64,
/// Maximum triples per sync batch (default 5000).
pub sync_batch_size: usize,
/// Maximum connected peers (default 32).
pub max_peers: usize,
/// Directory for persistent data (keypair, etc.).
pub data_dir: PathBuf,
/// Max triples accepted per peer per minute (default 1000).
pub max_triples_per_peer_per_min: usize,
/// Max triples accepted globally per minute (default 10000).
pub max_triples_global_per_min: usize,
}
impl Default for P2pConfig {
fn default() -> Self {
let data_dir = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".cortex");
Self {
enabled: false,
port: 19091,
seed: None,
manual_peers: Vec::new(),
mdns: false,
gossip_interval_ms: 5000,
sync_batch_size: 5000,
max_peers: 32,
data_dir,
max_triples_per_peer_per_min: 1000,
max_triples_global_per_min: 10000,
}
}
}
impl P2pConfig {
/// Validate configuration values.
pub fn validate(&self) -> Result<(), String> {
if self.port < 1024 {
return Err(format!(
"p2p port must be >= 1024, got {}",
self.port
));
}
if let Some(ref seed) = self.seed {
if seed.is_empty() {
return Err("p2p seed must not be empty".to_string());
}
if !seed.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') {
return Err("p2p seed must be alphanumeric (plus _ and -)".to_string());
}
}
if self.sync_batch_size < 100 || self.sync_batch_size > 50000 {
return Err(format!(
"sync_batch_size must be 100..50000, got {}",
self.sync_batch_size
));
}
if self.gossip_interval_ms < 1000 {
return Err(format!(
"gossip_interval_ms must be >= 1000, got {}",
self.gossip_interval_ms
));
}
Ok(())
}
/// Parse P2P flags from CLI arguments.
///
/// Recognises: `--p2p`, `--p2p-port`, `--p2p-seed`, `--p2p-peer`, `--p2p-mdns`.
pub fn from_args(args: &[String]) -> P2pConfig {
let mut cfg = P2pConfig::default();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--p2p" => cfg.enabled = true,
"--p2p-mdns" => cfg.mdns = true,
"--p2p-port" => {
if i + 1 < args.len() {
if let Ok(p) = args[i + 1].parse::<u16>() {
cfg.port = p;
}
i += 1;
}
}
"--p2p-seed" => {
if i + 1 < args.len() {
cfg.seed = Some(args[i + 1].clone());
i += 1;
}
}
"--p2p-peer" => {
if i + 1 < args.len() {
if let Ok(addr) = args[i + 1].parse::<SocketAddr>() {
cfg.manual_peers.push(addr);
}
i += 1;
}
}
_ => {}
}
i += 1;
}
cfg
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_valid() {
assert!(P2pConfig::default().validate().is_ok());
}
#[test]
fn rejects_invalid_port() {
let mut cfg = P2pConfig::default();
cfg.port = 0;
assert!(cfg.validate().is_err());
cfg.port = 80;
assert!(cfg.validate().is_err());
}
#[test]
fn parses_cli_args() {
let args: Vec<String> = vec![
"--p2p",
"--p2p-port",
"19091",
"--p2p-seed",
"abc123",
"--p2p-peer",
"1.2.3.4:19091",
]
.into_iter()
.map(String::from)
.collect();
let cfg = P2pConfig::from_args(&args);
assert!(cfg.enabled);
assert_eq!(cfg.port, 19091);
assert_eq!(cfg.seed.as_deref(), Some("abc123"));
assert_eq!(cfg.manual_peers.len(), 1);
assert_eq!(
cfg.manual_peers[0],
"1.2.3.4:19091".parse::<SocketAddr>().unwrap()
);
}
#[test]
fn rejects_empty_seed() {
let mut cfg = P2pConfig::default();
cfg.seed = Some(String::new());
assert!(cfg.validate().is_err());
}
}