ant_quic/api/config/
mod.rs

1//! Configuration module for ant-quic
2//!
3//! This module provides a clean, builder-based configuration API for the ant-quic library.
4//! It includes validation for configuration options and sensible defaults.
5
6use std::net::SocketAddr;
7use std::time::Duration;
8use std::collections::HashSet;
9use thiserror::Error;
10
11use ed25519_dalek::{SigningKey as Ed25519SecretKey, VerifyingKey as Ed25519PublicKey};
12use crate::nat_traversal::{BootstrapNode, NatTraversalConfig};
13
14mod validation;
15
16/// Configuration errors with detailed context
17#[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
44/// Result type for configuration operations
45pub type ConfigResult<T> = Result<T, ConfigError>;
46
47/// P2P configuration
48///
49/// This struct contains all configuration options for a P2P node.
50/// Use the builder pattern via `P2PConfig::builder()` to create a configuration.
51///
52/// # Example
53///
54/// ```
55/// use ant_quic::api::config::P2PConfig;
56/// use ant_quic::crypto::raw_public_keys::key_utils;
57/// use ant_quic::nat_traversal::BootstrapNode;
58/// use std::net::SocketAddr;
59///
60/// let bootstrap_addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
61/// let config = P2PConfig::builder()
62///     .with_bootstrap_nodes(vec![BootstrapNode::new(bootstrap_addr)])
63///     .with_keypair(key_utils::generate_ed25519_keypair())
64///     .with_nat_traversal(true)
65///     .build()
66///     .unwrap();
67/// ```
68#[derive(Clone, Debug)]
69pub struct P2PConfig {
70    /// Bootstrap nodes for NAT traversal
71    pub(crate) bootstrap_nodes: Vec<BootstrapNode>,
72    
73    /// Keypair for authentication
74    pub(crate) keypair: (Ed25519SecretKey, Ed25519PublicKey),
75    
76    /// Enable NAT traversal
77    pub(crate) nat_traversal_enabled: bool,
78    
79    /// Listen address
80    pub(crate) listen_address: SocketAddr,
81    
82    /// Connection timeout
83    pub(crate) connection_timeout: Duration,
84    
85    /// Maximum number of connection attempts
86    pub(crate) max_connection_attempts: u32,
87    
88    /// Maximum number of concurrent connections
89    pub(crate) max_concurrent_connections: u32,
90    
91    /// Advanced NAT traversal configuration
92    pub(crate) nat_traversal_config: Option<NatTraversalConfig>,
93}
94
95impl P2PConfig {
96    /// Create a new configuration builder
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// use ant_quic::api::config::P2PConfig;
102    ///
103    /// let builder = P2PConfig::builder();
104    /// ```
105    pub fn builder() -> P2PConfigBuilder {
106        P2PConfigBuilder::new()
107    }
108    
109    /// Get the bootstrap nodes
110    pub fn bootstrap_nodes(&self) -> &[BootstrapNode] {
111        &self.bootstrap_nodes
112    }
113    
114    /// Get the keypair
115    pub fn keypair(&self) -> &(Ed25519SecretKey, Ed25519PublicKey) {
116        &self.keypair
117    }
118    
119    /// Check if NAT traversal is enabled
120    pub fn nat_traversal_enabled(&self) -> bool {
121        self.nat_traversal_enabled
122    }
123    
124    /// Get the listen address
125    pub fn listen_address(&self) -> SocketAddr {
126        self.listen_address
127    }
128    
129    /// Get the connection timeout
130    pub fn connection_timeout(&self) -> Duration {
131        self.connection_timeout
132    }
133    
134    /// Get the maximum number of connection attempts
135    pub fn max_connection_attempts(&self) -> u32 {
136        self.max_connection_attempts
137    }
138    
139    /// Get the maximum number of concurrent connections
140    pub fn max_concurrent_connections(&self) -> u32 {
141        self.max_concurrent_connections
142    }
143    
144    /// Get the advanced NAT traversal configuration
145    pub fn nat_traversal_config(&self) -> Option<&NatTraversalConfig> {
146        self.nat_traversal_config.as_ref()
147    }
148}
149
150/// Builder for P2PConfig
151///
152/// This builder provides a fluent API for creating a P2PConfig.
153///
154/// # Example
155///
156/// ```
157/// use ant_quic::api::config::P2PConfigBuilder;
158/// use ant_quic::crypto::raw_public_keys::key_utils;
159/// use ant_quic::nat_traversal::BootstrapNode;
160/// use std::net::SocketAddr;
161///
162/// let bootstrap_addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
163/// let config = P2PConfigBuilder::new()
164///     .with_bootstrap_nodes(vec![BootstrapNode::new(bootstrap_addr)])
165///     .with_keypair(key_utils::generate_ed25519_keypair())
166///     .with_nat_traversal(true)
167///     .build()
168///     .unwrap();
169/// ```
170#[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    /// Create a new builder with default values
184    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    /// Set the bootstrap nodes
198    ///
199    /// Bootstrap nodes are used for NAT traversal coordination.
200    /// At least one bootstrap node is required if NAT traversal is enabled.
201    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    /// Add a bootstrap node
207    pub fn add_bootstrap_node(&mut self, node: BootstrapNode) -> &mut Self {
208        self.bootstrap_nodes.push(node);
209        self
210    }
211    
212    /// Set the keypair for authentication
213    pub fn with_keypair(&mut self, keypair: (Ed25519SecretKey, Ed25519PublicKey)) -> &mut Self {
214        self.keypair = Some(keypair);
215        self
216    }
217    
218    /// Enable or disable NAT traversal
219    pub fn with_nat_traversal(&mut self, enabled: bool) -> &mut Self {
220        self.nat_traversal_enabled = enabled;
221        self
222    }
223    
224    /// Set the listen address
225    pub fn with_listen_address(&mut self, address: SocketAddr) -> &mut Self {
226        self.listen_address = Some(address);
227        self
228    }
229    
230    /// Set the connection timeout
231    pub fn with_connection_timeout(&mut self, timeout: Duration) -> &mut Self {
232        self.connection_timeout = timeout;
233        self
234    }
235    
236    /// Set the maximum number of connection attempts
237    pub fn with_max_connection_attempts(&mut self, attempts: u32) -> &mut Self {
238        self.max_connection_attempts = attempts;
239        self
240    }
241    
242    /// Set the maximum number of concurrent connections
243    pub fn with_max_concurrent_connections(&mut self, connections: u32) -> &mut Self {
244        self.max_concurrent_connections = connections;
245        self
246    }
247    
248    /// Set advanced NAT traversal configuration
249    pub fn with_nat_traversal_config(&mut self, config: NatTraversalConfig) -> &mut Self {
250        self.nat_traversal_config = Some(config);
251        self
252    }
253    
254    /// Build the configuration
255    ///
256    /// This method validates the configuration and returns a `P2PConfig` if valid.
257    /// If the configuration is invalid, it returns a `ConfigError`.
258    pub fn build(&self) -> ConfigResult<P2PConfig> {
259        // Validate bootstrap nodes if NAT traversal is enabled
260        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        // Check for duplicate bootstrap nodes
267        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(
271                    format!("Duplicate bootstrap node at index {}: {}", i, node.address)
272                ));
273            }
274        }
275        
276        // Validate keypair
277        let keypair = self.keypair.clone().ok_or_else(|| {
278            ConfigError::MissingRequiredConfig("Keypair is required".to_string())
279        })?;
280        
281        // Validate listen address
282        let listen_address = self.listen_address.unwrap_or_else(|| {
283            // Default to a random port on all interfaces
284            "0.0.0.0:0".parse().unwrap()
285        });
286        
287        // Validate connection timeout
288        if self.connection_timeout < Duration::from_secs(1) || self.connection_timeout > Duration::from_secs(300) {
289            return Err(ConfigError::ValueOutOfRange(
290                format!("Connection timeout must be between 1 and 300 seconds, got {:?}", self.connection_timeout)
291            ));
292        }
293        
294        // Validate max connection attempts
295        if self.max_connection_attempts == 0 || self.max_connection_attempts > 10 {
296            return Err(ConfigError::ValueOutOfRange(
297                format!("Maximum connection attempts must be between 1 and 10, got {}", self.max_connection_attempts)
298            ));
299        }
300        
301        // Validate max concurrent connections
302        if self.max_concurrent_connections == 0 || self.max_concurrent_connections > 1000 {
303            return Err(ConfigError::ValueOutOfRange(
304                format!("Maximum concurrent connections must be between 1 and 1000, got {}", self.max_concurrent_connections)
305            ));
306        }
307        
308        // Create the configuration
309        Ok(P2PConfig {
310            bootstrap_nodes: self.bootstrap_nodes.clone(),
311            keypair,
312            nat_traversal_enabled: self.nat_traversal_enabled,
313            listen_address,
314            connection_timeout: self.connection_timeout,
315            max_connection_attempts: self.max_connection_attempts,
316            max_concurrent_connections: self.max_concurrent_connections,
317            nat_traversal_config: self.nat_traversal_config.clone(),
318        })
319    }
320}
321
322impl Default for P2PConfigBuilder {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    use std::net::{IpAddr, Ipv4Addr};
332    use crate::nat_traversal::BootstrapNode;
333    use crate::crypto::raw_public_keys::key_utils;
334
335    #[test]
336    fn test_builder_with_valid_config() {
337        let keypair = key_utils::generate_ed25519_keypair();
338        let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
339        
340        let config = P2PConfigBuilder::new()
341            .with_keypair(keypair)
342            .add_bootstrap_node(bootstrap_node)
343            .with_nat_traversal(true)
344            .with_listen_address(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000))
345            .with_connection_timeout(Duration::from_secs(60))
346            .build()
347            .unwrap();
348        
349        assert_eq!(config.bootstrap_nodes.len(), 1);
350        assert_eq!(config.bootstrap_nodes[0].address.to_string(), "127.0.0.1:9000");
351        assert!(config.nat_traversal_enabled);
352        assert_eq!(config.listen_address.to_string(), "127.0.0.1:8000");
353        assert_eq!(config.connection_timeout, Duration::from_secs(60));
354    }
355    
356    #[test]
357    fn test_builder_missing_keypair() {
358        let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
359        
360        let result = P2PConfigBuilder::new()
361            .add_bootstrap_node(bootstrap_node)
362            .with_nat_traversal(true)
363            .build();
364        
365        assert!(result.is_err());
366        match result {
367            Err(ConfigError::MissingRequiredConfig(msg)) => {
368                assert!(msg.contains("Keypair is required"));
369            }
370            _ => panic!("Expected MissingRequiredConfig error"),
371        }
372    }
373    
374    #[test]
375    fn test_builder_missing_bootstrap_nodes() {
376        let keypair = key_utils::generate_ed25519_keypair();
377        
378        let result = P2PConfigBuilder::new()
379            .with_keypair(keypair)
380            .with_nat_traversal(true)
381            .build();
382        
383        assert!(result.is_err());
384        match result {
385            Err(ConfigError::MissingRequiredConfig(msg)) => {
386                assert!(msg.contains("bootstrap node"));
387            }
388            _ => panic!("Expected MissingRequiredConfig error"),
389        }
390    }
391    
392    #[test]
393    fn test_builder_duplicate_bootstrap_nodes() {
394        let keypair = key_utils::generate_ed25519_keypair();
395        let addr = "127.0.0.1:9000".parse().unwrap();
396        let bootstrap_node1 = BootstrapNode::new(addr);
397        let bootstrap_node2 = BootstrapNode::new(addr);
398        
399        let result = P2PConfigBuilder::new()
400            .with_keypair(keypair)
401            .add_bootstrap_node(bootstrap_node1)
402            .add_bootstrap_node(bootstrap_node2)
403            .build();
404        
405        assert!(result.is_err());
406        match result {
407            Err(ConfigError::InvalidBootstrapNode(msg)) => {
408                assert!(msg.contains("Duplicate bootstrap node"));
409            }
410            _ => panic!("Expected InvalidBootstrapNode error"),
411        }
412    }
413    
414    #[test]
415    fn test_builder_invalid_connection_timeout() {
416        let keypair = key_utils::generate_ed25519_keypair();
417        let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
418        
419        let result = P2PConfigBuilder::new()
420            .with_keypair(keypair)
421            .add_bootstrap_node(bootstrap_node)
422            .with_connection_timeout(Duration::from_millis(500))
423            .build();
424        
425        assert!(result.is_err());
426        match result {
427            Err(ConfigError::ValueOutOfRange(msg)) => {
428                assert!(msg.contains("Connection timeout"));
429            }
430            _ => panic!("Expected ValueOutOfRange error"),
431        }
432    }
433    
434    #[test]
435    fn test_builder_nat_traversal_disabled() {
436        let keypair = key_utils::generate_ed25519_keypair();
437        
438        let config = P2PConfigBuilder::new()
439            .with_keypair(keypair)
440            .with_nat_traversal(false)
441            .build()
442            .unwrap();
443        
444        assert!(!config.nat_traversal_enabled);
445        assert_eq!(config.bootstrap_nodes.len(), 0);
446    }
447    
448    #[test]
449    fn test_config_getters() {
450        let keypair = key_utils::generate_ed25519_keypair();
451        let bootstrap_node = BootstrapNode::new("127.0.0.1:9000".parse().unwrap());
452        let listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000);
453        let timeout = Duration::from_secs(60);
454        
455        let config = P2PConfigBuilder::new()
456            .with_keypair(keypair)
457            .add_bootstrap_node(bootstrap_node)
458            .with_listen_address(listen_address)
459            .with_connection_timeout(timeout)
460            .with_max_connection_attempts(5)
461            .with_max_concurrent_connections(200)
462            .build()
463            .unwrap();
464        
465        assert_eq!(config.bootstrap_nodes().len(), 1);
466        assert_eq!(config.listen_address(), listen_address);
467        assert_eq!(config.connection_timeout(), timeout);
468        assert_eq!(config.max_connection_attempts(), 5);
469        assert_eq!(config.max_concurrent_connections(), 200);
470    }
471}