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