Skip to main content

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