1use crate::PeerId;
19use crate::adaptive::trust::{TrustRecord, TrustSnapshot};
20use crate::adaptive::{AdaptiveDHT, AdaptiveDhtConfig, TrustEngine, TrustEvent};
21use crate::bootstrap::cache::{CachedCloseGroupPeer, CloseGroupCache};
22use crate::bootstrap::routing_snapshot::{RoutingSnapshot, SnapshotPeer};
23use crate::dht::core_engine::AddressType;
24use crate::dht_network_manager::{DhtNetworkConfig, DhtNetworkEvent, DhtNetworkManager};
25use crate::error::{NetworkError, P2PError, P2pResult as Result};
26use crate::reachability::spawn_acquisition_driver;
27
28use crate::MultiAddr;
29use crate::identity::node_identity::{NodeIdentity, peer_id_from_public_key};
30use crate::quantum_crypto::saorsa_transport_integration::{MlDsaPublicKey, MlDsaSignature};
31use bytes::Bytes;
32use dashmap::DashMap;
33use futures::StreamExt;
34use parking_lot::Mutex as ParkingMutex;
35use serde::{Deserialize, Serialize};
36use std::collections::{HashMap, HashSet};
37use std::net::SocketAddr;
38use std::path::{Path, PathBuf};
39use std::sync::Arc;
40use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
41use std::time::{Duration, SystemTime, UNIX_EPOCH};
42use tokio::sync::{Mutex as TokioMutex, RwLock, broadcast};
43use tokio::time::Instant;
44use tokio_util::sync::CancellationToken;
45use tracing::{debug, info, trace, warn};
46
47fn bootstrap_peer_identity_matches(expected: Option<PeerId>, actual: PeerId) -> bool {
48 expected.is_none_or(|expected| expected == actual)
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
55pub(crate) struct WireMessage {
56 pub(crate) protocol: String,
58 pub(crate) data: Vec<u8>,
60 pub(crate) from: PeerId,
62 pub(crate) timestamp: u64,
64 #[serde(default)]
70 pub(crate) user_agent: String,
71 #[serde(default)]
73 pub(crate) public_key: Vec<u8>,
74 #[serde(default)]
76 pub(crate) signature: Vec<u8>,
77}
78
79#[derive(Serialize)]
86pub(crate) struct WireMessageRef<'a> {
87 pub(crate) protocol: &'a str,
88 pub(crate) data: &'a [u8],
89 pub(crate) from: &'a PeerId,
90 pub(crate) timestamp: u64,
91 pub(crate) user_agent: &'a str,
92 pub(crate) public_key: &'a [u8],
93 pub(crate) signature: &'a [u8],
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
102pub enum NodeMode {
103 #[default]
105 Node,
106 Client,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112enum ListenMode {
113 Public,
115 Local,
117}
118
119pub fn user_agent_for_mode(mode: NodeMode) -> String {
124 let prefix = match mode {
125 NodeMode::Node => "node",
126 NodeMode::Client => "client",
127 };
128 format!("{prefix}/{}", env!("CARGO_PKG_VERSION"))
129}
130
131pub fn is_dht_participant(user_agent: &str) -> bool {
133 user_agent.starts_with("node/")
134}
135
136pub(crate) const MESSAGE_RECV_CHANNEL_CAPACITY: usize = 256;
138
139pub(crate) const MAX_ACTIVE_REQUESTS: usize = 256;
141
142pub(crate) const MAX_REQUEST_TIMEOUT: Duration = Duration::from_secs(300);
144
145const DEFAULT_LISTEN_PORT: u16 = 9000;
147
148const DEFAULT_MAX_CONNECTIONS: usize = 10_000;
150
151const DEFAULT_CONNECTION_TIMEOUT_SECS: u64 = 25;
158
159const DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS: u64 = 60 * 60;
162
163const MIN_CLOSE_GROUP_CACHE_SAVE_INTERVAL: Duration = Duration::from_secs(60);
166
167const BOOTSTRAP_IDENTITY_TIMEOUT_SECS: u64 = 3;
181
182const MAX_CONCURRENT_BOOTSTRAP_DIALS: usize = 4;
189
190const CLIENT_BOOTSTRAP_TARGET: usize = 6;
199
200const MAX_CONCURRENT_SNAPSHOT_DIALS: usize = 16;
208
209const SNAPSHOT_FLOOR_ENFORCED_FOR: Duration = Duration::from_secs(60 * 60);
217
218const SNAPSHOT_FLOOR_UNRESOLVED: usize = usize::MAX;
221
222const MAX_SNAPSHOT_ADDRESSES_DIALLED: usize = 2;
228
229const SNAPSHOT_RESTORE_BUDGET: Duration = Duration::from_secs(20);
236
237const fn default_true() -> bool {
239 true
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct NodeConfig {
245 #[serde(default)]
251 pub local: bool,
252
253 #[serde(default)]
255 pub port: u16,
256
257 #[serde(default = "default_true")]
262 pub ipv6: bool,
263
264 pub bootstrap_peers: Vec<crate::MultiAddr>,
266
267 pub connection_timeout: Duration,
270
271 pub max_connections: usize,
273
274 pub dht_config: DHTConfig,
276
277 pub diversity_config: Option<crate::security::IPDiversityConfig>,
282
283 #[serde(default)]
287 pub max_message_size: Option<usize>,
288
289 #[serde(skip)]
294 pub node_identity: Option<Arc<NodeIdentity>>,
295
296 #[serde(default)]
302 pub mode: NodeMode,
303
304 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub custom_user_agent: Option<String>,
310
311 #[serde(default)]
319 pub allow_loopback: bool,
320
321 #[serde(default)]
329 pub adaptive_dht_config: AdaptiveDhtConfig,
330
331 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub close_group_cache_dir: Option<PathBuf>,
343
344 #[serde(default = "default_close_group_cache_max_age")]
349 pub close_group_cache_max_age: Option<Duration>,
350}
351
352fn default_close_group_cache_max_age() -> Option<Duration> {
353 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct DHTConfig {
359 pub k_value: usize,
361
362 pub alpha_value: usize,
364
365 pub refresh_interval: Duration,
367}
368
369#[inline]
382fn build_listen_addrs(port: u16, ipv6_enabled: bool, mode: ListenMode) -> Vec<MultiAddr> {
383 let mut addrs = Vec::with_capacity(if ipv6_enabled { 2 } else { 1 });
384
385 let (v4, v6) = match mode {
386 ListenMode::Public => (
387 std::net::Ipv4Addr::UNSPECIFIED,
388 std::net::Ipv6Addr::UNSPECIFIED,
389 ),
390 ListenMode::Local => (std::net::Ipv4Addr::LOCALHOST, std::net::Ipv6Addr::LOCALHOST),
391 };
392
393 if ipv6_enabled {
394 addrs.push(MultiAddr::quic(std::net::SocketAddr::new(
395 std::net::IpAddr::V6(v6),
396 port,
397 )));
398 }
399
400 addrs.push(MultiAddr::quic(std::net::SocketAddr::new(
401 std::net::IpAddr::V4(v4),
402 port,
403 )));
404
405 addrs
406}
407
408impl NodeConfig {
409 pub fn user_agent(&self) -> String {
414 self.custom_user_agent
415 .clone()
416 .unwrap_or_else(|| user_agent_for_mode(self.mode))
417 }
418
419 pub fn listen_addrs(&self) -> Vec<MultiAddr> {
424 let mode = if self.local {
425 ListenMode::Local
426 } else {
427 ListenMode::Public
428 };
429 build_listen_addrs(self.port, self.ipv6, mode)
430 }
431
432 pub fn new() -> Result<Self> {
438 Ok(Self::default())
439 }
440
441 pub fn builder() -> NodeConfigBuilder {
443 NodeConfigBuilder::default()
444 }
445}
446
447#[derive(Debug, Clone)]
470pub struct NodeConfigBuilder {
471 port: u16,
472 ipv6: bool,
473 local: bool,
474 bootstrap_peers: Vec<crate::MultiAddr>,
475 max_connections: Option<usize>,
476 connection_timeout: Option<Duration>,
477 dht_config: Option<DHTConfig>,
478 max_message_size: Option<usize>,
479 mode: NodeMode,
480 custom_user_agent: Option<String>,
481 allow_loopback: Option<bool>,
482 adaptive_dht_config: Option<AdaptiveDhtConfig>,
483 close_group_cache_dir: Option<PathBuf>,
484 close_group_cache_max_age: Option<Option<Duration>>,
487}
488
489impl Default for NodeConfigBuilder {
490 fn default() -> Self {
491 Self {
492 port: 0,
493 ipv6: true,
494 local: false,
495 bootstrap_peers: Vec::new(),
496 max_connections: None,
497 connection_timeout: None,
498 dht_config: None,
499 max_message_size: None,
500 mode: NodeMode::default(),
501 custom_user_agent: None,
502 allow_loopback: None,
503 adaptive_dht_config: None,
504 close_group_cache_dir: None,
505 close_group_cache_max_age: None,
506 }
507 }
508}
509
510impl NodeConfigBuilder {
511 pub fn port(mut self, port: u16) -> Self {
513 self.port = port;
514 self
515 }
516
517 pub fn ipv6(mut self, enabled: bool) -> Self {
519 self.ipv6 = enabled;
520 self
521 }
522
523 pub fn local(mut self, local: bool) -> Self {
530 self.local = local;
531 self
532 }
533
534 pub fn bootstrap_peer(mut self, addr: crate::MultiAddr) -> Self {
536 self.bootstrap_peers.push(addr);
537 self
538 }
539
540 pub fn max_connections(mut self, max: usize) -> Self {
542 self.max_connections = Some(max);
543 self
544 }
545
546 pub fn connection_timeout(mut self, timeout: Duration) -> Self {
548 self.connection_timeout = Some(timeout);
549 self
550 }
551
552 pub fn dht_config(mut self, config: DHTConfig) -> Self {
554 self.dht_config = Some(config);
555 self
556 }
557
558 pub fn max_message_size(mut self, max_message_size: usize) -> Self {
562 self.max_message_size = Some(max_message_size);
563 self
564 }
565
566 pub fn mode(mut self, mode: NodeMode) -> Self {
568 self.mode = mode;
569 self
570 }
571
572 pub fn custom_user_agent(mut self, user_agent: impl Into<String>) -> Self {
574 self.custom_user_agent = Some(user_agent.into());
575 self
576 }
577
578 pub fn allow_loopback(mut self, allow: bool) -> Self {
582 self.allow_loopback = Some(allow);
583 self
584 }
585
586 pub fn trust_enforcement(mut self, enabled: bool) -> Self {
599 let threshold = if enabled {
600 AdaptiveDhtConfig::default().swap_threshold
601 } else {
602 0.0
603 };
604 self.adaptive_dht_config = Some(AdaptiveDhtConfig {
605 swap_threshold: threshold,
606 });
607 self
608 }
609
610 pub fn adaptive_dht_config(mut self, config: AdaptiveDhtConfig) -> Self {
614 self.adaptive_dht_config = Some(config);
615 self
616 }
617
618 pub fn close_group_cache_dir(mut self, path: impl Into<PathBuf>) -> Self {
623 self.close_group_cache_dir = Some(path.into());
624 self
625 }
626
627 pub fn close_group_cache_max_age(mut self, max_age: Option<Duration>) -> Self {
630 self.close_group_cache_max_age = Some(max_age);
631 self
632 }
633
634 pub fn build(self) -> Result<NodeConfig> {
640 let allow_loopback = self.allow_loopback.unwrap_or(self.local);
642
643 Ok(NodeConfig {
644 local: self.local,
645 port: self.port,
646 ipv6: self.ipv6,
647 bootstrap_peers: self.bootstrap_peers,
648 connection_timeout: self
649 .connection_timeout
650 .unwrap_or(Duration::from_secs(DEFAULT_CONNECTION_TIMEOUT_SECS)),
651 max_connections: self.max_connections.unwrap_or(DEFAULT_MAX_CONNECTIONS),
652 dht_config: self.dht_config.unwrap_or_default(),
653 diversity_config: None,
654 max_message_size: self.max_message_size,
655 node_identity: None,
656 mode: self.mode,
657 custom_user_agent: self.custom_user_agent,
658 allow_loopback,
659 adaptive_dht_config: self.adaptive_dht_config.unwrap_or_default(),
660 close_group_cache_dir: self.close_group_cache_dir,
661 close_group_cache_max_age: self
662 .close_group_cache_max_age
663 .unwrap_or_else(default_close_group_cache_max_age),
664 })
665 }
666}
667
668impl Default for NodeConfig {
669 fn default() -> Self {
670 Self {
671 local: false,
672 port: DEFAULT_LISTEN_PORT,
673 ipv6: true,
674 bootstrap_peers: Vec::new(),
675 connection_timeout: Duration::from_secs(DEFAULT_CONNECTION_TIMEOUT_SECS),
676 max_connections: DEFAULT_MAX_CONNECTIONS,
677 dht_config: DHTConfig::default(),
678 diversity_config: None,
679 max_message_size: None,
680 node_identity: None,
681 mode: NodeMode::default(),
682 custom_user_agent: None,
683 allow_loopback: false,
684 adaptive_dht_config: AdaptiveDhtConfig::default(),
685 close_group_cache_dir: None,
686 close_group_cache_max_age: default_close_group_cache_max_age(),
687 }
688 }
689}
690
691impl DHTConfig {
692 pub const DEFAULT_K_VALUE: usize = crate::dht_lookup::DEFAULT_K_VALUE;
694 const DEFAULT_ALPHA_VALUE: usize = crate::dht_lookup::DEFAULT_ALPHA_VALUE;
695 const DEFAULT_REFRESH_INTERVAL_SECS: u64 = 600;
696 const MIN_K_VALUE: usize = 4;
698
699 pub fn validate(&self) -> Result<()> {
703 if self.k_value < Self::MIN_K_VALUE {
704 return Err(P2PError::Validation(
705 format!(
706 "k_value must be >= {} (got {}), values below {} produce degenerate behavior",
707 Self::MIN_K_VALUE,
708 self.k_value,
709 Self::MIN_K_VALUE,
710 )
711 .into(),
712 ));
713 }
714 if self.alpha_value < 1 {
715 return Err(P2PError::Validation(
716 format!("alpha_value must be >= 1 (got {})", self.alpha_value).into(),
717 ));
718 }
719 if self.refresh_interval.is_zero() {
720 return Err(P2PError::Validation("refresh_interval must be > 0".into()));
721 }
722 Ok(())
723 }
724}
725
726impl Default for DHTConfig {
727 fn default() -> Self {
728 Self {
729 k_value: Self::DEFAULT_K_VALUE,
730 alpha_value: Self::DEFAULT_ALPHA_VALUE,
731 refresh_interval: Duration::from_secs(Self::DEFAULT_REFRESH_INTERVAL_SECS),
732 }
733 }
734}
735
736#[derive(Debug, Clone)]
738pub struct PeerInfo {
739 #[allow(dead_code)]
741 pub(crate) channel_id: String,
742
743 pub addresses: Vec<MultiAddr>,
745
746 pub connected_at: Instant,
748
749 pub last_seen: Instant,
751
752 pub status: ConnectionStatus,
754
755 pub protocols: Vec<String>,
757
758 pub heartbeat_count: u64,
760}
761
762#[derive(Debug, Clone, PartialEq)]
764pub enum ConnectionStatus {
765 Connecting,
767 Connected,
769 Disconnecting,
771 Disconnected,
773 Failed(String),
775}
776
777#[derive(Debug, Clone, Copy, PartialEq, Eq)]
787pub enum PeerRouteKind {
788 Relay,
790 Direct,
792 Lan,
794 Unverified,
796 Unknown,
799}
800
801impl PeerRouteKind {
802 #[must_use]
805 pub const fn as_str(self) -> &'static str {
806 match self {
807 Self::Relay => "relay",
808 Self::Direct => "direct",
809 Self::Lan => "lan",
810 Self::Unverified => "unverified",
811 Self::Unknown => "unknown",
812 }
813 }
814}
815
816impl std::fmt::Display for PeerRouteKind {
817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
818 f.write_str(self.as_str())
819 }
820}
821
822impl From<AddressType> for PeerRouteKind {
823 fn from(ty: AddressType) -> Self {
824 match ty {
825 AddressType::Relay => Self::Relay,
826 AddressType::Direct => Self::Direct,
827 AddressType::Unverified => Self::Unverified,
828 AddressType::Lan => Self::Lan,
829 }
830 }
831}
832
833#[must_use]
859pub(crate) fn classify_peer_route_from_typed(
860 transport_source: Option<&MultiAddr>,
861 typed_addrs: &[(MultiAddr, AddressType)],
862) -> PeerRouteKind {
863 let Some(src) = transport_source else {
864 return PeerRouteKind::Unknown;
865 };
866 let Some(src_sa) = src.socket_addr() else {
867 return PeerRouteKind::Unknown;
868 };
869
870 let mut matched: Option<AddressType> = None;
874 let mut conflict = false;
875 for (addr, ty) in typed_addrs {
876 if addr.socket_addr().is_some_and(|sa| sa == src_sa) {
877 match matched {
878 None => matched = Some(*ty),
879 Some(prev) if prev == *ty => {}
880 Some(_) => {
881 conflict = true;
882 break;
883 }
884 }
885 }
886 }
887
888 match (matched, conflict) {
889 (Some(ty), false) => ty.into(),
890 _ => PeerRouteKind::Unknown,
891 }
892}
893
894#[derive(Debug, Clone)]
899pub enum P2PEvent {
900 Message {
902 topic: String,
904 source: Option<PeerId>,
907 transport_source: Option<MultiAddr>,
911 timestamp: u64,
919 data: Vec<u8>,
921 },
922 PeerConnected(PeerId, String),
925 PeerDisconnected(PeerId),
927}
928
929#[derive(Debug, Clone)]
934pub struct PeerResponse {
935 pub peer_id: PeerId,
937 pub data: Vec<u8>,
939 pub latency: Duration,
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
948pub(crate) struct RequestResponseEnvelope {
949 pub(crate) message_id: String,
951 pub(crate) is_response: bool,
953 pub(crate) payload: Vec<u8>,
955}
956
957pub(crate) struct PendingRequest {
959 pub(crate) response_tx: tokio::sync::oneshot::Sender<Vec<u8>>,
961 pub(crate) expected_peer: PeerId,
963}
964
965const QUIC_TEARDOWN_GRACE: Duration = Duration::from_millis(100);
971
972pub struct P2PNode {
983 config: NodeConfig,
985
986 peer_id: PeerId,
988
989 transport: Arc<crate::transport_handle::TransportHandle>,
991
992 start_time: Instant,
994
995 shutdown: CancellationToken,
997
998 close_group_cache_save_shutdown: CancellationToken,
1001 routing_snapshot_restored: AtomicBool,
1004 routing_snapshot_floor: Arc<AtomicUsize>,
1009 routing_snapshot_floor_at: Arc<AtomicU64>,
1011
1012 close_group_cache_save_handle: TokioMutex<Option<tokio::task::JoinHandle<()>>>,
1015
1016 adaptive_dht: AdaptiveDHT,
1019
1020 is_bootstrapped: Arc<AtomicBool>,
1022
1023 is_started: Arc<AtomicBool>,
1025
1026 reconnect_locks: ParkingMutex<HashMap<PeerId, Arc<TokioMutex<()>>>>,
1030
1031 relayer_peer_id: Arc<RwLock<Option<PeerId>>>,
1040
1041 relay_address: Arc<RwLock<Option<SocketAddr>>>,
1048}
1049
1050pub(crate) fn normalize_wildcard_to_loopback(addr: std::net::SocketAddr) -> std::net::SocketAddr {
1066 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
1067
1068 if addr.ip().is_unspecified() {
1069 let loopback_ip = match addr {
1071 std::net::SocketAddr::V6(_) => IpAddr::V6(Ipv6Addr::LOCALHOST), std::net::SocketAddr::V4(_) => IpAddr::V4(Ipv4Addr::LOCALHOST), };
1074 std::net::SocketAddr::new(loopback_ip, addr.port())
1075 } else {
1076 addr
1078 }
1079}
1080
1081impl P2PNode {
1082 pub async fn new(config: NodeConfig) -> Result<Self> {
1084 let node_identity = match config.node_identity.clone() {
1086 Some(identity) => identity,
1087 None => Arc::new(NodeIdentity::generate()?),
1088 };
1089
1090 let peer_id = *node_identity.peer_id();
1092
1093 config.dht_config.validate()?;
1096 if let Some(ref diversity) = config.diversity_config {
1097 diversity
1098 .validate()
1099 .map_err(|e| P2PError::Validation(format!("IP diversity config: {e}").into()))?;
1100 }
1101
1102 let transport_config = crate::transport_handle::TransportConfig::from_node_config(
1104 &config,
1105 crate::DEFAULT_EVENT_CHANNEL_CAPACITY,
1106 node_identity.clone(),
1107 );
1108 let transport =
1109 Arc::new(crate::transport_handle::TransportHandle::new(transport_config).await?);
1110
1111 let dht_manager_config = DhtNetworkConfig {
1113 peer_id,
1114 node_config: config.clone(),
1115 request_timeout: config.connection_timeout,
1116 max_concurrent_operations: MAX_ACTIVE_REQUESTS,
1117 enable_security: true,
1118 swap_threshold: 0.0, };
1120 let adaptive_dht = AdaptiveDHT::new(
1121 transport.clone(),
1122 dht_manager_config,
1123 config.adaptive_dht_config.clone(),
1124 )
1125 .await?;
1126
1127 let node = Self {
1128 config,
1129 peer_id,
1130 transport,
1131 start_time: Instant::now(),
1132 shutdown: CancellationToken::new(),
1133 close_group_cache_save_shutdown: CancellationToken::new(),
1134 routing_snapshot_restored: AtomicBool::new(false),
1135 routing_snapshot_floor: Arc::new(AtomicUsize::new(SNAPSHOT_FLOOR_UNRESOLVED)),
1136 routing_snapshot_floor_at: Arc::new(AtomicU64::new(0)),
1137 close_group_cache_save_handle: TokioMutex::new(None),
1138 adaptive_dht,
1139 is_bootstrapped: Arc::new(AtomicBool::new(false)),
1140 is_started: Arc::new(AtomicBool::new(false)),
1141 reconnect_locks: ParkingMutex::new(HashMap::new()),
1142 relayer_peer_id: Arc::new(RwLock::new(None)),
1143 relay_address: Arc::new(RwLock::new(None)),
1144 };
1145 info!(
1146 "Created P2P node with peer ID: {} (call start() to begin networking)",
1147 node.peer_id
1148 );
1149
1150 Ok(node)
1151 }
1152
1153 pub fn peer_id(&self) -> &PeerId {
1155 &self.peer_id
1156 }
1157
1158 pub fn transport(&self) -> &Arc<crate::transport_handle::TransportHandle> {
1160 &self.transport
1161 }
1162
1163 pub async fn relay_address(&self) -> Option<SocketAddr> {
1170 *self.relay_address.read().await
1171 }
1172
1173 pub fn local_addr(&self) -> Option<MultiAddr> {
1174 self.transport.local_addr()
1175 }
1176
1177 pub fn is_bootstrapped(&self) -> bool {
1182 self.is_bootstrapped.load(Ordering::SeqCst)
1183 }
1184
1185 pub async fn re_bootstrap(&self) -> Result<()> {
1190 self.is_bootstrapped.store(false, Ordering::SeqCst);
1191 self.connect_bootstrap_peers(None).await
1192 }
1193
1194 pub fn trust_engine(&self) -> Arc<TrustEngine> {
1200 self.adaptive_dht.trust_engine().clone()
1201 }
1202
1203 pub async fn report_trust_event(&self, peer_id: &PeerId, event: TrustEvent) {
1217 self.adaptive_dht.report_trust_event(peer_id, event).await;
1218 }
1219
1220 pub fn peer_trust(&self, peer_id: &PeerId) -> f64 {
1224 self.adaptive_dht.peer_trust(peer_id)
1225 }
1226
1227 pub fn adaptive_dht(&self) -> &AdaptiveDHT {
1229 &self.adaptive_dht
1230 }
1231
1232 pub async fn classify_peer_transport_route(
1248 &self,
1249 peer_id: &PeerId,
1250 transport_source: Option<&MultiAddr>,
1251 ) -> PeerRouteKind {
1252 let typed = self
1253 .adaptive_dht
1254 .peer_addresses_for_dial_typed(peer_id)
1255 .await;
1256 classify_peer_route_from_typed(transport_source, &typed)
1257 }
1258
1259 pub async fn peer_last_seen_elapsed(&self, peer_id: &PeerId) -> Option<Duration> {
1262 self.adaptive_dht
1263 .dht_manager()
1264 .peer_last_seen_elapsed(peer_id)
1265 .await
1266 }
1267
1268 pub async fn send_request(
1301 &self,
1302 peer_id: &PeerId,
1303 protocol: &str,
1304 data: Vec<u8>,
1305 timeout: Duration,
1306 ) -> Result<PeerResponse> {
1307 let result = self
1308 .send_request_reconnecting(peer_id, protocol, data, timeout)
1309 .await;
1310 if let Err(ref e) = result {
1311 let event = if matches!(e, P2PError::Timeout(_)) {
1312 TrustEvent::ConnectionTimeout
1313 } else {
1314 TrustEvent::ConnectionFailed
1315 };
1316 self.report_trust_event(peer_id, event).await;
1317 }
1318 result
1319 }
1320
1321 async fn send_request_reconnecting(
1338 &self,
1339 peer_id: &PeerId,
1340 protocol: &str,
1341 data: Vec<u8>,
1342 timeout: Duration,
1343 ) -> Result<PeerResponse> {
1344 let existing_channels = self.transport.channels_for_peer(peer_id).await;
1349
1350 if existing_channels.is_empty() {
1353 {
1358 let lock = self.reconnect_lock_for(peer_id);
1359 let _guard = lock.lock().await;
1360 if !self.transport.is_peer_connected(peer_id).await {
1362 self.ensure_channel(peer_id, &[], &[], &[]).await?;
1363 }
1364 }
1365 return self
1366 .transport
1367 .send_request(peer_id, protocol, data, timeout)
1368 .await;
1369 }
1370
1371 let saved_addrs: Vec<MultiAddr> = self
1374 .transport
1375 .peer_info(peer_id)
1376 .await
1377 .map(|info| info.addresses)
1378 .unwrap_or_default();
1379
1380 let retry_data = data.clone();
1383
1384 match self
1386 .transport
1387 .send_request(peer_id, protocol, data, timeout)
1388 .await
1389 {
1390 Ok(resp) => return Ok(resp),
1391 Err(e) => {
1392 if !e.is_stale_channel_send_failure() {
1396 return Err(e);
1397 }
1398 debug!(
1399 peer = %peer_id.to_hex(),
1400 error = %e,
1401 "stale channel request failed, attempting reconnect",
1402 );
1403 }
1404 }
1405
1406 {
1411 let lock = self.reconnect_lock_for(peer_id);
1412 let _guard = lock.lock().await;
1413
1414 if self.transport.is_peer_connected(peer_id).await {
1416 for channel_id in &existing_channels {
1419 self.transport.disconnect_channel(channel_id).await;
1420 }
1421 } else {
1422 self.ensure_channel(peer_id, &[], &saved_addrs, &existing_channels)
1423 .await?;
1424 }
1425 }
1426 self.transport
1427 .send_request(peer_id, protocol, retry_data, timeout)
1428 .await
1429 }
1430
1431 pub async fn send_response(
1432 &self,
1433 peer_id: &PeerId,
1434 protocol: &str,
1435 message_id: &str,
1436 data: Vec<u8>,
1437 ) -> Result<()> {
1438 self.transport
1439 .send_response(peer_id, protocol, message_id, data)
1440 .await
1441 }
1442
1443 pub fn parse_request_envelope(data: &[u8]) -> Option<(String, bool, Vec<u8>)> {
1444 crate::transport_handle::TransportHandle::parse_request_envelope(data)
1445 }
1446
1447 pub async fn subscribe(&self, topic: &str) -> Result<()> {
1448 self.transport.subscribe(topic).await
1449 }
1450
1451 pub async fn publish(&self, topic: &str, data: &[u8]) -> Result<()> {
1452 self.transport.publish(topic, data).await
1453 }
1454
1455 pub fn config(&self) -> &NodeConfig {
1457 &self.config
1458 }
1459
1460 pub async fn start(&self) -> Result<()> {
1462 info!("Starting P2P node...");
1463
1464 self.transport.start_network_listeners().await?;
1466
1467 self.adaptive_dht.start().await?;
1469
1470 let listen_addrs = self.transport.listen_addrs().await;
1472 info!("P2P node started on addresses: {:?}", listen_addrs);
1473
1474 let close_group_cache = if let Some(ref dir) = self.config.close_group_cache_dir {
1481 match CloseGroupCache::load_from_dir(dir).await {
1482 Ok(Some(cache)) => {
1483 let now_epoch = SystemTime::now()
1484 .duration_since(UNIX_EPOCH)
1485 .map_or(0, |duration| duration.as_secs());
1486 if cache.is_stale(now_epoch, self.config.close_group_cache_max_age) {
1487 warn!(
1488 cache_age_secs = now_epoch.saturating_sub(cache.saved_at_epoch_secs),
1489 max_age_secs = self
1490 .config
1491 .close_group_cache_max_age
1492 .map(|age| age.as_secs()),
1493 "Close group cache is stale; skipping cached trust and Priority-0 peers"
1494 );
1495 None
1496 } else {
1497 let original_count = cache.peers.len();
1500 let cache = CloseGroupCache {
1501 peers: cache
1502 .peers
1503 .into_iter()
1504 .filter(|p| p.trust.score.is_finite())
1505 .collect(),
1506 ..cache
1507 };
1508 let filtered_count = original_count - cache.peers.len();
1509 if filtered_count > 0 {
1510 warn!(
1511 "Filtered {filtered_count} peers with non-finite trust scores from close group cache"
1512 );
1513 }
1514
1515 let trust_snapshot = TrustSnapshot {
1516 peers: cache
1517 .peers
1518 .iter()
1519 .map(|p| (p.peer_id, p.trust.clone()))
1520 .collect(),
1521 };
1522 self.adaptive_dht
1523 .trust_engine()
1524 .import_snapshot(&trust_snapshot);
1525 info!(
1526 cache_age_secs = now_epoch.saturating_sub(cache.saved_at_epoch_secs),
1527 saved_at_epoch_secs = cache.saved_at_epoch_secs,
1528 "Loaded {} peers from close group cache (trust scores imported)",
1529 cache.peers.len()
1530 );
1531 Some(cache)
1532 }
1533 }
1534 Ok(None) => {
1535 debug!(
1536 "No close group cache found in {}, fresh start",
1537 dir.display()
1538 );
1539 None
1540 }
1541 Err(e) => {
1542 warn!(
1543 "Failed to load close group cache from {}: {e}",
1544 dir.display()
1545 );
1546 None
1547 }
1548 }
1549 } else {
1550 None
1551 };
1552
1553 self.connect_bootstrap_peers(close_group_cache.as_ref())
1555 .await?;
1556
1557 {
1564 let dht = self.adaptive_dht.dht_manager();
1565 let rt_size = dht.get_routing_table_size().await;
1566 dht.emit_event(DhtNetworkEvent::BootstrapComplete { num_peers: rt_size });
1567 }
1568
1569 if self.config.mode != NodeMode::Client {
1583 spawn_acquisition_driver(
1584 self.adaptive_dht.dht_manager().clone(),
1585 Arc::clone(&self.transport),
1586 Arc::clone(&self.relayer_peer_id),
1587 Arc::clone(&self.relay_address),
1588 self.shutdown.clone(),
1589 );
1590 } else {
1591 info!("client mode — skipping relay acquisition driver");
1592 }
1593
1594 if let Some(dir) = self.config.close_group_cache_dir.clone() {
1595 let interval = self
1596 .config
1597 .dht_config
1598 .refresh_interval
1599 .max(MIN_CLOSE_GROUP_CACHE_SAVE_INTERVAL);
1600 let mut task = self.close_group_cache_save_handle.lock().await;
1601 if task.is_none() {
1602 let dht_manager = Arc::clone(self.adaptive_dht.dht_manager());
1603 let trust_engine = Arc::clone(self.adaptive_dht.trust_engine());
1604 let peer_id = self.peer_id;
1605 let k_value = self.config.dht_config.k_value;
1606 let shutdown = self.close_group_cache_save_shutdown.clone();
1607 let snapshot_floor = Arc::clone(&self.routing_snapshot_floor);
1608 let snapshot_floor_at = Arc::clone(&self.routing_snapshot_floor_at);
1609 *task = Some(tokio::spawn(periodic_close_group_cache_save(
1610 dht_manager,
1611 trust_engine,
1612 peer_id,
1613 k_value,
1614 dir,
1615 interval,
1616 snapshot_floor,
1617 snapshot_floor_at,
1618 shutdown,
1619 )));
1620 info!(
1621 interval_secs = interval.as_secs(),
1622 "Started periodic close group cache persistence"
1623 );
1624 }
1625 }
1626
1627 self.is_started
1628 .store(true, std::sync::atomic::Ordering::Release);
1629
1630 Ok(())
1631 }
1632
1633 pub async fn run(&self) -> Result<()> {
1638 if !self.is_running() {
1639 self.start().await?;
1640 }
1641
1642 info!("P2P node running...");
1643
1644 self.shutdown.cancelled().await;
1647
1648 info!("P2P node stopped");
1649 Ok(())
1650 }
1651
1652 pub async fn stop(&self) -> Result<()> {
1654 info!("Stopping P2P node...");
1655
1656 self.close_group_cache_save_shutdown.cancel();
1659 let cache_task = self.close_group_cache_save_handle.lock().await.take();
1660 if let Some(cache_task) = cache_task
1661 && let Err(error) = cache_task.await
1662 {
1663 warn!("Periodic close group cache task failed during shutdown: {error}");
1664 }
1665
1666 if let Some(ref dir) = self.config.close_group_cache_dir {
1669 if let Err(e) = self.save_close_group_cache(dir, "shutdown").await {
1670 warn!("Failed to save close group cache on shutdown: {e}");
1671 }
1672 let now_epoch = SystemTime::now()
1673 .duration_since(UNIX_EPOCH)
1674 .map_or(0, |duration| duration.as_secs());
1675 save_routing_snapshot(
1676 self.dht_manager(),
1677 self.peer_id,
1678 now_epoch,
1679 dir,
1680 &self.routing_snapshot_floor,
1681 &self.routing_snapshot_floor_at,
1682 "shutdown",
1683 )
1684 .await;
1685 }
1686
1687 self.shutdown.cancel();
1689
1690 self.adaptive_dht.stop().await?;
1692
1693 self.transport.stop().await?;
1695
1696 self.is_started
1697 .store(false, std::sync::atomic::Ordering::Release);
1698
1699 info!("P2P node stopped");
1700 Ok(())
1701 }
1702
1703 pub async fn shutdown(&self) -> Result<()> {
1705 self.stop().await
1706 }
1707
1708 pub fn is_running(&self) -> bool {
1710 self.is_started.load(std::sync::atomic::Ordering::Acquire) && !self.shutdown.is_cancelled()
1711 }
1712
1713 pub async fn listen_addrs(&self) -> Vec<MultiAddr> {
1715 self.transport.listen_addrs().await
1716 }
1717
1718 pub async fn connected_peers(&self) -> Vec<PeerId> {
1720 self.transport.connected_peers().await
1721 }
1722
1723 pub async fn peer_count(&self) -> usize {
1725 self.transport.peer_count().await
1726 }
1727
1728 pub async fn peer_info(&self, peer_id: &PeerId) -> Option<PeerInfo> {
1730 self.transport.peer_info(peer_id).await
1731 }
1732
1733 #[allow(dead_code)]
1735 pub(crate) async fn get_channel_id_by_address(&self, addr: &MultiAddr) -> Option<String> {
1736 self.transport.get_channel_id_by_address(addr).await
1737 }
1738
1739 #[allow(dead_code)]
1741 pub(crate) async fn list_active_connections(&self) -> Vec<(String, Vec<MultiAddr>)> {
1742 self.transport.list_active_connections().await
1743 }
1744
1745 #[allow(dead_code)]
1747 pub(crate) async fn remove_channel(&self, channel_id: &str) -> bool {
1748 self.transport.remove_channel(channel_id).await
1749 }
1750
1751 pub(crate) async fn disconnect_channel(&self, channel_id: &str) {
1756 self.transport.disconnect_channel(channel_id).await;
1757 }
1758
1759 pub async fn is_peer_connected(&self, peer_id: &PeerId) -> bool {
1761 self.transport.is_peer_connected(peer_id).await
1762 }
1763
1764 pub async fn connect_peer(&self, address: &MultiAddr) -> Result<String> {
1775 self.transport.connect_peer(address).await
1776 }
1777
1778 pub async fn connect_peer_typed(
1785 &self,
1786 address: &MultiAddr,
1787 kind: AddressType,
1788 ) -> Result<String> {
1789 self.transport.connect_peer_typed(address, kind).await
1790 }
1791
1792 pub async fn wait_for_peer_identity(
1799 &self,
1800 channel_id: &str,
1801 timeout: Duration,
1802 ) -> Result<PeerId> {
1803 self.transport
1804 .wait_for_peer_identity(channel_id, timeout)
1805 .await
1806 }
1807
1808 pub async fn disconnect_peer(&self, peer_id: &PeerId) -> Result<()> {
1810 self.transport.disconnect_peer(peer_id).await
1811 }
1812
1813 #[allow(dead_code)]
1815 pub(crate) async fn is_connection_active(&self, channel_id: &str) -> bool {
1816 self.transport.is_connection_active(channel_id).await
1817 }
1818
1819 pub async fn send_message(
1834 &self,
1835 peer_id: &PeerId,
1836 protocol: &str,
1837 data: impl Into<Bytes>,
1838 addrs: &[MultiAddr],
1839 ) -> Result<()> {
1840 let data: Bytes = data.into();
1843 let existing_channels = self.transport.channels_for_peer(peer_id).await;
1848
1849 if existing_channels.is_empty() {
1852 let lock = self.reconnect_lock_for(peer_id);
1853 let _guard = lock.lock().await;
1854
1855 if self.transport.is_peer_connected(peer_id).await {
1857 return self.transport.send_message(peer_id, protocol, data).await;
1858 }
1859
1860 return self
1861 .reconnect_and_send(peer_id, protocol, data, addrs, &[], &[])
1862 .await;
1863 }
1864
1865 let saved_addrs: Vec<MultiAddr> = self
1868 .transport
1869 .peer_info(peer_id)
1870 .await
1871 .map(|info| info.addresses)
1872 .unwrap_or_default();
1873
1874 let retry_data = data.clone();
1877
1878 let send_result = self.transport.send_message(peer_id, protocol, data).await;
1880 match send_result {
1881 Ok(()) => return Ok(()),
1882 Err(e) => {
1883 if !e.is_stale_channel_send_failure() {
1884 debug!(
1885 peer = %peer_id.to_hex(),
1886 error = %e,
1887 "send failed during active channel use, not reconnecting",
1888 );
1889 return Err(e);
1890 }
1891
1892 debug!(
1893 peer = %peer_id.to_hex(),
1894 error = %e,
1895 "stale channel send failed, attempting reconnect",
1896 );
1897 }
1898 }
1899
1900 let lock = self.reconnect_lock_for(peer_id);
1903 let _guard = lock.lock().await;
1904
1905 if self.transport.is_peer_connected(peer_id).await {
1907 for channel_id in &existing_channels {
1911 self.transport.disconnect_channel(channel_id).await;
1912 }
1913 return self
1914 .transport
1915 .send_message(peer_id, protocol, retry_data)
1916 .await;
1917 }
1918
1919 self.reconnect_and_send(
1920 peer_id,
1921 protocol,
1922 retry_data,
1923 addrs,
1924 &saved_addrs,
1925 &existing_channels,
1926 )
1927 .await
1928 }
1929
1930 async fn ensure_channel(
1946 &self,
1947 peer_id: &PeerId,
1948 addrs: &[MultiAddr],
1949 saved_addrs: &[MultiAddr],
1950 stale_channels: &[String],
1951 ) -> Result<()> {
1952 if !stale_channels.is_empty() {
1958 for channel_id in stale_channels {
1959 self.transport.disconnect_channel(channel_id).await;
1960 }
1961 tokio::time::sleep(QUIC_TEARDOWN_GRACE).await;
1962 }
1963
1964 let candidates = self
1965 .resolve_dial_candidates(peer_id, addrs, saved_addrs)
1966 .await;
1967 if candidates.is_empty() {
1968 return Err(P2PError::Network(NetworkError::PeerNotFound(
1969 peer_id.to_hex().into(),
1970 )));
1971 }
1972 self.adaptive_dht
1973 .ensure_peer_channel(peer_id, &candidates)
1974 .await
1975 }
1976
1977 async fn reconnect_and_send(
1979 &self,
1980 peer_id: &PeerId,
1981 protocol: &str,
1982 data: Bytes,
1983 addrs: &[MultiAddr],
1984 saved_addrs: &[MultiAddr],
1985 stale_channels: &[String],
1986 ) -> Result<()> {
1987 self.ensure_channel(peer_id, addrs, saved_addrs, stale_channels)
1988 .await?;
1989 self.transport.send_message(peer_id, protocol, data).await
1991 }
1992
1993 async fn resolve_dial_candidates(
2004 &self,
2005 peer_id: &PeerId,
2006 caller_addrs: &[MultiAddr],
2007 saved_addrs: &[MultiAddr],
2008 ) -> Vec<(MultiAddr, AddressType)> {
2009 if caller_addrs.is_empty()
2010 && let Some(published) = self
2011 .dht_manager()
2012 .published_peer_addresses_for_dial_typed(peer_id)
2013 .await
2014 {
2015 return published;
2016 }
2017 let dht_candidates = self
2018 .adaptive_dht
2019 .peer_addresses_for_dial_typed(peer_id)
2020 .await;
2021 let preferred = if !caller_addrs.is_empty() {
2022 caller_addrs
2023 } else if !saved_addrs.is_empty() {
2024 saved_addrs
2025 } else {
2026 return dht_candidates;
2027 };
2028
2029 preferred
2030 .iter()
2031 .filter(|a| {
2032 let dialable = a
2033 .dialable_socket_addr()
2034 .is_some_and(|sa| !sa.ip().is_unspecified());
2035 if !dialable {
2036 trace!(address = %a, "skipping non-dialable address");
2037 }
2038 dialable
2039 })
2040 .map(|addr| {
2041 let kind = dht_candidates
2042 .iter()
2043 .find_map(|(candidate, kind)| (candidate == addr).then_some(*kind))
2044 .unwrap_or(AddressType::Unverified);
2045 (addr.clone(), kind)
2046 })
2047 .collect()
2048 }
2049
2050 fn reconnect_lock_for(&self, peer_id: &PeerId) -> Arc<TokioMutex<()>> {
2052 self.reconnect_locks
2053 .lock()
2054 .entry(*peer_id)
2055 .or_insert_with(|| Arc::new(TokioMutex::new(())))
2056 .clone()
2057 }
2058}
2059
2060fn protocol_error(msg: impl std::fmt::Display) -> P2PError {
2062 P2PError::Network(NetworkError::ProtocolError(msg.to_string().into()))
2063}
2064
2065pub(crate) fn broadcast_event(tx: &broadcast::Sender<P2PEvent>, event: P2PEvent) {
2067 if let Err(e) = tx.send(event) {
2068 tracing::trace!("Event broadcast has no receivers: {e}");
2069 }
2070}
2071
2072pub(crate) struct ParsedMessage {
2078 pub(crate) event: P2PEvent,
2080 pub(crate) authenticated_node_id: Option<PeerId>,
2082 pub(crate) user_agent: String,
2084 pub(crate) payload_len: usize,
2087}
2088
2089pub(crate) fn parse_protocol_message(bytes: &[u8], source: &str) -> Option<ParsedMessage> {
2099 let message: WireMessage = postcard::from_bytes(bytes).ok()?;
2100 let transport_source = source.parse::<SocketAddr>().ok().map(MultiAddr::quic);
2101
2102 let authenticated_node_id = if !message.signature.is_empty() {
2104 match verify_message_signature(&message) {
2105 Ok(peer_id) => {
2106 debug!(
2107 "Message from {} authenticated as app-level NodeId {}",
2108 source, peer_id
2109 );
2110 Some(peer_id)
2111 }
2112 Err(e) => {
2113 warn!("Rejecting message: signature verification failed: {}", e);
2116 return None;
2117 }
2118 }
2119 } else {
2120 None
2121 };
2122
2123 debug!(
2124 "Parsed P2PEvent::Message - topic: {}, source: {:?} (transport: {}, logical: {}), payload_len: {}",
2125 message.protocol,
2126 authenticated_node_id,
2127 source,
2128 message.from,
2129 message.data.len()
2130 );
2131
2132 let payload_len = message.data.len();
2133 Some(ParsedMessage {
2134 event: P2PEvent::Message {
2135 topic: message.protocol,
2136 source: authenticated_node_id,
2137 transport_source,
2138 timestamp: message.timestamp,
2139 data: message.data,
2140 },
2141 authenticated_node_id,
2142 payload_len,
2143 user_agent: message.user_agent,
2144 })
2145}
2146
2147fn verify_message_signature(message: &WireMessage) -> std::result::Result<PeerId, String> {
2154 let pubkey = MlDsaPublicKey::from_bytes(&message.public_key)
2155 .map_err(|e| format!("invalid public key: {e:?}"))?;
2156
2157 let peer_id = peer_id_from_public_key(&pubkey);
2158
2159 if message.from != peer_id {
2161 return Err(format!(
2162 "from field mismatch: message claims '{}' but public key derives '{}'",
2163 message.from, peer_id
2164 ));
2165 }
2166
2167 let signable = postcard::to_stdvec(&(
2168 &message.protocol,
2169 &message.data as &[u8],
2170 &message.from,
2171 message.timestamp,
2172 &message.user_agent,
2173 ))
2174 .map_err(|e| format!("failed to serialize signable bytes: {e}"))?;
2175
2176 let sig = MlDsaSignature::from_bytes(&message.signature)
2177 .map_err(|e| format!("invalid signature: {e:?}"))?;
2178
2179 let valid = crate::quantum_crypto::ml_dsa_verify(&pubkey, &signable, &sig)
2180 .map_err(|e| format!("verification error: {e}"))?;
2181
2182 if valid {
2183 Ok(peer_id)
2184 } else {
2185 Err("signature is invalid".to_string())
2186 }
2187}
2188
2189impl P2PNode {
2190 pub fn subscribe_events(&self) -> broadcast::Receiver<P2PEvent> {
2192 self.transport.subscribe_events()
2193 }
2194
2195 pub fn events(&self) -> broadcast::Receiver<P2PEvent> {
2197 self.subscribe_events()
2198 }
2199
2200 pub fn uptime(&self) -> Duration {
2202 self.start_time.elapsed()
2203 }
2204
2205 pub async fn health_check(&self) -> Result<()> {
2218 let peer_count = self.peer_count().await;
2219 if peer_count > self.config.max_connections {
2220 Err(protocol_error(format!(
2221 "Too many connections: {peer_count}"
2222 )))
2223 } else {
2224 Ok(())
2225 }
2226 }
2227
2228 pub fn dht_manager(&self) -> &Arc<DhtNetworkManager> {
2230 self.adaptive_dht.dht_manager()
2231 }
2232
2233 pub fn dht(&self) -> &Arc<DhtNetworkManager> {
2235 self.dht_manager()
2236 }
2237
2238 async fn connect_bootstrap_peers(
2245 &self,
2246 close_group_cache: Option<&CloseGroupCache>,
2247 ) -> Result<()> {
2248 let mut serial_addr_sets: Vec<(PeerId, Vec<MultiAddr>)> = Vec::new();
2253 let mut parallel_addr_sets: Vec<Vec<MultiAddr>> = Vec::new();
2254 let mut seen_addresses = std::collections::HashSet::new();
2255
2256 if let Some(cache) = close_group_cache {
2262 let mut sorted_peers: Vec<&CachedCloseGroupPeer> = cache.peers.iter().collect();
2263 sorted_peers.sort_by(|a, b| {
2264 let score_ord = match b.trust.score.partial_cmp(&a.trust.score) {
2268 Some(ord) => ord,
2269 None => {
2270 if a.trust.score.is_nan() {
2271 std::cmp::Ordering::Greater } else {
2273 std::cmp::Ordering::Less }
2275 }
2276 };
2277 score_ord.then_with(|| {
2278 let da = self.peer_id.xor_distance(&a.peer_id);
2279 let db = self.peer_id.xor_distance(&b.peer_id);
2280 da.cmp(&db)
2281 })
2282 });
2283
2284 let mut added_from_close_group = 0usize;
2285 for peer in &sorted_peers {
2286 let new_addresses: Vec<MultiAddr> = peer
2287 .addresses
2288 .iter()
2289 .filter(|a| {
2290 a.dialable_socket_addr()
2291 .is_some_and(|sa| !seen_addresses.contains(&sa))
2292 })
2293 .cloned()
2294 .collect();
2295
2296 if !new_addresses.is_empty() {
2297 for addr in &new_addresses {
2298 if let Some(sa) = addr.socket_addr() {
2299 seen_addresses.insert(sa);
2300 }
2301 }
2302 serial_addr_sets.push((peer.peer_id, new_addresses));
2303 added_from_close_group += 1;
2304 }
2305 }
2306 if added_from_close_group > 0 {
2307 info!(
2308 "Added {} close group cache peers (highest trust first)",
2309 added_from_close_group
2310 );
2311 }
2312 }
2313
2314 if !self.config.bootstrap_peers.is_empty() {
2316 info!(
2317 "Using {} configured bootstrap peers (priority)",
2318 self.config.bootstrap_peers.len()
2319 );
2320 for multiaddr in &self.config.bootstrap_peers {
2321 let Some(socket_addr) = multiaddr.dialable_socket_addr() else {
2322 warn!(
2323 "Skipping non-QUIC bootstrap peer: {}",
2324 multiaddr.transport()
2325 );
2326 continue;
2327 };
2328 seen_addresses.insert(socket_addr);
2329 parallel_addr_sets.push(vec![multiaddr.clone()]);
2330 }
2331 }
2332
2333 let snapshot_addr_sets = if self.config.mode == NodeMode::Client
2343 || self.routing_snapshot_restored.swap(true, Ordering::Relaxed)
2344 {
2345 None
2346 } else {
2347 self.routing_snapshot_dial_sets(&mut seen_addresses).await
2348 };
2349
2350 if serial_addr_sets.is_empty()
2351 && parallel_addr_sets.is_empty()
2352 && snapshot_addr_sets.is_none()
2353 {
2354 info!("No bootstrap peers configured");
2355 return Ok(());
2356 }
2357
2358 let identity_timeout = Duration::from_secs(BOOTSTRAP_IDENTITY_TIMEOUT_SECS);
2361 let mut successful_connections = 0;
2362 let cache_dial_candidates = serial_addr_sets.len();
2363 let configured_dial_candidates = parallel_addr_sets.len();
2364 let mut cache_dial_successes = 0usize;
2365 let mut configured_dial_successes = 0usize;
2366 let mut connected_peer_ids: Vec<PeerId> = Vec::new();
2367
2368 let client_mode = matches!(self.config.mode, NodeMode::Client);
2370 for (expected_peer_id, addrs) in &serial_addr_sets {
2371 if let Some(peer_id) = self
2372 .dial_bootstrap_addr_set(addrs, identity_timeout, "cache", Some(*expected_peer_id))
2373 .await
2374 {
2375 successful_connections += 1;
2376 cache_dial_successes += 1;
2377 connected_peer_ids.push(peer_id);
2378 if client_mode && successful_connections >= CLIENT_BOOTSTRAP_TARGET {
2379 debug!(
2380 "Client bootstrap target reached ({successful_connections} peers) — skipping remaining serial dials"
2381 );
2382 break;
2383 }
2384 }
2385 }
2386
2387 if !client_mode || successful_connections < CLIENT_BOOTSTRAP_TARGET {
2392 let mut parallel_stream =
2393 futures::stream::iter(parallel_addr_sets.into_iter().map(|addrs| async move {
2394 self.dial_bootstrap_addr_set(&addrs, identity_timeout, "configured", None)
2395 .await
2396 }))
2397 .buffer_unordered(MAX_CONCURRENT_BOOTSTRAP_DIALS);
2398 while let Some(result) = parallel_stream.next().await {
2399 if let Some(peer_id) = result {
2400 successful_connections += 1;
2401 configured_dial_successes += 1;
2402 connected_peer_ids.push(peer_id);
2403 if client_mode && successful_connections >= CLIENT_BOOTSTRAP_TARGET {
2404 debug!(
2405 "Client bootstrap target reached ({successful_connections} peers) — cancelling pending dials"
2406 );
2407 break;
2408 }
2409 }
2410 }
2411 }
2415
2416 let mut snapshot_dial_candidates = 0usize;
2440 let mut snapshot_dial_successes = 0usize;
2441 if let Some(snapshot_sets) = snapshot_addr_sets {
2442 snapshot_dial_candidates = snapshot_sets.len();
2443 let deadline = tokio::time::Instant::now() + SNAPSHOT_RESTORE_BUDGET;
2444 let mut snapshot_stream = futures::stream::iter(snapshot_sets.into_iter().map(
2445 |(expected_peer_id, addrs)| async move {
2446 if tokio::time::Instant::now() >= deadline {
2447 return None;
2448 }
2449 self.dial_bootstrap_addr_set(
2450 &addrs,
2451 identity_timeout,
2452 "routing_snapshot",
2453 Some(expected_peer_id),
2454 )
2455 .await
2456 },
2457 ))
2458 .buffer_unordered(MAX_CONCURRENT_SNAPSHOT_DIALS);
2459
2460 while let Some(outcome) = snapshot_stream.next().await {
2461 if outcome.is_some() {
2462 snapshot_dial_successes += 1;
2463 successful_connections += 1;
2464 }
2465 }
2466
2467 if snapshot_dial_successes < snapshot_dial_candidates {
2468 debug!(
2469 snapshot_dial_successes,
2470 snapshot_dial_candidates,
2471 "Some routing-snapshot peers were not restored; the rest is left to \
2472 ordinary discovery"
2473 );
2474 }
2475 }
2476
2477 info!(
2478 cache_dial_candidates,
2479 cache_dial_successes,
2480 configured_dial_candidates,
2481 configured_dial_successes,
2482 snapshot_dial_candidates,
2483 snapshot_dial_successes,
2484 outbound_bootstrap_successes = successful_connections,
2485 outbound_reachable = successful_connections > 0,
2486 "Bootstrap reachability summary"
2487 );
2488
2489 if successful_connections == 0 {
2490 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
2494 let transport_peers = self.transport.connected_peers().await;
2495 if !transport_peers.is_empty() {
2496 info!(
2497 "No outbound bootstrap succeeded, but {} inbound peer(s) connected — proceeding with DHT bootstrap",
2498 transport_peers.len()
2499 );
2500 connected_peer_ids = transport_peers;
2501 successful_connections = connected_peer_ids.len();
2502 } else {
2503 warn!("Failed to connect to any bootstrap peers");
2504 return Ok(());
2507 }
2508 }
2509
2510 info!(
2511 "Successfully connected to {} bootstrap peers",
2512 successful_connections
2513 );
2514
2515 match self
2517 .dht_manager()
2518 .bootstrap_from_peers(&connected_peer_ids)
2519 .await
2520 {
2521 Ok(count) => info!("DHT peer discovery found {} peers", count),
2522 Err(e) => warn!("DHT peer discovery failed: {}", e),
2523 }
2524
2525 if matches!(self.config.mode, NodeMode::Node) {
2535 const SELF_LOOKUP_ROUNDS: u8 = 2;
2536 for i in 1..=SELF_LOOKUP_ROUNDS {
2537 if let Err(e) = self.dht_manager().trigger_self_lookup().await {
2538 warn!("Post-bootstrap self-lookup {i}/{SELF_LOOKUP_ROUNDS} failed: {e}");
2539 } else {
2540 debug!("Post-bootstrap self-lookup {i}/{SELF_LOOKUP_ROUNDS} completed");
2541 }
2542 }
2543 } else {
2544 debug!("Skipping post-bootstrap self-lookups (client mode)");
2545 }
2546
2547 self.is_bootstrapped.store(true, Ordering::SeqCst);
2550 info!(
2551 "Bootstrap complete: connected to {} peers, initiated {} discovery requests",
2552 successful_connections,
2553 connected_peer_ids.len()
2554 );
2555
2556 if let Some(ref dir) = self.config.close_group_cache_dir
2559 && let Err(e) = self.save_close_group_cache(dir, "post_bootstrap").await
2560 {
2561 warn!("Failed to save close group cache after bootstrap: {e}");
2562 }
2563
2564 Ok(())
2565 }
2566
2567 async fn dial_bootstrap_addr_set(
2572 &self,
2573 addrs: &[MultiAddr],
2574 identity_timeout: Duration,
2575 source: &'static str,
2576 expected_peer_id: Option<PeerId>,
2577 ) -> Option<PeerId> {
2578 for addr in addrs {
2579 let log_addr = addr.transport();
2582 match self
2587 .transport
2588 .connect_peer_typed(addr, AddressType::Unverified)
2589 .await
2590 {
2591 Ok(channel_id) => match self
2592 .transport
2593 .wait_for_peer_identity(&channel_id, identity_timeout)
2594 .await
2595 {
2596 Ok(real_peer_id) => {
2597 if !bootstrap_peer_identity_matches(expected_peer_id, real_peer_id) {
2598 warn!(
2599 bootstrap_source = source,
2600 address = %log_addr,
2601 "Bootstrap cache identity mismatch; rejecting connection"
2602 );
2603 self.disconnect_channel(&channel_id).await;
2604 continue;
2605 }
2606 info!(
2607 bootstrap_source = source,
2608 outcome = "ok",
2609 address = %log_addr,
2610 "Bootstrap dial completed"
2611 );
2612 return Some(real_peer_id);
2613 }
2614 Err(e) => {
2615 info!(
2616 bootstrap_source = source,
2617 outcome = "identity_timeout",
2618 address = %log_addr,
2619 error = %e,
2620 "Bootstrap dial failed"
2621 );
2622 warn!(
2623 "Timeout waiting for identity from bootstrap peer {}: {}, \
2624 closing channel {}",
2625 log_addr, e, channel_id
2626 );
2627 self.disconnect_channel(&channel_id).await;
2628 }
2629 },
2630 Err(e) => {
2631 info!(
2632 bootstrap_source = source,
2633 outcome = "connect_error",
2634 address = %log_addr,
2635 error = %e,
2636 "Bootstrap dial failed"
2637 );
2638 warn!("Failed to connect to bootstrap peer {}: {}", log_addr, e);
2639 }
2640 }
2641 }
2642 None
2643 }
2644
2645 fn resolve_snapshot_floor(&self, restored_peers: usize) {
2651 let now_epoch = SystemTime::now()
2652 .duration_since(UNIX_EPOCH)
2653 .map_or(0, |duration| duration.as_secs());
2654 self.routing_snapshot_floor_at
2658 .store(now_epoch, Ordering::Relaxed);
2659 self.routing_snapshot_floor
2660 .store(restored_peers, Ordering::Release);
2661 }
2662
2663 async fn routing_snapshot_dial_sets(
2670 &self,
2671 seen_addresses: &mut HashSet<SocketAddr>,
2672 ) -> Option<Vec<(PeerId, Vec<MultiAddr>)>> {
2673 let dir = self.config.close_group_cache_dir.as_ref()?;
2674
2675 let snapshot = match RoutingSnapshot::load_from_dir(dir).await {
2676 Ok(Some(snapshot)) => snapshot,
2677 Ok(None) => {
2678 self.resolve_snapshot_floor(0);
2679 return None;
2680 }
2681 Err(rejection) => {
2682 warn!(%rejection, "Discarding routing snapshot");
2683 self.resolve_snapshot_floor(0);
2684 return None;
2685 }
2686 };
2687
2688 let now_epoch = SystemTime::now()
2689 .duration_since(UNIX_EPOCH)
2690 .map_or(0, |duration| duration.as_secs());
2691 let peers = match snapshot.peers_for(&self.peer_id, now_epoch) {
2692 Ok(peers) => peers,
2693 Err(rejection) => {
2694 warn!(%rejection, "Discarding routing snapshot");
2695 self.resolve_snapshot_floor(0);
2696 return None;
2697 }
2698 };
2699
2700 self.resolve_snapshot_floor(peers.len());
2703
2704 let sets = snapshot_dial_sets(peers, &self.peer_id, seen_addresses);
2705 if sets.is_empty() {
2706 return None;
2707 }
2708
2709 info!(
2710 snapshot_peers = peers.len(),
2711 new_candidates = sets.len(),
2712 age_secs = now_epoch.saturating_sub(snapshot.saved_at_epoch_secs),
2713 "Restoring routing table from snapshot"
2714 );
2715 Some(sets)
2716 }
2717
2718 async fn save_close_group_cache(
2720 &self,
2721 dir: &Path,
2722 save_reason: &'static str,
2723 ) -> anyhow::Result<()> {
2724 save_close_group_cache_snapshot(
2725 self.dht_manager(),
2726 self.adaptive_dht.trust_engine(),
2727 self.peer_id,
2728 self.config.dht_config.k_value,
2729 dir,
2730 save_reason,
2731 )
2732 .await
2733 }
2734
2735 }
2737
2738fn snapshot_dial_sets(
2745 peers: &[SnapshotPeer],
2746 self_id: &PeerId,
2747 seen_addresses: &mut HashSet<SocketAddr>,
2748) -> Vec<(PeerId, Vec<MultiAddr>)> {
2749 let mut sets: Vec<(PeerId, Vec<MultiAddr>)> = Vec::new();
2750 let mut seen_peers: HashSet<PeerId> = HashSet::new();
2751 for peer in peers {
2752 if peer.peer_id == *self_id || !seen_peers.insert(peer.peer_id) {
2753 continue;
2754 }
2755 let new_addresses: Vec<MultiAddr> = peer
2756 .addresses
2757 .iter()
2758 .filter(|addr| {
2759 addr.dialable_socket_addr()
2760 .is_some_and(|socket| !seen_addresses.contains(&socket))
2761 })
2762 .take(MAX_SNAPSHOT_ADDRESSES_DIALLED)
2763 .cloned()
2764 .collect();
2765 if new_addresses.is_empty() {
2766 continue;
2767 }
2768 seen_addresses.extend(
2769 new_addresses
2770 .iter()
2771 .filter_map(MultiAddr::dialable_socket_addr),
2772 );
2773 sets.push((peer.peer_id, new_addresses));
2774 }
2775 sets
2776}
2777
2778async fn save_close_group_cache_snapshot(
2783 dht_manager: &DhtNetworkManager,
2784 trust_engine: &TrustEngine,
2785 peer_id: PeerId,
2786 k_value: usize,
2787 dir: &Path,
2788 save_reason: &'static str,
2789) -> anyhow::Result<()> {
2790 let key: crate::dht::Key = *peer_id.as_bytes();
2791 let close_group = dht_manager.find_closest_nodes_local(&key, k_value).await;
2792
2793 let now_epoch = SystemTime::now()
2794 .duration_since(UNIX_EPOCH)
2795 .map_or(0, |duration| duration.as_secs());
2796 let peers: Vec<CachedCloseGroupPeer> = close_group
2797 .into_iter()
2798 .filter_map(|dht_node| {
2799 let score = trust_engine.score(&dht_node.peer_id);
2800 if !score.is_finite() {
2803 return None;
2804 }
2805 Some(CachedCloseGroupPeer {
2806 peer_id: dht_node.peer_id,
2807 addresses: dht_node.addresses,
2808 trust: TrustRecord {
2809 score,
2810 last_updated_epoch_secs: now_epoch,
2811 },
2812 })
2813 })
2814 .collect();
2815
2816 let peer_count = peers.len();
2817 let cache = CloseGroupCache {
2818 peers,
2819 saved_at_epoch_secs: now_epoch,
2820 };
2821
2822 cache.save_to_dir(dir).await?;
2823 info!(
2824 save_reason,
2825 "Saved {} close group peers to cache in {}",
2826 peer_count,
2827 dir.display()
2828 );
2829
2830 Ok(())
2831}
2832
2833async fn save_routing_snapshot(
2846 dht_manager: &DhtNetworkManager,
2847 peer_id: PeerId,
2848 now_epoch: u64,
2849 dir: &Path,
2850 floor: &AtomicUsize,
2851 floor_at: &AtomicU64,
2852 save_reason: &'static str,
2853) {
2854 let floor_peers = floor.load(Ordering::Acquire);
2855 if floor_peers == SNAPSHOT_FLOOR_UNRESOLVED {
2856 debug!(
2857 save_reason,
2858 "Skipping routing snapshot save: the restore step has not run"
2859 );
2860 return;
2861 }
2862
2863 let peers: Vec<SnapshotPeer> = dht_manager
2864 .routing_table_peers()
2865 .await
2866 .into_iter()
2867 .map(|node| SnapshotPeer {
2868 peer_id: node.peer_id,
2869 addresses: node.addresses,
2870 })
2871 .collect();
2872 let peer_count = peers.len();
2873
2874 if peer_count == 0 {
2875 debug!(
2876 save_reason,
2877 "Skipping routing snapshot save: the table is empty"
2878 );
2879 return;
2880 }
2881 let floor_applies = now_epoch.saturating_sub(floor_at.load(Ordering::Relaxed))
2882 < SNAPSHOT_FLOOR_ENFORCED_FOR.as_secs();
2883 if floor_applies && peer_count < floor_peers {
2884 debug!(
2885 save_reason,
2886 peer_count,
2887 floor_peers,
2888 "Skipping routing snapshot save: it would shrink the snapshot while the table is \
2889 still restoring"
2890 );
2891 return;
2892 }
2893
2894 let snapshot = RoutingSnapshot::new(peer_id, now_epoch, peers);
2895 match snapshot.save_to_dir(dir).await {
2896 Ok(()) => debug!(save_reason, peer_count, "Saved routing snapshot"),
2897 Err(error) => warn!(save_reason, %error, "Failed to save routing snapshot"),
2898 }
2899}
2900
2901async fn periodic_close_group_cache_save(
2903 dht_manager: Arc<DhtNetworkManager>,
2904 trust_engine: Arc<TrustEngine>,
2905 peer_id: PeerId,
2906 k_value: usize,
2907 dir: PathBuf,
2908 interval: Duration,
2909 snapshot_floor: Arc<AtomicUsize>,
2910 snapshot_floor_at: Arc<AtomicU64>,
2911 shutdown: CancellationToken,
2912) {
2913 let start = tokio::time::Instant::now() + interval;
2914 let mut ticker = tokio::time::interval_at(start, interval);
2915 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2916
2917 loop {
2918 tokio::select! {
2919 biased;
2920 () = shutdown.cancelled() => break,
2921 _ = ticker.tick() => {
2922 if let Err(error) = save_close_group_cache_snapshot(
2923 &dht_manager,
2924 &trust_engine,
2925 peer_id,
2926 k_value,
2927 &dir,
2928 "periodic",
2929 ).await {
2930 warn!("Periodic close group cache save failed: {error}");
2931 }
2932 let now_epoch = SystemTime::now()
2933 .duration_since(UNIX_EPOCH)
2934 .map_or(0, |duration| duration.as_secs());
2935 save_routing_snapshot(
2936 &dht_manager,
2937 peer_id,
2938 now_epoch,
2939 &dir,
2940 &snapshot_floor,
2941 &snapshot_floor_at,
2942 "periodic",
2943 ).await;
2944 }
2945 }
2946 }
2947}
2948
2949#[async_trait::async_trait]
2951#[allow(dead_code)]
2952pub trait NetworkSender: Send + Sync {
2953 async fn send_message(&self, peer_id: &PeerId, protocol: &str, data: Vec<u8>) -> Result<()>;
2955
2956 fn local_peer_id(&self) -> PeerId;
2958}
2959
2960pub(crate) fn register_new_channel(
2970 peers: &DashMap<String, PeerInfo>,
2971 channel_id: &str,
2972 remote_addr: &MultiAddr,
2973) {
2974 let peer_info = PeerInfo {
2975 channel_id: channel_id.to_owned(),
2976 addresses: vec![remote_addr.clone()],
2977 connected_at: tokio::time::Instant::now(),
2978 last_seen: tokio::time::Instant::now(),
2979 status: ConnectionStatus::Connected,
2980 protocols: vec!["p2p-core/1.0.0".to_string()],
2981 heartbeat_count: 0,
2982 };
2983 peers.insert(channel_id.to_owned(), peer_info);
2984}
2985
2986#[cfg(test)]
2987mod tests {
2988 use super::*;
2989 use std::time::Duration;
2991 use tokio::time::timeout;
2992
2993 const TEST_MAX_MESSAGE_SIZE: usize = 2 * 1024 * 1024;
2995 const UNCONNECTED_CHANNEL_ID: &str = "127.0.0.1:9";
2997
2998 #[test]
3001 fn wire_message_ref_serializes_like_wire_message() {
3002 let owned = WireMessage {
3003 protocol: "/ant/replication/1".to_string(),
3004 data: (0..=255u8).cycle().take(70_000).collect(),
3005 from: PeerId::from_bytes([7; 32]),
3006 timestamp: 1_790_000_000,
3007 user_agent: "node/0.27.4".to_string(),
3008 public_key: vec![1; 1952],
3009 signature: vec![2; 3309],
3010 };
3011 let borrowed = WireMessageRef {
3012 protocol: &owned.protocol,
3013 data: &owned.data,
3014 from: &owned.from,
3015 timestamp: owned.timestamp,
3016 user_agent: &owned.user_agent,
3017 public_key: &owned.public_key,
3018 signature: &owned.signature,
3019 };
3020
3021 let expected = postcard::to_stdvec(&owned).unwrap();
3022 let actual = postcard::to_stdvec(&borrowed).unwrap();
3023 assert_eq!(actual, expected);
3024 assert_eq!(
3025 postcard::experimental::serialized_size(&borrowed).unwrap(),
3026 actual.len()
3027 );
3028
3029 let decoded: WireMessage = postcard::from_bytes(&actual).unwrap();
3030 assert_eq!(decoded.protocol, owned.protocol);
3031 assert_eq!(decoded.data, owned.data);
3032 assert_eq!(decoded.from, owned.from);
3033 assert_eq!(decoded.timestamp, owned.timestamp);
3034 assert_eq!(decoded.user_agent, owned.user_agent);
3035 assert_eq!(decoded.public_key, owned.public_key);
3036 assert_eq!(decoded.signature, owned.signature);
3037 }
3038
3039 #[test]
3040 fn cached_bootstrap_identity_must_match_handshake_peer() {
3041 let expected = PeerId::from_bytes([1; 32]);
3042 let other = PeerId::from_bytes([2; 32]);
3043
3044 assert!(bootstrap_peer_identity_matches(Some(expected), expected));
3045 assert!(!bootstrap_peer_identity_matches(Some(expected), other));
3046 assert!(bootstrap_peer_identity_matches(None, other));
3047 }
3048
3049 #[test]
3050 fn user_agents_identify_software_without_capability_tokens() {
3051 assert_eq!(
3052 user_agent_for_mode(NodeMode::Node),
3053 format!("node/{}", env!("CARGO_PKG_VERSION"))
3054 );
3055 assert_eq!(
3056 user_agent_for_mode(NodeMode::Client),
3057 format!("client/{}", env!("CARGO_PKG_VERSION"))
3058 );
3059 let mut config = NodeConfig::default();
3060 for custom in ["my-app/1.0", "", "node/custom;custom-feature"] {
3061 config.custom_user_agent = Some(custom.to_owned());
3062 assert_eq!(config.user_agent(), custom);
3063 }
3064 }
3065
3066 fn create_test_node_config() -> NodeConfig {
3072 NodeConfig {
3073 local: true,
3074 port: 0,
3075 ipv6: true,
3076 bootstrap_peers: vec![],
3077 connection_timeout: Duration::from_secs(2),
3078 max_connections: 100,
3079 dht_config: DHTConfig::default(),
3080 diversity_config: None,
3081 max_message_size: None,
3082 node_identity: None,
3083 mode: NodeMode::default(),
3084 custom_user_agent: None,
3085 allow_loopback: true,
3086 adaptive_dht_config: AdaptiveDhtConfig::default(),
3087 close_group_cache_dir: None,
3088 close_group_cache_max_age: default_close_group_cache_max_age(),
3089 }
3090 }
3091
3092 fn quic_addr(s: &str) -> MultiAddr {
3105 MultiAddr::quic(s.parse().expect("valid socket addr"))
3106 }
3107
3108 #[test]
3109 fn test_peer_route_kind_display_labels() {
3110 assert_eq!(PeerRouteKind::Relay.to_string(), "relay");
3111 assert_eq!(PeerRouteKind::Direct.to_string(), "direct");
3112 assert_eq!(PeerRouteKind::Lan.to_string(), "lan");
3113 assert_eq!(PeerRouteKind::Unverified.to_string(), "unverified");
3114 assert_eq!(PeerRouteKind::Unknown.to_string(), "unknown");
3115 assert_eq!(PeerRouteKind::Relay.as_str(), "relay");
3117 }
3118
3119 #[test]
3120 fn test_classify_none_source_is_unknown() {
3121 let typed = vec![(quic_addr("203.0.113.7:9000"), AddressType::Relay)];
3122 assert_eq!(
3123 classify_peer_route_from_typed(None, &typed),
3124 PeerRouteKind::Unknown
3125 );
3126 }
3127
3128 #[test]
3129 fn test_classify_non_ip_source_is_unknown() {
3130 let ble = MultiAddr::new(crate::address::TransportAddr::Ble {
3132 mac: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
3133 psm: 128,
3134 });
3135 let typed = vec![(quic_addr("203.0.113.7:9000"), AddressType::Relay)];
3136 assert_eq!(
3137 classify_peer_route_from_typed(Some(&ble), &typed),
3138 PeerRouteKind::Unknown
3139 );
3140 }
3141
3142 #[test]
3143 fn test_classify_each_address_type() {
3144 let src = quic_addr("203.0.113.7:9000");
3145 for (ty, kind) in [
3146 (AddressType::Relay, PeerRouteKind::Relay),
3147 (AddressType::Direct, PeerRouteKind::Direct),
3148 (AddressType::Unverified, PeerRouteKind::Unverified),
3149 (AddressType::Lan, PeerRouteKind::Lan),
3150 ] {
3151 let typed = vec![(src.clone(), ty)];
3152 assert_eq!(
3153 classify_peer_route_from_typed(Some(&src), &typed),
3154 kind,
3155 "mismatch for {ty:?}"
3156 );
3157 }
3158 }
3159
3160 #[test]
3161 fn test_classify_no_match_is_unknown() {
3162 let src = quic_addr("198.51.100.42:7000");
3163 let typed = vec![
3164 (quic_addr("203.0.113.7:9000"), AddressType::Relay),
3165 (quic_addr("203.0.113.8:9000"), AddressType::Direct),
3166 ];
3167 assert_eq!(
3168 classify_peer_route_from_typed(Some(&src), &typed),
3169 PeerRouteKind::Unknown
3170 );
3171 }
3172
3173 #[test]
3174 fn test_classify_empty_typed_is_unknown() {
3175 let src = quic_addr("203.0.113.7:9000");
3176 let typed: Vec<(MultiAddr, AddressType)> = vec![];
3177 assert_eq!(
3178 classify_peer_route_from_typed(Some(&src), &typed),
3179 PeerRouteKind::Unknown
3180 );
3181 }
3182
3183 #[test]
3184 fn test_classify_duplicate_agreeing_type_dedups() {
3185 let src = quic_addr("203.0.113.7:9000");
3188 let typed = vec![
3189 (quic_addr("203.0.113.7:9000"), AddressType::Relay),
3190 (quic_addr("203.0.113.7:9000"), AddressType::Relay),
3191 ];
3192 assert_eq!(
3193 classify_peer_route_from_typed(Some(&src), &typed),
3194 PeerRouteKind::Relay
3195 );
3196 }
3197
3198 #[test]
3199 fn test_classify_duplicate_conflicting_types_is_unknown() {
3200 let src = quic_addr("203.0.113.7:9000");
3206 let typed = vec![
3207 (quic_addr("203.0.113.7:9000"), AddressType::Relay),
3208 (quic_addr("203.0.113.7:9000"), AddressType::Direct),
3209 ];
3210 assert_eq!(
3211 classify_peer_route_from_typed(Some(&src), &typed),
3212 PeerRouteKind::Unknown
3213 );
3214 }
3215
3216 #[test]
3217 fn test_classify_matches_in_any_position() {
3218 let src = quic_addr("203.0.113.9:9000");
3220 let typed = vec![
3221 (quic_addr("203.0.113.7:9000"), AddressType::Relay),
3222 (quic_addr("203.0.113.8:9000"), AddressType::Direct),
3223 (quic_addr("203.0.113.9:9000"), AddressType::Lan),
3224 (quic_addr("203.0.113.10:9000"), AddressType::Unverified),
3225 ];
3226 assert_eq!(
3227 classify_peer_route_from_typed(Some(&src), &typed),
3228 PeerRouteKind::Lan
3229 );
3230 }
3231
3232 #[test]
3233 fn test_classify_tcp_source_matches_tcp_entry() {
3234 let src = MultiAddr::tcp("203.0.113.7:9000".parse().unwrap());
3237 let typed = vec![(
3238 MultiAddr::tcp("203.0.113.7:9000".parse().unwrap()),
3239 AddressType::Direct,
3240 )];
3241 assert_eq!(
3242 classify_peer_route_from_typed(Some(&src), &typed),
3243 PeerRouteKind::Direct
3244 );
3245 }
3246
3247 #[tokio::test]
3248 async fn reconnect_uses_latest_v1_and_v2_publications_over_saved_addresses() {
3249 use crate::dht_network_manager::{DhtMessageType, DhtNetworkMessage, DhtNetworkOperation};
3250 use crate::signed_address::SignedAddressRecord;
3251 use crate::{KnownReachability, TransportAddressRecord};
3252
3253 let config = || {
3254 NodeConfig::builder()
3255 .local(true)
3256 .port(0)
3257 .ipv6(false)
3258 .build()
3259 .unwrap()
3260 };
3261 let receiver = P2PNode::new(config()).await.unwrap();
3262 let publisher = P2PNode::new(config()).await.unwrap();
3263 receiver.transport.start_network_listeners().await.unwrap();
3267 receiver.dht_manager().start().await.unwrap();
3268 publisher.transport.start_network_listeners().await.unwrap();
3269 let old_address = publisher
3270 .listen_addrs()
3271 .await
3272 .into_iter()
3273 .find(MultiAddr::is_ipv4)
3274 .unwrap();
3275 let channel = receiver.connect_peer(&old_address).await.unwrap();
3276 receiver
3277 .wait_for_peer_identity(&channel, Duration::from_secs(2))
3278 .await
3279 .unwrap();
3280 let owner = *publisher.peer_id();
3281 timeout(Duration::from_secs(2), async {
3282 while !publisher.is_peer_connected(receiver.peer_id()).await
3283 || !receiver.dht_manager().is_in_routing_table(&owner).await
3284 {
3285 tokio::time::sleep(Duration::from_millis(10)).await;
3286 }
3287 })
3288 .await
3289 .unwrap();
3290 let saved = receiver
3291 .transport
3292 .peer_info(&owner)
3293 .await
3294 .unwrap()
3295 .addresses;
3296 let first: MultiAddr = "/ip4/9.9.9.9/udp/9001/quic".parse().unwrap();
3297 let second: MultiAddr = "/ip4/1.1.1.1/udp/9002/quic".parse().unwrap();
3298 let sequence = u64::MAX - 2;
3299 let operations = [
3300 (
3301 "/dht/1.0.0",
3302 first.clone(),
3303 DhtNetworkOperation::PublishAddressSet {
3304 seq: sequence,
3305 addresses: vec![(first.clone(), AddressType::Direct)],
3306 },
3307 ),
3308 (
3309 "/dht/address/2.0.0",
3310 second.clone(),
3311 DhtNetworkOperation::PublishAddressSetV2 {
3312 record: SignedAddressRecord::sign(
3313 publisher.transport.node_identity(),
3314 sequence - 1,
3315 vec![
3316 TransportAddressRecord::from_multiaddr(
3317 &second,
3318 KnownReachability::Direct,
3319 )
3320 .unwrap()
3321 .unwrap(),
3322 ],
3323 )
3324 .unwrap(),
3325 },
3326 ),
3327 (
3328 "/dht/1.0.0",
3329 second.clone(),
3330 DhtNetworkOperation::PublishAddressSet {
3331 seq: u64::MAX,
3332 addresses: vec![(first.clone(), AddressType::Direct)],
3333 },
3334 ),
3335 ];
3336 for (topic, expected, operation) in operations {
3337 let message = DhtNetworkMessage {
3338 message_id: "reconnect-publication".into(),
3339 source: owner,
3340 target: None,
3341 message_type: DhtMessageType::Request,
3342 payload: operation,
3343 result: None,
3344 timestamp: 1,
3345 ttl: 10,
3346 hop_count: 0,
3347 signed_records: Vec::new(),
3348 };
3349 publisher
3350 .transport
3351 .send_message(
3352 receiver.peer_id(),
3353 topic,
3354 postcard::to_stdvec(&message).unwrap(),
3355 )
3356 .await
3357 .unwrap();
3358 timeout(Duration::from_secs(2), async {
3359 loop {
3360 let candidates = receiver.resolve_dial_candidates(&owner, &[], &saved).await;
3361 if candidates == vec![(expected.clone(), AddressType::Direct)] {
3362 break;
3363 }
3364 tokio::time::sleep(Duration::from_millis(10)).await;
3365 }
3366 })
3367 .await
3368 .expect("reconnect must use the published endpoint without the old cached endpoint");
3369 }
3370 let explicit = receiver
3372 .resolve_dial_candidates(&owner, std::slice::from_ref(&old_address), &saved)
3373 .await;
3374 assert_eq!(explicit, vec![(old_address, AddressType::Unverified)]);
3375 receiver.stop().await.unwrap();
3376 publisher.stop().await.unwrap();
3377 }
3378
3379 #[tokio::test]
3380 async fn reconnect_uses_saved_addresses_without_an_owner_publication() {
3381 let node = P2PNode::new(create_test_node_config()).await.unwrap();
3382 let peer = PeerId::random();
3383 let saved: MultiAddr = "/ip4/9.9.9.9/udp/9001/quic".parse().unwrap();
3384 assert_eq!(
3385 node.dht_manager()
3386 .published_peer_addresses_for_dial_typed(&peer)
3387 .await,
3388 None
3389 );
3390 assert_eq!(
3391 node.resolve_dial_candidates(&peer, &[], std::slice::from_ref(&saved))
3392 .await,
3393 vec![(saved, AddressType::Unverified)]
3394 );
3395 }
3396
3397 #[tokio::test]
3398 async fn test_node_config_default() {
3399 let config = NodeConfig::default();
3400
3401 assert_eq!(config.listen_addrs().len(), 2); assert_eq!(config.max_connections, 10000);
3403 assert_eq!(config.connection_timeout, Duration::from_secs(25));
3404 assert_eq!(
3405 config.close_group_cache_max_age,
3406 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
3407 );
3408 }
3409
3410 #[test]
3411 fn close_group_cache_builder_default_and_explicit_disable() {
3412 let defaulted = NodeConfig::builder().build().unwrap();
3413 assert_eq!(
3414 defaulted.close_group_cache_max_age,
3415 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
3416 );
3417
3418 let disabled = NodeConfig::builder()
3419 .close_group_cache_max_age(None)
3420 .build()
3421 .unwrap();
3422 assert_eq!(disabled.close_group_cache_max_age, None);
3423 let disabled_json = serde_json::to_string(&disabled).unwrap();
3424 let disabled_roundtrip: NodeConfig = serde_json::from_str(&disabled_json).unwrap();
3425 assert_eq!(disabled_roundtrip.close_group_cache_max_age, None);
3426
3427 let custom = NodeConfig::builder()
3428 .close_group_cache_max_age(Some(Duration::from_secs(120)))
3429 .build()
3430 .unwrap();
3431 assert_eq!(
3432 custom.close_group_cache_max_age,
3433 Some(Duration::from_secs(120))
3434 );
3435 }
3436
3437 #[test]
3438 fn old_serialized_config_gets_default_cache_max_age() {
3439 let mut value = serde_json::to_value(NodeConfig::default()).unwrap();
3440 value
3441 .as_object_mut()
3442 .unwrap()
3443 .remove("close_group_cache_max_age");
3444
3445 let decoded: NodeConfig = serde_json::from_value(value).unwrap();
3446 assert_eq!(
3447 decoded.close_group_cache_max_age,
3448 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
3449 );
3450 }
3451
3452 #[tokio::test]
3453 async fn test_dht_config_default() {
3454 let config = DHTConfig::default();
3455
3456 assert_eq!(config.k_value, 20);
3457 assert_eq!(config.alpha_value, 3);
3458 assert_eq!(config.refresh_interval, Duration::from_secs(600));
3459 }
3460
3461 #[test]
3462 fn test_connection_status_variants() {
3463 let connecting = ConnectionStatus::Connecting;
3464 let connected = ConnectionStatus::Connected;
3465 let disconnecting = ConnectionStatus::Disconnecting;
3466 let disconnected = ConnectionStatus::Disconnected;
3467 let failed = ConnectionStatus::Failed("test error".to_string());
3468
3469 assert_eq!(connecting, ConnectionStatus::Connecting);
3470 assert_eq!(connected, ConnectionStatus::Connected);
3471 assert_eq!(disconnecting, ConnectionStatus::Disconnecting);
3472 assert_eq!(disconnected, ConnectionStatus::Disconnected);
3473 assert_ne!(connecting, connected);
3474
3475 if let ConnectionStatus::Failed(msg) = failed {
3476 assert_eq!(msg, "test error");
3477 } else {
3478 panic!("Expected Failed status");
3479 }
3480 }
3481
3482 #[tokio::test]
3483 async fn test_node_creation() -> Result<()> {
3484 let config = create_test_node_config();
3485 let node = P2PNode::new(config).await?;
3486
3487 assert_eq!(node.peer_id().to_hex().len(), 64);
3489 assert!(!node.is_running());
3490 assert_eq!(node.peer_count().await, 0);
3491 assert!(node.connected_peers().await.is_empty());
3492
3493 Ok(())
3494 }
3495
3496 #[tokio::test]
3497 async fn test_node_lifecycle() -> Result<()> {
3498 let config = create_test_node_config();
3499 let node = P2PNode::new(config).await?;
3500
3501 assert!(!node.is_running());
3503
3504 node.start().await?;
3506 assert!(node.is_running());
3507
3508 let listen_addrs = node.listen_addrs().await;
3510 assert!(
3511 !listen_addrs.is_empty(),
3512 "Expected at least one listening address"
3513 );
3514
3515 node.stop().await?;
3517 assert!(!node.is_running());
3518
3519 Ok(())
3520 }
3521
3522 #[tokio::test]
3523 async fn close_group_cache_task_is_joined_on_stop() -> Result<()> {
3524 let cache_dir = tempfile::tempdir().unwrap();
3525 let mut config = create_test_node_config();
3526 config.close_group_cache_dir = Some(cache_dir.path().to_path_buf());
3527 let node = P2PNode::new(config).await?;
3528
3529 node.start().await?;
3530 assert!(node.close_group_cache_save_handle.lock().await.is_some());
3531
3532 node.stop().await?;
3533 assert!(node.close_group_cache_save_handle.lock().await.is_none());
3534
3535 Ok(())
3536 }
3537
3538 #[tokio::test]
3539 async fn test_peer_connection() -> Result<()> {
3540 let config1 = create_test_node_config();
3541 let config2 = create_test_node_config();
3542
3543 let node1 = P2PNode::new(config1).await?;
3544 let node2 = P2PNode::new(config2).await?;
3545
3546 node1.start().await?;
3547 node2.start().await?;
3548
3549 let node2_addr = node2
3550 .listen_addrs()
3551 .await
3552 .into_iter()
3553 .find(|a| a.is_ipv4())
3554 .ok_or_else(|| {
3555 P2PError::Network(crate::error::NetworkError::InvalidAddress(
3556 "Node 2 did not expose an IPv4 listen address".into(),
3557 ))
3558 })?;
3559
3560 let channel_id = node1.connect_peer(&node2_addr).await?;
3563
3564 assert!(node1.is_connection_active(&channel_id).await);
3567
3568 let peer_info = node1.transport.peer_info_by_channel(&channel_id).await;
3570 assert!(peer_info.is_some());
3571 let info = peer_info.expect("Peer info should exist after connect");
3572 assert_eq!(info.channel_id, channel_id);
3573 assert_eq!(info.status, ConnectionStatus::Connected);
3574 assert!(info.protocols.contains(&"p2p-foundation/1.0".to_string()));
3575
3576 node1.remove_channel(&channel_id).await;
3578 assert!(!node1.is_connection_active(&channel_id).await);
3579
3580 node1.stop().await?;
3581 node2.stop().await?;
3582
3583 Ok(())
3584 }
3585
3586 #[tokio::test]
3591 async fn send_failure_keeps_channel_only_while_connection_is_open() -> Result<()> {
3592 let node1 = P2PNode::new(create_test_node_config()).await?;
3593 let node2 = P2PNode::new(create_test_node_config()).await?;
3594 node1.start().await?;
3595 node2.start().await?;
3596
3597 let node2_addr = node2
3598 .listen_addrs()
3599 .await
3600 .into_iter()
3601 .find(|a| a.is_ipv4())
3602 .ok_or_else(|| {
3603 P2PError::Network(crate::error::NetworkError::InvalidAddress(
3604 "Node 2 did not expose an IPv4 listen address".into(),
3605 ))
3606 })?;
3607 let channel_id = node1.connect_peer(&node2_addr).await?;
3608
3609 let stalled_write = P2PError::Transport(crate::error::TransportError::SendFailed {
3610 kind: crate::error::SendFailureKind::WriteProgressTimeout,
3611 reason: "stream write made no progress".into(),
3612 });
3613 assert!(
3614 !node1
3615 .transport
3616 .remove_channel_after_send_failure(&channel_id, &stalled_write)
3617 .await,
3618 "a stalled stream on an open connection must keep the channel"
3619 );
3620 assert!(
3621 node1
3622 .transport
3623 .peer_info_by_channel(&channel_id)
3624 .await
3625 .is_some()
3626 );
3627
3628 let stale = P2PError::Network(crate::error::NetworkError::PeerNotFound(
3629 channel_id.clone().into(),
3630 ));
3631 assert!(
3632 node1
3633 .transport
3634 .remove_channel_after_send_failure(&channel_id, &stale)
3635 .await,
3636 "a stale-channel failure must remove the channel"
3637 );
3638 assert!(
3639 node1
3640 .transport
3641 .peer_info_by_channel(&channel_id)
3642 .await
3643 .is_none()
3644 );
3645
3646 assert!(
3647 node1
3648 .transport
3649 .remove_channel_after_send_failure(UNCONNECTED_CHANNEL_ID, &stalled_write)
3650 .await,
3651 "a channel with no open connection must be removed whatever the failure"
3652 );
3653
3654 node1.stop().await?;
3655 node2.stop().await?;
3656 Ok(())
3657 }
3658
3659 #[tokio::test]
3660 async fn test_connect_peer_rejects_tcp_multiaddr() -> Result<()> {
3661 let config = create_test_node_config();
3662 let node = P2PNode::new(config).await?;
3663
3664 let tcp_addr: MultiAddr = "/ip4/127.0.0.1/tcp/1".parse().unwrap();
3665 let result = node.connect_peer(&tcp_addr).await;
3666
3667 assert!(
3668 matches!(
3669 result,
3670 Err(P2PError::Network(
3671 crate::error::NetworkError::InvalidAddress(_)
3672 ))
3673 ),
3674 "TCP multiaddrs should be rejected before a QUIC dial is attempted, got: {:?}",
3675 result
3676 );
3677
3678 Ok(())
3679 }
3680
3681 #[cfg_attr(target_os = "windows", ignore)]
3688 #[tokio::test]
3689 async fn test_event_subscription() -> Result<()> {
3690 let identity1 =
3694 Arc::new(NodeIdentity::generate().expect("should generate identity for test node1"));
3695 let identity2 =
3696 Arc::new(NodeIdentity::generate().expect("should generate identity for test node2"));
3697
3698 let mut config1 = create_test_node_config();
3699 config1.ipv6 = false;
3700 config1.node_identity = Some(identity1);
3701
3702 let node2_peer_id = *identity2.peer_id();
3703 let mut config2 = create_test_node_config();
3704 config2.ipv6 = false;
3705 config2.node_identity = Some(identity2);
3706
3707 let node1 = P2PNode::new(config1).await?;
3708 let node2 = P2PNode::new(config2).await?;
3709
3710 node1.start().await?;
3711 node2.start().await?;
3712
3713 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
3714
3715 let mut events = node2.subscribe_events();
3717
3718 let node2_addr = node2.local_addr().ok_or_else(|| {
3719 P2PError::Network(crate::error::NetworkError::ProtocolError(
3720 "No listening address".to_string().into(),
3721 ))
3722 })?;
3723
3724 let mut channel_id = None;
3726 for attempt in 0..3 {
3727 if attempt > 0 {
3728 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
3729 }
3730 match timeout(Duration::from_secs(2), node1.connect_peer(&node2_addr)).await {
3731 Ok(Ok(id)) => {
3732 channel_id = Some(id);
3733 break;
3734 }
3735 Ok(Err(_)) | Err(_) => continue,
3736 }
3737 }
3738 let channel_id = channel_id.expect("Failed to connect after 3 attempts");
3739
3740 let target_peer_id = node1
3742 .wait_for_peer_identity(&channel_id, Duration::from_secs(2))
3743 .await?;
3744 assert_eq!(target_peer_id, node2_peer_id);
3745
3746 node1
3748 .send_message(&target_peer_id, "test-topic", b"hello".to_vec(), &[])
3749 .await?;
3750
3751 let event = timeout(Duration::from_secs(2), async {
3753 loop {
3754 match events.recv().await {
3755 Ok(P2PEvent::PeerConnected(id, _)) => return Ok(id),
3756 Ok(P2PEvent::Message { .. }) => continue, Ok(_) => continue,
3758 Err(e) => return Err(e),
3759 }
3760 }
3761 })
3762 .await;
3763 assert!(event.is_ok(), "Should receive PeerConnected event");
3764 let connected_peer_id = event.expect("Timed out").expect("Channel error");
3765 assert!(
3767 connected_peer_id.0.iter().any(|&b| b != 0),
3768 "PeerConnected should carry a non-zero peer ID"
3769 );
3770
3771 node1.stop().await?;
3772 node2.stop().await?;
3773
3774 Ok(())
3775 }
3776
3777 #[cfg_attr(target_os = "windows", ignore)]
3779 #[tokio::test]
3780 async fn test_message_sending() -> Result<()> {
3781 let mut config1 = create_test_node_config();
3783 config1.ipv6 = false;
3784 let node1 = P2PNode::new(config1).await?;
3785 node1.start().await?;
3786
3787 let mut config2 = create_test_node_config();
3788 config2.ipv6 = false;
3789 let node2 = P2PNode::new(config2).await?;
3790 node2.start().await?;
3791
3792 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
3794
3795 let node2_addr = node2.local_addr().ok_or_else(|| {
3797 P2PError::Network(crate::error::NetworkError::ProtocolError(
3798 "No listening address".to_string().into(),
3799 ))
3800 })?;
3801
3802 let channel_id =
3804 match timeout(Duration::from_millis(500), node1.connect_peer(&node2_addr)).await {
3805 Ok(res) => res?,
3806 Err(_) => return Err(P2PError::Network(NetworkError::Timeout)),
3807 };
3808
3809 let target_peer_id = node1
3811 .wait_for_peer_identity(&channel_id, Duration::from_secs(2))
3812 .await?;
3813 assert_eq!(target_peer_id, node2.peer_id().clone());
3814
3815 let message_data = b"Hello, peer!".to_vec();
3817 let result = match timeout(
3818 Duration::from_millis(500),
3819 node1.send_message(&target_peer_id, "test-protocol", message_data, &[]),
3820 )
3821 .await
3822 {
3823 Ok(res) => res,
3824 Err(_) => return Err(P2PError::Network(NetworkError::Timeout)),
3825 };
3826 if let Err(e) = &result {
3829 assert!(!e.to_string().contains("not connected"), "Got error: {}", e);
3830 }
3831
3832 let non_existent_peer = PeerId::from_bytes([0xFFu8; 32]);
3834 let result = node1
3835 .send_message(&non_existent_peer, "test-protocol", vec![], &[])
3836 .await;
3837 assert!(result.is_err(), "Sending to non-existent peer should fail");
3838
3839 node1.stop().await?;
3840 node2.stop().await?;
3841
3842 Ok(())
3843 }
3844
3845 #[tokio::test]
3846 async fn test_remote_mcp_operations() -> Result<()> {
3847 let config = create_test_node_config();
3848 let node = P2PNode::new(config).await?;
3849
3850 node.start().await?;
3852 node.stop().await?;
3853 Ok(())
3854 }
3855
3856 #[tokio::test]
3857 async fn test_health_check() -> Result<()> {
3858 let config = create_test_node_config();
3859 let node = P2PNode::new(config).await?;
3860
3861 let result = node.health_check().await;
3863 assert!(result.is_ok());
3864
3865 Ok(())
3870 }
3871
3872 #[tokio::test]
3873 async fn test_node_uptime() -> Result<()> {
3874 let config = create_test_node_config();
3875 let node = P2PNode::new(config).await?;
3876
3877 let uptime1 = node.uptime();
3878 assert!(uptime1 >= Duration::from_secs(0));
3879
3880 tokio::time::sleep(Duration::from_millis(10)).await;
3882
3883 let uptime2 = node.uptime();
3884 assert!(uptime2 > uptime1);
3885
3886 Ok(())
3887 }
3888
3889 #[tokio::test]
3890 async fn test_node_config_access() -> Result<()> {
3891 let config = create_test_node_config();
3892 let node = P2PNode::new(config).await?;
3893
3894 let node_config = node.config();
3895 assert_eq!(node_config.max_connections, 100);
3896 Ok(())
3899 }
3900
3901 #[tokio::test]
3902 async fn test_mcp_server_access() -> Result<()> {
3903 let config = create_test_node_config();
3904 let _node = P2PNode::new(config).await?;
3905
3906 Ok(())
3908 }
3909
3910 #[tokio::test]
3911 async fn test_dht_access() -> Result<()> {
3912 let config = create_test_node_config();
3913 let node = P2PNode::new(config).await?;
3914
3915 let _dht = node.dht();
3917
3918 Ok(())
3919 }
3920
3921 #[tokio::test]
3922 async fn test_node_config_builder() -> Result<()> {
3923 let bootstrap: MultiAddr = "/ip4/127.0.0.1/udp/9000/quic".parse().unwrap();
3924
3925 let config = NodeConfig::builder()
3926 .local(true)
3927 .ipv6(true)
3928 .bootstrap_peer(bootstrap)
3929 .connection_timeout(Duration::from_secs(15))
3930 .max_connections(200)
3931 .max_message_size(TEST_MAX_MESSAGE_SIZE)
3932 .build()?;
3933
3934 assert_eq!(config.listen_addrs().len(), 2); assert!(config.local);
3936 assert!(config.ipv6);
3937 assert_eq!(config.bootstrap_peers.len(), 1);
3938 assert_eq!(config.connection_timeout, Duration::from_secs(15));
3939 assert_eq!(config.max_connections, 200);
3940 assert_eq!(config.max_message_size, Some(TEST_MAX_MESSAGE_SIZE));
3941 assert!(config.allow_loopback); Ok(())
3944 }
3945
3946 #[tokio::test]
3947 async fn test_bootstrap_peers() -> Result<()> {
3948 let mut config = create_test_node_config();
3949 config.bootstrap_peers = vec![
3950 crate::MultiAddr::from_ipv4(std::net::Ipv4Addr::LOCALHOST, 9200),
3951 crate::MultiAddr::from_ipv4(std::net::Ipv4Addr::LOCALHOST, 9201),
3952 ];
3953
3954 let node = P2PNode::new(config).await?;
3955
3956 node.start().await?;
3958
3959 let _peer_count = node.peer_count().await;
3963
3964 node.stop().await?;
3965 Ok(())
3966 }
3967
3968 #[tokio::test]
3969 async fn test_peer_info_structure() {
3970 let peer_info = PeerInfo {
3971 channel_id: "test_peer".to_string(),
3972 addresses: vec!["/ip4/127.0.0.1/tcp/9000".parse::<MultiAddr>().unwrap()],
3973 connected_at: Instant::now(),
3974 last_seen: Instant::now(),
3975 status: ConnectionStatus::Connected,
3976 protocols: vec!["test-protocol".to_string()],
3977 heartbeat_count: 0,
3978 };
3979
3980 assert_eq!(peer_info.channel_id, "test_peer");
3981 assert_eq!(peer_info.addresses.len(), 1);
3982 assert_eq!(peer_info.status, ConnectionStatus::Connected);
3983 assert_eq!(peer_info.protocols.len(), 1);
3984 }
3985
3986 #[tokio::test]
3987 async fn test_serialization() -> Result<()> {
3988 let config = create_test_node_config();
3990 let serialized = serde_json::to_string(&config)?;
3991 let deserialized: NodeConfig = serde_json::from_str(&serialized)?;
3992
3993 assert_eq!(config.local, deserialized.local);
3994 assert_eq!(config.port, deserialized.port);
3995 assert_eq!(config.ipv6, deserialized.ipv6);
3996 assert_eq!(config.bootstrap_peers, deserialized.bootstrap_peers);
3997
3998 Ok(())
3999 }
4000
4001 #[tokio::test]
4002 async fn test_get_channel_id_by_address_found() -> Result<()> {
4003 let config = create_test_node_config();
4004 let node = P2PNode::new(config).await?;
4005
4006 let test_channel_id = "peer_test_123".to_string();
4008 let test_address = "192.168.1.100:9000";
4009 let test_multiaddr = MultiAddr::quic(test_address.parse().unwrap());
4010
4011 let peer_info = PeerInfo {
4012 channel_id: test_channel_id.clone(),
4013 addresses: vec![test_multiaddr],
4014 connected_at: Instant::now(),
4015 last_seen: Instant::now(),
4016 status: ConnectionStatus::Connected,
4017 protocols: vec!["test-protocol".to_string()],
4018 heartbeat_count: 0,
4019 };
4020
4021 node.transport
4022 .inject_peer(test_channel_id.clone(), peer_info)
4023 .await;
4024
4025 let lookup_addr = MultiAddr::quic(test_address.parse().unwrap());
4027 let found_channel_id = node.get_channel_id_by_address(&lookup_addr).await;
4028 assert_eq!(found_channel_id, Some(test_channel_id));
4029
4030 Ok(())
4031 }
4032
4033 #[tokio::test]
4034 async fn test_get_channel_id_by_address_not_found() -> Result<()> {
4035 let config = create_test_node_config();
4036 let node = P2PNode::new(config).await?;
4037
4038 let unknown_addr = MultiAddr::quic("192.168.1.200:9000".parse().unwrap());
4040 let result = node.get_channel_id_by_address(&unknown_addr).await;
4041 assert_eq!(result, None);
4042
4043 Ok(())
4044 }
4045
4046 #[tokio::test]
4047 async fn test_get_channel_id_by_address_invalid_format() -> Result<()> {
4048 let config = create_test_node_config();
4049 let node = P2PNode::new(config).await?;
4050
4051 let ble_addr = MultiAddr::new(crate::address::TransportAddr::Ble {
4053 mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
4054 psm: 0x0025,
4055 });
4056 let result = node.get_channel_id_by_address(&ble_addr).await;
4057 assert_eq!(result, None);
4058
4059 Ok(())
4060 }
4061
4062 #[tokio::test]
4063 async fn test_get_channel_id_by_address_multiple_peers() -> Result<()> {
4064 let config = create_test_node_config();
4065 let node = P2PNode::new(config).await?;
4066
4067 let peer1_id = "peer_1".to_string();
4069 let peer1_addr_str = "192.168.1.101:9001";
4070 let peer1_multiaddr = MultiAddr::quic(peer1_addr_str.parse().unwrap());
4071
4072 let peer2_id = "peer_2".to_string();
4073 let peer2_addr_str = "192.168.1.102:9002";
4074 let peer2_multiaddr = MultiAddr::quic(peer2_addr_str.parse().unwrap());
4075
4076 let peer1_info = PeerInfo {
4077 channel_id: peer1_id.clone(),
4078 addresses: vec![peer1_multiaddr],
4079 connected_at: Instant::now(),
4080 last_seen: Instant::now(),
4081 status: ConnectionStatus::Connected,
4082 protocols: vec!["test-protocol".to_string()],
4083 heartbeat_count: 0,
4084 };
4085
4086 let peer2_info = PeerInfo {
4087 channel_id: peer2_id.clone(),
4088 addresses: vec![peer2_multiaddr],
4089 connected_at: Instant::now(),
4090 last_seen: Instant::now(),
4091 status: ConnectionStatus::Connected,
4092 protocols: vec!["test-protocol".to_string()],
4093 heartbeat_count: 0,
4094 };
4095
4096 node.transport
4097 .inject_peer(peer1_id.clone(), peer1_info)
4098 .await;
4099 node.transport
4100 .inject_peer(peer2_id.clone(), peer2_info)
4101 .await;
4102
4103 let found_peer1 = node
4105 .get_channel_id_by_address(&MultiAddr::quic(peer1_addr_str.parse().unwrap()))
4106 .await;
4107 let found_peer2 = node
4108 .get_channel_id_by_address(&MultiAddr::quic(peer2_addr_str.parse().unwrap()))
4109 .await;
4110
4111 assert_eq!(found_peer1, Some(peer1_id));
4112 assert_eq!(found_peer2, Some(peer2_id));
4113
4114 Ok(())
4115 }
4116
4117 #[tokio::test]
4118 async fn test_list_active_connections_empty() -> Result<()> {
4119 let config = create_test_node_config();
4120 let node = P2PNode::new(config).await?;
4121
4122 let connections = node.list_active_connections().await;
4124 assert!(connections.is_empty());
4125
4126 Ok(())
4127 }
4128
4129 #[tokio::test]
4130 async fn test_list_active_connections_with_peers() -> Result<()> {
4131 let config = create_test_node_config();
4132 let node = P2PNode::new(config).await?;
4133
4134 let peer1_id = "peer_1".to_string();
4136 let peer1_addrs = vec![
4137 MultiAddr::quic("192.168.1.101:9001".parse().unwrap()),
4138 MultiAddr::quic("192.168.1.101:9002".parse().unwrap()),
4139 ];
4140
4141 let peer2_id = "peer_2".to_string();
4142 let peer2_addrs = vec![MultiAddr::quic("192.168.1.102:9003".parse().unwrap())];
4143
4144 let peer1_info = PeerInfo {
4145 channel_id: peer1_id.clone(),
4146 addresses: peer1_addrs.clone(),
4147 connected_at: Instant::now(),
4148 last_seen: Instant::now(),
4149 status: ConnectionStatus::Connected,
4150 protocols: vec!["test-protocol".to_string()],
4151 heartbeat_count: 0,
4152 };
4153
4154 let peer2_info = PeerInfo {
4155 channel_id: peer2_id.clone(),
4156 addresses: peer2_addrs.clone(),
4157 connected_at: Instant::now(),
4158 last_seen: Instant::now(),
4159 status: ConnectionStatus::Connected,
4160 protocols: vec!["test-protocol".to_string()],
4161 heartbeat_count: 0,
4162 };
4163
4164 node.transport
4165 .inject_peer(peer1_id.clone(), peer1_info)
4166 .await;
4167 node.transport
4168 .inject_peer(peer2_id.clone(), peer2_info)
4169 .await;
4170
4171 node.transport
4173 .inject_active_connection(peer1_id.clone())
4174 .await;
4175 node.transport
4176 .inject_active_connection(peer2_id.clone())
4177 .await;
4178
4179 let connections = node.list_active_connections().await;
4181 assert_eq!(connections.len(), 2);
4182
4183 let peer1_conn = connections.iter().find(|(id, _)| id == &peer1_id);
4185 let peer2_conn = connections.iter().find(|(id, _)| id == &peer2_id);
4186
4187 assert!(peer1_conn.is_some());
4188 assert!(peer2_conn.is_some());
4189
4190 assert_eq!(peer1_conn.unwrap().1, peer1_addrs);
4192 assert_eq!(peer2_conn.unwrap().1, peer2_addrs);
4193
4194 Ok(())
4195 }
4196
4197 #[tokio::test]
4198 async fn test_remove_channel_success() -> Result<()> {
4199 let config = create_test_node_config();
4200 let node = P2PNode::new(config).await?;
4201
4202 let channel_id = "peer_to_remove".to_string();
4204 let channel_peer_id = PeerId::from_name(&channel_id);
4205 let peer_info = PeerInfo {
4206 channel_id: channel_id.clone(),
4207 addresses: vec![MultiAddr::quic("192.168.1.100:9000".parse().unwrap())],
4208 connected_at: Instant::now(),
4209 last_seen: Instant::now(),
4210 status: ConnectionStatus::Connected,
4211 protocols: vec!["test-protocol".to_string()],
4212 heartbeat_count: 0,
4213 };
4214
4215 node.transport
4216 .inject_peer(channel_id.clone(), peer_info)
4217 .await;
4218 node.transport
4219 .inject_peer_to_channel(channel_peer_id, channel_id.clone())
4220 .await;
4221
4222 assert!(node.is_peer_connected(&channel_peer_id).await);
4224
4225 let removed = node.remove_channel(&channel_id).await;
4227 assert!(removed);
4228
4229 assert!(!node.is_peer_connected(&channel_peer_id).await);
4231
4232 Ok(())
4233 }
4234
4235 #[tokio::test]
4236 async fn test_remove_channel_nonexistent() -> Result<()> {
4237 let config = create_test_node_config();
4238 let node = P2PNode::new(config).await?;
4239
4240 let removed = node.remove_channel("nonexistent_peer").await;
4242 assert!(!removed);
4243
4244 Ok(())
4245 }
4246
4247 #[tokio::test]
4248 async fn test_is_peer_connected() -> Result<()> {
4249 let config = create_test_node_config();
4250 let node = P2PNode::new(config).await?;
4251
4252 let channel_id = "test_peer".to_string();
4253 let channel_peer_id = PeerId::from_name(&channel_id);
4254
4255 assert!(!node.is_peer_connected(&channel_peer_id).await);
4257
4258 let peer_info = PeerInfo {
4260 channel_id: channel_id.clone(),
4261 addresses: vec![MultiAddr::quic("192.168.1.100:9000".parse().unwrap())],
4262 connected_at: Instant::now(),
4263 last_seen: Instant::now(),
4264 status: ConnectionStatus::Connected,
4265 protocols: vec!["test-protocol".to_string()],
4266 heartbeat_count: 0,
4267 };
4268
4269 node.transport
4270 .inject_peer(channel_id.clone(), peer_info)
4271 .await;
4272 node.transport
4273 .inject_peer_to_channel(channel_peer_id, channel_id.clone())
4274 .await;
4275
4276 assert!(node.is_peer_connected(&channel_peer_id).await);
4278
4279 node.remove_channel(&channel_id).await;
4281
4282 assert!(!node.is_peer_connected(&channel_peer_id).await);
4284
4285 Ok(())
4286 }
4287
4288 #[test]
4289 fn test_normalize_ipv6_wildcard() {
4290 use std::net::{IpAddr, Ipv6Addr, SocketAddr};
4291
4292 let wildcard = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080);
4293 let normalized = normalize_wildcard_to_loopback(wildcard);
4294
4295 assert_eq!(normalized.ip(), IpAddr::V6(Ipv6Addr::LOCALHOST));
4296 assert_eq!(normalized.port(), 8080);
4297 }
4298
4299 #[test]
4300 fn test_normalize_ipv4_wildcard() {
4301 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
4302
4303 let wildcard = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9000);
4304 let normalized = normalize_wildcard_to_loopback(wildcard);
4305
4306 assert_eq!(normalized.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST));
4307 assert_eq!(normalized.port(), 9000);
4308 }
4309
4310 #[test]
4311 fn test_normalize_specific_address_unchanged() {
4312 let specific: std::net::SocketAddr = "192.168.1.100:3000".parse().unwrap();
4313 let normalized = normalize_wildcard_to_loopback(specific);
4314
4315 assert_eq!(normalized, specific);
4316 }
4317
4318 #[test]
4319 fn test_normalize_loopback_unchanged() {
4320 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
4321
4322 let loopback_v6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5000);
4323 let normalized_v6 = normalize_wildcard_to_loopback(loopback_v6);
4324 assert_eq!(normalized_v6, loopback_v6);
4325
4326 let loopback_v4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
4327 let normalized_v4 = normalize_wildcard_to_loopback(loopback_v4);
4328 assert_eq!(normalized_v4, loopback_v4);
4329 }
4330
4331 fn current_timestamp() -> u64 {
4335 std::time::SystemTime::now()
4336 .duration_since(std::time::UNIX_EPOCH)
4337 .map(|d| d.as_secs())
4338 .unwrap_or(0)
4339 }
4340
4341 fn make_wire_bytes(protocol: &str, data: Vec<u8>, from: &str, timestamp: u64) -> Vec<u8> {
4343 let msg = WireMessage {
4344 protocol: protocol.to_string(),
4345 data,
4346 from: PeerId::from_name(from),
4347 timestamp,
4348 user_agent: String::new(),
4349 public_key: Vec::new(),
4350 signature: Vec::new(),
4351 };
4352 postcard::to_stdvec(&msg).unwrap()
4353 }
4354
4355 fn make_signed_wire_bytes(
4357 identity: &NodeIdentity,
4358 protocol: &str,
4359 data: Vec<u8>,
4360 timestamp: u64,
4361 ) -> Vec<u8> {
4362 let from = *identity.peer_id();
4363 let user_agent = "test/1.0";
4364 let signable =
4365 postcard::to_stdvec(&(protocol, data.as_slice(), &from, timestamp, user_agent))
4366 .unwrap();
4367 let sig = identity.sign(&signable).expect("signing should succeed");
4368 let msg = WireMessage {
4369 protocol: protocol.to_string(),
4370 data,
4371 from,
4372 timestamp,
4373 user_agent: user_agent.to_string(),
4374 public_key: identity.public_key().as_bytes().to_vec(),
4375 signature: sig.as_bytes().to_vec(),
4376 };
4377 postcard::to_stdvec(&msg).unwrap()
4378 }
4379
4380 #[test]
4381 fn test_parse_protocol_message_uses_transport_peer_id_as_source() {
4382 let transport_id = "abcdef0123456789";
4385 let logical_id = "spoofed-logical-id";
4386 let bytes = make_wire_bytes("test/v1", vec![1, 2, 3], logical_id, current_timestamp());
4387
4388 let parsed =
4389 parse_protocol_message(&bytes, transport_id).expect("valid message should parse");
4390
4391 assert!(parsed.authenticated_node_id.is_none());
4393
4394 match parsed.event {
4395 P2PEvent::Message {
4396 topic,
4397 source,
4398 transport_source,
4399 timestamp: _,
4400 data,
4401 } => {
4402 assert!(source.is_none(), "unsigned message source must be None");
4403 assert!(
4404 transport_source.is_none(),
4405 "non-socket transport source should not produce an IP transport address"
4406 );
4407 assert_eq!(topic, "test/v1");
4408 assert_eq!(data, vec![1u8, 2, 3]);
4409 }
4410 other => panic!("expected P2PEvent::Message, got {:?}", other),
4411 }
4412 }
4413
4414 #[test]
4415 fn test_parse_protocol_message_rejects_invalid_bytes() {
4416 assert!(parse_protocol_message(b"not valid bincode", "peer-id").is_none());
4418 }
4419
4420 #[test]
4421 fn test_parse_protocol_message_rejects_truncated_message() {
4422 let full_bytes = make_wire_bytes("test/v1", vec![1, 2, 3], "sender", current_timestamp());
4424 let truncated = &full_bytes[..full_bytes.len() / 2];
4425 assert!(parse_protocol_message(truncated, "peer-id").is_none());
4426 }
4427
4428 #[test]
4429 fn test_parse_protocol_message_empty_payload() {
4430 let bytes = make_wire_bytes("ping", vec![], "sender", current_timestamp());
4431
4432 let parsed = parse_protocol_message(&bytes, "transport-peer")
4433 .expect("valid message with empty data should parse");
4434
4435 match parsed.event {
4436 P2PEvent::Message { data, .. } => assert!(data.is_empty()),
4437 other => panic!("expected P2PEvent::Message, got {:?}", other),
4438 }
4439 }
4440
4441 #[test]
4442 fn test_parse_protocol_message_records_ip_transport_source() {
4443 let bytes = make_wire_bytes("ping", vec![1], "sender", current_timestamp());
4444
4445 let parsed =
4446 parse_protocol_message(&bytes, "192.168.1.2:4567").expect("valid message should parse");
4447
4448 match parsed.event {
4449 P2PEvent::Message {
4450 transport_source, ..
4451 } => {
4452 assert_eq!(
4453 transport_source,
4454 Some(MultiAddr::quic("192.168.1.2:4567".parse().unwrap()))
4455 );
4456 }
4457 other => panic!("expected P2PEvent::Message, got {:?}", other),
4458 }
4459 }
4460
4461 #[test]
4462 fn test_parse_protocol_message_preserves_binary_payload() {
4463 let payload: Vec<u8> = (0..=255).collect();
4465 let bytes = make_wire_bytes("binary/v1", payload.clone(), "sender", current_timestamp());
4466
4467 let parsed = parse_protocol_message(&bytes, "peer-id")
4468 .expect("valid message with full byte range should parse");
4469
4470 match parsed.event {
4471 P2PEvent::Message { data, topic, .. } => {
4472 assert_eq!(topic, "binary/v1");
4473 assert_eq!(
4474 data, payload,
4475 "payload must survive bincode round-trip exactly"
4476 );
4477 }
4478 other => panic!("expected P2PEvent::Message, got {:?}", other),
4479 }
4480 }
4481
4482 #[test]
4483 fn test_parse_signed_message_verifies_and_uses_node_id() {
4484 let identity = NodeIdentity::generate().expect("should generate identity");
4485 let protocol = "test/signed";
4486 let data: Vec<u8> = vec![10, 20, 30];
4487 let from = *identity.peer_id();
4489 let timestamp = current_timestamp();
4490 let user_agent = "test/1.0";
4491
4492 let signable =
4494 postcard::to_stdvec(&(protocol, data.as_slice(), &from, timestamp, user_agent))
4495 .unwrap();
4496 let sig = identity.sign(&signable).expect("signing should succeed");
4497
4498 let msg = WireMessage {
4499 protocol: protocol.to_string(),
4500 data: data.clone(),
4501 from,
4502 timestamp,
4503 user_agent: user_agent.to_string(),
4504 public_key: identity.public_key().as_bytes().to_vec(),
4505 signature: sig.as_bytes().to_vec(),
4506 };
4507 let bytes = postcard::to_stdvec(&msg).unwrap();
4508
4509 let parsed =
4510 parse_protocol_message(&bytes, "transport-xyz").expect("signed message should parse");
4511
4512 let expected_peer_id = *identity.peer_id();
4513 assert_eq!(
4514 parsed.authenticated_node_id.as_ref(),
4515 Some(&expected_peer_id)
4516 );
4517
4518 match parsed.event {
4519 P2PEvent::Message { source, .. } => {
4520 assert_eq!(
4521 source.as_ref(),
4522 Some(&expected_peer_id),
4523 "source should be the verified PeerId"
4524 );
4525 }
4526 other => panic!("expected P2PEvent::Message, got {:?}", other),
4527 }
4528 }
4529
4530 #[test]
4531 fn test_parse_message_with_bad_signature_is_rejected() {
4532 let identity = NodeIdentity::generate().expect("should generate identity");
4533 let protocol = "test/bad-sig";
4534 let data: Vec<u8> = vec![1, 2, 3];
4535 let from = *identity.peer_id();
4536 let timestamp = current_timestamp();
4537 let user_agent = "test/1.0";
4538
4539 let signable =
4541 postcard::to_stdvec(&(protocol, data.as_slice(), &from, timestamp, user_agent))
4542 .unwrap();
4543 let sig = identity.sign(&signable).expect("signing should succeed");
4544
4545 let msg = WireMessage {
4547 protocol: protocol.to_string(),
4548 data: vec![99, 99, 99],
4549 from,
4550 timestamp,
4551 user_agent: user_agent.to_string(),
4552 public_key: identity.public_key().as_bytes().to_vec(),
4553 signature: sig.as_bytes().to_vec(),
4554 };
4555 let bytes = postcard::to_stdvec(&msg).unwrap();
4556
4557 assert!(
4558 parse_protocol_message(&bytes, "transport-xyz").is_none(),
4559 "message with bad signature should be rejected"
4560 );
4561 }
4562
4563 #[test]
4564 fn test_parse_message_with_mismatched_from_is_rejected() {
4565 let identity = NodeIdentity::generate().expect("should generate identity");
4566 let protocol = "test/from-mismatch";
4567 let data: Vec<u8> = vec![1, 2, 3];
4568 let fake_from = PeerId::from_bytes([0xDE; 32]);
4570 let timestamp = current_timestamp();
4571 let user_agent = "test/1.0";
4572
4573 let signable =
4574 postcard::to_stdvec(&(protocol, data.as_slice(), &fake_from, timestamp, user_agent))
4575 .unwrap();
4576 let sig = identity.sign(&signable).expect("signing should succeed");
4577
4578 let msg = WireMessage {
4579 protocol: protocol.to_string(),
4580 data,
4581 from: fake_from,
4582 timestamp,
4583 user_agent: user_agent.to_string(),
4584 public_key: identity.public_key().as_bytes().to_vec(),
4585 signature: sig.as_bytes().to_vec(),
4586 };
4587 let bytes = postcard::to_stdvec(&msg).unwrap();
4588
4589 assert!(
4590 parse_protocol_message(&bytes, "transport-xyz").is_none(),
4591 "message with mismatched from field should be rejected"
4592 );
4593 }
4594
4595 #[test]
4596 fn test_parse_protocol_message_accepts_arbitrary_timestamps() {
4597 let payload = vec![1, 2, 3];
4601
4602 let old_ts = current_timestamp().saturating_sub(36_000);
4604 let old_bytes = make_wire_bytes("test/old", payload.clone(), "sender", old_ts);
4605 assert!(
4606 parse_protocol_message(&old_bytes, "peer-id").is_some(),
4607 "should accept unsigned message with timestamp 10h in the past"
4608 );
4609
4610 let future_ts = current_timestamp().saturating_add(36_000);
4612 let future_bytes = make_wire_bytes("test/future", payload.clone(), "sender", future_ts);
4613 assert!(
4614 parse_protocol_message(&future_bytes, "peer-id").is_some(),
4615 "should accept unsigned message with timestamp 10h in the future"
4616 );
4617
4618 let identity = NodeIdentity::generate().expect("should generate identity");
4621 let signed_old =
4622 make_signed_wire_bytes(&identity, "test/signed-old", payload.clone(), old_ts);
4623 assert!(
4624 parse_protocol_message(&signed_old, "transport-xyz").is_some(),
4625 "should accept signed message with timestamp 10h in the past"
4626 );
4627
4628 let signed_future =
4629 make_signed_wire_bytes(&identity, "test/signed-future", payload, future_ts);
4630 assert!(
4631 parse_protocol_message(&signed_future, "transport-xyz").is_some(),
4632 "should accept signed message with timestamp 10h in the future"
4633 );
4634 }
4635
4636 #[test]
4637 fn test_parse_protocol_message_exposes_timestamp_on_event() {
4638 let ts: u64 = 1_234_567_890;
4642 let bytes = make_wire_bytes("test/ts", vec![9, 9, 9], "sender", ts);
4643 let parsed = parse_protocol_message(&bytes, "peer-id").expect("valid message should parse");
4644 match parsed.event {
4645 P2PEvent::Message { timestamp, .. } => {
4646 assert_eq!(timestamp, ts, "P2PEvent::Message.timestamp must round-trip");
4647 }
4648 other => panic!("expected P2PEvent::Message, got {:?}", other),
4649 }
4650 }
4651
4652 #[test]
4653 fn test_signed_message_timestamp_is_signature_covered() {
4654 let identity = NodeIdentity::generate().expect("should generate identity");
4658 let ts: u64 = 1_700_000_000;
4659 let signed = make_signed_wire_bytes(&identity, "test/sig", vec![1, 2, 3], ts);
4660
4661 let parsed = parse_protocol_message(&signed, "transport-xyz")
4663 .expect("unmodified signed message should parse");
4664 assert!(parsed.authenticated_node_id.is_some());
4665
4666 let mut tampered: WireMessage =
4668 postcard::from_bytes(&signed).expect("signed bytes must deserialize");
4669 tampered.timestamp = ts.wrapping_add(1);
4670 let tampered_bytes = postcard::to_stdvec(&tampered).expect("re-serialize");
4671
4672 assert!(
4673 parse_protocol_message(&tampered_bytes, "transport-xyz").is_none(),
4674 "timestamp-only mutation on a signed message must fail signature verification"
4675 );
4676 }
4677
4678 fn snapshot_peer(addr: &str) -> SnapshotPeer {
4679 SnapshotPeer {
4680 peer_id: PeerId::random(),
4681 addresses: vec![addr.parse().expect("valid multiaddr")],
4682 }
4683 }
4684
4685 #[test]
4686 fn snapshot_dial_sets_skips_self() {
4687 let self_id = PeerId::random();
4688 let mut peers = vec![snapshot_peer("/ip4/10.0.0.1/udp/9000/quic")];
4689 peers[0].peer_id = self_id;
4690 let mut seen = HashSet::new();
4691
4692 assert!(snapshot_dial_sets(&peers, &self_id, &mut seen).is_empty());
4693 assert!(seen.is_empty(), "self must not reserve an address");
4694 }
4695
4696 #[test]
4697 fn snapshot_dial_sets_skips_addresses_already_queued() {
4698 let self_id = PeerId::random();
4699 let queued = snapshot_peer("/ip4/10.0.0.2/udp/9000/quic");
4700 let fresh = snapshot_peer("/ip4/10.0.0.3/udp/9000/quic");
4701 let mut seen: HashSet<SocketAddr> = queued
4702 .addresses
4703 .iter()
4704 .filter_map(MultiAddr::dialable_socket_addr)
4705 .collect();
4706
4707 let sets = snapshot_dial_sets(&[queued, fresh.clone()], &self_id, &mut seen);
4708
4709 assert_eq!(
4710 sets.len(),
4711 1,
4712 "the already-queued peer must not be redialled"
4713 );
4714 assert_eq!(sets[0].0, fresh.peer_id);
4715 assert_eq!(
4716 seen.len(),
4717 2,
4718 "the new address is reserved for later phases"
4719 );
4720 }
4721
4722 #[test]
4723 fn snapshot_dial_sets_drops_undialable_addresses() {
4724 let self_id = PeerId::random();
4725 let peers = vec![snapshot_peer("/ip4/10.0.0.4/tcp/9000")];
4726 let mut seen = HashSet::new();
4727
4728 assert!(
4729 snapshot_dial_sets(&peers, &self_id, &mut seen).is_empty(),
4730 "only QUIC addresses are dialable today"
4731 );
4732 }
4733
4734 #[test]
4735 fn snapshot_dial_sets_dedupes_repeated_peers_and_bounds_addresses() {
4736 let self_id = PeerId::random();
4737 let mut peer = snapshot_peer("/ip4/10.0.0.5/udp/9000/quic");
4738 peer.addresses = (0..6)
4739 .map(|i| {
4740 format!("/ip4/10.0.0.5/udp/900{i}/quic")
4741 .parse()
4742 .expect("valid multiaddr")
4743 })
4744 .collect();
4745 let mut seen = HashSet::new();
4746
4747 let sets = snapshot_dial_sets(&[peer.clone(), peer], &self_id, &mut seen);
4748
4749 assert_eq!(sets.len(), 1, "the same peer twice is one candidate");
4750 assert_eq!(
4751 sets[0].1.len(),
4752 MAX_SNAPSHOT_ADDRESSES_DIALLED,
4753 "the dial tail past the budget stays bounded"
4754 );
4755 }
4756
4757 #[test]
4758 fn snapshot_dial_sets_returns_every_other_peer_once() {
4759 let self_id = PeerId::random();
4760 let peers: Vec<SnapshotPeer> = (0..130)
4761 .map(|i| snapshot_peer(&format!("/ip4/10.1.{}.{}/udp/9000/quic", i / 256, i % 256)))
4762 .collect();
4763 let mut seen = HashSet::new();
4764
4765 let sets = snapshot_dial_sets(&peers, &self_id, &mut seen);
4766
4767 assert_eq!(sets.len(), 130);
4768 assert_eq!(seen.len(), 130);
4769 }
4770}