1use std::collections::HashSet;
7use std::net::SocketAddr;
8use std::time::Duration;
9use thiserror::Error;
10
11use crate::nat_traversal::{BootstrapNode, NatTraversalConfig};
12use ed25519_dalek::{SigningKey as Ed25519SecretKey, VerifyingKey as Ed25519PublicKey};
13
14mod validation;
15
16#[derive(Error, Debug)]
18pub enum ConfigError {
19 #[error("Invalid bootstrap node configuration: {0}")]
20 InvalidBootstrapNode(String),
21
22 #[error("Invalid network configuration: {0}")]
23 InvalidNetwork(String),
24
25 #[error("Invalid timeout configuration: {0}")]
26 InvalidTimeout(String),
27
28 #[error("Invalid role configuration: {0}")]
29 InvalidRole(String),
30
31 #[error("Missing required configuration: {0}")]
32 MissingRequiredConfig(String),
33
34 #[error("Configuration value out of range: {0}")]
35 ValueOutOfRange(String),
36
37 #[error("Invalid address format: {0}")]
38 InvalidAddress(String),
39
40 #[error("Platform-specific configuration error: {0}")]
41 PlatformSpecific(String),
42}
43
44pub type ConfigResult<T> = Result<T, ConfigError>;
46
47#[derive(Clone, Debug)]
69pub struct P2PConfig {
70 pub(crate) bootstrap_nodes: Vec<BootstrapNode>,
72
73 pub(crate) keypair: (Ed25519SecretKey, Ed25519PublicKey),
75
76 pub(crate) nat_traversal_enabled: bool,
78
79 pub(crate) listen_address: SocketAddr,
81
82 pub(crate) connection_timeout: Duration,
84
85 pub(crate) max_connection_attempts: u32,
87
88 pub(crate) max_concurrent_connections: u32,
90
91 pub(crate) nat_traversal_config: Option<NatTraversalConfig>,
93}
94
95impl P2PConfig {
96 pub fn builder() -> P2PConfigBuilder {
106 P2PConfigBuilder::new()
107 }
108
109 pub fn bootstrap_nodes(&self) -> &[BootstrapNode] {
111 &self.bootstrap_nodes
112 }
113
114 pub fn keypair(&self) -> &(Ed25519SecretKey, Ed25519PublicKey) {
116 &self.keypair
117 }
118
119 pub fn nat_traversal_enabled(&self) -> bool {
121 self.nat_traversal_enabled
122 }
123
124 pub fn listen_address(&self) -> SocketAddr {
126 self.listen_address
127 }
128
129 pub fn connection_timeout(&self) -> Duration {
131 self.connection_timeout
132 }
133
134 pub fn max_connection_attempts(&self) -> u32 {
136 self.max_connection_attempts
137 }
138
139 pub fn max_concurrent_connections(&self) -> u32 {
141 self.max_concurrent_connections
142 }
143
144 pub fn nat_traversal_config(&self) -> Option<&NatTraversalConfig> {
146 self.nat_traversal_config.as_ref()
147 }
148}
149
150#[derive(Clone, Debug)]
171pub struct P2PConfigBuilder {
172 bootstrap_nodes: Vec<BootstrapNode>,
173 keypair: Option<(Ed25519SecretKey, Ed25519PublicKey)>,
174 nat_traversal_enabled: bool,
175 listen_address: Option<SocketAddr>,
176 connection_timeout: Duration,
177 max_connection_attempts: u32,
178 max_concurrent_connections: u32,
179 nat_traversal_config: Option<NatTraversalConfig>,
180}
181
182impl P2PConfigBuilder {
183 pub fn new() -> Self {
185 Self {
186 bootstrap_nodes: Vec::new(),
187 keypair: None,
188 nat_traversal_enabled: true,
189 listen_address: None,
190 connection_timeout: Duration::from_secs(30),
191 max_connection_attempts: 3,
192 max_concurrent_connections: 100,
193 nat_traversal_config: None,
194 }
195 }
196
197 pub fn with_bootstrap_nodes<T: Into<Vec<BootstrapNode>>>(&mut self, nodes: T) -> &mut Self {
202 self.bootstrap_nodes = nodes.into();
203 self
204 }
205
206 pub fn add_bootstrap_node(&mut self, node: BootstrapNode) -> &mut Self {
208 self.bootstrap_nodes.push(node);
209 self
210 }
211
212 pub fn with_keypair(&mut self, keypair: (Ed25519SecretKey, Ed25519PublicKey)) -> &mut Self {
214 self.keypair = Some(keypair);
215 self
216 }
217
218 pub fn with_nat_traversal(&mut self, enabled: bool) -> &mut Self {
220 self.nat_traversal_enabled = enabled;
221 self
222 }
223
224 pub fn with_listen_address(&mut self, address: SocketAddr) -> &mut Self {
226 self.listen_address = Some(address);
227 self
228 }
229
230 pub fn with_connection_timeout(&mut self, timeout: Duration) -> &mut Self {
232 self.connection_timeout = timeout;
233 self
234 }
235
236 pub fn with_max_connection_attempts(&mut self, attempts: u32) -> &mut Self {
238 self.max_connection_attempts = attempts;
239 self
240 }
241
242 pub fn with_max_concurrent_connections(&mut self, connections: u32) -> &mut Self {
244 self.max_concurrent_connections = connections;
245 self
246 }
247
248 pub fn with_nat_traversal_config(&mut self, config: NatTraversalConfig) -> &mut Self {
250 self.nat_traversal_config = Some(config);
251 self
252 }
253
254 pub fn build(&self) -> ConfigResult<P2PConfig> {
259 if self.nat_traversal_enabled && self.bootstrap_nodes.is_empty() {
261 return Err(ConfigError::MissingRequiredConfig(
262 "At least one bootstrap node is required when NAT traversal is enabled".to_string(),
263 ));
264 }
265
266 let mut seen = HashSet::new();
268 for (i, node) in self.bootstrap_nodes.iter().enumerate() {
269 if !seen.insert(node.address) {
270 return Err(ConfigError::InvalidBootstrapNode(format!(
271 "Duplicate bootstrap node at index {}: {}",
272 i, node.address
273 )));
274 }
275 }
276
277 let keypair = self
279 .keypair
280 .clone()
281 .ok_or_else(|| ConfigError::MissingRequiredConfig("Keypair is required".to_string()))?;
282
283 let listen_address = self.listen_address.unwrap_or_else(|| {
285 "0.0.0.0:0".parse().unwrap()
287 });
288
289 if self.connection_timeout < Duration::from_secs(1)
291 || self.connection_timeout > Duration::from_secs(300)
292 {
293 return Err(ConfigError::ValueOutOfRange(format!(
294 "Connection timeout must be between 1 and 300 seconds, got {:?}",
295 self.connection_timeout
296 )));
297 }
298
299 if self.max_connection_attempts == 0 || self.max_connection_attempts > 10 {
301 return Err(ConfigError::ValueOutOfRange(format!(
302 "Maximum connection attempts must be between 1 and 10, got {}",
303 self.max_connection_attempts
304 )));
305 }
306
307 if self.max_concurrent_connections == 0 || self.max_concurrent_connections > 1000 {
309 return Err(ConfigError::ValueOutOfRange(format!(
310 "Maximum concurrent connections must be between 1 and 1000, got {}",
311 self.max_concurrent_connections
312 )));
313 }
314
315 Ok(P2PConfig {
317 bootstrap_nodes: self.bootstrap_nodes.clone(),
318 keypair,
319 nat_traversal_enabled: self.nat_traversal_enabled,
320 listen_address,
321 connection_timeout: self.connection_timeout,
322 max_connection_attempts: self.max_connection_attempts,
323 max_concurrent_connections: self.max_concurrent_connections,
324 nat_traversal_config: self.nat_traversal_config.clone(),
325 })
326 }
327}
328
329impl Default for P2PConfigBuilder {
330 fn default() -> Self {
331 Self::new()
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::crypto::raw_public_keys::key_utils;
339 use crate::nat_traversal::BootstrapNode;
340 use std::net::{IpAddr, Ipv4Addr};
341
342 #[test]
343 fn test_builder_with_valid_config() {
344 let keypair = key_utils::generate_ed25519_keypair();
345 let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
346
347 let config = P2PConfigBuilder::new()
348 .with_keypair(keypair)
349 .add_bootstrap_node(bootstrap_node)
350 .with_nat_traversal(true)
351 .with_listen_address(SocketAddr::new(
352 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
353 8000,
354 ))
355 .with_connection_timeout(Duration::from_secs(60))
356 .build()
357 .unwrap();
358
359 assert_eq!(config.bootstrap_nodes.len(), 1);
360 assert_eq!(
361 config.bootstrap_nodes[0].address.to_string(),
362 "127.0.0.1:9000"
363 );
364 assert!(config.nat_traversal_enabled);
365 assert_eq!(config.listen_address.to_string(), "127.0.0.1:8000");
366 assert_eq!(config.connection_timeout, Duration::from_secs(60));
367 }
368
369 #[test]
370 fn test_builder_missing_keypair() {
371 let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
372
373 let result = P2PConfigBuilder::new()
374 .add_bootstrap_node(bootstrap_node)
375 .with_nat_traversal(true)
376 .build();
377
378 assert!(result.is_err());
379 match result {
380 Err(ConfigError::MissingRequiredConfig(msg)) => {
381 assert!(msg.contains("Keypair is required"));
382 }
383 _ => panic!("Expected MissingRequiredConfig error"),
384 }
385 }
386
387 #[test]
388 fn test_builder_missing_bootstrap_nodes() {
389 let keypair = key_utils::generate_ed25519_keypair();
390
391 let result = P2PConfigBuilder::new()
392 .with_keypair(keypair)
393 .with_nat_traversal(true)
394 .build();
395
396 assert!(result.is_err());
397 match result {
398 Err(ConfigError::MissingRequiredConfig(msg)) => {
399 assert!(msg.contains("bootstrap node"));
400 }
401 _ => panic!("Expected MissingRequiredConfig error"),
402 }
403 }
404
405 #[test]
406 fn test_builder_duplicate_bootstrap_nodes() {
407 let keypair = key_utils::generate_ed25519_keypair();
408 let addr = "127.0.0.1:9000".parse().unwrap();
409 let bootstrap_node1 = BootstrapNode::new(addr);
410 let bootstrap_node2 = BootstrapNode::new(addr);
411
412 let result = P2PConfigBuilder::new()
413 .with_keypair(keypair)
414 .add_bootstrap_node(bootstrap_node1)
415 .add_bootstrap_node(bootstrap_node2)
416 .build();
417
418 assert!(result.is_err());
419 match result {
420 Err(ConfigError::InvalidBootstrapNode(msg)) => {
421 assert!(msg.contains("Duplicate bootstrap node"));
422 }
423 _ => panic!("Expected InvalidBootstrapNode error"),
424 }
425 }
426
427 #[test]
428 fn test_builder_invalid_connection_timeout() {
429 let keypair = key_utils::generate_ed25519_keypair();
430 let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
431
432 let result = P2PConfigBuilder::new()
433 .with_keypair(keypair)
434 .add_bootstrap_node(bootstrap_node)
435 .with_connection_timeout(Duration::from_millis(500))
436 .build();
437
438 assert!(result.is_err());
439 match result {
440 Err(ConfigError::ValueOutOfRange(msg)) => {
441 assert!(msg.contains("Connection timeout"));
442 }
443 _ => panic!("Expected ValueOutOfRange error"),
444 }
445 }
446
447 #[test]
448 fn test_builder_nat_traversal_disabled() {
449 let keypair = key_utils::generate_ed25519_keypair();
450
451 let config = P2PConfigBuilder::new()
452 .with_keypair(keypair)
453 .with_nat_traversal(false)
454 .build()
455 .unwrap();
456
457 assert!(!config.nat_traversal_enabled);
458 assert_eq!(config.bootstrap_nodes.len(), 0);
459 }
460
461 #[test]
462 fn test_config_getters() {
463 let keypair = key_utils::generate_ed25519_keypair();
464 let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
465 let listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000);
466 let timeout = Duration::from_secs(60);
467
468 let config = P2PConfigBuilder::new()
469 .with_keypair(keypair)
470 .add_bootstrap_node(bootstrap_node)
471 .with_listen_address(listen_address)
472 .with_connection_timeout(timeout)
473 .with_max_connection_attempts(5)
474 .with_max_concurrent_connections(200)
475 .build()
476 .unwrap();
477
478 assert_eq!(config.bootstrap_nodes().len(), 1);
479 assert_eq!(config.listen_address(), listen_address);
480 assert_eq!(config.connection_timeout(), timeout);
481 assert_eq!(config.max_connection_attempts(), 5);
482 assert_eq!(config.max_concurrent_connections(), 200);
483 }
484}