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}