ant_quic/
node_config.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Minimal configuration for zero-config P2P nodes
9//!
10//! This module provides [`NodeConfig`] - a simple configuration struct
11//! with only 3 optional fields. Most applications need zero configuration.
12//!
13//! # Zero Configuration
14//!
15//! ```rust,ignore
16//! use ant_quic::Node;
17//!
18//! // No configuration needed - just create a node
19//! let node = Node::new().await?;
20//! ```
21//!
22//! # Optional Configuration
23//!
24//! ```rust,ignore
25//! use ant_quic::{Node, NodeConfig};
26//!
27//! // Only configure what you need
28//! let config = NodeConfig::builder()
29//!     .known_peer("quic.saorsalabs.com:9000".parse()?)
30//!     .build();
31//!
32//! let node = Node::with_config(config).await?;
33//! ```
34
35use std::net::SocketAddr;
36
37use crate::crypto::pqc::types::{MlDsaPublicKey, MlDsaSecretKey};
38
39/// Minimal configuration for P2P nodes
40///
41/// All fields are optional - the node will auto-configure everything.
42/// - `bind_addr`: Defaults to `0.0.0.0:0` (random port)
43/// - `known_peers`: Defaults to empty (node can still accept connections)
44/// - `keypair`: Defaults to fresh generated keypair
45///
46/// # Example
47///
48/// ```rust,ignore
49/// // Zero configuration
50/// let config = NodeConfig::default();
51///
52/// // Or with known peers
53/// let config = NodeConfig::builder()
54///     .known_peer("peer1.example.com:9000".parse()?)
55///     .build();
56/// ```
57#[derive(Clone, Default)]
58pub struct NodeConfig {
59    /// Bind address. Default: 0.0.0.0:0 (random port)
60    pub bind_addr: Option<SocketAddr>,
61
62    /// Known peers for initial discovery. Default: empty
63    /// When empty, node can still accept incoming connections.
64    pub known_peers: Vec<SocketAddr>,
65
66    /// Identity keypair (ML-DSA-65). Default: fresh generated
67    /// Provide for persistent identity across restarts.
68    pub keypair: Option<(MlDsaPublicKey, MlDsaSecretKey)>,
69}
70
71impl std::fmt::Debug for NodeConfig {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("NodeConfig")
74            .field("bind_addr", &self.bind_addr)
75            .field("known_peers", &self.known_peers)
76            .field("keypair", &self.keypair.as_ref().map(|_| "[REDACTED]"))
77            .finish()
78    }
79}
80
81impl NodeConfig {
82    /// Create a new config with defaults
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    /// Create a builder for fluent construction
88    pub fn builder() -> NodeConfigBuilder {
89        NodeConfigBuilder::default()
90    }
91
92    /// Create config with a specific bind address
93    pub fn with_bind_addr(addr: SocketAddr) -> Self {
94        Self {
95            bind_addr: Some(addr),
96            ..Default::default()
97        }
98    }
99
100    /// Create config with known peers
101    pub fn with_known_peers(peers: Vec<SocketAddr>) -> Self {
102        Self {
103            known_peers: peers,
104            ..Default::default()
105        }
106    }
107
108    /// Create config with a specific ML-DSA-65 keypair
109    pub fn with_keypair(public_key: MlDsaPublicKey, secret_key: MlDsaSecretKey) -> Self {
110        Self {
111            keypair: Some((public_key, secret_key)),
112            ..Default::default()
113        }
114    }
115}
116
117/// Builder for [`NodeConfig`]
118#[derive(Default)]
119pub struct NodeConfigBuilder {
120    bind_addr: Option<SocketAddr>,
121    known_peers: Vec<SocketAddr>,
122    keypair: Option<(MlDsaPublicKey, MlDsaSecretKey)>,
123}
124
125impl NodeConfigBuilder {
126    /// Set the bind address
127    pub fn bind_addr(mut self, addr: SocketAddr) -> Self {
128        self.bind_addr = Some(addr);
129        self
130    }
131
132    /// Add a known peer
133    pub fn known_peer(mut self, addr: SocketAddr) -> Self {
134        self.known_peers.push(addr);
135        self
136    }
137
138    /// Add multiple known peers
139    pub fn known_peers(mut self, addrs: impl IntoIterator<Item = SocketAddr>) -> Self {
140        self.known_peers.extend(addrs);
141        self
142    }
143
144    /// Set the identity keypair (ML-DSA-65)
145    pub fn keypair(mut self, public_key: MlDsaPublicKey, secret_key: MlDsaSecretKey) -> Self {
146        self.keypair = Some((public_key, secret_key));
147        self
148    }
149
150    /// Build the configuration
151    pub fn build(self) -> NodeConfig {
152        NodeConfig {
153            bind_addr: self.bind_addr,
154            known_peers: self.known_peers,
155            keypair: self.keypair,
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_default_config() {
166        let config = NodeConfig::default();
167        assert!(config.bind_addr.is_none());
168        assert!(config.known_peers.is_empty());
169        assert!(config.keypair.is_none());
170    }
171
172    #[test]
173    fn test_builder_with_bind_addr() {
174        let addr: SocketAddr = "0.0.0.0:9000".parse().unwrap();
175        let config = NodeConfig::builder().bind_addr(addr).build();
176        assert_eq!(config.bind_addr, Some(addr));
177    }
178
179    #[test]
180    fn test_builder_with_known_peers() {
181        let peer1: SocketAddr = "127.0.0.1:9000".parse().unwrap();
182        let peer2: SocketAddr = "127.0.0.1:9001".parse().unwrap();
183
184        let config = NodeConfig::builder()
185            .known_peer(peer1)
186            .known_peer(peer2)
187            .build();
188
189        assert_eq!(config.known_peers.len(), 2);
190        assert!(config.known_peers.contains(&peer1));
191        assert!(config.known_peers.contains(&peer2));
192    }
193
194    #[test]
195    fn test_builder_with_multiple_peers() {
196        let peers: Vec<SocketAddr> = vec![
197            "127.0.0.1:9000".parse().unwrap(),
198            "127.0.0.1:9001".parse().unwrap(),
199        ];
200
201        let config = NodeConfig::builder().known_peers(peers.clone()).build();
202
203        assert_eq!(config.known_peers, peers);
204    }
205
206    #[test]
207    fn test_with_bind_addr() {
208        let addr: SocketAddr = "0.0.0.0:9000".parse().unwrap();
209        let config = NodeConfig::with_bind_addr(addr);
210        assert_eq!(config.bind_addr, Some(addr));
211        assert!(config.known_peers.is_empty());
212        assert!(config.keypair.is_none());
213    }
214
215    #[test]
216    fn test_with_known_peers() {
217        let peers: Vec<SocketAddr> = vec![
218            "127.0.0.1:9000".parse().unwrap(),
219            "127.0.0.1:9001".parse().unwrap(),
220        ];
221
222        let config = NodeConfig::with_known_peers(peers.clone());
223        assert!(config.bind_addr.is_none());
224        assert_eq!(config.known_peers, peers);
225        assert!(config.keypair.is_none());
226    }
227
228    #[test]
229    fn test_debug_redacts_keypair() {
230        use crate::crypto::raw_public_keys::key_utils::generate_ml_dsa_keypair;
231        let (public_key, secret_key) = generate_ml_dsa_keypair().unwrap();
232        let config = NodeConfig::with_keypair(public_key, secret_key);
233        let debug_str = format!("{:?}", config);
234        assert!(debug_str.contains("[REDACTED]"));
235        assert!(!debug_str.contains(&format!("{:?}", config.keypair)));
236    }
237
238    #[test]
239    fn test_config_is_clone() {
240        let config = NodeConfig::builder()
241            .bind_addr("0.0.0.0:9000".parse().unwrap())
242            .known_peer("127.0.0.1:9001".parse().unwrap())
243            .build();
244
245        let cloned = config.clone();
246        assert_eq!(config.bind_addr, cloned.bind_addr);
247        assert_eq!(config.known_peers, cloned.known_peers);
248    }
249}