Skip to main content

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}