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::path::Path;
36use std::sync::Arc;
37
38use crate::crypto::pqc::types::{MlDsaPublicKey, MlDsaSecretKey};
39use crate::host_identity::HostIdentity;
40use crate::transport::{TransportAddr, TransportProvider, TransportRegistry};
41use crate::unified_config::load_or_generate_endpoint_keypair;
42
43/// Minimal configuration for P2P nodes
44///
45/// All fields are optional - the node will auto-configure everything.
46/// - `bind_addr`: Defaults to `0.0.0.0:0` (random port)
47/// - `known_peers`: Defaults to empty (node can still accept connections)
48/// - `keypair`: Defaults to fresh generated keypair
49/// - `transport_providers`: Defaults to UDP transport only
50///
51/// # Example
52///
53/// ```rust,ignore
54/// // Zero configuration
55/// let config = NodeConfig::default();
56///
57/// // Or with known peers
58/// let config = NodeConfig::builder()
59/// .known_peer("peer1.example.com:9000".parse()?)
60/// .build();
61///
62/// // Or with additional transport providers
63/// #[cfg(feature = "ble")]
64/// let config = NodeConfig::builder()
65/// .transport_provider(Arc::new(BleTransport::new().await?))
66/// .build();
67/// ```
68#[derive(Clone, Default)]
69pub struct NodeConfig {
70 /// Bind address. Default: 0.0.0.0:0 (random port)
71 pub bind_addr: Option<TransportAddr>,
72
73 /// Known peers for initial discovery. Default: empty
74 /// When empty, node can still accept incoming connections.
75 pub known_peers: Vec<TransportAddr>,
76
77 /// Identity keypair (ML-DSA-65). Default: fresh generated
78 /// Provide for persistent identity across restarts.
79 pub keypair: Option<(MlDsaPublicKey, MlDsaSecretKey)>,
80
81 /// Additional transport providers beyond the default UDP transport.
82 ///
83 /// The UDP transport is always included by default. Use this to add
84 /// additional transports like BLE, LoRa, serial, etc.
85 ///
86 /// Transport capabilities are propagated to peer advertisements and
87 /// used for routing decisions.
88 pub transport_providers: Vec<Arc<dyn TransportProvider>>,
89}
90
91impl std::fmt::Debug for NodeConfig {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.debug_struct("NodeConfig")
94 .field("bind_addr", &self.bind_addr)
95 .field("known_peers", &self.known_peers)
96 .field("keypair", &self.keypair.as_ref().map(|_| "[REDACTED]"))
97 .field("transport_providers", &self.transport_providers.len())
98 .finish()
99 }
100}
101
102impl NodeConfig {
103 /// Create a new config with defaults
104 pub fn new() -> Self {
105 Self::default()
106 }
107
108 /// Create a builder for fluent construction
109 pub fn builder() -> NodeConfigBuilder {
110 NodeConfigBuilder::default()
111 }
112
113 /// Create config with a specific bind address
114 pub fn with_bind_addr(addr: impl Into<TransportAddr>) -> Self {
115 Self {
116 bind_addr: Some(addr.into()),
117 ..Default::default()
118 }
119 }
120
121 /// Create config with known peers
122 pub fn with_known_peers(peers: impl IntoIterator<Item = impl Into<TransportAddr>>) -> Self {
123 Self {
124 known_peers: peers.into_iter().map(|p| p.into()).collect(),
125 ..Default::default()
126 }
127 }
128
129 /// Create config with a specific ML-DSA-65 keypair
130 pub fn with_keypair(public_key: MlDsaPublicKey, secret_key: MlDsaSecretKey) -> Self {
131 Self {
132 keypair: Some((public_key, secret_key)),
133 ..Default::default()
134 }
135 }
136}
137
138/// Builder for [`NodeConfig`]
139#[derive(Default)]
140pub struct NodeConfigBuilder {
141 bind_addr: Option<TransportAddr>,
142 known_peers: Vec<TransportAddr>,
143 keypair: Option<(MlDsaPublicKey, MlDsaSecretKey)>,
144 transport_providers: Vec<Arc<dyn TransportProvider>>,
145}
146
147impl NodeConfigBuilder {
148 /// Set the local address to bind to
149 ///
150 /// Accepts any type implementing `Into<TransportAddr>`:
151 /// - `SocketAddr` - Auto-converts to `TransportAddr::Udp` (backward compatible)
152 /// - `TransportAddr` - Enables multi-transport support (BLE, LoRa, etc.)
153 ///
154 /// If not specified, defaults to `0.0.0.0:0` (random ephemeral port).
155 ///
156 /// # Examples
157 ///
158 /// ```rust,ignore
159 /// use ant_quic::NodeConfig;
160 /// use std::net::SocketAddr;
161 ///
162 /// // Backward compatible: SocketAddr
163 /// let config = NodeConfig::builder()
164 /// .bind_addr("0.0.0.0:9000".parse::<SocketAddr>().unwrap())
165 /// .build();
166 ///
167 /// // Multi-transport: Explicit TransportAddr
168 /// use ant_quic::transport::TransportAddr;
169 /// let config = NodeConfig::builder()
170 /// .bind_addr(TransportAddr::Udp("0.0.0.0:0".parse().unwrap()))
171 /// .build();
172 /// ```
173 pub fn bind_addr(mut self, addr: impl Into<TransportAddr>) -> Self {
174 self.bind_addr = Some(addr.into());
175 self
176 }
177
178 /// Add a known peer for initial network connectivity
179 ///
180 /// Known peers are used for initial discovery and connection establishment.
181 /// The node will learn about additional peers through the network.
182 ///
183 /// Accepts any type implementing `Into<TransportAddr>`:
184 /// - `SocketAddr` - Auto-converts to `TransportAddr::Udp`
185 /// - `TransportAddr` - Supports multiple transport types
186 ///
187 /// # Examples
188 ///
189 /// ```rust,ignore
190 /// use ant_quic::NodeConfig;
191 /// use std::net::SocketAddr;
192 ///
193 /// // Backward compatible: SocketAddr
194 /// let config = NodeConfig::builder()
195 /// .known_peer("peer.example.com:9000".parse::<SocketAddr>().unwrap())
196 /// .build();
197 ///
198 /// // Multi-transport: Mix different transport types
199 /// use ant_quic::transport::TransportAddr;
200 /// let config = NodeConfig::builder()
201 /// .known_peer(TransportAddr::Udp("192.168.1.1:9000".parse().unwrap()))
202 /// .known_peer(TransportAddr::ble([0x11, 0x22, 0x33, 0x44, 0x55, 0x66], None))
203 /// .build();
204 /// ```
205 pub fn known_peer(mut self, addr: impl Into<TransportAddr>) -> Self {
206 self.known_peers.push(addr.into());
207 self
208 }
209
210 /// Add multiple known peers at once
211 ///
212 /// Convenient method to add a collection of peers. Each item is automatically
213 /// converted via `Into<TransportAddr>`, supporting both `SocketAddr` and
214 /// `TransportAddr` for backward compatibility and multi-transport scenarios.
215 ///
216 /// # Examples
217 ///
218 /// ```rust,ignore
219 /// use ant_quic::NodeConfig;
220 /// use std::net::SocketAddr;
221 ///
222 /// // Backward compatible: Vec<SocketAddr>
223 /// let peers: Vec<SocketAddr> = vec![
224 /// "peer1.example.com:9000".parse().unwrap(),
225 /// "peer2.example.com:9000".parse().unwrap(),
226 /// ];
227 /// let config = NodeConfig::builder()
228 /// .known_peers(peers)
229 /// .build();
230 ///
231 /// // Multi-transport: Heterogeneous transport list
232 /// use ant_quic::transport::TransportAddr;
233 /// let mixed = vec![
234 /// TransportAddr::Udp("192.168.1.1:9000".parse().unwrap()),
235 /// TransportAddr::ble([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], None),
236 /// TransportAddr::serial("/dev/ttyUSB0"),
237 /// ];
238 /// let config = NodeConfig::builder()
239 /// .known_peers(mixed)
240 /// .build();
241 /// ```
242 pub fn known_peers(
243 mut self,
244 addrs: impl IntoIterator<Item = impl Into<TransportAddr>>,
245 ) -> Self {
246 self.known_peers.extend(addrs.into_iter().map(|a| a.into()));
247 self
248 }
249
250 /// Set the identity keypair (ML-DSA-65)
251 pub fn keypair(mut self, public_key: MlDsaPublicKey, secret_key: MlDsaSecretKey) -> Self {
252 self.keypair = Some((public_key, secret_key));
253 self
254 }
255
256 /// Set the identity from a HostIdentity with encrypted storage
257 ///
258 /// This loads or generates a keypair using the HostIdentity for encryption.
259 /// The keypair is stored encrypted at rest in the specified directory.
260 ///
261 /// # Arguments
262 ///
263 /// * `host` - The HostIdentity for key derivation
264 /// * `network_id` - Network identifier for per-network keypair isolation
265 /// * `storage_dir` - Directory to store the encrypted keypair
266 ///
267 /// # Errors
268 ///
269 /// Returns an error if the keypair cannot be loaded or generated.
270 pub fn with_host_identity(
271 mut self,
272 host: &HostIdentity,
273 network_id: &[u8],
274 storage_dir: &Path,
275 ) -> Result<Self, String> {
276 let (public_key, secret_key) =
277 load_or_generate_endpoint_keypair(host, network_id, storage_dir)
278 .map_err(|e| format!("Failed to load/generate keypair: {e}"))?;
279 self.keypair = Some((public_key, secret_key));
280 Ok(self)
281 }
282
283 /// Add a transport provider
284 ///
285 /// Transport providers are used for multi-transport P2P networking.
286 /// The UDP transport is always included by default.
287 ///
288 /// # Example
289 ///
290 /// ```rust,ignore
291 /// #[cfg(feature = "ble")]
292 /// let config = NodeConfig::builder()
293 /// .transport_provider(Arc::new(BleTransport::new().await?))
294 /// .build();
295 /// ```
296 pub fn transport_provider(mut self, provider: Arc<dyn TransportProvider>) -> Self {
297 self.transport_providers.push(provider);
298 self
299 }
300
301 /// Add multiple transport providers
302 pub fn transport_providers(
303 mut self,
304 providers: impl IntoIterator<Item = Arc<dyn TransportProvider>>,
305 ) -> Self {
306 self.transport_providers.extend(providers);
307 self
308 }
309
310 /// Build the configuration
311 pub fn build(self) -> NodeConfig {
312 NodeConfig {
313 bind_addr: self.bind_addr,
314 known_peers: self.known_peers,
315 keypair: self.keypair,
316 transport_providers: self.transport_providers,
317 }
318 }
319}
320
321impl NodeConfig {
322 /// Build a transport registry from this configuration
323 ///
324 /// Creates a registry containing all configured transport providers.
325 /// If no providers are configured, returns an empty registry (UDP
326 /// should be added by the caller based on bind_addr).
327 pub fn build_transport_registry(&self) -> TransportRegistry {
328 let mut registry = TransportRegistry::new();
329 for provider in &self.transport_providers {
330 registry.register(provider.clone());
331 }
332 registry
333 }
334
335 /// Check if this configuration has any non-UDP transport providers
336 pub fn has_constrained_transports(&self) -> bool {
337 use crate::transport::TransportType;
338 self.transport_providers
339 .iter()
340 .any(|p| p.transport_type() != TransportType::Udp)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use std::net::SocketAddr;
348
349 #[test]
350 fn test_default_config() {
351 let config = NodeConfig::default();
352 assert!(config.bind_addr.is_none());
353 assert!(config.known_peers.is_empty());
354 assert!(config.keypair.is_none());
355 assert!(config.transport_providers.is_empty());
356 }
357
358 #[test]
359 fn test_builder_with_bind_addr() {
360 let addr: SocketAddr = "0.0.0.0:9000".parse().unwrap();
361 let config = NodeConfig::builder().bind_addr(addr).build();
362 assert_eq!(config.bind_addr, Some(TransportAddr::from(addr)));
363 }
364
365 #[test]
366 fn test_builder_with_known_peers() {
367 let peer1: SocketAddr = "127.0.0.1:9000".parse().unwrap();
368 let peer2: SocketAddr = "127.0.0.1:9001".parse().unwrap();
369
370 let config = NodeConfig::builder()
371 .known_peer(peer1)
372 .known_peer(peer2)
373 .build();
374
375 assert_eq!(config.known_peers.len(), 2);
376 assert!(config.known_peers.contains(&TransportAddr::from(peer1)));
377 assert!(config.known_peers.contains(&TransportAddr::from(peer2)));
378 }
379
380 #[test]
381 fn test_builder_with_multiple_peers() {
382 let peers: Vec<SocketAddr> = vec![
383 "127.0.0.1:9000".parse().unwrap(),
384 "127.0.0.1:9001".parse().unwrap(),
385 ];
386
387 let config = NodeConfig::builder().known_peers(peers.clone()).build();
388
389 assert_eq!(config.known_peers.len(), 2);
390 assert_eq!(
391 config.known_peers,
392 peers
393 .into_iter()
394 .map(TransportAddr::from)
395 .collect::<Vec<_>>()
396 );
397 }
398
399 #[test]
400 fn test_with_bind_addr() {
401 let addr: SocketAddr = "0.0.0.0:9000".parse().unwrap();
402 let config = NodeConfig::with_bind_addr(addr);
403 assert_eq!(config.bind_addr, Some(TransportAddr::from(addr)));
404 assert!(config.known_peers.is_empty());
405 assert!(config.keypair.is_none());
406 }
407
408 #[test]
409 fn test_with_known_peers() {
410 let peers: Vec<SocketAddr> = vec![
411 "127.0.0.1:9000".parse().unwrap(),
412 "127.0.0.1:9001".parse().unwrap(),
413 ];
414
415 let config = NodeConfig::with_known_peers(peers.clone());
416 assert!(config.bind_addr.is_none());
417 assert_eq!(
418 config.known_peers,
419 peers
420 .into_iter()
421 .map(TransportAddr::from)
422 .collect::<Vec<_>>()
423 );
424 assert!(config.keypair.is_none());
425 }
426
427 #[test]
428 fn test_debug_redacts_keypair() {
429 use crate::crypto::raw_public_keys::key_utils::generate_ml_dsa_keypair;
430 let (public_key, secret_key) = generate_ml_dsa_keypair().unwrap();
431 let config = NodeConfig::with_keypair(public_key, secret_key);
432 let debug_str = format!("{:?}", config);
433 assert!(debug_str.contains("[REDACTED]"));
434 assert!(!debug_str.contains(&format!("{:?}", config.keypair)));
435 }
436
437 #[test]
438 fn test_config_is_clone() {
439 let addr: SocketAddr = "0.0.0.0:9000".parse().unwrap();
440 let peer: SocketAddr = "127.0.0.1:9001".parse().unwrap();
441 let config = NodeConfig::builder()
442 .bind_addr(addr)
443 .known_peer(peer)
444 .build();
445
446 let cloned = config.clone();
447 assert_eq!(config.bind_addr, cloned.bind_addr);
448 assert_eq!(config.known_peers, cloned.known_peers);
449 }
450
451 #[test]
452 fn test_build_transport_registry() {
453 let config = NodeConfig::default();
454 let registry = config.build_transport_registry();
455 assert!(registry.is_empty());
456 }
457
458 #[test]
459 fn test_has_constrained_transports_default() {
460 let config = NodeConfig::default();
461 assert!(!config.has_constrained_transports());
462 }
463
464 #[test]
465 fn test_debug_shows_transport_count() {
466 let config = NodeConfig::default();
467 let debug_str = format!("{:?}", config);
468 assert!(debug_str.contains("transport_providers: 0"));
469 }
470
471 #[test]
472 fn test_node_config_with_transport_addr() {
473 // Create NodeConfig with TransportAddr bind and peers
474 let bind_addr = TransportAddr::from("0.0.0.0:9000".parse::<SocketAddr>().unwrap());
475 let peer1 = TransportAddr::from("127.0.0.1:9001".parse::<SocketAddr>().unwrap());
476 let peer2 = TransportAddr::from("127.0.0.1:9002".parse::<SocketAddr>().unwrap());
477
478 let config = NodeConfig::builder()
479 .bind_addr(bind_addr.clone())
480 .known_peer(peer1.clone())
481 .known_peer(peer2.clone())
482 .build();
483
484 // Verify fields set correctly
485 assert_eq!(config.bind_addr, Some(bind_addr));
486 assert_eq!(config.known_peers.len(), 2);
487 assert!(config.known_peers.contains(&peer1));
488 assert!(config.known_peers.contains(&peer2));
489 }
490
491 #[test]
492 fn test_node_config_builder_backward_compat() {
493 // Use builder with SocketAddr (should auto-convert via Into trait)
494 let bind_socket: SocketAddr = "0.0.0.0:9000".parse().unwrap();
495 let peer_socket: SocketAddr = "127.0.0.1:9001".parse().unwrap();
496
497 let config = NodeConfig::builder()
498 .bind_addr(bind_socket)
499 .known_peer(peer_socket)
500 .build();
501
502 // Verify Into trait conversion works
503 assert_eq!(config.bind_addr, Some(TransportAddr::from(bind_socket)));
504 assert_eq!(config.known_peers.len(), 1);
505 assert_eq!(config.known_peers[0], TransportAddr::from(peer_socket));
506
507 // Verify it's the same as explicit TransportAddr usage
508 let explicit_config = NodeConfig::builder()
509 .bind_addr(TransportAddr::from(bind_socket))
510 .known_peer(TransportAddr::from(peer_socket))
511 .build();
512
513 assert_eq!(config.bind_addr, explicit_config.bind_addr);
514 assert_eq!(config.known_peers, explicit_config.known_peers);
515 }
516
517 #[test]
518 fn test_node_config_transport_addr_preservation() {
519 // Create NodeConfig with various TransportAddr types
520 let udp_bind = TransportAddr::from("0.0.0.0:0".parse::<SocketAddr>().unwrap());
521 let udp_peer = TransportAddr::from("127.0.0.1:9000".parse::<SocketAddr>().unwrap());
522 let ipv6_peer = TransportAddr::from("[::1]:9001".parse::<SocketAddr>().unwrap());
523
524 let config = NodeConfig::builder()
525 .bind_addr(udp_bind.clone())
526 .known_peer(udp_peer.clone())
527 .known_peer(ipv6_peer.clone())
528 .build();
529
530 // Verify address types preserved
531 assert_eq!(config.bind_addr, Some(udp_bind));
532 assert_eq!(config.known_peers.len(), 2);
533
534 // Check that TransportAddr types are maintained
535 assert!(matches!(config.known_peers[0], TransportAddr::Udp(_)));
536 assert!(matches!(config.known_peers[1], TransportAddr::Udp(_)));
537
538 // Verify actual addresses match
539 assert_eq!(config.known_peers[0], udp_peer);
540 assert_eq!(config.known_peers[1], ipv6_peer);
541 }
542}