ant_quic/node.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//! Zero-configuration P2P node
9//!
10//! This module provides [`Node`] - the simple API for creating P2P nodes
11//! that work out of the box with zero configuration. Every node automatically:
12//!
13//! - Uses 100% post-quantum cryptography (ML-KEM-768)
14//! - Works behind any NAT via native QUIC hole punching
15//! - Can act as coordinator/relay if environment allows
16//! - Exposes complete observability via [`NodeStatus`]
17//!
18//! # Zero Configuration
19//!
20//! ```rust,ignore
21//! use ant_quic::Node;
22//!
23//! #[tokio::main]
24//! async fn main() -> anyhow::Result<()> {
25//! // Create a node - that's it!
26//! let node = Node::new().await?;
27//!
28//! println!("I am: {:?}", node.peer_id());
29//! println!("Listening on: {:?}", node.local_addr());
30//!
31//! // Check status
32//! let status = node.status().await;
33//! println!("NAT type: {}", status.nat_type);
34//! println!("Can receive direct: {}", status.can_receive_direct);
35//! println!("Acting as relay: {}", status.is_relaying);
36//!
37//! // Connect to a peer
38//! let conn = node.connect_addr("quic.saorsalabs.com:9000".parse()?).await?;
39//!
40//! // Accept connections
41//! let incoming = node.accept().await;
42//!
43//! Ok(())
44//! }
45//! ```
46
47use std::net::SocketAddr;
48use std::sync::Arc;
49use std::time::{Duration, Instant};
50
51use crate::crypto::pqc::types::{MlDsaPublicKey, MlDsaSecretKey};
52use tokio::sync::broadcast;
53use tracing::info;
54
55use crate::host_identity::HostIdentity;
56use crate::nat_traversal_api::PeerId;
57use crate::node_config::NodeConfig;
58use crate::node_event::NodeEvent;
59use crate::node_status::{NatType, NodeStatus};
60use crate::p2p_endpoint::{EndpointError, P2pEndpoint, P2pEvent, PeerConnection};
61use crate::unified_config::P2pConfig;
62use crate::unified_config::load_or_generate_endpoint_keypair;
63
64/// Error type for Node operations
65#[derive(Debug, thiserror::Error)]
66pub enum NodeError {
67 /// Failed to create node
68 #[error("Failed to create node: {0}")]
69 Creation(String),
70
71 /// Connection error
72 #[error("Connection error: {0}")]
73 Connection(String),
74
75 /// Endpoint error
76 #[error("Endpoint error: {0}")]
77 Endpoint(#[from] EndpointError),
78
79 /// Shutting down
80 #[error("Node is shutting down")]
81 ShuttingDown,
82}
83
84/// Zero-configuration P2P node
85///
86/// This is the primary API for ant-quic. Create a node with zero configuration
87/// and it will automatically handle NAT traversal, post-quantum cryptography,
88/// and peer discovery.
89///
90/// # Symmetric P2P
91///
92/// All nodes are equal - every node can:
93/// - Connect to other nodes
94/// - Accept incoming connections
95/// - Act as coordinator for NAT traversal
96/// - Act as relay for peers behind restrictive NATs
97///
98/// # Post-Quantum Security
99///
100/// v0.2: Every connection uses pure post-quantum cryptography:
101/// - Key Exchange: ML-KEM-768 (FIPS 203)
102/// - Authentication: ML-DSA-65 (FIPS 204)
103/// - Ed25519 is used ONLY for the 32-byte PeerId compact identifier
104///
105/// There is no classical crypto fallback - security is quantum-resistant by default.
106///
107/// # Example
108///
109/// ```rust,ignore
110/// use ant_quic::Node;
111///
112/// // Zero configuration
113/// let node = Node::new().await?;
114///
115/// // Or with known peers
116/// let node = Node::with_peers(vec!["quic.saorsalabs.com:9000".parse()?]).await?;
117///
118/// // Or with persistent identity
119/// let keypair = load_keypair()?;
120/// let node = Node::with_keypair(keypair).await?;
121/// ```
122pub struct Node {
123 /// Inner P2pEndpoint
124 inner: Arc<P2pEndpoint>,
125
126 /// Start time for uptime calculation
127 start_time: Instant,
128
129 /// Event broadcaster for unified events
130 event_tx: broadcast::Sender<NodeEvent>,
131}
132
133impl std::fmt::Debug for Node {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("Node")
136 .field("peer_id", &self.peer_id())
137 .field("local_addr", &self.local_addr())
138 .finish_non_exhaustive()
139 }
140}
141
142impl Node {
143 // === Creation ===
144
145 /// Create a node with automatic configuration
146 ///
147 /// This is the recommended way to create a node. It will:
148 /// - Bind to a random port on all interfaces (0.0.0.0:0)
149 /// - Generate a fresh Ed25519 keypair
150 /// - Enable all NAT traversal capabilities
151 /// - Use 100% post-quantum cryptography
152 ///
153 /// # Example
154 ///
155 /// ```rust,ignore
156 /// let node = Node::new().await?;
157 /// ```
158 pub async fn new() -> Result<Self, NodeError> {
159 Self::with_config(NodeConfig::default()).await
160 }
161
162 /// Create a node with a specific bind address
163 ///
164 /// Use this when you need a specific port for firewall rules or port forwarding.
165 ///
166 /// # Example
167 ///
168 /// ```rust,ignore
169 /// let node = Node::bind("0.0.0.0:9000".parse()?).await?;
170 /// ```
171 pub async fn bind(addr: SocketAddr) -> Result<Self, NodeError> {
172 Self::with_config(NodeConfig::with_bind_addr(addr)).await
173 }
174
175 /// Create a node with known peers
176 ///
177 /// Use this when you have a list of known peers to connect to initially.
178 /// These can be any nodes in the network - they'll help with NAT traversal.
179 ///
180 /// # Example
181 ///
182 /// ```rust,ignore
183 /// let node = Node::with_peers(vec![
184 /// "quic.saorsalabs.com:9000".parse()?,
185 /// "peer2.example.com:9000".parse()?,
186 /// ]).await?;
187 /// ```
188 pub async fn with_peers(peers: Vec<SocketAddr>) -> Result<Self, NodeError> {
189 Self::with_config(NodeConfig::with_known_peers(peers)).await
190 }
191
192 /// Create a node with an existing keypair
193 ///
194 /// Use this for persistent identity across restarts. The peer ID
195 /// is derived from the public key, so using the same keypair
196 /// gives you the same peer ID.
197 ///
198 /// # Example
199 ///
200 /// ```rust,ignore
201 /// let (public_key, secret_key) = load_keypair_from_file("~/.ant-quic/identity.key")?;
202 /// let node = Node::with_keypair(public_key, secret_key).await?;
203 /// ```
204 pub async fn with_keypair(
205 public_key: MlDsaPublicKey,
206 secret_key: MlDsaSecretKey,
207 ) -> Result<Self, NodeError> {
208 Self::with_config(NodeConfig::with_keypair(public_key, secret_key)).await
209 }
210
211 /// Create a node with a HostIdentity for persistent encrypted identity
212 ///
213 /// This is the recommended way to create a node with persistent identity.
214 /// The keypair is encrypted at rest using a key derived from the HostIdentity.
215 ///
216 /// # Arguments
217 ///
218 /// * `host` - The HostIdentity for key derivation
219 /// * `network_id` - Network identifier for per-network keypair isolation
220 /// * `storage_dir` - Directory to store the encrypted keypair
221 ///
222 /// # Example
223 ///
224 /// ```rust,ignore
225 /// use ant_quic::{Node, HostIdentity};
226 ///
227 /// let host = HostIdentity::generate();
228 /// let node = Node::with_host_identity(
229 /// &host,
230 /// b"my-network",
231 /// "/var/lib/ant-quic",
232 /// ).await?;
233 /// ```
234 pub async fn with_host_identity(
235 host: &HostIdentity,
236 network_id: &[u8],
237 storage_dir: impl AsRef<std::path::Path>,
238 ) -> Result<Self, NodeError> {
239 let (public_key, secret_key) =
240 load_or_generate_endpoint_keypair(host, network_id, storage_dir.as_ref()).map_err(
241 |e| NodeError::Creation(format!("Failed to load/generate keypair: {e}")),
242 )?;
243
244 Self::with_keypair(public_key, secret_key).await
245 }
246
247 /// Create a node with full configuration
248 ///
249 /// For power users who need specific settings. Most applications
250 /// should use `Node::new()` or one of the convenience methods.
251 ///
252 /// # Example
253 ///
254 /// ```rust,ignore
255 /// let config = NodeConfig::builder()
256 /// .bind_addr("0.0.0.0:9000".parse()?)
257 /// .known_peer("quic.saorsalabs.com:9000".parse()?)
258 /// .keypair(load_keypair()?)
259 /// .build();
260 ///
261 /// let node = Node::with_config(config).await?;
262 /// ```
263 pub async fn with_config(config: NodeConfig) -> Result<Self, NodeError> {
264 // Convert NodeConfig to P2pConfig
265 let mut p2p_config = P2pConfig::default();
266
267 // Build transport registry first (before any partial moves)
268 p2p_config.transport_registry = config.build_transport_registry();
269
270 if let Some(bind_addr) = config.bind_addr {
271 p2p_config.bind_addr = Some(bind_addr.into());
272 }
273
274 p2p_config.known_peers = config.known_peers.into_iter().map(Into::into).collect();
275 p2p_config.keypair = config.keypair;
276
277 // Create event channel
278 let (event_tx, _) = broadcast::channel(256);
279
280 // Create P2pEndpoint
281 let endpoint = P2pEndpoint::new(p2p_config)
282 .await
283 .map_err(NodeError::Endpoint)?;
284
285 info!("Node created with peer ID: {:?}", endpoint.peer_id());
286
287 let inner = Arc::new(endpoint);
288
289 // Spawn event bridge task to forward P2pEvent -> NodeEvent
290 Self::spawn_event_bridge(Arc::clone(&inner), event_tx.clone());
291
292 Ok(Self {
293 inner,
294 start_time: Instant::now(),
295 event_tx,
296 })
297 }
298
299 /// Spawn a background task to bridge P2pEvents to NodeEvents
300 fn spawn_event_bridge(endpoint: Arc<P2pEndpoint>, event_tx: broadcast::Sender<NodeEvent>) {
301 let mut p2p_events = endpoint.subscribe();
302
303 tokio::spawn(async move {
304 loop {
305 match p2p_events.recv().await {
306 Ok(p2p_event) => {
307 if let Some(node_event) = Self::convert_event(p2p_event) {
308 // Ignore send errors - means no subscribers
309 let _ = event_tx.send(node_event);
310 }
311 }
312 Err(broadcast::error::RecvError::Closed) => {
313 // Channel closed, endpoint shutting down
314 break;
315 }
316 Err(broadcast::error::RecvError::Lagged(n)) => {
317 // Subscriber lagged behind, log and continue
318 tracing::warn!("Event bridge lagged by {} events", n);
319 }
320 }
321 }
322 });
323 }
324
325 /// Convert a P2pEvent to a NodeEvent
326 ///
327 /// Uses the From trait implementation for DisconnectReason conversion.
328 fn convert_event(p2p_event: P2pEvent) -> Option<NodeEvent> {
329 match p2p_event {
330 P2pEvent::PeerConnected {
331 peer_id,
332 addr,
333 side: _,
334 } => Some(NodeEvent::PeerConnected {
335 peer_id,
336 addr,
337 direct: true, // P2pEvent doesn't distinguish, assume direct
338 }),
339 P2pEvent::PeerDisconnected { peer_id, reason } => Some(NodeEvent::PeerDisconnected {
340 peer_id,
341 reason: reason.into(), // Use From trait
342 }),
343 P2pEvent::ExternalAddressDiscovered { addr } => {
344 Some(NodeEvent::ExternalAddressDiscovered { addr })
345 }
346 P2pEvent::DataReceived { peer_id, bytes } => Some(NodeEvent::DataReceived {
347 peer_id,
348 stream_id: 0, // P2pEvent doesn't track stream IDs
349 bytes,
350 }),
351 P2pEvent::ConstrainedDataReceived {
352 remote_addr,
353 connection_id,
354 data,
355 } => {
356 // For constrained data, derive a synthetic peer ID from the transport address
357 let synthetic_peer_id = {
358 use std::collections::hash_map::DefaultHasher;
359 use std::hash::{Hash, Hasher};
360 let synthetic_addr = remote_addr.to_synthetic_socket_addr();
361 let mut hasher = DefaultHasher::new();
362 synthetic_addr.hash(&mut hasher);
363 let hash = hasher.finish();
364 let mut peer_id_bytes = [0u8; 32];
365 peer_id_bytes[..8].copy_from_slice(&hash.to_le_bytes());
366 PeerId(peer_id_bytes)
367 };
368 Some(NodeEvent::DataReceived {
369 peer_id: synthetic_peer_id,
370 stream_id: connection_id as u64,
371 bytes: data.len(),
372 })
373 }
374 // Events without direct NodeEvent equivalents are ignored
375 P2pEvent::NatTraversalProgress { .. }
376 | P2pEvent::BootstrapStatus { .. }
377 | P2pEvent::PeerAuthenticated { .. } => None,
378 }
379 }
380
381 // === Identity ===
382
383 /// Get this node's peer ID
384 ///
385 /// The peer ID is derived from the Ed25519 public key and is
386 /// the unique identifier for this node on the network.
387 pub fn peer_id(&self) -> PeerId {
388 self.inner.peer_id()
389 }
390
391 /// Get the local bind address
392 ///
393 /// Returns `None` if the endpoint hasn't bound yet.
394 pub fn local_addr(&self) -> Option<SocketAddr> {
395 self.inner.local_addr()
396 }
397
398 /// Get the observed external address
399 ///
400 /// This is the address as seen by other peers on the network.
401 /// Returns `None` if no external address has been discovered yet.
402 pub fn external_addr(&self) -> Option<SocketAddr> {
403 self.inner.external_addr()
404 }
405
406 /// Get the ML-DSA-65 public key bytes (1952 bytes)
407 pub fn public_key_bytes(&self) -> &[u8] {
408 self.inner.public_key_bytes()
409 }
410
411 /// Get access to the underlying P2pEndpoint for advanced operations.
412 pub fn inner_endpoint(&self) -> &Arc<P2pEndpoint> {
413 &self.inner
414 }
415
416 /// Get the transport registry for this node
417 ///
418 /// The transport registry contains all registered transport providers (UDP, BLE, etc.)
419 /// that this node can use for connectivity.
420 pub fn transport_registry(&self) -> &crate::transport::TransportRegistry {
421 self.inner.transport_registry()
422 }
423
424 // === Connections ===
425
426 /// Connect to a peer by address
427 ///
428 /// This creates a direct connection to the specified address.
429 /// NAT traversal is handled automatically if needed.
430 ///
431 /// # Example
432 ///
433 /// ```rust,ignore
434 /// let conn = node.connect_addr("quic.saorsalabs.com:9000".parse()?).await?;
435 /// println!("Connected to: {:?}", conn.peer_id);
436 /// ```
437 pub async fn connect_addr(&self, addr: SocketAddr) -> Result<PeerConnection, NodeError> {
438 self.inner.connect(addr).await.map_err(NodeError::Endpoint)
439 }
440
441 /// Connect to a peer by ID
442 ///
443 /// This uses NAT traversal to find and connect to the peer.
444 /// A coordinator (known peer) is used to help with hole punching.
445 ///
446 /// # Example
447 ///
448 /// ```rust,ignore
449 /// let conn = node.connect(peer_id).await?;
450 /// ```
451 pub async fn connect(&self, peer_id: PeerId) -> Result<PeerConnection, NodeError> {
452 self.inner
453 .connect_to_peer(peer_id, None)
454 .await
455 .map_err(NodeError::Endpoint)
456 }
457
458 /// Accept an incoming connection
459 ///
460 /// Waits for and accepts the next incoming connection.
461 /// Returns `None` if the node is shutting down.
462 ///
463 /// # Example
464 ///
465 /// ```rust,ignore
466 /// while let Some(conn) = node.accept().await {
467 /// println!("Accepted connection from: {:?}", conn.peer_id);
468 /// // Handle connection...
469 /// }
470 /// ```
471 pub async fn accept(&self) -> Option<PeerConnection> {
472 self.inner.accept().await
473 }
474
475 /// Add a known peer dynamically
476 ///
477 /// Known peers help with NAT traversal and peer discovery.
478 /// You can add more peers at runtime.
479 pub async fn add_peer(&self, addr: SocketAddr) {
480 self.inner.add_bootstrap(addr).await;
481 }
482
483 /// Connect to all known peers
484 ///
485 /// Returns the number of successful connections.
486 pub async fn connect_known_peers(&self) -> Result<usize, NodeError> {
487 self.inner
488 .connect_known_peers()
489 .await
490 .map_err(NodeError::Endpoint)
491 }
492
493 /// Disconnect from a peer
494 pub async fn disconnect(&self, peer_id: &PeerId) -> Result<(), NodeError> {
495 self.inner
496 .disconnect(peer_id)
497 .await
498 .map_err(NodeError::Endpoint)
499 }
500
501 /// Get list of connected peers
502 pub async fn connected_peers(&self) -> Vec<PeerConnection> {
503 self.inner.connected_peers().await
504 }
505
506 /// Check if connected to a peer
507 pub async fn is_connected(&self, peer_id: &PeerId) -> bool {
508 self.inner.is_connected(peer_id).await
509 }
510
511 /// Query the health status of a connection to a specific peer.
512 ///
513 /// Returns `None` if the peer is not connected.
514 pub async fn connection_health(&self, peer_id: &PeerId) -> Option<crate::ConnectionHealth> {
515 self.inner.connection_health(peer_id).await
516 }
517
518 // === Messaging ===
519
520 /// Send data to a peer
521 pub async fn send(&self, peer_id: &PeerId, data: &[u8]) -> Result<(), NodeError> {
522 self.inner
523 .send(peer_id, data)
524 .await
525 .map_err(NodeError::Endpoint)
526 }
527
528 /// Receive data from any peer
529 pub async fn recv(&self) -> Result<(PeerId, Vec<u8>), NodeError> {
530 self.inner.recv().await.map_err(NodeError::Endpoint)
531 }
532
533 // === Observability ===
534
535 /// Get a snapshot of the node's current status
536 ///
537 /// This provides complete visibility into the node's state,
538 /// including NAT type, connectivity, relay status, and performance.
539 ///
540 /// # Example
541 ///
542 /// ```rust,ignore
543 /// let status = node.status().await;
544 /// println!("NAT type: {}", status.nat_type);
545 /// println!("Connected peers: {}", status.connected_peers);
546 /// println!("Acting as relay: {}", status.is_relaying);
547 /// ```
548 pub async fn status(&self) -> NodeStatus {
549 let stats = self.inner.stats().await;
550 let nat_stats = self.inner.nat_stats().ok();
551 let connected_peers = self.inner.connected_peers().await;
552
553 // Determine NAT type from stats
554 let nat_type = self.detect_nat_type(&stats, nat_stats.as_ref());
555
556 // Check if we have public IP
557 let local_addr = self.local_addr();
558 let external_addr = self.external_addr();
559 let has_public_ip = match (local_addr, external_addr) {
560 (Some(local), Some(external)) => {
561 // Public if external matches local (ignoring port differences)
562 local.ip() == external.ip()
563 }
564 _ => false,
565 };
566
567 // Collect external addresses
568 let mut external_addrs = Vec::new();
569 if let Some(addr) = external_addr {
570 external_addrs.push(addr);
571 }
572
573 // Calculate hole punch success rate
574 let hole_punch_success_rate = if stats.nat_traversal_attempts > 0 {
575 stats.nat_traversal_successes as f64 / stats.nat_traversal_attempts as f64
576 } else {
577 0.0
578 };
579
580 // Determine if we can help with traversal
581 let can_receive_direct =
582 has_public_ip || nat_type == NatType::FullCone || nat_type == NatType::None;
583
584 // Check relay status from NAT stats
585 // Currently, relay status is indicated by having relayed_connections > 0
586 // and active sessions that may be acting as relays
587 let (is_relaying, relay_sessions, relay_bytes_forwarded) = if let Some(ref nat) = nat_stats
588 {
589 // If we have any active sessions and are accepting connections,
590 // we're potentially relaying
591 let relaying = nat.relayed_connections > 0 && can_receive_direct;
592 (
593 relaying,
594 if relaying { nat.active_sessions } else { 0 },
595 0u64, // Not tracked yet - future enhancement
596 )
597 } else {
598 (false, 0, 0)
599 };
600
601 // Check coordination status
602 // Any node with active sessions is acting as a coordinator
603 let (is_coordinating, coordination_sessions) = if let Some(ref nat) = nat_stats {
604 (nat.active_sessions > 0, nat.active_sessions)
605 } else {
606 (false, 0)
607 };
608
609 // Calculate average RTT from connected peers
610 let mut total_rtt = Duration::ZERO;
611 let mut rtt_count = 0u32;
612 for peer in &connected_peers {
613 if let Some(metrics) = self.inner.connection_metrics(&peer.peer_id).await {
614 if let Some(rtt) = metrics.rtt {
615 total_rtt += rtt;
616 rtt_count += 1;
617 }
618 }
619 }
620 let avg_rtt = if rtt_count > 0 {
621 total_rtt / rtt_count
622 } else {
623 Duration::ZERO
624 };
625
626 NodeStatus {
627 peer_id: self.peer_id(),
628 local_addr: local_addr.unwrap_or_else(|| {
629 "0.0.0.0:0".parse().unwrap_or_else(|_| {
630 SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0)
631 })
632 }),
633 external_addrs,
634 nat_type,
635 can_receive_direct,
636 has_public_ip,
637 connected_peers: connected_peers.len(),
638 active_connections: stats.active_connections,
639 pending_connections: 0, // Not tracked yet
640 direct_connections: stats.direct_connections,
641 relayed_connections: stats.relayed_connections,
642 hole_punch_success_rate,
643 is_relaying,
644 relay_sessions,
645 relay_bytes_forwarded,
646 is_coordinating,
647 coordination_sessions,
648 avg_rtt,
649 uptime: self.start_time.elapsed(),
650 }
651 }
652
653 /// Subscribe to node events
654 ///
655 /// Returns a receiver for all significant node events including
656 /// connections, disconnections, NAT detection, and relay activity.
657 ///
658 /// # Example
659 ///
660 /// ```rust,ignore
661 /// let mut events = node.subscribe();
662 /// tokio::spawn(async move {
663 /// while let Ok(event) = events.recv().await {
664 /// match event {
665 /// NodeEvent::PeerConnected { peer_id, .. } => {
666 /// println!("Connected: {:?}", peer_id);
667 /// }
668 /// _ => {}
669 /// }
670 /// }
671 /// });
672 /// ```
673 pub fn subscribe(&self) -> broadcast::Receiver<NodeEvent> {
674 self.event_tx.subscribe()
675 }
676
677 /// Subscribe to raw P2pEvents (for advanced use)
678 ///
679 /// This provides access to the underlying P2pEndpoint events.
680 /// Most applications should use `subscribe()` for NodeEvents.
681 pub fn subscribe_raw(&self) -> broadcast::Receiver<P2pEvent> {
682 self.inner.subscribe()
683 }
684
685 // === Shutdown ===
686
687 /// Gracefully shut down the node
688 ///
689 /// This closes all connections and releases resources.
690 pub async fn shutdown(self) {
691 self.inner.shutdown().await;
692 }
693
694 /// Check if the node is still running
695 pub fn is_running(&self) -> bool {
696 self.inner.is_running()
697 }
698
699 // === Private Helpers ===
700
701 /// Detect NAT type from statistics
702 fn detect_nat_type(
703 &self,
704 stats: &crate::p2p_endpoint::EndpointStats,
705 nat_stats: Option<&crate::nat_traversal_api::NatTraversalStatistics>,
706 ) -> NatType {
707 // If we have lots of direct connections and no relayed, likely no/easy NAT
708 if stats.direct_connections > 0 && stats.relayed_connections == 0 {
709 if let Some(nat) = nat_stats {
710 // Calculate direct connection rate
711 let total = nat.direct_connections + nat.relayed_connections;
712 if total > 0 {
713 let direct_rate = nat.direct_connections as f64 / total as f64;
714 if direct_rate > 0.9 {
715 return NatType::FullCone;
716 }
717 }
718 }
719 return NatType::FullCone; // Assume easy NAT if all direct
720 }
721
722 // If we have mixed connections, harder NAT
723 if stats.direct_connections > 0 && stats.relayed_connections > 0 {
724 if let Some(nat) = nat_stats {
725 // Calculate success rate from total attempts vs successful connections
726 let success_rate = if nat.total_attempts > 0 {
727 nat.successful_connections as f64 / nat.total_attempts as f64
728 } else {
729 0.0
730 };
731
732 if success_rate > 0.7 {
733 return NatType::PortRestricted;
734 } else if success_rate > 0.3 {
735 return NatType::AddressRestricted;
736 }
737 }
738 return NatType::PortRestricted;
739 }
740
741 // If mostly relayed, likely symmetric NAT
742 if stats.relayed_connections > stats.direct_connections {
743 return NatType::Symmetric;
744 }
745
746 // Not enough data yet
747 NatType::Unknown
748 }
749}
750
751// Enable cloning through Arc
752impl Clone for Node {
753 fn clone(&self) -> Self {
754 Self {
755 inner: Arc::clone(&self.inner),
756 start_time: self.start_time,
757 event_tx: self.event_tx.clone(),
758 }
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765 use crate::derive_peer_id_from_public_key;
766
767 #[tokio::test]
768 async fn test_node_new_default() {
769 let node = Node::new().await;
770 assert!(node.is_ok(), "Node::new() should succeed: {:?}", node.err());
771
772 let node = node.unwrap();
773 assert!(node.is_running());
774
775 // Peer ID should be valid (non-zero)
776 let peer_id = node.peer_id();
777 assert_ne!(peer_id.0, [0u8; 32]);
778
779 node.shutdown().await;
780 }
781
782 #[tokio::test]
783 async fn test_node_bind() {
784 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
785 let node = Node::bind(addr).await;
786 assert!(node.is_ok(), "Node::bind() should succeed");
787
788 let node = node.unwrap();
789 assert!(node.local_addr().is_some());
790
791 node.shutdown().await;
792 }
793
794 #[tokio::test]
795 async fn test_node_with_peers() {
796 let peers = vec!["127.0.0.1:9000".parse().unwrap()];
797 let node = Node::with_peers(peers).await;
798 assert!(node.is_ok(), "Node::with_peers() should succeed");
799
800 node.unwrap().shutdown().await;
801 }
802
803 #[tokio::test]
804 async fn test_node_with_config() {
805 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
806 let config = NodeConfig::builder().bind_addr(addr).build();
807
808 let node = Node::with_config(config).await;
809 assert!(node.is_ok(), "Node::with_config() should succeed");
810
811 node.unwrap().shutdown().await;
812 }
813
814 #[tokio::test]
815 async fn test_node_status() {
816 let node = Node::new().await.unwrap();
817 let status = node.status().await;
818
819 // Check status fields are populated
820 assert_ne!(status.peer_id.0, [0u8; 32]);
821 assert_eq!(status.connected_peers, 0); // No connections yet
822
823 node.shutdown().await;
824 }
825
826 #[tokio::test]
827 async fn test_node_subscribe() {
828 let node = Node::new().await.unwrap();
829 let _events = node.subscribe();
830
831 // Just verify subscription works
832 node.shutdown().await;
833 }
834
835 #[tokio::test]
836 async fn test_node_is_clone() {
837 let node1 = Node::new().await.unwrap();
838 let node2 = node1.clone();
839
840 // Both should have same peer ID
841 assert_eq!(node1.peer_id(), node2.peer_id());
842
843 node1.shutdown().await;
844 // node2 still references the same Arc, so shutdown already happened
845 }
846
847 #[tokio::test]
848 async fn test_node_debug() {
849 let node = Node::new().await.unwrap();
850 let debug_str = format!("{:?}", node);
851 assert!(debug_str.contains("Node"));
852 assert!(debug_str.contains("peer_id"));
853
854 node.shutdown().await;
855 }
856
857 #[tokio::test]
858 async fn test_node_identity() {
859 use crate::crypto::raw_public_keys::key_utils::derive_peer_id_from_key_bytes;
860
861 let node = Node::new().await.unwrap();
862
863 // Verify identity methods
864 let peer_id = node.peer_id();
865 let public_key = node.public_key_bytes();
866
867 // Peer ID should be derived from public key (ML-DSA-65)
868 let derived = derive_peer_id_from_key_bytes(public_key).unwrap();
869 assert_eq!(peer_id, derived);
870
871 node.shutdown().await;
872 }
873
874 #[tokio::test]
875 async fn test_connected_peers_empty() {
876 let node = Node::new().await.unwrap();
877 let peers = node.connected_peers().await;
878 assert!(peers.is_empty());
879
880 node.shutdown().await;
881 }
882
883 #[tokio::test]
884 async fn test_node_error_types() {
885 // Test error conversions
886 let err = NodeError::Creation("test".to_string());
887 assert!(err.to_string().contains("test"));
888
889 let err = NodeError::Connection("connection failed".to_string());
890 assert!(err.to_string().contains("connection"));
891
892 let err = NodeError::ShuttingDown;
893 assert!(err.to_string().contains("shutting down"));
894 }
895
896 #[tokio::test]
897 async fn test_node_with_keypair_persistence() {
898 use crate::crypto::raw_public_keys::key_utils::generate_ml_dsa_keypair;
899
900 // Generate an ML-DSA-65 keypair
901 let (public_key, secret_key) = generate_ml_dsa_keypair().unwrap();
902 let expected_peer_id = derive_peer_id_from_public_key(&public_key);
903 let expected_public_key_bytes = public_key.as_bytes().to_vec();
904
905 // Create node with the keypair
906 let node = Node::with_keypair(public_key, secret_key).await.unwrap();
907
908 // Verify the node uses the same identity
909 assert_eq!(node.peer_id(), expected_peer_id);
910 assert_eq!(node.public_key_bytes(), expected_public_key_bytes);
911
912 node.shutdown().await;
913 }
914
915 #[tokio::test]
916 async fn test_node_keypair_via_config() {
917 use crate::crypto::raw_public_keys::key_utils::generate_ml_dsa_keypair;
918
919 // Generate an ML-DSA-65 keypair
920 let (public_key, secret_key) = generate_ml_dsa_keypair().unwrap();
921 let expected_peer_id = derive_peer_id_from_public_key(&public_key);
922 let expected_public_key_bytes = public_key.as_bytes().to_vec();
923
924 // Create node via config with keypair
925 let config = NodeConfig::with_keypair(public_key, secret_key);
926 let node = Node::with_config(config).await.unwrap();
927
928 // Verify the node uses the same identity
929 assert_eq!(node.peer_id(), expected_peer_id);
930 assert_eq!(node.public_key_bytes(), expected_public_key_bytes);
931
932 node.shutdown().await;
933 }
934
935 #[tokio::test]
936 async fn test_node_event_bridge_exists() {
937 let node = Node::new().await.unwrap();
938
939 // Subscribe to events - this should work
940 let mut events = node.subscribe();
941
942 // The event channel should be connected (won't receive anything yet,
943 // but the bridge task should be running)
944 // We can't easily test event reception without connections,
945 // but we verify the infrastructure is in place
946 assert!(events.try_recv().is_err()); // No events yet
947
948 node.shutdown().await;
949 }
950
951 #[tokio::test]
952 async fn test_node_with_host_identity() {
953 use crate::host_identity::HostIdentity;
954
955 // Create a temporary directory for storage
956 let temp_dir =
957 std::env::temp_dir().join(format!("ant-quic-test-node-{}", std::process::id()));
958 let _ = std::fs::create_dir_all(&temp_dir);
959
960 // Generate a HostIdentity
961 let host = HostIdentity::generate();
962 let network_id = b"test-network";
963
964 // Create first node with host identity
965 let node1 = Node::with_host_identity(&host, network_id, &temp_dir)
966 .await
967 .unwrap();
968 let peer_id_1 = node1.peer_id();
969 let public_key_1 = node1.public_key_bytes().to_vec();
970
971 // Verify the node is running
972 assert!(node1.is_running());
973
974 // Shutdown and cleanup
975 node1.shutdown().await;
976
977 // Create second node with same host identity - should have same identity
978 let node2 = Node::with_host_identity(&host, network_id, &temp_dir)
979 .await
980 .unwrap();
981 let peer_id_2 = node2.peer_id();
982 let public_key_2 = node2.public_key_bytes().to_vec();
983
984 // Verify both nodes have the same identity
985 assert_eq!(peer_id_1, peer_id_2);
986 assert_eq!(public_key_1, public_key_2);
987
988 node2.shutdown().await;
989
990 // Cleanup temp directory
991 let _ = std::fs::remove_dir_all(&temp_dir);
992 }
993
994 #[tokio::test]
995 async fn test_node_host_identity_per_network_isolation() {
996 use crate::host_identity::HostIdentity;
997
998 // Create a temporary directory for storage
999 let temp_dir =
1000 std::env::temp_dir().join(format!("ant-quic-test-isolation-{}", std::process::id()));
1001 let _ = std::fs::create_dir_all(&temp_dir);
1002
1003 // Generate a HostIdentity
1004 let host = HostIdentity::generate();
1005
1006 // Create nodes with different network IDs
1007 let node1 = Node::with_host_identity(&host, b"network-1", &temp_dir)
1008 .await
1009 .unwrap();
1010 let peer_id_1 = node1.peer_id();
1011
1012 let node2 = Node::with_host_identity(&host, b"network-2", &temp_dir)
1013 .await
1014 .unwrap();
1015 let peer_id_2 = node2.peer_id();
1016
1017 // Different networks should have different identities (privacy)
1018 assert_ne!(peer_id_1, peer_id_2);
1019
1020 node1.shutdown().await;
1021 node2.shutdown().await;
1022
1023 // Cleanup temp directory
1024 let _ = std::fs::remove_dir_all(&temp_dir);
1025 }
1026}