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}