1use crate::PeerId;
19use crate::adaptive::trust::{TrustRecord, TrustSnapshot};
20use crate::adaptive::{AdaptiveDHT, AdaptiveDhtConfig, TrustEngine, TrustEvent};
21use crate::bootstrap::cache::{CachedCloseGroupPeer, CloseGroupCache};
22use crate::dht::core_engine::AddressType;
23use crate::dht_network_manager::{DhtNetworkConfig, DhtNetworkEvent, DhtNetworkManager};
24use crate::error::{NetworkError, P2PError, P2pResult as Result};
25use crate::reachability::spawn_acquisition_driver;
26
27use crate::MultiAddr;
28use crate::identity::node_identity::{NodeIdentity, peer_id_from_public_key};
29use crate::quantum_crypto::saorsa_transport_integration::{MlDsaPublicKey, MlDsaSignature};
30use dashmap::DashMap;
31use futures::StreamExt;
32use parking_lot::Mutex as ParkingMutex;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::net::SocketAddr;
36use std::path::{Path, PathBuf};
37use std::sync::Arc;
38use std::sync::atomic::{AtomicBool, Ordering};
39use std::time::{Duration, SystemTime, UNIX_EPOCH};
40use tokio::sync::{Mutex as TokioMutex, RwLock, broadcast};
41use tokio::time::Instant;
42use tokio_util::sync::CancellationToken;
43use tracing::{debug, info, trace, warn};
44
45fn bootstrap_peer_identity_matches(expected: Option<PeerId>, actual: PeerId) -> bool {
46 expected.is_none_or(|expected| expected == actual)
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
53pub(crate) struct WireMessage {
54 pub(crate) protocol: String,
56 pub(crate) data: Vec<u8>,
58 pub(crate) from: PeerId,
60 pub(crate) timestamp: u64,
62 #[serde(default)]
68 pub(crate) user_agent: String,
69 #[serde(default)]
71 pub(crate) public_key: Vec<u8>,
72 #[serde(default)]
74 pub(crate) signature: Vec<u8>,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
83pub enum NodeMode {
84 #[default]
86 Node,
87 Client,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93enum ListenMode {
94 Public,
96 Local,
98}
99
100pub fn user_agent_for_mode(mode: NodeMode) -> String {
105 let prefix = match mode {
106 NodeMode::Node => "node",
107 NodeMode::Client => "client",
108 };
109 format!("{prefix}/{}", env!("CARGO_PKG_VERSION"))
110}
111
112pub fn is_dht_participant(user_agent: &str) -> bool {
114 user_agent.starts_with("node/")
115}
116
117pub(crate) const MESSAGE_RECV_CHANNEL_CAPACITY: usize = 256;
119
120pub(crate) const MAX_ACTIVE_REQUESTS: usize = 256;
122
123pub(crate) const MAX_REQUEST_TIMEOUT: Duration = Duration::from_secs(300);
125
126const DEFAULT_LISTEN_PORT: u16 = 9000;
128
129const DEFAULT_MAX_CONNECTIONS: usize = 10_000;
131
132const DEFAULT_CONNECTION_TIMEOUT_SECS: u64 = 25;
139
140const DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS: u64 = 60 * 60;
143
144const MIN_CLOSE_GROUP_CACHE_SAVE_INTERVAL: Duration = Duration::from_secs(60);
147
148const BOOTSTRAP_IDENTITY_TIMEOUT_SECS: u64 = 3;
162
163const MAX_CONCURRENT_BOOTSTRAP_DIALS: usize = 4;
170
171const CLIENT_BOOTSTRAP_TARGET: usize = 6;
180
181const fn default_true() -> bool {
183 true
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct NodeConfig {
189 #[serde(default)]
195 pub local: bool,
196
197 #[serde(default)]
199 pub port: u16,
200
201 #[serde(default = "default_true")]
206 pub ipv6: bool,
207
208 pub bootstrap_peers: Vec<crate::MultiAddr>,
210
211 pub connection_timeout: Duration,
214
215 pub max_connections: usize,
217
218 pub dht_config: DHTConfig,
220
221 pub diversity_config: Option<crate::security::IPDiversityConfig>,
226
227 #[serde(default)]
231 pub max_message_size: Option<usize>,
232
233 #[serde(skip)]
238 pub node_identity: Option<Arc<NodeIdentity>>,
239
240 #[serde(default)]
246 pub mode: NodeMode,
247
248 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub custom_user_agent: Option<String>,
254
255 #[serde(default)]
263 pub allow_loopback: bool,
264
265 #[serde(default)]
273 pub adaptive_dht_config: AdaptiveDhtConfig,
274
275 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub close_group_cache_dir: Option<PathBuf>,
287
288 #[serde(default = "default_close_group_cache_max_age")]
293 pub close_group_cache_max_age: Option<Duration>,
294}
295
296fn default_close_group_cache_max_age() -> Option<Duration> {
297 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct DHTConfig {
303 pub k_value: usize,
305
306 pub alpha_value: usize,
308
309 pub refresh_interval: Duration,
311}
312
313#[inline]
326fn build_listen_addrs(port: u16, ipv6_enabled: bool, mode: ListenMode) -> Vec<MultiAddr> {
327 let mut addrs = Vec::with_capacity(if ipv6_enabled { 2 } else { 1 });
328
329 let (v4, v6) = match mode {
330 ListenMode::Public => (
331 std::net::Ipv4Addr::UNSPECIFIED,
332 std::net::Ipv6Addr::UNSPECIFIED,
333 ),
334 ListenMode::Local => (std::net::Ipv4Addr::LOCALHOST, std::net::Ipv6Addr::LOCALHOST),
335 };
336
337 if ipv6_enabled {
338 addrs.push(MultiAddr::quic(std::net::SocketAddr::new(
339 std::net::IpAddr::V6(v6),
340 port,
341 )));
342 }
343
344 addrs.push(MultiAddr::quic(std::net::SocketAddr::new(
345 std::net::IpAddr::V4(v4),
346 port,
347 )));
348
349 addrs
350}
351
352impl NodeConfig {
353 pub fn user_agent(&self) -> String {
358 self.custom_user_agent
359 .clone()
360 .unwrap_or_else(|| user_agent_for_mode(self.mode))
361 }
362
363 pub fn listen_addrs(&self) -> Vec<MultiAddr> {
368 let mode = if self.local {
369 ListenMode::Local
370 } else {
371 ListenMode::Public
372 };
373 build_listen_addrs(self.port, self.ipv6, mode)
374 }
375
376 pub fn new() -> Result<Self> {
382 Ok(Self::default())
383 }
384
385 pub fn builder() -> NodeConfigBuilder {
387 NodeConfigBuilder::default()
388 }
389}
390
391#[derive(Debug, Clone)]
414pub struct NodeConfigBuilder {
415 port: u16,
416 ipv6: bool,
417 local: bool,
418 bootstrap_peers: Vec<crate::MultiAddr>,
419 max_connections: Option<usize>,
420 connection_timeout: Option<Duration>,
421 dht_config: Option<DHTConfig>,
422 max_message_size: Option<usize>,
423 mode: NodeMode,
424 custom_user_agent: Option<String>,
425 allow_loopback: Option<bool>,
426 adaptive_dht_config: Option<AdaptiveDhtConfig>,
427 close_group_cache_dir: Option<PathBuf>,
428 close_group_cache_max_age: Option<Option<Duration>>,
431}
432
433impl Default for NodeConfigBuilder {
434 fn default() -> Self {
435 Self {
436 port: 0,
437 ipv6: true,
438 local: false,
439 bootstrap_peers: Vec::new(),
440 max_connections: None,
441 connection_timeout: None,
442 dht_config: None,
443 max_message_size: None,
444 mode: NodeMode::default(),
445 custom_user_agent: None,
446 allow_loopback: None,
447 adaptive_dht_config: None,
448 close_group_cache_dir: None,
449 close_group_cache_max_age: None,
450 }
451 }
452}
453
454impl NodeConfigBuilder {
455 pub fn port(mut self, port: u16) -> Self {
457 self.port = port;
458 self
459 }
460
461 pub fn ipv6(mut self, enabled: bool) -> Self {
463 self.ipv6 = enabled;
464 self
465 }
466
467 pub fn local(mut self, local: bool) -> Self {
474 self.local = local;
475 self
476 }
477
478 pub fn bootstrap_peer(mut self, addr: crate::MultiAddr) -> Self {
480 self.bootstrap_peers.push(addr);
481 self
482 }
483
484 pub fn max_connections(mut self, max: usize) -> Self {
486 self.max_connections = Some(max);
487 self
488 }
489
490 pub fn connection_timeout(mut self, timeout: Duration) -> Self {
492 self.connection_timeout = Some(timeout);
493 self
494 }
495
496 pub fn dht_config(mut self, config: DHTConfig) -> Self {
498 self.dht_config = Some(config);
499 self
500 }
501
502 pub fn max_message_size(mut self, max_message_size: usize) -> Self {
506 self.max_message_size = Some(max_message_size);
507 self
508 }
509
510 pub fn mode(mut self, mode: NodeMode) -> Self {
512 self.mode = mode;
513 self
514 }
515
516 pub fn custom_user_agent(mut self, user_agent: impl Into<String>) -> Self {
518 self.custom_user_agent = Some(user_agent.into());
519 self
520 }
521
522 pub fn allow_loopback(mut self, allow: bool) -> Self {
526 self.allow_loopback = Some(allow);
527 self
528 }
529
530 pub fn trust_enforcement(mut self, enabled: bool) -> Self {
543 let threshold = if enabled {
544 AdaptiveDhtConfig::default().swap_threshold
545 } else {
546 0.0
547 };
548 self.adaptive_dht_config = Some(AdaptiveDhtConfig {
549 swap_threshold: threshold,
550 });
551 self
552 }
553
554 pub fn adaptive_dht_config(mut self, config: AdaptiveDhtConfig) -> Self {
558 self.adaptive_dht_config = Some(config);
559 self
560 }
561
562 pub fn close_group_cache_dir(mut self, path: impl Into<PathBuf>) -> Self {
567 self.close_group_cache_dir = Some(path.into());
568 self
569 }
570
571 pub fn close_group_cache_max_age(mut self, max_age: Option<Duration>) -> Self {
574 self.close_group_cache_max_age = Some(max_age);
575 self
576 }
577
578 pub fn build(self) -> Result<NodeConfig> {
584 let allow_loopback = self.allow_loopback.unwrap_or(self.local);
586
587 Ok(NodeConfig {
588 local: self.local,
589 port: self.port,
590 ipv6: self.ipv6,
591 bootstrap_peers: self.bootstrap_peers,
592 connection_timeout: self
593 .connection_timeout
594 .unwrap_or(Duration::from_secs(DEFAULT_CONNECTION_TIMEOUT_SECS)),
595 max_connections: self.max_connections.unwrap_or(DEFAULT_MAX_CONNECTIONS),
596 dht_config: self.dht_config.unwrap_or_default(),
597 diversity_config: None,
598 max_message_size: self.max_message_size,
599 node_identity: None,
600 mode: self.mode,
601 custom_user_agent: self.custom_user_agent,
602 allow_loopback,
603 adaptive_dht_config: self.adaptive_dht_config.unwrap_or_default(),
604 close_group_cache_dir: self.close_group_cache_dir,
605 close_group_cache_max_age: self
606 .close_group_cache_max_age
607 .unwrap_or_else(default_close_group_cache_max_age),
608 })
609 }
610}
611
612impl Default for NodeConfig {
613 fn default() -> Self {
614 Self {
615 local: false,
616 port: DEFAULT_LISTEN_PORT,
617 ipv6: true,
618 bootstrap_peers: Vec::new(),
619 connection_timeout: Duration::from_secs(DEFAULT_CONNECTION_TIMEOUT_SECS),
620 max_connections: DEFAULT_MAX_CONNECTIONS,
621 dht_config: DHTConfig::default(),
622 diversity_config: None,
623 max_message_size: None,
624 node_identity: None,
625 mode: NodeMode::default(),
626 custom_user_agent: None,
627 allow_loopback: false,
628 adaptive_dht_config: AdaptiveDhtConfig::default(),
629 close_group_cache_dir: None,
630 close_group_cache_max_age: default_close_group_cache_max_age(),
631 }
632 }
633}
634
635impl DHTConfig {
636 pub const DEFAULT_K_VALUE: usize = 20;
638 const DEFAULT_ALPHA_VALUE: usize = 3;
639 const DEFAULT_REFRESH_INTERVAL_SECS: u64 = 600;
640 const MIN_K_VALUE: usize = 4;
642
643 pub fn validate(&self) -> Result<()> {
647 if self.k_value < Self::MIN_K_VALUE {
648 return Err(P2PError::Validation(
649 format!(
650 "k_value must be >= {} (got {}), values below {} produce degenerate behavior",
651 Self::MIN_K_VALUE,
652 self.k_value,
653 Self::MIN_K_VALUE,
654 )
655 .into(),
656 ));
657 }
658 if self.alpha_value < 1 {
659 return Err(P2PError::Validation(
660 format!("alpha_value must be >= 1 (got {})", self.alpha_value).into(),
661 ));
662 }
663 if self.refresh_interval.is_zero() {
664 return Err(P2PError::Validation("refresh_interval must be > 0".into()));
665 }
666 Ok(())
667 }
668}
669
670impl Default for DHTConfig {
671 fn default() -> Self {
672 Self {
673 k_value: Self::DEFAULT_K_VALUE,
674 alpha_value: Self::DEFAULT_ALPHA_VALUE,
675 refresh_interval: Duration::from_secs(Self::DEFAULT_REFRESH_INTERVAL_SECS),
676 }
677 }
678}
679
680#[derive(Debug, Clone)]
682pub struct PeerInfo {
683 #[allow(dead_code)]
685 pub(crate) channel_id: String,
686
687 pub addresses: Vec<MultiAddr>,
689
690 pub connected_at: Instant,
692
693 pub last_seen: Instant,
695
696 pub status: ConnectionStatus,
698
699 pub protocols: Vec<String>,
701
702 pub heartbeat_count: u64,
704}
705
706#[derive(Debug, Clone, PartialEq)]
708pub enum ConnectionStatus {
709 Connecting,
711 Connected,
713 Disconnecting,
715 Disconnected,
717 Failed(String),
719}
720
721#[derive(Debug, Clone)]
726pub enum P2PEvent {
727 Message {
729 topic: String,
731 source: Option<PeerId>,
734 transport_source: Option<MultiAddr>,
738 timestamp: u64,
746 data: Vec<u8>,
748 },
749 PeerConnected(PeerId, String),
752 PeerDisconnected(PeerId),
754}
755
756#[derive(Debug, Clone)]
761pub struct PeerResponse {
762 pub peer_id: PeerId,
764 pub data: Vec<u8>,
766 pub latency: Duration,
768}
769
770#[derive(Debug, Clone, Serialize, Deserialize)]
775pub(crate) struct RequestResponseEnvelope {
776 pub(crate) message_id: String,
778 pub(crate) is_response: bool,
780 pub(crate) payload: Vec<u8>,
782}
783
784pub(crate) struct PendingRequest {
786 pub(crate) response_tx: tokio::sync::oneshot::Sender<Vec<u8>>,
788 pub(crate) expected_peer: PeerId,
790}
791
792const QUIC_TEARDOWN_GRACE: Duration = Duration::from_millis(100);
798
799pub struct P2PNode {
810 config: NodeConfig,
812
813 peer_id: PeerId,
815
816 transport: Arc<crate::transport_handle::TransportHandle>,
818
819 start_time: Instant,
821
822 shutdown: CancellationToken,
824
825 close_group_cache_save_shutdown: CancellationToken,
828
829 close_group_cache_save_handle: TokioMutex<Option<tokio::task::JoinHandle<()>>>,
832
833 adaptive_dht: AdaptiveDHT,
836
837 is_bootstrapped: Arc<AtomicBool>,
839
840 is_started: Arc<AtomicBool>,
842
843 reconnect_locks: ParkingMutex<HashMap<PeerId, Arc<TokioMutex<()>>>>,
847
848 relayer_peer_id: Arc<RwLock<Option<PeerId>>>,
857
858 relay_address: Arc<RwLock<Option<SocketAddr>>>,
865}
866
867pub(crate) fn normalize_wildcard_to_loopback(addr: std::net::SocketAddr) -> std::net::SocketAddr {
883 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
884
885 if addr.ip().is_unspecified() {
886 let loopback_ip = match addr {
888 std::net::SocketAddr::V6(_) => IpAddr::V6(Ipv6Addr::LOCALHOST), std::net::SocketAddr::V4(_) => IpAddr::V4(Ipv4Addr::LOCALHOST), };
891 std::net::SocketAddr::new(loopback_ip, addr.port())
892 } else {
893 addr
895 }
896}
897
898impl P2PNode {
899 pub async fn new(config: NodeConfig) -> Result<Self> {
901 let node_identity = match config.node_identity.clone() {
903 Some(identity) => identity,
904 None => Arc::new(NodeIdentity::generate()?),
905 };
906
907 let peer_id = *node_identity.peer_id();
909
910 config.dht_config.validate()?;
913 if let Some(ref diversity) = config.diversity_config {
914 diversity
915 .validate()
916 .map_err(|e| P2PError::Validation(format!("IP diversity config: {e}").into()))?;
917 }
918
919 let transport_config = crate::transport_handle::TransportConfig::from_node_config(
921 &config,
922 crate::DEFAULT_EVENT_CHANNEL_CAPACITY,
923 node_identity.clone(),
924 );
925 let transport =
926 Arc::new(crate::transport_handle::TransportHandle::new(transport_config).await?);
927
928 let dht_manager_config = DhtNetworkConfig {
930 peer_id,
931 node_config: config.clone(),
932 request_timeout: config.connection_timeout,
933 max_concurrent_operations: MAX_ACTIVE_REQUESTS,
934 enable_security: true,
935 swap_threshold: 0.0, };
937 let adaptive_dht = AdaptiveDHT::new(
938 transport.clone(),
939 dht_manager_config,
940 config.adaptive_dht_config.clone(),
941 )
942 .await?;
943
944 let node = Self {
945 config,
946 peer_id,
947 transport,
948 start_time: Instant::now(),
949 shutdown: CancellationToken::new(),
950 close_group_cache_save_shutdown: CancellationToken::new(),
951 close_group_cache_save_handle: TokioMutex::new(None),
952 adaptive_dht,
953 is_bootstrapped: Arc::new(AtomicBool::new(false)),
954 is_started: Arc::new(AtomicBool::new(false)),
955 reconnect_locks: ParkingMutex::new(HashMap::new()),
956 relayer_peer_id: Arc::new(RwLock::new(None)),
957 relay_address: Arc::new(RwLock::new(None)),
958 };
959 info!(
960 "Created P2P node with peer ID: {} (call start() to begin networking)",
961 node.peer_id
962 );
963
964 Ok(node)
965 }
966
967 pub fn peer_id(&self) -> &PeerId {
969 &self.peer_id
970 }
971
972 pub fn transport(&self) -> &Arc<crate::transport_handle::TransportHandle> {
974 &self.transport
975 }
976
977 pub async fn relay_address(&self) -> Option<SocketAddr> {
984 *self.relay_address.read().await
985 }
986
987 pub fn local_addr(&self) -> Option<MultiAddr> {
988 self.transport.local_addr()
989 }
990
991 pub fn is_bootstrapped(&self) -> bool {
996 self.is_bootstrapped.load(Ordering::SeqCst)
997 }
998
999 pub async fn re_bootstrap(&self) -> Result<()> {
1004 self.is_bootstrapped.store(false, Ordering::SeqCst);
1005 self.connect_bootstrap_peers(None).await
1006 }
1007
1008 pub fn trust_engine(&self) -> Arc<TrustEngine> {
1014 self.adaptive_dht.trust_engine().clone()
1015 }
1016
1017 pub async fn report_trust_event(&self, peer_id: &PeerId, event: TrustEvent) {
1031 self.adaptive_dht.report_trust_event(peer_id, event).await;
1032 }
1033
1034 pub fn peer_trust(&self, peer_id: &PeerId) -> f64 {
1038 self.adaptive_dht.peer_trust(peer_id)
1039 }
1040
1041 pub fn adaptive_dht(&self) -> &AdaptiveDHT {
1043 &self.adaptive_dht
1044 }
1045
1046 pub async fn send_request(
1079 &self,
1080 peer_id: &PeerId,
1081 protocol: &str,
1082 data: Vec<u8>,
1083 timeout: Duration,
1084 ) -> Result<PeerResponse> {
1085 let result = self
1086 .send_request_reconnecting(peer_id, protocol, data, timeout)
1087 .await;
1088 if let Err(ref e) = result {
1089 let event = if matches!(e, P2PError::Timeout(_)) {
1090 TrustEvent::ConnectionTimeout
1091 } else {
1092 TrustEvent::ConnectionFailed
1093 };
1094 self.report_trust_event(peer_id, event).await;
1095 }
1096 result
1097 }
1098
1099 async fn send_request_reconnecting(
1116 &self,
1117 peer_id: &PeerId,
1118 protocol: &str,
1119 data: Vec<u8>,
1120 timeout: Duration,
1121 ) -> Result<PeerResponse> {
1122 let existing_channels = self.transport.channels_for_peer(peer_id).await;
1127
1128 if existing_channels.is_empty() {
1131 {
1136 let lock = self.reconnect_lock_for(peer_id);
1137 let _guard = lock.lock().await;
1138 if !self.transport.is_peer_connected(peer_id).await {
1140 self.ensure_channel(peer_id, &[], &[], &[]).await?;
1141 }
1142 }
1143 return self
1144 .transport
1145 .send_request(peer_id, protocol, data, timeout)
1146 .await;
1147 }
1148
1149 let saved_addrs: Vec<MultiAddr> = self
1152 .transport
1153 .peer_info(peer_id)
1154 .await
1155 .map(|info| info.addresses)
1156 .unwrap_or_default();
1157
1158 let retry_data = data.clone();
1161
1162 match self
1164 .transport
1165 .send_request(peer_id, protocol, data, timeout)
1166 .await
1167 {
1168 Ok(resp) => return Ok(resp),
1169 Err(e) => {
1170 if !e.is_stale_channel_send_failure() {
1174 return Err(e);
1175 }
1176 debug!(
1177 peer = %peer_id.to_hex(),
1178 error = %e,
1179 "stale channel request failed, attempting reconnect",
1180 );
1181 }
1182 }
1183
1184 {
1189 let lock = self.reconnect_lock_for(peer_id);
1190 let _guard = lock.lock().await;
1191
1192 if self.transport.is_peer_connected(peer_id).await {
1194 for channel_id in &existing_channels {
1197 self.transport.disconnect_channel(channel_id).await;
1198 }
1199 } else {
1200 self.ensure_channel(peer_id, &[], &saved_addrs, &existing_channels)
1201 .await?;
1202 }
1203 }
1204 self.transport
1205 .send_request(peer_id, protocol, retry_data, timeout)
1206 .await
1207 }
1208
1209 pub async fn send_response(
1210 &self,
1211 peer_id: &PeerId,
1212 protocol: &str,
1213 message_id: &str,
1214 data: Vec<u8>,
1215 ) -> Result<()> {
1216 self.transport
1217 .send_response(peer_id, protocol, message_id, data)
1218 .await
1219 }
1220
1221 pub fn parse_request_envelope(data: &[u8]) -> Option<(String, bool, Vec<u8>)> {
1222 crate::transport_handle::TransportHandle::parse_request_envelope(data)
1223 }
1224
1225 pub async fn subscribe(&self, topic: &str) -> Result<()> {
1226 self.transport.subscribe(topic).await
1227 }
1228
1229 pub async fn publish(&self, topic: &str, data: &[u8]) -> Result<()> {
1230 self.transport.publish(topic, data).await
1231 }
1232
1233 pub fn config(&self) -> &NodeConfig {
1235 &self.config
1236 }
1237
1238 pub async fn start(&self) -> Result<()> {
1240 info!("Starting P2P node...");
1241
1242 self.transport.start_network_listeners().await?;
1244
1245 self.adaptive_dht.start().await?;
1247
1248 let listen_addrs = self.transport.listen_addrs().await;
1250 info!("P2P node started on addresses: {:?}", listen_addrs);
1251
1252 let close_group_cache = if let Some(ref dir) = self.config.close_group_cache_dir {
1259 match CloseGroupCache::load_from_dir(dir).await {
1260 Ok(Some(cache)) => {
1261 let now_epoch = SystemTime::now()
1262 .duration_since(UNIX_EPOCH)
1263 .map_or(0, |duration| duration.as_secs());
1264 if cache.is_stale(now_epoch, self.config.close_group_cache_max_age) {
1265 warn!(
1266 cache_age_secs = now_epoch.saturating_sub(cache.saved_at_epoch_secs),
1267 max_age_secs = self
1268 .config
1269 .close_group_cache_max_age
1270 .map(|age| age.as_secs()),
1271 "Close group cache is stale; skipping cached trust and Priority-0 peers"
1272 );
1273 None
1274 } else {
1275 let original_count = cache.peers.len();
1278 let cache = CloseGroupCache {
1279 peers: cache
1280 .peers
1281 .into_iter()
1282 .filter(|p| p.trust.score.is_finite())
1283 .collect(),
1284 ..cache
1285 };
1286 let filtered_count = original_count - cache.peers.len();
1287 if filtered_count > 0 {
1288 warn!(
1289 "Filtered {filtered_count} peers with non-finite trust scores from close group cache"
1290 );
1291 }
1292
1293 let trust_snapshot = TrustSnapshot {
1294 peers: cache
1295 .peers
1296 .iter()
1297 .map(|p| (p.peer_id, p.trust.clone()))
1298 .collect(),
1299 };
1300 self.adaptive_dht
1301 .trust_engine()
1302 .import_snapshot(&trust_snapshot);
1303 info!(
1304 cache_age_secs = now_epoch.saturating_sub(cache.saved_at_epoch_secs),
1305 saved_at_epoch_secs = cache.saved_at_epoch_secs,
1306 "Loaded {} peers from close group cache (trust scores imported)",
1307 cache.peers.len()
1308 );
1309 Some(cache)
1310 }
1311 }
1312 Ok(None) => {
1313 debug!(
1314 "No close group cache found in {}, fresh start",
1315 dir.display()
1316 );
1317 None
1318 }
1319 Err(e) => {
1320 warn!(
1321 "Failed to load close group cache from {}: {e}",
1322 dir.display()
1323 );
1324 None
1325 }
1326 }
1327 } else {
1328 None
1329 };
1330
1331 self.connect_bootstrap_peers(close_group_cache.as_ref())
1333 .await?;
1334
1335 {
1342 let dht = self.adaptive_dht.dht_manager();
1343 let rt_size = dht.get_routing_table_size().await;
1344 dht.emit_event(DhtNetworkEvent::BootstrapComplete { num_peers: rt_size });
1345 }
1346
1347 if self.config.mode != NodeMode::Client {
1361 spawn_acquisition_driver(
1362 self.adaptive_dht.dht_manager().clone(),
1363 Arc::clone(&self.transport),
1364 Arc::clone(&self.relayer_peer_id),
1365 Arc::clone(&self.relay_address),
1366 self.shutdown.clone(),
1367 );
1368 } else {
1369 info!("client mode — skipping relay acquisition driver");
1370 }
1371
1372 if let Some(dir) = self.config.close_group_cache_dir.clone() {
1373 let interval = self
1374 .config
1375 .dht_config
1376 .refresh_interval
1377 .max(MIN_CLOSE_GROUP_CACHE_SAVE_INTERVAL);
1378 let mut task = self.close_group_cache_save_handle.lock().await;
1379 if task.is_none() {
1380 let dht_manager = Arc::clone(self.adaptive_dht.dht_manager());
1381 let trust_engine = Arc::clone(self.adaptive_dht.trust_engine());
1382 let peer_id = self.peer_id;
1383 let k_value = self.config.dht_config.k_value;
1384 let shutdown = self.close_group_cache_save_shutdown.clone();
1385 *task = Some(tokio::spawn(periodic_close_group_cache_save(
1386 dht_manager,
1387 trust_engine,
1388 peer_id,
1389 k_value,
1390 dir,
1391 interval,
1392 shutdown,
1393 )));
1394 info!(
1395 interval_secs = interval.as_secs(),
1396 "Started periodic close group cache persistence"
1397 );
1398 }
1399 }
1400
1401 self.is_started
1402 .store(true, std::sync::atomic::Ordering::Release);
1403
1404 Ok(())
1405 }
1406
1407 pub async fn run(&self) -> Result<()> {
1412 if !self.is_running() {
1413 self.start().await?;
1414 }
1415
1416 info!("P2P node running...");
1417
1418 self.shutdown.cancelled().await;
1421
1422 info!("P2P node stopped");
1423 Ok(())
1424 }
1425
1426 pub async fn stop(&self) -> Result<()> {
1428 info!("Stopping P2P node...");
1429
1430 self.close_group_cache_save_shutdown.cancel();
1433 let cache_task = self.close_group_cache_save_handle.lock().await.take();
1434 if let Some(cache_task) = cache_task
1435 && let Err(error) = cache_task.await
1436 {
1437 warn!("Periodic close group cache task failed during shutdown: {error}");
1438 }
1439
1440 if let Some(ref dir) = self.config.close_group_cache_dir
1442 && let Err(e) = self.save_close_group_cache(dir, "shutdown").await
1443 {
1444 warn!("Failed to save close group cache on shutdown: {e}");
1445 }
1446
1447 self.shutdown.cancel();
1449
1450 self.adaptive_dht.stop().await?;
1452
1453 self.transport.stop().await?;
1455
1456 self.is_started
1457 .store(false, std::sync::atomic::Ordering::Release);
1458
1459 info!("P2P node stopped");
1460 Ok(())
1461 }
1462
1463 pub async fn shutdown(&self) -> Result<()> {
1465 self.stop().await
1466 }
1467
1468 pub fn is_running(&self) -> bool {
1470 self.is_started.load(std::sync::atomic::Ordering::Acquire) && !self.shutdown.is_cancelled()
1471 }
1472
1473 pub async fn listen_addrs(&self) -> Vec<MultiAddr> {
1475 self.transport.listen_addrs().await
1476 }
1477
1478 pub async fn connected_peers(&self) -> Vec<PeerId> {
1480 self.transport.connected_peers().await
1481 }
1482
1483 pub async fn peer_count(&self) -> usize {
1485 self.transport.peer_count().await
1486 }
1487
1488 pub async fn peer_info(&self, peer_id: &PeerId) -> Option<PeerInfo> {
1490 self.transport.peer_info(peer_id).await
1491 }
1492
1493 #[allow(dead_code)]
1495 pub(crate) async fn get_channel_id_by_address(&self, addr: &MultiAddr) -> Option<String> {
1496 self.transport.get_channel_id_by_address(addr).await
1497 }
1498
1499 #[allow(dead_code)]
1501 pub(crate) async fn list_active_connections(&self) -> Vec<(String, Vec<MultiAddr>)> {
1502 self.transport.list_active_connections().await
1503 }
1504
1505 #[allow(dead_code)]
1507 pub(crate) async fn remove_channel(&self, channel_id: &str) -> bool {
1508 self.transport.remove_channel(channel_id).await
1509 }
1510
1511 pub(crate) async fn disconnect_channel(&self, channel_id: &str) {
1516 self.transport.disconnect_channel(channel_id).await;
1517 }
1518
1519 pub async fn is_peer_connected(&self, peer_id: &PeerId) -> bool {
1521 self.transport.is_peer_connected(peer_id).await
1522 }
1523
1524 pub async fn connect_peer(&self, address: &MultiAddr) -> Result<String> {
1535 self.transport.connect_peer(address).await
1536 }
1537
1538 pub async fn connect_peer_typed(
1545 &self,
1546 address: &MultiAddr,
1547 kind: AddressType,
1548 ) -> Result<String> {
1549 self.transport.connect_peer_typed(address, kind).await
1550 }
1551
1552 pub async fn wait_for_peer_identity(
1559 &self,
1560 channel_id: &str,
1561 timeout: Duration,
1562 ) -> Result<PeerId> {
1563 self.transport
1564 .wait_for_peer_identity(channel_id, timeout)
1565 .await
1566 }
1567
1568 pub async fn disconnect_peer(&self, peer_id: &PeerId) -> Result<()> {
1570 self.transport.disconnect_peer(peer_id).await
1571 }
1572
1573 #[allow(dead_code)]
1575 pub(crate) async fn is_connection_active(&self, channel_id: &str) -> bool {
1576 self.transport.is_connection_active(channel_id).await
1577 }
1578
1579 pub async fn send_message(
1593 &self,
1594 peer_id: &PeerId,
1595 protocol: &str,
1596 data: Vec<u8>,
1597 addrs: &[MultiAddr],
1598 ) -> Result<()> {
1599 let existing_channels = self.transport.channels_for_peer(peer_id).await;
1604
1605 if existing_channels.is_empty() {
1608 let lock = self.reconnect_lock_for(peer_id);
1609 let _guard = lock.lock().await;
1610
1611 if self.transport.is_peer_connected(peer_id).await {
1613 return self.transport.send_message(peer_id, protocol, data).await;
1614 }
1615
1616 return self
1617 .reconnect_and_send(peer_id, protocol, data, addrs, &[], &[])
1618 .await;
1619 }
1620
1621 let saved_addrs: Vec<MultiAddr> = self
1624 .transport
1625 .peer_info(peer_id)
1626 .await
1627 .map(|info| info.addresses)
1628 .unwrap_or_default();
1629
1630 let retry_data = data.clone();
1633
1634 let send_result = self.transport.send_message(peer_id, protocol, data).await;
1636 match send_result {
1637 Ok(()) => return Ok(()),
1638 Err(e) => {
1639 if !e.is_stale_channel_send_failure() {
1640 debug!(
1641 peer = %peer_id.to_hex(),
1642 error = %e,
1643 "send failed during active channel use, not reconnecting",
1644 );
1645 return Err(e);
1646 }
1647
1648 debug!(
1649 peer = %peer_id.to_hex(),
1650 error = %e,
1651 "stale channel send failed, attempting reconnect",
1652 );
1653 }
1654 }
1655
1656 let lock = self.reconnect_lock_for(peer_id);
1659 let _guard = lock.lock().await;
1660
1661 if self.transport.is_peer_connected(peer_id).await {
1663 for channel_id in &existing_channels {
1667 self.transport.disconnect_channel(channel_id).await;
1668 }
1669 return self
1670 .transport
1671 .send_message(peer_id, protocol, retry_data)
1672 .await;
1673 }
1674
1675 self.reconnect_and_send(
1676 peer_id,
1677 protocol,
1678 retry_data,
1679 addrs,
1680 &saved_addrs,
1681 &existing_channels,
1682 )
1683 .await
1684 }
1685
1686 async fn ensure_channel(
1701 &self,
1702 peer_id: &PeerId,
1703 addrs: &[MultiAddr],
1704 saved_addrs: &[MultiAddr],
1705 stale_channels: &[String],
1706 ) -> Result<()> {
1707 if !stale_channels.is_empty() {
1713 for channel_id in stale_channels {
1714 self.transport.disconnect_channel(channel_id).await;
1715 }
1716 tokio::time::sleep(QUIC_TEARDOWN_GRACE).await;
1717 }
1718
1719 let candidates = self
1720 .resolve_dial_candidates(peer_id, addrs, saved_addrs)
1721 .await;
1722 if candidates.is_empty() {
1723 return Err(P2PError::Network(NetworkError::PeerNotFound(
1724 peer_id.to_hex().into(),
1725 )));
1726 }
1727 self.adaptive_dht
1728 .ensure_peer_channel(peer_id, &candidates)
1729 .await
1730 }
1731
1732 async fn reconnect_and_send(
1734 &self,
1735 peer_id: &PeerId,
1736 protocol: &str,
1737 data: Vec<u8>,
1738 addrs: &[MultiAddr],
1739 saved_addrs: &[MultiAddr],
1740 stale_channels: &[String],
1741 ) -> Result<()> {
1742 self.ensure_channel(peer_id, addrs, saved_addrs, stale_channels)
1743 .await?;
1744 self.transport.send_message(peer_id, protocol, data).await
1746 }
1747
1748 async fn resolve_dial_candidates(
1757 &self,
1758 peer_id: &PeerId,
1759 caller_addrs: &[MultiAddr],
1760 saved_addrs: &[MultiAddr],
1761 ) -> Vec<(MultiAddr, AddressType)> {
1762 let dht_candidates = self
1763 .adaptive_dht
1764 .peer_addresses_for_dial_typed(peer_id)
1765 .await;
1766 let preferred = if !caller_addrs.is_empty() {
1767 caller_addrs
1768 } else if !saved_addrs.is_empty() {
1769 saved_addrs
1770 } else {
1771 return dht_candidates;
1772 };
1773
1774 preferred
1775 .iter()
1776 .filter(|a| {
1777 let dialable = a
1778 .dialable_socket_addr()
1779 .is_some_and(|sa| !sa.ip().is_unspecified());
1780 if !dialable {
1781 trace!(address = %a, "skipping non-dialable address");
1782 }
1783 dialable
1784 })
1785 .map(|addr| {
1786 let kind = dht_candidates
1787 .iter()
1788 .find_map(|(candidate, kind)| (candidate == addr).then_some(*kind))
1789 .unwrap_or(AddressType::Unverified);
1790 (addr.clone(), kind)
1791 })
1792 .collect()
1793 }
1794
1795 fn reconnect_lock_for(&self, peer_id: &PeerId) -> Arc<TokioMutex<()>> {
1797 self.reconnect_locks
1798 .lock()
1799 .entry(*peer_id)
1800 .or_insert_with(|| Arc::new(TokioMutex::new(())))
1801 .clone()
1802 }
1803}
1804
1805fn protocol_error(msg: impl std::fmt::Display) -> P2PError {
1807 P2PError::Network(NetworkError::ProtocolError(msg.to_string().into()))
1808}
1809
1810pub(crate) fn broadcast_event(tx: &broadcast::Sender<P2PEvent>, event: P2PEvent) {
1812 if let Err(e) = tx.send(event) {
1813 tracing::trace!("Event broadcast has no receivers: {e}");
1814 }
1815}
1816
1817pub(crate) struct ParsedMessage {
1823 pub(crate) event: P2PEvent,
1825 pub(crate) authenticated_node_id: Option<PeerId>,
1827 pub(crate) user_agent: String,
1829 pub(crate) payload_len: usize,
1832}
1833
1834pub(crate) fn parse_protocol_message(bytes: &[u8], source: &str) -> Option<ParsedMessage> {
1844 let message: WireMessage = postcard::from_bytes(bytes).ok()?;
1845 let transport_source = source.parse::<SocketAddr>().ok().map(MultiAddr::quic);
1846
1847 let authenticated_node_id = if !message.signature.is_empty() {
1849 match verify_message_signature(&message) {
1850 Ok(peer_id) => {
1851 debug!(
1852 "Message from {} authenticated as app-level NodeId {}",
1853 source, peer_id
1854 );
1855 Some(peer_id)
1856 }
1857 Err(e) => {
1858 warn!(
1859 "Rejecting message from {}: signature verification failed: {}",
1860 source, e
1861 );
1862 return None;
1863 }
1864 }
1865 } else {
1866 None
1867 };
1868
1869 debug!(
1870 "Parsed P2PEvent::Message - topic: {}, source: {:?} (transport: {}, logical: {}), payload_len: {}",
1871 message.protocol,
1872 authenticated_node_id,
1873 source,
1874 message.from,
1875 message.data.len()
1876 );
1877
1878 let payload_len = message.data.len();
1879 Some(ParsedMessage {
1880 event: P2PEvent::Message {
1881 topic: message.protocol,
1882 source: authenticated_node_id,
1883 transport_source,
1884 timestamp: message.timestamp,
1885 data: message.data,
1886 },
1887 authenticated_node_id,
1888 payload_len,
1889 user_agent: message.user_agent,
1890 })
1891}
1892
1893fn verify_message_signature(message: &WireMessage) -> std::result::Result<PeerId, String> {
1900 let pubkey = MlDsaPublicKey::from_bytes(&message.public_key)
1901 .map_err(|e| format!("invalid public key: {e:?}"))?;
1902
1903 let peer_id = peer_id_from_public_key(&pubkey);
1904
1905 if message.from != peer_id {
1907 return Err(format!(
1908 "from field mismatch: message claims '{}' but public key derives '{}'",
1909 message.from, peer_id
1910 ));
1911 }
1912
1913 let signable = postcard::to_stdvec(&(
1914 &message.protocol,
1915 &message.data as &[u8],
1916 &message.from,
1917 message.timestamp,
1918 &message.user_agent,
1919 ))
1920 .map_err(|e| format!("failed to serialize signable bytes: {e}"))?;
1921
1922 let sig = MlDsaSignature::from_bytes(&message.signature)
1923 .map_err(|e| format!("invalid signature: {e:?}"))?;
1924
1925 let valid = crate::quantum_crypto::ml_dsa_verify(&pubkey, &signable, &sig)
1926 .map_err(|e| format!("verification error: {e}"))?;
1927
1928 if valid {
1929 Ok(peer_id)
1930 } else {
1931 Err("signature is invalid".to_string())
1932 }
1933}
1934
1935impl P2PNode {
1936 pub fn subscribe_events(&self) -> broadcast::Receiver<P2PEvent> {
1938 self.transport.subscribe_events()
1939 }
1940
1941 pub fn events(&self) -> broadcast::Receiver<P2PEvent> {
1943 self.subscribe_events()
1944 }
1945
1946 pub fn uptime(&self) -> Duration {
1948 self.start_time.elapsed()
1949 }
1950
1951 pub async fn health_check(&self) -> Result<()> {
1964 let peer_count = self.peer_count().await;
1965 if peer_count > self.config.max_connections {
1966 Err(protocol_error(format!(
1967 "Too many connections: {peer_count}"
1968 )))
1969 } else {
1970 Ok(())
1971 }
1972 }
1973
1974 pub fn dht_manager(&self) -> &Arc<DhtNetworkManager> {
1976 self.adaptive_dht.dht_manager()
1977 }
1978
1979 pub fn dht(&self) -> &Arc<DhtNetworkManager> {
1981 self.dht_manager()
1982 }
1983
1984 async fn connect_bootstrap_peers(
1991 &self,
1992 close_group_cache: Option<&CloseGroupCache>,
1993 ) -> Result<()> {
1994 let mut serial_addr_sets: Vec<(PeerId, Vec<MultiAddr>)> = Vec::new();
1999 let mut parallel_addr_sets: Vec<Vec<MultiAddr>> = Vec::new();
2000 let mut seen_addresses = std::collections::HashSet::new();
2001
2002 if let Some(cache) = close_group_cache {
2008 let mut sorted_peers: Vec<&CachedCloseGroupPeer> = cache.peers.iter().collect();
2009 sorted_peers.sort_by(|a, b| {
2010 let score_ord = match b.trust.score.partial_cmp(&a.trust.score) {
2014 Some(ord) => ord,
2015 None => {
2016 if a.trust.score.is_nan() {
2017 std::cmp::Ordering::Greater } else {
2019 std::cmp::Ordering::Less }
2021 }
2022 };
2023 score_ord.then_with(|| {
2024 let da = self.peer_id.xor_distance(&a.peer_id);
2025 let db = self.peer_id.xor_distance(&b.peer_id);
2026 da.cmp(&db)
2027 })
2028 });
2029
2030 let mut added_from_close_group = 0usize;
2031 for peer in &sorted_peers {
2032 let new_addresses: Vec<MultiAddr> = peer
2033 .addresses
2034 .iter()
2035 .filter(|a| {
2036 a.dialable_socket_addr()
2037 .is_some_and(|sa| !seen_addresses.contains(&sa))
2038 })
2039 .cloned()
2040 .collect();
2041
2042 if !new_addresses.is_empty() {
2043 for addr in &new_addresses {
2044 if let Some(sa) = addr.socket_addr() {
2045 seen_addresses.insert(sa);
2046 }
2047 }
2048 serial_addr_sets.push((peer.peer_id, new_addresses));
2049 added_from_close_group += 1;
2050 }
2051 }
2052 if added_from_close_group > 0 {
2053 info!(
2054 "Added {} close group cache peers (highest trust first)",
2055 added_from_close_group
2056 );
2057 }
2058 }
2059
2060 if !self.config.bootstrap_peers.is_empty() {
2062 info!(
2063 "Using {} configured bootstrap peers (priority)",
2064 self.config.bootstrap_peers.len()
2065 );
2066 for multiaddr in &self.config.bootstrap_peers {
2067 let Some(socket_addr) = multiaddr.dialable_socket_addr() else {
2068 warn!("Skipping non-QUIC bootstrap peer: {}", multiaddr);
2069 continue;
2070 };
2071 seen_addresses.insert(socket_addr);
2072 parallel_addr_sets.push(vec![multiaddr.clone()]);
2073 }
2074 }
2075
2076 if serial_addr_sets.is_empty() && parallel_addr_sets.is_empty() {
2077 info!("No bootstrap peers configured");
2078 return Ok(());
2079 }
2080
2081 let identity_timeout = Duration::from_secs(BOOTSTRAP_IDENTITY_TIMEOUT_SECS);
2084 let mut successful_connections = 0;
2085 let cache_dial_candidates = serial_addr_sets.len();
2086 let configured_dial_candidates = parallel_addr_sets.len();
2087 let mut cache_dial_successes = 0usize;
2088 let mut configured_dial_successes = 0usize;
2089 let mut connected_peer_ids: Vec<PeerId> = Vec::new();
2090
2091 let client_mode = matches!(self.config.mode, NodeMode::Client);
2093 for (expected_peer_id, addrs) in &serial_addr_sets {
2094 if let Some(peer_id) = self
2095 .dial_bootstrap_addr_set(addrs, identity_timeout, "cache", Some(*expected_peer_id))
2096 .await
2097 {
2098 successful_connections += 1;
2099 cache_dial_successes += 1;
2100 connected_peer_ids.push(peer_id);
2101 if client_mode && successful_connections >= CLIENT_BOOTSTRAP_TARGET {
2102 debug!(
2103 "Client bootstrap target reached ({successful_connections} peers) — skipping remaining serial dials"
2104 );
2105 break;
2106 }
2107 }
2108 }
2109
2110 if !client_mode || successful_connections < CLIENT_BOOTSTRAP_TARGET {
2115 let mut parallel_stream =
2116 futures::stream::iter(parallel_addr_sets.into_iter().map(|addrs| async move {
2117 self.dial_bootstrap_addr_set(&addrs, identity_timeout, "configured", None)
2118 .await
2119 }))
2120 .buffer_unordered(MAX_CONCURRENT_BOOTSTRAP_DIALS);
2121 while let Some(result) = parallel_stream.next().await {
2122 if let Some(peer_id) = result {
2123 successful_connections += 1;
2124 configured_dial_successes += 1;
2125 connected_peer_ids.push(peer_id);
2126 if client_mode && successful_connections >= CLIENT_BOOTSTRAP_TARGET {
2127 debug!(
2128 "Client bootstrap target reached ({successful_connections} peers) — cancelling pending dials"
2129 );
2130 break;
2131 }
2132 }
2133 }
2134 }
2138
2139 info!(
2140 cache_dial_candidates,
2141 cache_dial_successes,
2142 configured_dial_candidates,
2143 configured_dial_successes,
2144 outbound_bootstrap_successes = successful_connections,
2145 outbound_reachable = successful_connections > 0,
2146 "Bootstrap reachability summary"
2147 );
2148
2149 if successful_connections == 0 {
2150 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
2154 let transport_peers = self.transport.connected_peers().await;
2155 if !transport_peers.is_empty() {
2156 info!(
2157 "No outbound bootstrap succeeded, but {} inbound peer(s) connected — proceeding with DHT bootstrap",
2158 transport_peers.len()
2159 );
2160 connected_peer_ids = transport_peers;
2161 successful_connections = connected_peer_ids.len();
2162 } else {
2163 warn!("Failed to connect to any bootstrap peers");
2164 return Ok(());
2167 }
2168 }
2169
2170 info!(
2171 "Successfully connected to {} bootstrap peers",
2172 successful_connections
2173 );
2174
2175 match self
2177 .dht_manager()
2178 .bootstrap_from_peers(&connected_peer_ids)
2179 .await
2180 {
2181 Ok(count) => info!("DHT peer discovery found {} peers", count),
2182 Err(e) => warn!("DHT peer discovery failed: {}", e),
2183 }
2184
2185 if matches!(self.config.mode, NodeMode::Node) {
2195 const SELF_LOOKUP_ROUNDS: u8 = 2;
2196 for i in 1..=SELF_LOOKUP_ROUNDS {
2197 if let Err(e) = self.dht_manager().trigger_self_lookup().await {
2198 warn!("Post-bootstrap self-lookup {i}/{SELF_LOOKUP_ROUNDS} failed: {e}");
2199 } else {
2200 debug!("Post-bootstrap self-lookup {i}/{SELF_LOOKUP_ROUNDS} completed");
2201 }
2202 }
2203 } else {
2204 debug!("Skipping post-bootstrap self-lookups (client mode)");
2205 }
2206
2207 self.is_bootstrapped.store(true, Ordering::SeqCst);
2210 info!(
2211 "Bootstrap complete: connected to {} peers, initiated {} discovery requests",
2212 successful_connections,
2213 connected_peer_ids.len()
2214 );
2215
2216 if let Some(ref dir) = self.config.close_group_cache_dir
2219 && let Err(e) = self.save_close_group_cache(dir, "post_bootstrap").await
2220 {
2221 warn!("Failed to save close group cache after bootstrap: {e}");
2222 }
2223
2224 Ok(())
2225 }
2226
2227 async fn dial_bootstrap_addr_set(
2232 &self,
2233 addrs: &[MultiAddr],
2234 identity_timeout: Duration,
2235 source: &'static str,
2236 expected_peer_id: Option<PeerId>,
2237 ) -> Option<PeerId> {
2238 for addr in addrs {
2239 match self
2244 .transport
2245 .connect_peer_typed(addr, AddressType::Unverified)
2246 .await
2247 {
2248 Ok(channel_id) => match self
2249 .transport
2250 .wait_for_peer_identity(&channel_id, identity_timeout)
2251 .await
2252 {
2253 Ok(real_peer_id) => {
2254 if !bootstrap_peer_identity_matches(expected_peer_id, real_peer_id) {
2255 warn!(
2256 bootstrap_source = source,
2257 address = %addr,
2258 expected_peer_id = ?expected_peer_id,
2259 actual_peer_id = %real_peer_id,
2260 "Bootstrap cache identity mismatch; rejecting connection"
2261 );
2262 self.disconnect_channel(&channel_id).await;
2263 continue;
2264 }
2265 info!(
2266 bootstrap_source = source,
2267 outcome = "ok",
2268 address = %addr,
2269 peer_id = %real_peer_id,
2270 "Bootstrap dial completed"
2271 );
2272 return Some(real_peer_id);
2273 }
2274 Err(e) => {
2275 info!(
2276 bootstrap_source = source,
2277 outcome = "identity_timeout",
2278 address = %addr,
2279 error = %e,
2280 "Bootstrap dial failed"
2281 );
2282 warn!(
2283 "Timeout waiting for identity from bootstrap peer {}: {}, \
2284 closing channel {}",
2285 addr, e, channel_id
2286 );
2287 self.disconnect_channel(&channel_id).await;
2288 }
2289 },
2290 Err(e) => {
2291 info!(
2292 bootstrap_source = source,
2293 outcome = "connect_error",
2294 address = %addr,
2295 error = %e,
2296 "Bootstrap dial failed"
2297 );
2298 warn!("Failed to connect to bootstrap peer {}: {}", addr, e);
2299 }
2300 }
2301 }
2302 None
2303 }
2304
2305 async fn save_close_group_cache(
2307 &self,
2308 dir: &Path,
2309 save_reason: &'static str,
2310 ) -> anyhow::Result<()> {
2311 save_close_group_cache_snapshot(
2312 self.dht_manager(),
2313 self.adaptive_dht.trust_engine(),
2314 self.peer_id,
2315 self.config.dht_config.k_value,
2316 dir,
2317 save_reason,
2318 )
2319 .await
2320 }
2321
2322 }
2324
2325async fn save_close_group_cache_snapshot(
2330 dht_manager: &DhtNetworkManager,
2331 trust_engine: &TrustEngine,
2332 peer_id: PeerId,
2333 k_value: usize,
2334 dir: &Path,
2335 save_reason: &'static str,
2336) -> anyhow::Result<()> {
2337 let key: crate::dht::Key = *peer_id.as_bytes();
2338 let close_group = dht_manager.find_closest_nodes_local(&key, k_value).await;
2339
2340 let now_epoch = SystemTime::now()
2341 .duration_since(UNIX_EPOCH)
2342 .map_or(0, |duration| duration.as_secs());
2343 let peers: Vec<CachedCloseGroupPeer> = close_group
2344 .into_iter()
2345 .filter_map(|dht_node| {
2346 let score = trust_engine.score(&dht_node.peer_id);
2347 if !score.is_finite() {
2350 return None;
2351 }
2352 Some(CachedCloseGroupPeer {
2353 peer_id: dht_node.peer_id,
2354 addresses: dht_node.addresses,
2355 trust: TrustRecord {
2356 score,
2357 last_updated_epoch_secs: now_epoch,
2358 },
2359 })
2360 })
2361 .collect();
2362
2363 let peer_count = peers.len();
2364 let cache = CloseGroupCache {
2365 peers,
2366 saved_at_epoch_secs: now_epoch,
2367 };
2368
2369 cache.save_to_dir(dir).await?;
2370 info!(
2371 save_reason,
2372 "Saved {} close group peers to cache in {}",
2373 peer_count,
2374 dir.display()
2375 );
2376 Ok(())
2377}
2378
2379async fn periodic_close_group_cache_save(
2381 dht_manager: Arc<DhtNetworkManager>,
2382 trust_engine: Arc<TrustEngine>,
2383 peer_id: PeerId,
2384 k_value: usize,
2385 dir: PathBuf,
2386 interval: Duration,
2387 shutdown: CancellationToken,
2388) {
2389 let start = tokio::time::Instant::now() + interval;
2390 let mut ticker = tokio::time::interval_at(start, interval);
2391 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2392
2393 loop {
2394 tokio::select! {
2395 biased;
2396 () = shutdown.cancelled() => break,
2397 _ = ticker.tick() => {
2398 if let Err(error) = save_close_group_cache_snapshot(
2399 &dht_manager,
2400 &trust_engine,
2401 peer_id,
2402 k_value,
2403 &dir,
2404 "periodic",
2405 ).await {
2406 warn!("Periodic close group cache save failed: {error}");
2407 }
2408 }
2409 }
2410 }
2411}
2412
2413#[async_trait::async_trait]
2415#[allow(dead_code)]
2416pub trait NetworkSender: Send + Sync {
2417 async fn send_message(&self, peer_id: &PeerId, protocol: &str, data: Vec<u8>) -> Result<()>;
2419
2420 fn local_peer_id(&self) -> PeerId;
2422}
2423
2424pub(crate) fn register_new_channel(
2434 peers: &DashMap<String, PeerInfo>,
2435 channel_id: &str,
2436 remote_addr: &MultiAddr,
2437) {
2438 let peer_info = PeerInfo {
2439 channel_id: channel_id.to_owned(),
2440 addresses: vec![remote_addr.clone()],
2441 connected_at: tokio::time::Instant::now(),
2442 last_seen: tokio::time::Instant::now(),
2443 status: ConnectionStatus::Connected,
2444 protocols: vec!["p2p-core/1.0.0".to_string()],
2445 heartbeat_count: 0,
2446 };
2447 peers.insert(channel_id.to_owned(), peer_info);
2448}
2449
2450#[cfg(test)]
2451mod tests {
2452 use super::*;
2453 use std::time::Duration;
2455 use tokio::time::timeout;
2456
2457 const TEST_MAX_MESSAGE_SIZE: usize = 2 * 1024 * 1024;
2459
2460 #[test]
2461 fn cached_bootstrap_identity_must_match_handshake_peer() {
2462 let expected = PeerId::from_bytes([1; 32]);
2463 let other = PeerId::from_bytes([2; 32]);
2464
2465 assert!(bootstrap_peer_identity_matches(Some(expected), expected));
2466 assert!(!bootstrap_peer_identity_matches(Some(expected), other));
2467 assert!(bootstrap_peer_identity_matches(None, other));
2468 }
2469
2470 fn create_test_node_config() -> NodeConfig {
2476 NodeConfig {
2477 local: true,
2478 port: 0,
2479 ipv6: true,
2480 bootstrap_peers: vec![],
2481 connection_timeout: Duration::from_secs(2),
2482 max_connections: 100,
2483 dht_config: DHTConfig::default(),
2484 diversity_config: None,
2485 max_message_size: None,
2486 node_identity: None,
2487 mode: NodeMode::default(),
2488 custom_user_agent: None,
2489 allow_loopback: true,
2490 adaptive_dht_config: AdaptiveDhtConfig::default(),
2491 close_group_cache_dir: None,
2492 close_group_cache_max_age: default_close_group_cache_max_age(),
2493 }
2494 }
2495
2496 #[tokio::test]
2500 async fn test_node_config_default() {
2501 let config = NodeConfig::default();
2502
2503 assert_eq!(config.listen_addrs().len(), 2); assert_eq!(config.max_connections, 10000);
2505 assert_eq!(config.connection_timeout, Duration::from_secs(25));
2506 assert_eq!(
2507 config.close_group_cache_max_age,
2508 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
2509 );
2510 }
2511
2512 #[test]
2513 fn close_group_cache_builder_default_and_explicit_disable() {
2514 let defaulted = NodeConfig::builder().build().unwrap();
2515 assert_eq!(
2516 defaulted.close_group_cache_max_age,
2517 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
2518 );
2519
2520 let disabled = NodeConfig::builder()
2521 .close_group_cache_max_age(None)
2522 .build()
2523 .unwrap();
2524 assert_eq!(disabled.close_group_cache_max_age, None);
2525 let disabled_json = serde_json::to_string(&disabled).unwrap();
2526 let disabled_roundtrip: NodeConfig = serde_json::from_str(&disabled_json).unwrap();
2527 assert_eq!(disabled_roundtrip.close_group_cache_max_age, None);
2528
2529 let custom = NodeConfig::builder()
2530 .close_group_cache_max_age(Some(Duration::from_secs(120)))
2531 .build()
2532 .unwrap();
2533 assert_eq!(
2534 custom.close_group_cache_max_age,
2535 Some(Duration::from_secs(120))
2536 );
2537 }
2538
2539 #[test]
2540 fn old_serialized_config_gets_default_cache_max_age() {
2541 let mut value = serde_json::to_value(NodeConfig::default()).unwrap();
2542 value
2543 .as_object_mut()
2544 .unwrap()
2545 .remove("close_group_cache_max_age");
2546
2547 let decoded: NodeConfig = serde_json::from_value(value).unwrap();
2548 assert_eq!(
2549 decoded.close_group_cache_max_age,
2550 Some(Duration::from_secs(DEFAULT_CLOSE_GROUP_CACHE_MAX_AGE_SECS))
2551 );
2552 }
2553
2554 #[tokio::test]
2555 async fn test_dht_config_default() {
2556 let config = DHTConfig::default();
2557
2558 assert_eq!(config.k_value, 20);
2559 assert_eq!(config.alpha_value, 3);
2560 assert_eq!(config.refresh_interval, Duration::from_secs(600));
2561 }
2562
2563 #[test]
2564 fn test_connection_status_variants() {
2565 let connecting = ConnectionStatus::Connecting;
2566 let connected = ConnectionStatus::Connected;
2567 let disconnecting = ConnectionStatus::Disconnecting;
2568 let disconnected = ConnectionStatus::Disconnected;
2569 let failed = ConnectionStatus::Failed("test error".to_string());
2570
2571 assert_eq!(connecting, ConnectionStatus::Connecting);
2572 assert_eq!(connected, ConnectionStatus::Connected);
2573 assert_eq!(disconnecting, ConnectionStatus::Disconnecting);
2574 assert_eq!(disconnected, ConnectionStatus::Disconnected);
2575 assert_ne!(connecting, connected);
2576
2577 if let ConnectionStatus::Failed(msg) = failed {
2578 assert_eq!(msg, "test error");
2579 } else {
2580 panic!("Expected Failed status");
2581 }
2582 }
2583
2584 #[tokio::test]
2585 async fn test_node_creation() -> Result<()> {
2586 let config = create_test_node_config();
2587 let node = P2PNode::new(config).await?;
2588
2589 assert_eq!(node.peer_id().to_hex().len(), 64);
2591 assert!(!node.is_running());
2592 assert_eq!(node.peer_count().await, 0);
2593 assert!(node.connected_peers().await.is_empty());
2594
2595 Ok(())
2596 }
2597
2598 #[tokio::test]
2599 async fn test_node_lifecycle() -> Result<()> {
2600 let config = create_test_node_config();
2601 let node = P2PNode::new(config).await?;
2602
2603 assert!(!node.is_running());
2605
2606 node.start().await?;
2608 assert!(node.is_running());
2609
2610 let listen_addrs = node.listen_addrs().await;
2612 assert!(
2613 !listen_addrs.is_empty(),
2614 "Expected at least one listening address"
2615 );
2616
2617 node.stop().await?;
2619 assert!(!node.is_running());
2620
2621 Ok(())
2622 }
2623
2624 #[tokio::test]
2625 async fn close_group_cache_task_is_joined_on_stop() -> Result<()> {
2626 let cache_dir = tempfile::tempdir().unwrap();
2627 let mut config = create_test_node_config();
2628 config.close_group_cache_dir = Some(cache_dir.path().to_path_buf());
2629 let node = P2PNode::new(config).await?;
2630
2631 node.start().await?;
2632 assert!(node.close_group_cache_save_handle.lock().await.is_some());
2633
2634 node.stop().await?;
2635 assert!(node.close_group_cache_save_handle.lock().await.is_none());
2636
2637 Ok(())
2638 }
2639
2640 #[tokio::test]
2641 async fn test_peer_connection() -> Result<()> {
2642 let config1 = create_test_node_config();
2643 let config2 = create_test_node_config();
2644
2645 let node1 = P2PNode::new(config1).await?;
2646 let node2 = P2PNode::new(config2).await?;
2647
2648 node1.start().await?;
2649 node2.start().await?;
2650
2651 let node2_addr = node2
2652 .listen_addrs()
2653 .await
2654 .into_iter()
2655 .find(|a| a.is_ipv4())
2656 .ok_or_else(|| {
2657 P2PError::Network(crate::error::NetworkError::InvalidAddress(
2658 "Node 2 did not expose an IPv4 listen address".into(),
2659 ))
2660 })?;
2661
2662 let channel_id = node1.connect_peer(&node2_addr).await?;
2665
2666 assert!(node1.is_connection_active(&channel_id).await);
2669
2670 let peer_info = node1.transport.peer_info_by_channel(&channel_id).await;
2672 assert!(peer_info.is_some());
2673 let info = peer_info.expect("Peer info should exist after connect");
2674 assert_eq!(info.channel_id, channel_id);
2675 assert_eq!(info.status, ConnectionStatus::Connected);
2676 assert!(info.protocols.contains(&"p2p-foundation/1.0".to_string()));
2677
2678 node1.remove_channel(&channel_id).await;
2680 assert!(!node1.is_connection_active(&channel_id).await);
2681
2682 node1.stop().await?;
2683 node2.stop().await?;
2684
2685 Ok(())
2686 }
2687
2688 #[tokio::test]
2689 async fn test_connect_peer_rejects_tcp_multiaddr() -> Result<()> {
2690 let config = create_test_node_config();
2691 let node = P2PNode::new(config).await?;
2692
2693 let tcp_addr: MultiAddr = "/ip4/127.0.0.1/tcp/1".parse().unwrap();
2694 let result = node.connect_peer(&tcp_addr).await;
2695
2696 assert!(
2697 matches!(
2698 result,
2699 Err(P2PError::Network(
2700 crate::error::NetworkError::InvalidAddress(_)
2701 ))
2702 ),
2703 "TCP multiaddrs should be rejected before a QUIC dial is attempted, got: {:?}",
2704 result
2705 );
2706
2707 Ok(())
2708 }
2709
2710 #[cfg_attr(target_os = "windows", ignore)]
2717 #[tokio::test]
2718 async fn test_event_subscription() -> Result<()> {
2719 let identity1 =
2723 Arc::new(NodeIdentity::generate().expect("should generate identity for test node1"));
2724 let identity2 =
2725 Arc::new(NodeIdentity::generate().expect("should generate identity for test node2"));
2726
2727 let mut config1 = create_test_node_config();
2728 config1.ipv6 = false;
2729 config1.node_identity = Some(identity1);
2730
2731 let node2_peer_id = *identity2.peer_id();
2732 let mut config2 = create_test_node_config();
2733 config2.ipv6 = false;
2734 config2.node_identity = Some(identity2);
2735
2736 let node1 = P2PNode::new(config1).await?;
2737 let node2 = P2PNode::new(config2).await?;
2738
2739 node1.start().await?;
2740 node2.start().await?;
2741
2742 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
2743
2744 let mut events = node2.subscribe_events();
2746
2747 let node2_addr = node2.local_addr().ok_or_else(|| {
2748 P2PError::Network(crate::error::NetworkError::ProtocolError(
2749 "No listening address".to_string().into(),
2750 ))
2751 })?;
2752
2753 let mut channel_id = None;
2755 for attempt in 0..3 {
2756 if attempt > 0 {
2757 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
2758 }
2759 match timeout(Duration::from_secs(2), node1.connect_peer(&node2_addr)).await {
2760 Ok(Ok(id)) => {
2761 channel_id = Some(id);
2762 break;
2763 }
2764 Ok(Err(_)) | Err(_) => continue,
2765 }
2766 }
2767 let channel_id = channel_id.expect("Failed to connect after 3 attempts");
2768
2769 let target_peer_id = node1
2771 .wait_for_peer_identity(&channel_id, Duration::from_secs(2))
2772 .await?;
2773 assert_eq!(target_peer_id, node2_peer_id);
2774
2775 node1
2777 .send_message(&target_peer_id, "test-topic", b"hello".to_vec(), &[])
2778 .await?;
2779
2780 let event = timeout(Duration::from_secs(2), async {
2782 loop {
2783 match events.recv().await {
2784 Ok(P2PEvent::PeerConnected(id, _)) => return Ok(id),
2785 Ok(P2PEvent::Message { .. }) => continue, Ok(_) => continue,
2787 Err(e) => return Err(e),
2788 }
2789 }
2790 })
2791 .await;
2792 assert!(event.is_ok(), "Should receive PeerConnected event");
2793 let connected_peer_id = event.expect("Timed out").expect("Channel error");
2794 assert!(
2796 connected_peer_id.0.iter().any(|&b| b != 0),
2797 "PeerConnected should carry a non-zero peer ID"
2798 );
2799
2800 node1.stop().await?;
2801 node2.stop().await?;
2802
2803 Ok(())
2804 }
2805
2806 #[cfg_attr(target_os = "windows", ignore)]
2808 #[tokio::test]
2809 async fn test_message_sending() -> Result<()> {
2810 let mut config1 = create_test_node_config();
2812 config1.ipv6 = false;
2813 let node1 = P2PNode::new(config1).await?;
2814 node1.start().await?;
2815
2816 let mut config2 = create_test_node_config();
2817 config2.ipv6 = false;
2818 let node2 = P2PNode::new(config2).await?;
2819 node2.start().await?;
2820
2821 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
2823
2824 let node2_addr = node2.local_addr().ok_or_else(|| {
2826 P2PError::Network(crate::error::NetworkError::ProtocolError(
2827 "No listening address".to_string().into(),
2828 ))
2829 })?;
2830
2831 let channel_id =
2833 match timeout(Duration::from_millis(500), node1.connect_peer(&node2_addr)).await {
2834 Ok(res) => res?,
2835 Err(_) => return Err(P2PError::Network(NetworkError::Timeout)),
2836 };
2837
2838 let target_peer_id = node1
2840 .wait_for_peer_identity(&channel_id, Duration::from_secs(2))
2841 .await?;
2842 assert_eq!(target_peer_id, node2.peer_id().clone());
2843
2844 let message_data = b"Hello, peer!".to_vec();
2846 let result = match timeout(
2847 Duration::from_millis(500),
2848 node1.send_message(&target_peer_id, "test-protocol", message_data, &[]),
2849 )
2850 .await
2851 {
2852 Ok(res) => res,
2853 Err(_) => return Err(P2PError::Network(NetworkError::Timeout)),
2854 };
2855 if let Err(e) = &result {
2858 assert!(!e.to_string().contains("not connected"), "Got error: {}", e);
2859 }
2860
2861 let non_existent_peer = PeerId::from_bytes([0xFFu8; 32]);
2863 let result = node1
2864 .send_message(&non_existent_peer, "test-protocol", vec![], &[])
2865 .await;
2866 assert!(result.is_err(), "Sending to non-existent peer should fail");
2867
2868 node1.stop().await?;
2869 node2.stop().await?;
2870
2871 Ok(())
2872 }
2873
2874 #[tokio::test]
2875 async fn test_remote_mcp_operations() -> Result<()> {
2876 let config = create_test_node_config();
2877 let node = P2PNode::new(config).await?;
2878
2879 node.start().await?;
2881 node.stop().await?;
2882 Ok(())
2883 }
2884
2885 #[tokio::test]
2886 async fn test_health_check() -> Result<()> {
2887 let config = create_test_node_config();
2888 let node = P2PNode::new(config).await?;
2889
2890 let result = node.health_check().await;
2892 assert!(result.is_ok());
2893
2894 Ok(())
2899 }
2900
2901 #[tokio::test]
2902 async fn test_node_uptime() -> Result<()> {
2903 let config = create_test_node_config();
2904 let node = P2PNode::new(config).await?;
2905
2906 let uptime1 = node.uptime();
2907 assert!(uptime1 >= Duration::from_secs(0));
2908
2909 tokio::time::sleep(Duration::from_millis(10)).await;
2911
2912 let uptime2 = node.uptime();
2913 assert!(uptime2 > uptime1);
2914
2915 Ok(())
2916 }
2917
2918 #[tokio::test]
2919 async fn test_node_config_access() -> Result<()> {
2920 let config = create_test_node_config();
2921 let node = P2PNode::new(config).await?;
2922
2923 let node_config = node.config();
2924 assert_eq!(node_config.max_connections, 100);
2925 Ok(())
2928 }
2929
2930 #[tokio::test]
2931 async fn test_mcp_server_access() -> Result<()> {
2932 let config = create_test_node_config();
2933 let _node = P2PNode::new(config).await?;
2934
2935 Ok(())
2937 }
2938
2939 #[tokio::test]
2940 async fn test_dht_access() -> Result<()> {
2941 let config = create_test_node_config();
2942 let node = P2PNode::new(config).await?;
2943
2944 let _dht = node.dht();
2946
2947 Ok(())
2948 }
2949
2950 #[tokio::test]
2951 async fn test_node_config_builder() -> Result<()> {
2952 let bootstrap: MultiAddr = "/ip4/127.0.0.1/udp/9000/quic".parse().unwrap();
2953
2954 let config = NodeConfig::builder()
2955 .local(true)
2956 .ipv6(true)
2957 .bootstrap_peer(bootstrap)
2958 .connection_timeout(Duration::from_secs(15))
2959 .max_connections(200)
2960 .max_message_size(TEST_MAX_MESSAGE_SIZE)
2961 .build()?;
2962
2963 assert_eq!(config.listen_addrs().len(), 2); assert!(config.local);
2965 assert!(config.ipv6);
2966 assert_eq!(config.bootstrap_peers.len(), 1);
2967 assert_eq!(config.connection_timeout, Duration::from_secs(15));
2968 assert_eq!(config.max_connections, 200);
2969 assert_eq!(config.max_message_size, Some(TEST_MAX_MESSAGE_SIZE));
2970 assert!(config.allow_loopback); Ok(())
2973 }
2974
2975 #[tokio::test]
2976 async fn test_bootstrap_peers() -> Result<()> {
2977 let mut config = create_test_node_config();
2978 config.bootstrap_peers = vec![
2979 crate::MultiAddr::from_ipv4(std::net::Ipv4Addr::LOCALHOST, 9200),
2980 crate::MultiAddr::from_ipv4(std::net::Ipv4Addr::LOCALHOST, 9201),
2981 ];
2982
2983 let node = P2PNode::new(config).await?;
2984
2985 node.start().await?;
2987
2988 let _peer_count = node.peer_count().await;
2992
2993 node.stop().await?;
2994 Ok(())
2995 }
2996
2997 #[tokio::test]
2998 async fn test_peer_info_structure() {
2999 let peer_info = PeerInfo {
3000 channel_id: "test_peer".to_string(),
3001 addresses: vec!["/ip4/127.0.0.1/tcp/9000".parse::<MultiAddr>().unwrap()],
3002 connected_at: Instant::now(),
3003 last_seen: Instant::now(),
3004 status: ConnectionStatus::Connected,
3005 protocols: vec!["test-protocol".to_string()],
3006 heartbeat_count: 0,
3007 };
3008
3009 assert_eq!(peer_info.channel_id, "test_peer");
3010 assert_eq!(peer_info.addresses.len(), 1);
3011 assert_eq!(peer_info.status, ConnectionStatus::Connected);
3012 assert_eq!(peer_info.protocols.len(), 1);
3013 }
3014
3015 #[tokio::test]
3016 async fn test_serialization() -> Result<()> {
3017 let config = create_test_node_config();
3019 let serialized = serde_json::to_string(&config)?;
3020 let deserialized: NodeConfig = serde_json::from_str(&serialized)?;
3021
3022 assert_eq!(config.local, deserialized.local);
3023 assert_eq!(config.port, deserialized.port);
3024 assert_eq!(config.ipv6, deserialized.ipv6);
3025 assert_eq!(config.bootstrap_peers, deserialized.bootstrap_peers);
3026
3027 Ok(())
3028 }
3029
3030 #[tokio::test]
3031 async fn test_get_channel_id_by_address_found() -> Result<()> {
3032 let config = create_test_node_config();
3033 let node = P2PNode::new(config).await?;
3034
3035 let test_channel_id = "peer_test_123".to_string();
3037 let test_address = "192.168.1.100:9000";
3038 let test_multiaddr = MultiAddr::quic(test_address.parse().unwrap());
3039
3040 let peer_info = PeerInfo {
3041 channel_id: test_channel_id.clone(),
3042 addresses: vec![test_multiaddr],
3043 connected_at: Instant::now(),
3044 last_seen: Instant::now(),
3045 status: ConnectionStatus::Connected,
3046 protocols: vec!["test-protocol".to_string()],
3047 heartbeat_count: 0,
3048 };
3049
3050 node.transport
3051 .inject_peer(test_channel_id.clone(), peer_info)
3052 .await;
3053
3054 let lookup_addr = MultiAddr::quic(test_address.parse().unwrap());
3056 let found_channel_id = node.get_channel_id_by_address(&lookup_addr).await;
3057 assert_eq!(found_channel_id, Some(test_channel_id));
3058
3059 Ok(())
3060 }
3061
3062 #[tokio::test]
3063 async fn test_get_channel_id_by_address_not_found() -> Result<()> {
3064 let config = create_test_node_config();
3065 let node = P2PNode::new(config).await?;
3066
3067 let unknown_addr = MultiAddr::quic("192.168.1.200:9000".parse().unwrap());
3069 let result = node.get_channel_id_by_address(&unknown_addr).await;
3070 assert_eq!(result, None);
3071
3072 Ok(())
3073 }
3074
3075 #[tokio::test]
3076 async fn test_get_channel_id_by_address_invalid_format() -> Result<()> {
3077 let config = create_test_node_config();
3078 let node = P2PNode::new(config).await?;
3079
3080 let ble_addr = MultiAddr::new(crate::address::TransportAddr::Ble {
3082 mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
3083 psm: 0x0025,
3084 });
3085 let result = node.get_channel_id_by_address(&ble_addr).await;
3086 assert_eq!(result, None);
3087
3088 Ok(())
3089 }
3090
3091 #[tokio::test]
3092 async fn test_get_channel_id_by_address_multiple_peers() -> Result<()> {
3093 let config = create_test_node_config();
3094 let node = P2PNode::new(config).await?;
3095
3096 let peer1_id = "peer_1".to_string();
3098 let peer1_addr_str = "192.168.1.101:9001";
3099 let peer1_multiaddr = MultiAddr::quic(peer1_addr_str.parse().unwrap());
3100
3101 let peer2_id = "peer_2".to_string();
3102 let peer2_addr_str = "192.168.1.102:9002";
3103 let peer2_multiaddr = MultiAddr::quic(peer2_addr_str.parse().unwrap());
3104
3105 let peer1_info = PeerInfo {
3106 channel_id: peer1_id.clone(),
3107 addresses: vec![peer1_multiaddr],
3108 connected_at: Instant::now(),
3109 last_seen: Instant::now(),
3110 status: ConnectionStatus::Connected,
3111 protocols: vec!["test-protocol".to_string()],
3112 heartbeat_count: 0,
3113 };
3114
3115 let peer2_info = PeerInfo {
3116 channel_id: peer2_id.clone(),
3117 addresses: vec![peer2_multiaddr],
3118 connected_at: Instant::now(),
3119 last_seen: Instant::now(),
3120 status: ConnectionStatus::Connected,
3121 protocols: vec!["test-protocol".to_string()],
3122 heartbeat_count: 0,
3123 };
3124
3125 node.transport
3126 .inject_peer(peer1_id.clone(), peer1_info)
3127 .await;
3128 node.transport
3129 .inject_peer(peer2_id.clone(), peer2_info)
3130 .await;
3131
3132 let found_peer1 = node
3134 .get_channel_id_by_address(&MultiAddr::quic(peer1_addr_str.parse().unwrap()))
3135 .await;
3136 let found_peer2 = node
3137 .get_channel_id_by_address(&MultiAddr::quic(peer2_addr_str.parse().unwrap()))
3138 .await;
3139
3140 assert_eq!(found_peer1, Some(peer1_id));
3141 assert_eq!(found_peer2, Some(peer2_id));
3142
3143 Ok(())
3144 }
3145
3146 #[tokio::test]
3147 async fn test_list_active_connections_empty() -> Result<()> {
3148 let config = create_test_node_config();
3149 let node = P2PNode::new(config).await?;
3150
3151 let connections = node.list_active_connections().await;
3153 assert!(connections.is_empty());
3154
3155 Ok(())
3156 }
3157
3158 #[tokio::test]
3159 async fn test_list_active_connections_with_peers() -> Result<()> {
3160 let config = create_test_node_config();
3161 let node = P2PNode::new(config).await?;
3162
3163 let peer1_id = "peer_1".to_string();
3165 let peer1_addrs = vec![
3166 MultiAddr::quic("192.168.1.101:9001".parse().unwrap()),
3167 MultiAddr::quic("192.168.1.101:9002".parse().unwrap()),
3168 ];
3169
3170 let peer2_id = "peer_2".to_string();
3171 let peer2_addrs = vec![MultiAddr::quic("192.168.1.102:9003".parse().unwrap())];
3172
3173 let peer1_info = PeerInfo {
3174 channel_id: peer1_id.clone(),
3175 addresses: peer1_addrs.clone(),
3176 connected_at: Instant::now(),
3177 last_seen: Instant::now(),
3178 status: ConnectionStatus::Connected,
3179 protocols: vec!["test-protocol".to_string()],
3180 heartbeat_count: 0,
3181 };
3182
3183 let peer2_info = PeerInfo {
3184 channel_id: peer2_id.clone(),
3185 addresses: peer2_addrs.clone(),
3186 connected_at: Instant::now(),
3187 last_seen: Instant::now(),
3188 status: ConnectionStatus::Connected,
3189 protocols: vec!["test-protocol".to_string()],
3190 heartbeat_count: 0,
3191 };
3192
3193 node.transport
3194 .inject_peer(peer1_id.clone(), peer1_info)
3195 .await;
3196 node.transport
3197 .inject_peer(peer2_id.clone(), peer2_info)
3198 .await;
3199
3200 node.transport
3202 .inject_active_connection(peer1_id.clone())
3203 .await;
3204 node.transport
3205 .inject_active_connection(peer2_id.clone())
3206 .await;
3207
3208 let connections = node.list_active_connections().await;
3210 assert_eq!(connections.len(), 2);
3211
3212 let peer1_conn = connections.iter().find(|(id, _)| id == &peer1_id);
3214 let peer2_conn = connections.iter().find(|(id, _)| id == &peer2_id);
3215
3216 assert!(peer1_conn.is_some());
3217 assert!(peer2_conn.is_some());
3218
3219 assert_eq!(peer1_conn.unwrap().1, peer1_addrs);
3221 assert_eq!(peer2_conn.unwrap().1, peer2_addrs);
3222
3223 Ok(())
3224 }
3225
3226 #[tokio::test]
3227 async fn test_remove_channel_success() -> Result<()> {
3228 let config = create_test_node_config();
3229 let node = P2PNode::new(config).await?;
3230
3231 let channel_id = "peer_to_remove".to_string();
3233 let channel_peer_id = PeerId::from_name(&channel_id);
3234 let peer_info = PeerInfo {
3235 channel_id: channel_id.clone(),
3236 addresses: vec![MultiAddr::quic("192.168.1.100:9000".parse().unwrap())],
3237 connected_at: Instant::now(),
3238 last_seen: Instant::now(),
3239 status: ConnectionStatus::Connected,
3240 protocols: vec!["test-protocol".to_string()],
3241 heartbeat_count: 0,
3242 };
3243
3244 node.transport
3245 .inject_peer(channel_id.clone(), peer_info)
3246 .await;
3247 node.transport
3248 .inject_peer_to_channel(channel_peer_id, channel_id.clone())
3249 .await;
3250
3251 assert!(node.is_peer_connected(&channel_peer_id).await);
3253
3254 let removed = node.remove_channel(&channel_id).await;
3256 assert!(removed);
3257
3258 assert!(!node.is_peer_connected(&channel_peer_id).await);
3260
3261 Ok(())
3262 }
3263
3264 #[tokio::test]
3265 async fn test_remove_channel_nonexistent() -> Result<()> {
3266 let config = create_test_node_config();
3267 let node = P2PNode::new(config).await?;
3268
3269 let removed = node.remove_channel("nonexistent_peer").await;
3271 assert!(!removed);
3272
3273 Ok(())
3274 }
3275
3276 #[tokio::test]
3277 async fn test_is_peer_connected() -> Result<()> {
3278 let config = create_test_node_config();
3279 let node = P2PNode::new(config).await?;
3280
3281 let channel_id = "test_peer".to_string();
3282 let channel_peer_id = PeerId::from_name(&channel_id);
3283
3284 assert!(!node.is_peer_connected(&channel_peer_id).await);
3286
3287 let peer_info = PeerInfo {
3289 channel_id: channel_id.clone(),
3290 addresses: vec![MultiAddr::quic("192.168.1.100:9000".parse().unwrap())],
3291 connected_at: Instant::now(),
3292 last_seen: Instant::now(),
3293 status: ConnectionStatus::Connected,
3294 protocols: vec!["test-protocol".to_string()],
3295 heartbeat_count: 0,
3296 };
3297
3298 node.transport
3299 .inject_peer(channel_id.clone(), peer_info)
3300 .await;
3301 node.transport
3302 .inject_peer_to_channel(channel_peer_id, channel_id.clone())
3303 .await;
3304
3305 assert!(node.is_peer_connected(&channel_peer_id).await);
3307
3308 node.remove_channel(&channel_id).await;
3310
3311 assert!(!node.is_peer_connected(&channel_peer_id).await);
3313
3314 Ok(())
3315 }
3316
3317 #[test]
3318 fn test_normalize_ipv6_wildcard() {
3319 use std::net::{IpAddr, Ipv6Addr, SocketAddr};
3320
3321 let wildcard = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080);
3322 let normalized = normalize_wildcard_to_loopback(wildcard);
3323
3324 assert_eq!(normalized.ip(), IpAddr::V6(Ipv6Addr::LOCALHOST));
3325 assert_eq!(normalized.port(), 8080);
3326 }
3327
3328 #[test]
3329 fn test_normalize_ipv4_wildcard() {
3330 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
3331
3332 let wildcard = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9000);
3333 let normalized = normalize_wildcard_to_loopback(wildcard);
3334
3335 assert_eq!(normalized.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST));
3336 assert_eq!(normalized.port(), 9000);
3337 }
3338
3339 #[test]
3340 fn test_normalize_specific_address_unchanged() {
3341 let specific: std::net::SocketAddr = "192.168.1.100:3000".parse().unwrap();
3342 let normalized = normalize_wildcard_to_loopback(specific);
3343
3344 assert_eq!(normalized, specific);
3345 }
3346
3347 #[test]
3348 fn test_normalize_loopback_unchanged() {
3349 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
3350
3351 let loopback_v6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5000);
3352 let normalized_v6 = normalize_wildcard_to_loopback(loopback_v6);
3353 assert_eq!(normalized_v6, loopback_v6);
3354
3355 let loopback_v4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
3356 let normalized_v4 = normalize_wildcard_to_loopback(loopback_v4);
3357 assert_eq!(normalized_v4, loopback_v4);
3358 }
3359
3360 fn current_timestamp() -> u64 {
3364 std::time::SystemTime::now()
3365 .duration_since(std::time::UNIX_EPOCH)
3366 .map(|d| d.as_secs())
3367 .unwrap_or(0)
3368 }
3369
3370 fn make_wire_bytes(protocol: &str, data: Vec<u8>, from: &str, timestamp: u64) -> Vec<u8> {
3372 let msg = WireMessage {
3373 protocol: protocol.to_string(),
3374 data,
3375 from: PeerId::from_name(from),
3376 timestamp,
3377 user_agent: String::new(),
3378 public_key: Vec::new(),
3379 signature: Vec::new(),
3380 };
3381 postcard::to_stdvec(&msg).unwrap()
3382 }
3383
3384 fn make_signed_wire_bytes(
3386 identity: &NodeIdentity,
3387 protocol: &str,
3388 data: Vec<u8>,
3389 timestamp: u64,
3390 ) -> Vec<u8> {
3391 let from = *identity.peer_id();
3392 let user_agent = "test/1.0";
3393 let signable =
3394 postcard::to_stdvec(&(protocol, data.as_slice(), &from, timestamp, user_agent))
3395 .unwrap();
3396 let sig = identity.sign(&signable).expect("signing should succeed");
3397 let msg = WireMessage {
3398 protocol: protocol.to_string(),
3399 data,
3400 from,
3401 timestamp,
3402 user_agent: user_agent.to_string(),
3403 public_key: identity.public_key().as_bytes().to_vec(),
3404 signature: sig.as_bytes().to_vec(),
3405 };
3406 postcard::to_stdvec(&msg).unwrap()
3407 }
3408
3409 #[test]
3410 fn test_parse_protocol_message_uses_transport_peer_id_as_source() {
3411 let transport_id = "abcdef0123456789";
3414 let logical_id = "spoofed-logical-id";
3415 let bytes = make_wire_bytes("test/v1", vec![1, 2, 3], logical_id, current_timestamp());
3416
3417 let parsed =
3418 parse_protocol_message(&bytes, transport_id).expect("valid message should parse");
3419
3420 assert!(parsed.authenticated_node_id.is_none());
3422
3423 match parsed.event {
3424 P2PEvent::Message {
3425 topic,
3426 source,
3427 transport_source,
3428 timestamp: _,
3429 data,
3430 } => {
3431 assert!(source.is_none(), "unsigned message source must be None");
3432 assert!(
3433 transport_source.is_none(),
3434 "non-socket transport source should not produce an IP transport address"
3435 );
3436 assert_eq!(topic, "test/v1");
3437 assert_eq!(data, vec![1u8, 2, 3]);
3438 }
3439 other => panic!("expected P2PEvent::Message, got {:?}", other),
3440 }
3441 }
3442
3443 #[test]
3444 fn test_parse_protocol_message_rejects_invalid_bytes() {
3445 assert!(parse_protocol_message(b"not valid bincode", "peer-id").is_none());
3447 }
3448
3449 #[test]
3450 fn test_parse_protocol_message_rejects_truncated_message() {
3451 let full_bytes = make_wire_bytes("test/v1", vec![1, 2, 3], "sender", current_timestamp());
3453 let truncated = &full_bytes[..full_bytes.len() / 2];
3454 assert!(parse_protocol_message(truncated, "peer-id").is_none());
3455 }
3456
3457 #[test]
3458 fn test_parse_protocol_message_empty_payload() {
3459 let bytes = make_wire_bytes("ping", vec![], "sender", current_timestamp());
3460
3461 let parsed = parse_protocol_message(&bytes, "transport-peer")
3462 .expect("valid message with empty data should parse");
3463
3464 match parsed.event {
3465 P2PEvent::Message { data, .. } => assert!(data.is_empty()),
3466 other => panic!("expected P2PEvent::Message, got {:?}", other),
3467 }
3468 }
3469
3470 #[test]
3471 fn test_parse_protocol_message_records_ip_transport_source() {
3472 let bytes = make_wire_bytes("ping", vec![1], "sender", current_timestamp());
3473
3474 let parsed =
3475 parse_protocol_message(&bytes, "192.168.1.2:4567").expect("valid message should parse");
3476
3477 match parsed.event {
3478 P2PEvent::Message {
3479 transport_source, ..
3480 } => {
3481 assert_eq!(
3482 transport_source,
3483 Some(MultiAddr::quic("192.168.1.2:4567".parse().unwrap()))
3484 );
3485 }
3486 other => panic!("expected P2PEvent::Message, got {:?}", other),
3487 }
3488 }
3489
3490 #[test]
3491 fn test_parse_protocol_message_preserves_binary_payload() {
3492 let payload: Vec<u8> = (0..=255).collect();
3494 let bytes = make_wire_bytes("binary/v1", payload.clone(), "sender", current_timestamp());
3495
3496 let parsed = parse_protocol_message(&bytes, "peer-id")
3497 .expect("valid message with full byte range should parse");
3498
3499 match parsed.event {
3500 P2PEvent::Message { data, topic, .. } => {
3501 assert_eq!(topic, "binary/v1");
3502 assert_eq!(
3503 data, payload,
3504 "payload must survive bincode round-trip exactly"
3505 );
3506 }
3507 other => panic!("expected P2PEvent::Message, got {:?}", other),
3508 }
3509 }
3510
3511 #[test]
3512 fn test_parse_signed_message_verifies_and_uses_node_id() {
3513 let identity = NodeIdentity::generate().expect("should generate identity");
3514 let protocol = "test/signed";
3515 let data: Vec<u8> = vec![10, 20, 30];
3516 let from = *identity.peer_id();
3518 let timestamp = current_timestamp();
3519 let user_agent = "test/1.0";
3520
3521 let signable =
3523 postcard::to_stdvec(&(protocol, data.as_slice(), &from, timestamp, user_agent))
3524 .unwrap();
3525 let sig = identity.sign(&signable).expect("signing should succeed");
3526
3527 let msg = WireMessage {
3528 protocol: protocol.to_string(),
3529 data: data.clone(),
3530 from,
3531 timestamp,
3532 user_agent: user_agent.to_string(),
3533 public_key: identity.public_key().as_bytes().to_vec(),
3534 signature: sig.as_bytes().to_vec(),
3535 };
3536 let bytes = postcard::to_stdvec(&msg).unwrap();
3537
3538 let parsed =
3539 parse_protocol_message(&bytes, "transport-xyz").expect("signed message should parse");
3540
3541 let expected_peer_id = *identity.peer_id();
3542 assert_eq!(
3543 parsed.authenticated_node_id.as_ref(),
3544 Some(&expected_peer_id)
3545 );
3546
3547 match parsed.event {
3548 P2PEvent::Message { source, .. } => {
3549 assert_eq!(
3550 source.as_ref(),
3551 Some(&expected_peer_id),
3552 "source should be the verified PeerId"
3553 );
3554 }
3555 other => panic!("expected P2PEvent::Message, got {:?}", other),
3556 }
3557 }
3558
3559 #[test]
3560 fn test_parse_message_with_bad_signature_is_rejected() {
3561 let identity = NodeIdentity::generate().expect("should generate identity");
3562 let protocol = "test/bad-sig";
3563 let data: Vec<u8> = vec![1, 2, 3];
3564 let from = *identity.peer_id();
3565 let timestamp = current_timestamp();
3566 let user_agent = "test/1.0";
3567
3568 let signable =
3570 postcard::to_stdvec(&(protocol, data.as_slice(), &from, timestamp, user_agent))
3571 .unwrap();
3572 let sig = identity.sign(&signable).expect("signing should succeed");
3573
3574 let msg = WireMessage {
3576 protocol: protocol.to_string(),
3577 data: vec![99, 99, 99],
3578 from,
3579 timestamp,
3580 user_agent: user_agent.to_string(),
3581 public_key: identity.public_key().as_bytes().to_vec(),
3582 signature: sig.as_bytes().to_vec(),
3583 };
3584 let bytes = postcard::to_stdvec(&msg).unwrap();
3585
3586 assert!(
3587 parse_protocol_message(&bytes, "transport-xyz").is_none(),
3588 "message with bad signature should be rejected"
3589 );
3590 }
3591
3592 #[test]
3593 fn test_parse_message_with_mismatched_from_is_rejected() {
3594 let identity = NodeIdentity::generate().expect("should generate identity");
3595 let protocol = "test/from-mismatch";
3596 let data: Vec<u8> = vec![1, 2, 3];
3597 let fake_from = PeerId::from_bytes([0xDE; 32]);
3599 let timestamp = current_timestamp();
3600 let user_agent = "test/1.0";
3601
3602 let signable =
3603 postcard::to_stdvec(&(protocol, data.as_slice(), &fake_from, timestamp, user_agent))
3604 .unwrap();
3605 let sig = identity.sign(&signable).expect("signing should succeed");
3606
3607 let msg = WireMessage {
3608 protocol: protocol.to_string(),
3609 data,
3610 from: fake_from,
3611 timestamp,
3612 user_agent: user_agent.to_string(),
3613 public_key: identity.public_key().as_bytes().to_vec(),
3614 signature: sig.as_bytes().to_vec(),
3615 };
3616 let bytes = postcard::to_stdvec(&msg).unwrap();
3617
3618 assert!(
3619 parse_protocol_message(&bytes, "transport-xyz").is_none(),
3620 "message with mismatched from field should be rejected"
3621 );
3622 }
3623
3624 #[test]
3625 fn test_parse_protocol_message_accepts_arbitrary_timestamps() {
3626 let payload = vec![1, 2, 3];
3630
3631 let old_ts = current_timestamp().saturating_sub(36_000);
3633 let old_bytes = make_wire_bytes("test/old", payload.clone(), "sender", old_ts);
3634 assert!(
3635 parse_protocol_message(&old_bytes, "peer-id").is_some(),
3636 "should accept unsigned message with timestamp 10h in the past"
3637 );
3638
3639 let future_ts = current_timestamp().saturating_add(36_000);
3641 let future_bytes = make_wire_bytes("test/future", payload.clone(), "sender", future_ts);
3642 assert!(
3643 parse_protocol_message(&future_bytes, "peer-id").is_some(),
3644 "should accept unsigned message with timestamp 10h in the future"
3645 );
3646
3647 let identity = NodeIdentity::generate().expect("should generate identity");
3650 let signed_old =
3651 make_signed_wire_bytes(&identity, "test/signed-old", payload.clone(), old_ts);
3652 assert!(
3653 parse_protocol_message(&signed_old, "transport-xyz").is_some(),
3654 "should accept signed message with timestamp 10h in the past"
3655 );
3656
3657 let signed_future =
3658 make_signed_wire_bytes(&identity, "test/signed-future", payload, future_ts);
3659 assert!(
3660 parse_protocol_message(&signed_future, "transport-xyz").is_some(),
3661 "should accept signed message with timestamp 10h in the future"
3662 );
3663 }
3664
3665 #[test]
3666 fn test_parse_protocol_message_exposes_timestamp_on_event() {
3667 let ts: u64 = 1_234_567_890;
3671 let bytes = make_wire_bytes("test/ts", vec![9, 9, 9], "sender", ts);
3672 let parsed = parse_protocol_message(&bytes, "peer-id").expect("valid message should parse");
3673 match parsed.event {
3674 P2PEvent::Message { timestamp, .. } => {
3675 assert_eq!(timestamp, ts, "P2PEvent::Message.timestamp must round-trip");
3676 }
3677 other => panic!("expected P2PEvent::Message, got {:?}", other),
3678 }
3679 }
3680
3681 #[test]
3682 fn test_signed_message_timestamp_is_signature_covered() {
3683 let identity = NodeIdentity::generate().expect("should generate identity");
3687 let ts: u64 = 1_700_000_000;
3688 let signed = make_signed_wire_bytes(&identity, "test/sig", vec![1, 2, 3], ts);
3689
3690 let parsed = parse_protocol_message(&signed, "transport-xyz")
3692 .expect("unmodified signed message should parse");
3693 assert!(parsed.authenticated_node_id.is_some());
3694
3695 let mut tampered: WireMessage =
3697 postcard::from_bytes(&signed).expect("signed bytes must deserialize");
3698 tampered.timestamp = ts.wrapping_add(1);
3699 let tampered_bytes = postcard::to_stdvec(&tampered).expect("re-serialize");
3700
3701 assert!(
3702 parse_protocol_message(&tampered_bytes, "transport-xyz").is_none(),
3703 "timestamp-only mutation on a signed message must fail signature verification"
3704 );
3705 }
3706}