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::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/// 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(format!(
271                    "Duplicate bootstrap node at index {}: {}",
272                    i, node.address
273                )));
274            }
275        }
276
277        // Validate keypair
278        let keypair = self
279            .keypair
280            .clone()
281            .ok_or_else(|| ConfigError::MissingRequiredConfig("Keypair is required".to_string()))?;
282
283        // Validate listen address
284        let listen_address = self.listen_address.unwrap_or_else(|| {
285            // Default to a random port on all interfaces
286            "0.0.0.0:0".parse().unwrap()
287        });
288
289        // Validate connection timeout
290        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        // Validate max connection attempts
300        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        // Validate max concurrent connections
308        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        // Create the configuration
316        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}