Skip to main content

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}