1use parking_lot::RwLock as ParkingRwLock;
4use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
5use std::net::SocketAddr;
6use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
7use std::sync::{Arc, Weak};
8use std::time::Instant;
9use tokio::sync::mpsc::Sender;
10use tokio::sync::Mutex;
11
12use crate::config::{Config, DeliveryConfig};
13use crate::events::model::LogLevel;
14use crate::network::connection::{ConnectionPolicy, RouteHealth, RouteKind};
15use crate::network::delivery::{DeliveryClass, DeliveryOutcome, MessageId};
16use crate::network::events::emit_network_event;
17use crate::network::message::{Message, MessageType, Payload};
18use crate::network::{Peer, PeerSource, PeerStore};
19use crate::plugin_host::manager::PluginManager;
20use crate::realms::RealmInfo;
21
22#[derive(Clone, Debug)]
23struct DeliveryAttemptState {
24 class: DeliveryClass,
25 attempts: u32,
26 outcome: Option<DeliveryOutcome>,
27 expires_at: std::time::Instant,
28}
29
30#[derive(Clone, Debug)]
31struct OrderedDeliveryBuffer {
32 next_expected: u64,
33 pending: BTreeMap<u64, Message>,
34 last_activity: std::time::Instant,
37}
38
39#[derive(Clone, Debug)]
40struct RouteHealthRecord {
41 health: RouteHealth,
42 last_success: Option<Instant>,
43 last_failure: Option<Instant>,
44}
45
46impl Default for RouteHealthRecord {
47 fn default() -> Self {
48 Self {
49 health: RouteHealth::Unknown,
50 last_success: None,
51 last_failure: None,
52 }
53 }
54}
55
56#[derive(Clone)]
57struct ConnectionLifecycleRuntime {
58 policy: ConnectionPolicy,
59 config: Config,
60 local_node_id: String,
61 peer_store: PeerStore,
62 plugin_manager: Weak<PluginManager>,
63 allow_console: bool,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
71#[serde(rename_all = "lowercase")]
72pub enum TransportKind {
73 Tcp,
74 Udp,
75}
76
77#[derive(Clone, Debug)]
80pub struct PendingPunch {
81 pub attempt_id: String,
82 pub initiator_node_id: String,
84 pub responder_node_id: String,
85 pub timeout_ms: u64,
86}
87
88#[derive(Clone, Debug)]
89pub struct ObservedUdpAddrRecord {
90 pub addr: String,
91 pub observed_at: std::time::Instant,
92 pub observer_node_id: Option<String>,
93 pub request_nonce: Option<[u8; 8]>,
94}
95
96#[derive(Clone, Debug)]
97pub struct PendingObservation {
98 pub observer_node_id: String,
99 pub expected_source: SocketAddr,
100 pub nonce: [u8; 8],
101 pub requested_at: std::time::Instant,
102}
103
104#[cfg(feature = "noise")]
108#[derive(Clone)]
109pub(crate) struct UdpHandle {
110 pub socket: Arc<tokio::net::UdpSocket>,
111 pub sessions: crate::network::udp_listener::UdpSessions,
112 pub static_private: Vec<u8>,
113}
114
115#[derive(Clone)]
116#[allow(clippy::type_complexity)]
117pub struct PeerManager {
118 peers: Arc<Mutex<HashMap<SocketAddr, Sender<String>>>>,
119 node_ids: Arc<Mutex<HashMap<String, SocketAddr>>>, listen_addrs: Arc<Mutex<HashMap<String, String>>>,
123 relay_forwarded: Arc<AtomicU64>,
125 relay_dropped: Arc<AtomicU64>,
126 relay_bindings: Arc<Mutex<HashMap<(String, String), BindingPrefs>>>,
128 relay_queue: Arc<Mutex<HashMap<String, Vec<(String, Option<u64>, Option<String>)>>>>,
130 relay_queue_max_per_target: Arc<AtomicUsize>,
132 relay_queue_max_global: Arc<AtomicUsize>,
133 relay_last_sequence: Arc<Mutex<HashMap<(String, String), u64>>>,
135 capabilities_by_node: Arc<Mutex<HashMap<String, Vec<String>>>>,
136 relay_binding_ids: Arc<Mutex<HashMap<String, (String, String)>>>,
138 reliable_inflight: Arc<Mutex<HashSet<(String, String, u64)>>>,
140 delivery_attempts: Arc<Mutex<HashMap<String, DeliveryAttemptState>>>,
142 delivery_dedup: Arc<Mutex<HashMap<(String, String), std::time::Instant>>>,
143 delivery_next_ordering_sequence: Arc<Mutex<HashMap<(String, String, String), u64>>>,
144 delivery_ordered_inbound: Arc<Mutex<HashMap<(String, String, String), OrderedDeliveryBuffer>>>,
145 relay_next_sequence: Arc<Mutex<HashMap<(String, String), u64>>>,
146 delivery_dedup_window_secs: Arc<AtomicU64>,
147 delivery_ordered_buffer_limit: Arc<AtomicUsize>,
148 route_health: Arc<Mutex<HashMap<(String, RouteKind), RouteHealthRecord>>>,
149 last_peer_activity: Arc<Mutex<HashMap<String, Instant>>>,
150 lifecycle_generations: Arc<Mutex<HashMap<String, u64>>>,
151 next_lifecycle_generation: Arc<AtomicU64>,
152 reconnecting_peers: Arc<Mutex<HashSet<String>>>,
153 lifecycle_runtime: Arc<Mutex<Option<ConnectionLifecycleRuntime>>>,
154 transport_kind: Arc<Mutex<HashMap<String, TransportKind>>>,
157 udp_listen_addrs: Arc<Mutex<HashMap<String, String>>>,
159 udp_session_ids: Arc<Mutex<HashMap<String, [u8; 8]>>>,
161 own_observed_addr: Arc<Mutex<Option<ObservedUdpAddrRecord>>>,
164 own_observed_addr_samples: Arc<Mutex<HashMap<String, ObservedUdpAddrRecord>>>,
166 udp_observed_addrs: Arc<Mutex<HashMap<String, ObservedUdpAddrRecord>>>,
169 gateway_external_ip: Arc<ParkingRwLock<Option<String>>>,
171 public_tcp_hello_addr: Arc<ParkingRwLock<Option<String>>>,
173 direct_tcp_hint: Arc<AtomicBool>,
175 nat_diagnostic_summary: Arc<ParkingRwLock<Option<String>>>,
177 pending_punches: Arc<Mutex<HashMap<String, PendingPunch>>>,
179 pending_observations: Arc<Mutex<HashMap<[u8; 8], PendingObservation>>>,
180 #[cfg(feature = "noise")]
182 udp_handle: Arc<Mutex<Option<UdpHandle>>>,
183 #[cfg(feature = "noise")]
185 nat_state:
186 Arc<tokio::sync::RwLock<Option<std::sync::Arc<crate::network::nat_traversal::NatState>>>>,
187}
188
189impl Default for PeerManager {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl PeerManager {
196 const DEFAULT_QUEUE_MAX_PER_TARGET: usize = 1024;
197 const DEFAULT_QUEUE_MAX_GLOBAL: usize = 8192;
198
199 pub fn new() -> Self {
200 Self {
201 peers: Arc::new(Mutex::new(HashMap::new())),
202 node_ids: Arc::new(Mutex::new(HashMap::new())),
203 listen_addrs: Arc::new(Mutex::new(HashMap::new())),
204 capabilities_by_node: Arc::new(Mutex::new(HashMap::new())),
205 relay_forwarded: Arc::new(AtomicU64::new(0)),
206 relay_dropped: Arc::new(AtomicU64::new(0)),
207 relay_bindings: Arc::new(Mutex::new(HashMap::new())),
208 relay_queue: Arc::new(Mutex::new(HashMap::new())),
209 relay_queue_max_per_target: Arc::new(AtomicUsize::new(
210 Self::DEFAULT_QUEUE_MAX_PER_TARGET,
211 )),
212 relay_queue_max_global: Arc::new(AtomicUsize::new(Self::DEFAULT_QUEUE_MAX_GLOBAL)),
213 relay_binding_ids: Arc::new(Mutex::new(HashMap::new())),
214 relay_last_sequence: Arc::new(Mutex::new(HashMap::new())),
215 reliable_inflight: Arc::new(Mutex::new(HashSet::new())),
216 delivery_attempts: Arc::new(Mutex::new(HashMap::new())),
217 delivery_dedup: Arc::new(Mutex::new(HashMap::new())),
218 delivery_next_ordering_sequence: Arc::new(Mutex::new(HashMap::new())),
219 delivery_ordered_inbound: Arc::new(Mutex::new(HashMap::new())),
220 relay_next_sequence: Arc::new(Mutex::new(HashMap::new())),
221 delivery_dedup_window_secs: Arc::new(AtomicU64::new(3600)),
222 delivery_ordered_buffer_limit: Arc::new(AtomicUsize::new(1024)),
223 route_health: Arc::new(Mutex::new(HashMap::new())),
224 last_peer_activity: Arc::new(Mutex::new(HashMap::new())),
225 lifecycle_generations: Arc::new(Mutex::new(HashMap::new())),
226 next_lifecycle_generation: Arc::new(AtomicU64::new(1)),
227 reconnecting_peers: Arc::new(Mutex::new(HashSet::new())),
228 lifecycle_runtime: Arc::new(Mutex::new(None)),
229 transport_kind: Arc::new(Mutex::new(HashMap::new())),
230 udp_listen_addrs: Arc::new(Mutex::new(HashMap::new())),
231 udp_session_ids: Arc::new(Mutex::new(HashMap::new())),
232 own_observed_addr: Arc::new(Mutex::new(None)),
233 own_observed_addr_samples: Arc::new(Mutex::new(HashMap::new())),
234 udp_observed_addrs: Arc::new(Mutex::new(HashMap::new())),
235 gateway_external_ip: Arc::new(ParkingRwLock::new(None)),
236 public_tcp_hello_addr: Arc::new(ParkingRwLock::new(None)),
237 direct_tcp_hint: Arc::new(AtomicBool::new(false)),
238 nat_diagnostic_summary: Arc::new(ParkingRwLock::new(None)),
239 pending_punches: Arc::new(Mutex::new(HashMap::new())),
240 pending_observations: Arc::new(Mutex::new(HashMap::new())),
241 #[cfg(feature = "noise")]
242 udp_handle: Arc::new(Mutex::new(None)),
243 #[cfg(feature = "noise")]
244 nat_state: Arc::new(tokio::sync::RwLock::new(None)),
245 }
246 }
247
248 pub fn set_relay_queue_caps(
249 &self,
250 per_target: Option<usize>,
251 global: Option<usize>,
252 ) -> (usize, usize) {
253 let per_target_cap = per_target
254 .unwrap_or(Self::DEFAULT_QUEUE_MAX_PER_TARGET)
255 .max(1);
256 let global_cap = global.unwrap_or(Self::DEFAULT_QUEUE_MAX_GLOBAL).max(1);
257 self.relay_queue_max_per_target
258 .store(per_target_cap, Ordering::Relaxed);
259 self.relay_queue_max_global
260 .store(global_cap, Ordering::Relaxed);
261 (per_target_cap, global_cap)
262 }
263
264 pub fn relay_queue_caps(&self) -> (usize, usize) {
265 (
266 self.relay_queue_max_per_target.load(Ordering::Relaxed),
267 self.relay_queue_max_global.load(Ordering::Relaxed),
268 )
269 }
270
271 pub fn set_delivery_config(&self, delivery: Option<DeliveryConfig>) {
272 let delivery = delivery.unwrap_or_default();
273 self.delivery_dedup_window_secs.store(
274 delivery.dedup_window_secs.unwrap_or(3600),
275 Ordering::Relaxed,
276 );
277 self.delivery_ordered_buffer_limit.store(
278 delivery
279 .ordered_max_buffered_messages
280 .unwrap_or(1024)
281 .max(1),
282 Ordering::Relaxed,
283 );
284 }
285
286 pub fn delivery_dedup_window_secs(&self) -> u64 {
287 self.delivery_dedup_window_secs.load(Ordering::Relaxed)
288 }
289
290 pub fn delivery_ordered_buffer_limit(&self) -> usize {
291 self.delivery_ordered_buffer_limit.load(Ordering::Relaxed)
292 }
293
294 pub async fn configure_connection_lifecycle(
295 &self,
296 config: Config,
297 local_node_id: String,
298 plugin_manager: &Arc<PluginManager>,
299 peer_store: PeerStore,
300 allow_console: bool,
301 ) {
302 let runtime = ConnectionLifecycleRuntime {
303 policy: ConnectionPolicy::from_network_config(&config),
304 config,
305 local_node_id,
306 peer_store,
307 plugin_manager: Arc::downgrade(plugin_manager),
308 allow_console,
309 };
310 *self.lifecycle_runtime.lock().await = Some(runtime);
311
312 let peers: Vec<(String, SocketAddr)> = self
313 .node_ids
314 .lock()
315 .await
316 .iter()
317 .map(|(node_id, addr)| (node_id.clone(), *addr))
318 .collect();
319 for (node_id, addr) in peers {
320 self.start_liveness_monitor(node_id, Some(addr)).await;
321 }
322 }
323
324 pub async fn list_peers(&self) -> Vec<SocketAddr> {
326 let peers = self.peers.lock().await;
327 peers.keys().cloned().collect()
328 }
329
330 pub async fn list_node_ids(&self) -> Vec<String> {
332 let ids = self.node_ids.lock().await;
333 ids.keys().cloned().collect()
334 }
335
336 pub async fn set_peer_capabilities(&self, node_id: &str, caps: Option<Vec<String>>) {
338 let mut map = self.capabilities_by_node.lock().await;
339 match caps {
340 Some(v) => {
341 map.insert(node_id.to_string(), v);
342 }
343 None => {
344 map.remove(node_id);
345 }
346 }
347 }
348
349 pub async fn peer_has_capability(&self, node_id: &str, cap: &str) -> bool {
350 let map = self.capabilities_by_node.lock().await;
351 map.get(node_id)
352 .map(|v| v.iter().any(|c| c == cap))
353 .unwrap_or(false)
354 }
355
356 pub async fn add_peer(
358 &self,
359 addr: SocketAddr,
360 sender: Sender<String>,
361 node_id: String,
362 ) -> Result<(), String> {
363 {
365 let ids = self.node_ids.lock().await;
366 if ids.contains_key(&node_id) {
367 return Err(format!("duplicate node id {} already connected", node_id));
368 }
369 }
370 self.peers.lock().await.insert(addr, sender);
371 self.node_ids.lock().await.insert(node_id.clone(), addr);
372 self.mark_route_success(&node_id, RouteKind::Tcp).await;
373 self.last_peer_activity
374 .lock()
375 .await
376 .insert(node_id.clone(), Instant::now());
377 if let Some(runtime) = self.lifecycle_runtime.lock().await.clone() {
378 runtime.peer_store.reset_reconnect(&addr).await;
379 }
380 let queued = {
382 let mut q = self.relay_queue.lock().await;
383 if let Some(v) = q.get_mut(&node_id) {
385 v.retain(|(_, exp, _)| exp.map(|e| e > current_unix_ts()).unwrap_or(true));
386 }
387 q.remove(&node_id)
388 };
389 if let Some(frames) = queued {
390 if let Some(sndr) = self.peers.lock().await.get(&addr).cloned() {
391 for (frame, _exp, _origin) in frames {
392 let _ = sndr.try_send(frame);
394 }
395 }
396 }
397 self.start_liveness_monitor(node_id, Some(addr)).await;
398 Ok(())
399 }
400
401 pub async fn has_node_id(&self, node_id: &str) -> bool {
403 self.node_ids.lock().await.contains_key(node_id)
404 }
405
406 pub async fn has_addr(&self, addr: &SocketAddr) -> bool {
408 self.peers.lock().await.contains_key(addr)
409 }
410
411 pub async fn remove_peer(&self, addr: &SocketAddr) -> Option<String> {
413 let removed = self.peers.lock().await.remove(addr).is_some();
414 if removed {
415 let mut ids = self.node_ids.lock().await;
416 let remove_key: Option<String> =
417 ids.iter()
418 .find_map(|(k, v)| if v == addr { Some(k.clone()) } else { None });
419 if let Some(ref k) = remove_key {
420 ids.remove(k);
421 }
422 drop(ids);
423 if let Some(ref dup_node_id) = remove_key {
424 if self.udp_session_id_for(dup_node_id).await.is_some() {
425 self.transport_kind
426 .lock()
427 .await
428 .insert(dup_node_id.clone(), TransportKind::Udp);
429 } else {
430 self.lifecycle_generations.lock().await.remove(dup_node_id);
431 self.transport_kind.lock().await.remove(dup_node_id);
432 self.last_peer_activity.lock().await.remove(dup_node_id);
433 }
434 self.pending_punches.lock().await.retain(|_, pending| {
435 pending.initiator_node_id != *dup_node_id
436 && pending.responder_node_id != *dup_node_id
437 });
438 self.pending_observations
439 .lock()
440 .await
441 .retain(|_, pending| pending.observer_node_id != *dup_node_id);
442 }
443 return remove_key;
444 }
445 None
446 }
447
448 pub async fn handle_peer_disconnected(&self, addr: &SocketAddr) -> Option<String> {
449 let node_id = self.remove_peer(addr).await?;
450 self.mark_route_unresponsive(&node_id, RouteKind::Tcp).await;
451 let reconnect_addr = self
452 .tcp_listen_addr_for(&node_id)
453 .await
454 .and_then(|candidate| candidate.parse().ok())
455 .unwrap_or(*addr);
456 self.schedule_reconnect(node_id.clone(), reconnect_addr);
457 Some(node_id)
458 }
459
460 pub async fn route_health(&self, node_id: &str, kind: RouteKind) -> RouteHealth {
461 let health = self.route_health.lock().await;
462 let Some(record) = health.get(&(node_id.to_string(), kind)) else {
463 return RouteHealth::Unknown;
464 };
465 if record.health == RouteHealth::Unresponsive {
466 return RouteHealth::Unresponsive;
467 }
468 match (record.last_success, record.last_failure) {
469 (None, None) => RouteHealth::Unknown,
470 (Some(_), None) => RouteHealth::Healthy,
471 (None, Some(_)) => RouteHealth::Suspect,
472 (Some(success), Some(failure)) if success >= failure => RouteHealth::Healthy,
473 (Some(_), Some(_)) => RouteHealth::Suspect,
474 }
475 }
476
477 pub async fn mark_route_success(&self, node_id: &str, kind: RouteKind) {
478 let mut health = self.route_health.lock().await;
479 let record = health
480 .entry((node_id.to_string(), kind))
481 .or_insert_with(RouteHealthRecord::default);
482 record.health = RouteHealth::Healthy;
483 record.last_success = Some(Instant::now());
484 Self::prune_stale_route_health(&mut health);
485 }
486
487 pub async fn mark_route_failure(&self, node_id: &str, kind: RouteKind) {
488 let mut health = self.route_health.lock().await;
489 let record = health
490 .entry((node_id.to_string(), kind))
491 .or_insert_with(RouteHealthRecord::default);
492 if record.health != RouteHealth::Unresponsive {
496 record.health = RouteHealth::Suspect;
497 }
498 record.last_failure = Some(Instant::now());
499 Self::prune_stale_route_health(&mut health);
500 }
501
502 fn prune_stale_route_health(health: &mut HashMap<(String, RouteKind), RouteHealthRecord>) {
509 const RETENTION: std::time::Duration = std::time::Duration::from_secs(3600);
510 health.retain(|_, record| {
511 record
512 .last_success
513 .is_some_and(|t| t.elapsed() <= RETENTION)
514 || record
515 .last_failure
516 .is_some_and(|t| t.elapsed() <= RETENTION)
517 });
518 }
519
520 pub async fn mark_route_unresponsive(&self, node_id: &str, kind: RouteKind) {
521 let mut health = self.route_health.lock().await;
522 let record = health
523 .entry((node_id.to_string(), kind))
524 .or_insert_with(RouteHealthRecord::default);
525 record.health = RouteHealth::Unresponsive;
526 record.last_failure = Some(Instant::now());
527 }
528
529 pub async fn record_peer_activity(&self, node_id: &str, kind: RouteKind) {
530 self.last_peer_activity
531 .lock()
532 .await
533 .insert(node_id.to_string(), Instant::now());
534 self.mark_route_success(node_id, kind).await;
535 }
536
537 pub async fn record_peer_activity_on_preferred_route(&self, node_id: &str) {
538 let kind = match self.get_transport_kind(node_id).await {
539 Some(TransportKind::Udp) => RouteKind::Udp,
540 Some(TransportKind::Tcp) => RouteKind::Tcp,
541 None if self.has_node_id(node_id).await => RouteKind::Tcp,
542 None => return,
543 };
544 self.record_peer_activity(node_id, kind).await;
545 }
546
547 async fn start_liveness_monitor(&self, node_id: String, tcp_addr: Option<SocketAddr>) {
548 let Some(runtime) = self.lifecycle_runtime.lock().await.clone() else {
549 return;
550 };
551 let generation = self
552 .next_lifecycle_generation
553 .fetch_add(1, Ordering::Relaxed);
554 {
555 let mut generations = self.lifecycle_generations.lock().await;
556 if generations.contains_key(&node_id) {
557 return;
558 }
559 generations.insert(node_id.clone(), generation);
560 }
561
562 let peer_manager = self.clone();
563 tokio::spawn(async move {
564 let mut ticker = tokio::time::interval_at(
565 tokio::time::Instant::now() + runtime.policy.heartbeat_interval,
566 runtime.policy.heartbeat_interval,
567 );
568 loop {
569 ticker.tick().await;
570 let is_current = peer_manager
571 .lifecycle_generations
572 .lock()
573 .await
574 .get(&node_id)
575 .is_some_and(|current| *current == generation);
576 if !is_current {
577 break;
578 }
579
580 let last_activity = peer_manager
581 .last_peer_activity
582 .lock()
583 .await
584 .get(&node_id)
585 .copied();
586 if last_activity
587 .is_none_or(|seen| seen.elapsed() >= runtime.policy.heartbeat_timeout)
588 {
589 peer_manager
590 .mark_peer_unresponsive(&node_id, tcp_addr)
591 .await;
592 break;
593 }
594
595 let heartbeat = Message::new(
596 &runtime.local_node_id,
597 &node_id,
598 MessageType::Heartbeat,
599 Some(Payload::Text("liveness_probe_v1".to_string())),
600 runtime.config.realm.clone(),
601 );
602 let (kind, result) = if peer_manager.has_node_id(&node_id).await {
603 (
604 RouteKind::Tcp,
605 peer_manager
606 .send_to_node_id(&node_id, heartbeat.as_json())
607 .await,
608 )
609 } else if peer_manager.udp_session_id_for(&node_id).await.is_some() {
610 (
611 RouteKind::Udp,
612 peer_manager
613 .send_udp_message_to_node(&node_id, heartbeat.as_json().as_bytes())
614 .await,
615 )
616 } else {
617 peer_manager
618 .mark_peer_unresponsive(&node_id, tcp_addr)
619 .await;
620 break;
621 };
622 if result.is_err() {
623 peer_manager.mark_route_failure(&node_id, kind).await;
624 }
625 }
626
627 let mut generations = peer_manager.lifecycle_generations.lock().await;
628 if generations
629 .get(&node_id)
630 .is_some_and(|current| *current == generation)
631 {
632 generations.remove(&node_id);
633 }
634 });
635 }
636
637 async fn mark_peer_unresponsive(&self, node_id: &str, tcp_addr: Option<SocketAddr>) {
638 self.mark_route_unresponsive(node_id, RouteKind::Tcp).await;
639 if self.udp_session_id_for(node_id).await.is_some() {
640 self.mark_route_unresponsive(node_id, RouteKind::Udp).await;
641 }
642 self.udp_session_ids.lock().await.remove(node_id);
643 self.transport_kind.lock().await.remove(node_id);
644 let active_tcp_addr = self.node_ids.lock().await.get(node_id).copied();
645 if let Some(addr) = active_tcp_addr {
646 self.remove_peer(&addr).await;
647 } else {
648 self.lifecycle_generations.lock().await.remove(node_id);
649 self.last_peer_activity.lock().await.remove(node_id);
650 }
651
652 emit_network_event(
653 "peer_manager",
654 LogLevel::Warn,
655 "peer_unresponsive",
656 tcp_addr.map(|addr| addr.to_string()),
657 Some(format!("node_id={node_id}")),
658 self.lifecycle_runtime
659 .lock()
660 .await
661 .as_ref()
662 .is_some_and(|runtime| runtime.allow_console),
663 );
664
665 let reconnect_addr = self
666 .tcp_listen_addr_for(node_id)
667 .await
668 .and_then(|candidate| candidate.parse().ok())
669 .or(tcp_addr);
670 if let Some(addr) = reconnect_addr {
671 self.schedule_reconnect(node_id.to_string(), addr);
672 }
673 }
674
675 fn schedule_reconnect(&self, node_id: String, addr: SocketAddr) {
676 let peer_manager = self.clone();
677 tokio::spawn(async move {
678 let Some(runtime) = peer_manager.lifecycle_runtime.lock().await.clone() else {
679 return;
680 };
681 {
682 let mut reconnecting = peer_manager.reconnecting_peers.lock().await;
683 if !reconnecting.insert(node_id.clone()) {
684 return;
685 }
686 }
687 runtime.peer_store.insert(addr, PeerSource::Handshake).await;
688
689 loop {
690 if peer_manager.has_node_id(&node_id).await {
691 break;
692 }
693 let Some(attempt) = runtime
694 .peer_store
695 .next_reconnect_attempt(&addr, runtime.policy.reconnect_max_attempts)
696 .await
697 else {
698 emit_network_event(
699 "peer_manager",
700 LogLevel::Warn,
701 "reconnect_exhausted",
702 Some(addr.to_string()),
703 Some(format!("node_id={node_id}")),
704 runtime.allow_console,
705 );
706 break;
707 };
708
709 tokio::time::sleep(runtime.policy.reconnect_delay_with_random_jitter(attempt))
710 .await;
711 if peer_manager.has_node_id(&node_id).await {
712 break;
713 }
714 let Some(plugin_manager) = runtime.plugin_manager.upgrade() else {
715 break;
716 };
717 let peer = Peer::new(format!("reconnect-{node_id}"), addr.to_string());
718 let result = crate::network::transport::connect_to_peer(
719 crate::network::transport::ConnectToPeerParams {
720 peer: &peer,
721 our_realm: runtime
722 .config
723 .realm
724 .clone()
725 .unwrap_or_else(RealmInfo::default),
726 our_port: runtime.config.port,
727 peer_manager: peer_manager.clone(),
728 plugin_manager,
729 allow_console: runtime.allow_console,
730 config: runtime.config.clone(),
731 local_node_id: runtime.local_node_id.clone(),
732 peer_store: Some(runtime.peer_store.clone()),
733 },
734 )
735 .await;
736
737 if result.is_err() || !peer_manager.has_node_id(&node_id).await {
738 runtime.peer_store.mark_failure(&addr).await;
739 peer_manager
740 .mark_route_failure(&node_id, RouteKind::Tcp)
741 .await;
742 continue;
743 }
744 break;
745 }
746 peer_manager
747 .reconnecting_peers
748 .lock()
749 .await
750 .remove(&node_id);
751 });
752 }
753
754 pub async fn broadcast(&self, message: &str) {
755 let peers = self.peers.lock().await;
756 for (addr, sender) in peers.iter() {
757 if let Err(e) = sender.send(message.to_string()).await {
758 eprintln!("❌ Failed to send to {}: {}", addr, e);
759 }
760 }
761 }
762
763 pub async fn get_sender_by_addr(&self, addr: &SocketAddr) -> Option<Sender<String>> {
765 self.peers.lock().await.get(addr).cloned()
766 }
767
768 pub async fn get_sender_by_node_id(&self, node_id: &str) -> Option<Sender<String>> {
770 let addr_opt = { self.node_ids.lock().await.get(node_id).cloned() };
771 if let Some(addr) = addr_opt {
772 return self.peers.lock().await.get(&addr).cloned();
773 }
774 None
775 }
776
777 pub async fn send_to_addr(&self, addr: &SocketAddr, message: String) -> Result<(), String> {
779 if let Some(sender) = self.get_sender_by_addr(addr).await {
780 sender
781 .try_send(message)
782 .map_err(|e| format!("send_to_addr failed: {}", e))
783 } else {
784 Err("peer sender not found".to_string())
785 }
786 }
787
788 pub async fn send_to_node_id(&self, node_id: &str, message: String) -> Result<(), String> {
790 if let Some(sender) = self.get_sender_by_node_id(node_id).await {
791 sender
792 .try_send(message)
793 .map_err(|e| format!("send_to_node_id failed: {}", e))
794 } else {
795 Err("peer sender not found".to_string())
796 }
797 }
798
799 pub async fn add_listen_addr(&self, listen_addr: &str, node_id: &str) {
801 self.listen_addrs
802 .lock()
803 .await
804 .insert(listen_addr.to_string(), node_id.to_string());
805 }
806
807 pub async fn has_listen_addr(&self, listen_addr: &str) -> bool {
809 self.listen_addrs.lock().await.contains_key(listen_addr)
810 }
811
812 pub async fn tcp_listen_addr_for(&self, node_id: &str) -> Option<String> {
818 let map = self.listen_addrs.lock().await;
819 map.iter().find_map(|(addr, nid)| {
820 if nid == node_id {
821 Some(addr.clone())
822 } else {
823 None
824 }
825 })
826 }
827
828 pub async fn reconnect_known_peers(
829 &self,
830 plugin_manager: Arc<PluginManager>,
831 peer_store: PeerStore,
832 config: &Config,
833 ) {
834 let mut targets: BTreeSet<SocketAddr> = peer_store.all().await.into_iter().collect();
835 let mut parse_failures: Vec<String> = Vec::new();
836
837 if let Some(bootstrap) = &config.bootstrap_nodes {
838 for entry in bootstrap {
839 match entry.parse::<SocketAddr>() {
840 Ok(addr) => {
841 targets.insert(addr);
842 }
843 Err(err) => parse_failures.push(format!("{} ({})", entry, err)),
844 }
845 }
846 }
847
848 if targets.is_empty() {
849 println!(
850 "{}No stored peer addresses to retry after promotion.",
851 crate::constants::ICON_PLACEHOLDER
852 );
853 return;
854 }
855
856 let total_candidates = targets.len();
857 let connected: HashSet<SocketAddr> = self.list_peers().await.into_iter().collect();
858
859 let realm = config.realm.clone().unwrap_or_else(RealmInfo::default);
860 let local_node_id = config
861 .node
862 .as_ref()
863 .map(|n| n.resolve_node_id())
864 .unwrap_or_else(|| "unknown-node".to_string());
865
866 let mut attempted = 0usize;
867 let mut succeeded = 0usize;
868 let mut skipped_connected = 0usize;
869
870 println!(
871 "{}Retrying {} known peer(s)...",
872 crate::constants::ICON_PLACEHOLDER,
873 total_candidates
874 );
875
876 let peer_manager = self.clone();
877 for addr in targets {
878 if connected.contains(&addr) {
879 skipped_connected += 1;
880 continue;
881 }
882 attempted += 1;
883 let addr_str = addr.to_string();
884 let peer = Peer::new(format!("retry-{}", addr_str), addr_str.clone());
885 match crate::network::transport::connect_to_peer(
886 crate::network::transport::ConnectToPeerParams {
887 peer: &peer,
888 our_realm: realm.clone(),
889 our_port: config.port,
890 peer_manager: peer_manager.clone(),
891 plugin_manager: plugin_manager.clone(),
892 allow_console: true,
893 config: config.clone(),
894 local_node_id: local_node_id.clone(),
895 peer_store: None,
896 },
897 )
898 .await
899 {
900 Ok(()) => {
901 succeeded += 1;
902 println!(
903 "{}Connected to {}",
904 crate::constants::ICON_PLACEHOLDER,
905 addr
906 );
907 }
908 Err(err) => {
909 println!(
910 "{}Failed to connect to {}: {}",
911 crate::constants::ICON_PLACEHOLDER,
912 addr,
913 err
914 );
915 }
916 }
917 }
918
919 println!(
920 "{}Reconnect summary: {} candidate(s), {} attempted, {} succeeded, {} already connected.",
921 crate::constants::ICON_PLACEHOLDER,
922 total_candidates,
923 attempted,
924 succeeded,
925 skipped_connected
926 );
927
928 if !parse_failures.is_empty() {
929 println!(
930 "{}Skipped invalid addresses:",
931 crate::constants::ICON_PLACEHOLDER
932 );
933 for detail in parse_failures {
934 println!(" {}", detail);
935 }
936 }
937 }
938
939 pub fn inc_relay_forwarded(&self) {
941 self.relay_forwarded.fetch_add(1, Ordering::Relaxed);
942 }
943 pub fn inc_relay_dropped(&self) {
944 self.relay_dropped.fetch_add(1, Ordering::Relaxed);
945 }
946 pub fn relay_stats(&self) -> (u64, u64) {
947 (
948 self.relay_forwarded.load(Ordering::Relaxed),
949 self.relay_dropped.load(Ordering::Relaxed),
950 )
951 }
952
953 pub async fn add_inflight(&self, from: &str, to: &str, seq: u64) {
955 self.reliable_inflight
956 .lock()
957 .await
958 .insert((from.to_string(), to.to_string(), seq));
959 }
960
961 pub async fn remove_inflight(&self, from: &str, to: &str, seq: u64) {
962 self.reliable_inflight
963 .lock()
964 .await
965 .remove(&(from.to_string(), to.to_string(), seq));
966 }
967
968 pub async fn is_inflight(&self, from: &str, to: &str, seq: u64) -> bool {
969 self.reliable_inflight
970 .lock()
971 .await
972 .contains(&(from.to_string(), to.to_string(), seq))
973 }
974
975 pub async fn record_delivery_attempt(
976 &self,
977 message_id: &MessageId,
978 class: DeliveryClass,
979 expires_at: std::time::Instant,
980 ) {
981 self.delivery_attempts.lock().await.insert(
982 message_id.as_str().to_string(),
983 DeliveryAttemptState {
984 class,
985 attempts: 0,
986 outcome: None,
987 expires_at,
988 },
989 );
990 }
991
992 pub async fn increment_delivery_attempts(&self, message_id: &MessageId) -> Option<u32> {
993 let mut attempts = self.delivery_attempts.lock().await;
994 let state = attempts.get_mut(message_id.as_str())?;
995 state.attempts = state.attempts.saturating_add(1);
996 Some(state.attempts)
997 }
998
999 pub async fn set_delivery_outcome(&self, message_id: &MessageId, outcome: DeliveryOutcome) {
1000 if let Some(state) = self
1001 .delivery_attempts
1002 .lock()
1003 .await
1004 .get_mut(message_id.as_str())
1005 {
1006 state.outcome = Some(outcome);
1007 }
1008 }
1009
1010 pub async fn delivery_outcome(&self, message_id: &MessageId) -> Option<DeliveryOutcome> {
1011 self.delivery_attempts
1012 .lock()
1013 .await
1014 .get(message_id.as_str())
1015 .and_then(|state| state.outcome)
1016 }
1017
1018 pub async fn clear_delivery_attempt(&self, message_id: &MessageId) {
1019 self.delivery_attempts
1020 .lock()
1021 .await
1022 .remove(message_id.as_str());
1023 }
1024
1025 pub async fn delivery_expired(&self, message_id: &MessageId) -> bool {
1026 self.delivery_attempts
1027 .lock()
1028 .await
1029 .get(message_id.as_str())
1030 .map(|state| std::time::Instant::now() >= state.expires_at)
1031 .unwrap_or(false)
1032 }
1033
1034 pub async fn delivery_class_for(&self, message_id: &MessageId) -> Option<DeliveryClass> {
1035 self.delivery_attempts
1036 .lock()
1037 .await
1038 .get(message_id.as_str())
1039 .map(|state| state.class)
1040 }
1041
1042 pub async fn prune_delivery_dedup(&self, max_age: std::time::Duration) {
1043 let mut dedup = self.delivery_dedup.lock().await;
1044 let now = std::time::Instant::now();
1045 dedup.retain(|_, seen_at| now.duration_since(*seen_at) <= max_age);
1046 }
1047
1048 pub async fn record_delivery_receipt(
1049 &self,
1050 origin_node_id: &str,
1051 message_id: &MessageId,
1052 ) -> bool {
1053 let key = (origin_node_id.to_string(), message_id.as_str().to_string());
1054 let mut dedup = self.delivery_dedup.lock().await;
1055 let is_new = !dedup.contains_key(&key);
1056 dedup.insert(key, std::time::Instant::now());
1057 is_new
1058 }
1059
1060 pub async fn next_ordering_sequence(&self, from: &str, to: &str, ordering_key: &str) -> u64 {
1061 let key = (from.to_string(), to.to_string(), ordering_key.to_string());
1062 let mut sequences = self.delivery_next_ordering_sequence.lock().await;
1063 let next = sequences.get(&key).copied().unwrap_or(1);
1064 sequences.insert(key, next.saturating_add(1));
1065 next
1066 }
1067
1068 pub async fn accept_ordered_incoming(
1069 &self,
1070 origin_node_id: &str,
1071 destination_node_id: &str,
1072 ordering_key: &str,
1073 ordering_sequence: u64,
1074 message: Message,
1075 max_buffered_messages: usize,
1076 ) -> Vec<Message> {
1077 let key = (
1078 origin_node_id.to_string(),
1079 destination_node_id.to_string(),
1080 ordering_key.to_string(),
1081 );
1082
1083 let mut buffers = self.delivery_ordered_inbound.lock().await;
1084 let bucket = buffers.entry(key).or_insert_with(|| OrderedDeliveryBuffer {
1085 next_expected: 1,
1086 pending: BTreeMap::new(),
1087 last_activity: std::time::Instant::now(),
1088 });
1089
1090 if ordering_sequence < bucket.next_expected {
1091 return Vec::new();
1092 }
1093
1094 if ordering_sequence > bucket.next_expected {
1095 if bucket.pending.len() < max_buffered_messages {
1096 bucket.pending.insert(ordering_sequence, message);
1097 bucket.last_activity = std::time::Instant::now();
1098 }
1099 return Vec::new();
1100 }
1101
1102 bucket.last_activity = std::time::Instant::now();
1103 let mut ready = vec![message];
1104 bucket.next_expected = bucket.next_expected.saturating_add(1);
1105 while let Some(next_message) = bucket.pending.remove(&bucket.next_expected) {
1106 ready.push(next_message);
1107 bucket.next_expected = bucket.next_expected.saturating_add(1);
1108 }
1109
1110 ready
1111 }
1112
1113 pub async fn prune_ordered_inbound(&self, max_age: std::time::Duration) {
1117 let mut buffers = self.delivery_ordered_inbound.lock().await;
1118 let now = std::time::Instant::now();
1119 buffers.retain(|_, bucket| now.duration_since(bucket.last_activity) <= max_age);
1120 }
1121
1122 pub async fn next_relay_sequence(&self, from: &str, to: &str) -> u64 {
1123 let key = (from.to_string(), to.to_string());
1124 let mut sequences = self.relay_next_sequence.lock().await;
1125 let next = sequences.get(&key).copied().unwrap_or(1);
1126 sequences.insert(key, next.saturating_add(1));
1127 next
1128 }
1129
1130 pub async fn set_binding(
1132 &self,
1133 from_node_id: &str,
1134 to_node_id: &str,
1135 store_forward: bool,
1136 expires_at: Option<u64>,
1137 qos: Option<String>,
1138 ) {
1139 self.relay_bindings.lock().await.insert(
1140 (from_node_id.to_string(), to_node_id.to_string()),
1141 BindingPrefs {
1142 store_forward,
1143 expires_at,
1144 qos,
1145 },
1146 );
1147 }
1148
1149 pub async fn binding_store_forward(&self, from_node_id: &str, to_node_id: &str) -> bool {
1150 self.relay_bindings
1151 .lock()
1152 .await
1153 .get(&(from_node_id.to_string(), to_node_id.to_string()))
1154 .map(|b| b.store_forward)
1155 .unwrap_or(false)
1156 }
1157
1158 pub async fn binding_expires_at(&self, from_node_id: &str, to_node_id: &str) -> Option<u64> {
1159 self.relay_bindings
1160 .lock()
1161 .await
1162 .get(&(from_node_id.to_string(), to_node_id.to_string()))
1163 .and_then(|b| b.expires_at)
1164 }
1165
1166 pub async fn binding_qos(&self, from_node_id: &str, to_node_id: &str) -> Option<String> {
1167 self.relay_bindings
1168 .lock()
1169 .await
1170 .get(&(from_node_id.to_string(), to_node_id.to_string()))
1171 .and_then(|b| b.qos.clone())
1172 }
1173
1174 pub async fn last_sequence(&self, from_node_id: &str, to_node_id: &str) -> Option<u64> {
1175 self.relay_last_sequence
1176 .lock()
1177 .await
1178 .get(&(from_node_id.to_string(), to_node_id.to_string()))
1179 .cloned()
1180 }
1181
1182 pub async fn update_sequence(&self, from_node_id: &str, to_node_id: &str, seq: u64) {
1183 self.relay_last_sequence
1184 .lock()
1185 .await
1186 .insert((from_node_id.to_string(), to_node_id.to_string()), seq);
1187 }
1188
1189 pub async fn is_bound(&self, from_node_id: &str, to_node_id: &str) -> bool {
1190 self.relay_bindings
1191 .lock()
1192 .await
1193 .contains_key(&(from_node_id.to_string(), to_node_id.to_string()))
1194 }
1195
1196 pub async fn add_binding_id(&self, binding_id: &str, from_node_id: &str, to_node_id: &str) {
1197 self.relay_binding_ids.lock().await.insert(
1198 binding_id.to_string(),
1199 (from_node_id.to_string(), to_node_id.to_string()),
1200 );
1201 }
1202
1203 pub async fn remove_binding_by_id(&self, binding_id: &str) -> Option<(String, String)> {
1204 let mut ids = self.relay_binding_ids.lock().await;
1205 let pair = ids.remove(binding_id);
1206 if let Some((from, to)) = &pair {
1207 self.relay_bindings
1208 .lock()
1209 .await
1210 .remove(&(from.clone(), to.clone()));
1211 }
1212 pair
1213 }
1214
1215 pub async fn list_bindings_for_from(
1217 &self,
1218 from_node_id: &str,
1219 ) -> Vec<(String, Option<String>)> {
1220 let bindings = self.relay_bindings.lock().await;
1221 let mut results: Vec<(String, Option<String>)> = Vec::new();
1222 let ids = self.relay_binding_ids.lock().await;
1224 for (from, to) in bindings.keys() {
1225 if from == from_node_id {
1226 let bid = ids.iter().find_map(|(id, pair)| {
1227 if pair.0 == *from && pair.1 == *to {
1228 Some(id.clone())
1229 } else {
1230 None
1231 }
1232 });
1233 results.push((to.clone(), bid));
1234 }
1235 }
1236 results
1237 }
1238
1239 pub async fn enqueue_store_forward(
1240 &self,
1241 to_node_id: &str,
1242 message_json: String,
1243 expires_at: Option<u64>,
1244 priority_front: bool,
1245 soft_drop_bulk: bool,
1246 origin_from: Option<String>,
1247 ) -> bool {
1248 let (max_queue_per_target, max_queue_global) = self.relay_queue_caps();
1249 let mut to_notify: Vec<(String, crate::network::message::Reason)> = Vec::new();
1251 if let Some(exp) = expires_at {
1253 if exp <= current_unix_ts() {
1254 if let Some(o) = origin_from.as_ref() {
1255 if self.has_node_id(o).await {
1256 let notify = crate::network::message::Message::new(
1257 o,
1258 o,
1259 crate::network::message::MessageType::RelayNotify {
1260 notif_type: crate::network::message::Reason::Timeout,
1261 binding_id: None,
1262 detail: Some(format!("target={}", to_node_id)),
1263 },
1264 None,
1265 None,
1266 );
1267 let _ = self.send_to_node_id(o, notify.as_json()).await;
1268 }
1269 }
1270 return false;
1271 }
1272 }
1273 let mut q = self.relay_queue.lock().await;
1274 if let Some(v) = q.get_mut(to_node_id) {
1277 let now = current_unix_ts();
1279 let mut kept: Vec<(String, Option<u64>, Option<String>)> = Vec::with_capacity(v.len());
1280 for (frame, exp, origin) in v.drain(..) {
1281 if exp.map(|e| e > now).unwrap_or(true) {
1282 kept.push((frame, exp, origin));
1283 } else if let Some(o) = origin.as_ref() {
1284 to_notify.push((o.clone(), crate::network::message::Reason::Timeout));
1285 }
1286 }
1287 *v = kept;
1288 if v.len() >= max_queue_per_target {
1294 let drop_count = v.len() + 1 - max_queue_per_target;
1295 for _ in 0..drop_count {
1296 let removed = v.remove(0);
1297 if let Some(o) = removed.2.as_ref() {
1298 to_notify.push((o.clone(), crate::network::message::Reason::Overload));
1299 }
1300 }
1301 }
1302 }
1303 let entry = q.entry(to_node_id.to_string()).or_insert_with(Vec::new);
1304 if entry.len() >= max_queue_per_target {
1305 if soft_drop_bulk {
1307 return false;
1308 }
1309 let removed = entry.remove(0);
1311 if let Some(o) = removed.2.as_ref() {
1312 to_notify.push((o.clone(), crate::network::message::Reason::Overload));
1313 }
1314 }
1315 if priority_front {
1316 entry.insert(0, (message_json, expires_at, origin_from));
1317 } else {
1318 entry.push((message_json, expires_at, origin_from));
1319 }
1320
1321 let mut total_len: usize = q.values().map(|v| v.len()).sum();
1323 if total_len > max_queue_global {
1324 let mut keys: Vec<String> = q.keys().cloned().collect();
1325 keys.sort();
1326 let mut idx = 0usize;
1327 while total_len > max_queue_global && !keys.is_empty() {
1328 let k = &keys[idx % keys.len()];
1329 if let Some(vec) = q.get_mut(k) {
1330 if !vec.is_empty() {
1331 let removed = vec.remove(0);
1332 if let Some(o) = removed.2.as_ref() {
1333 to_notify.push((o.clone(), crate::network::message::Reason::Overload));
1334 }
1335 total_len -= 1;
1336 } else {
1337 keys.remove(idx % keys.len());
1339 continue;
1340 }
1341 }
1342 idx += 1;
1343 }
1344 }
1345 drop(q);
1346
1347 for (o, reason) in to_notify {
1349 if self.has_node_id(&o).await {
1350 let notify = crate::network::message::Message::new(
1351 &o,
1352 &o,
1353 crate::network::message::MessageType::RelayNotify {
1354 notif_type: reason,
1355 binding_id: None,
1356 detail: Some(format!("target={}", to_node_id)),
1357 },
1358 None,
1359 None,
1360 );
1361 let _ = self.send_to_node_id(&o, notify.as_json()).await;
1362 }
1363 }
1364 true
1365 }
1366
1367 pub async fn can_enqueue_store_forward(&self, to_node_id: &str) -> bool {
1368 let (max_queue_per_target, max_queue_global) = self.relay_queue_caps();
1369 let q = self.relay_queue.lock().await;
1370 let per_target_ok = q
1371 .get(to_node_id)
1372 .map(|v| v.len() < max_queue_per_target)
1373 .unwrap_or(true);
1374 let total_len: usize = q.values().map(|v| v.len()).sum();
1375 let global_ok = total_len < max_queue_global;
1376 per_target_ok && global_ok
1377 }
1378
1379 pub async fn test_get_queue_for(
1381 &self,
1382 to_node_id: &str,
1383 ) -> Vec<(String, Option<u64>, Option<String>)> {
1384 let q = self.relay_queue.lock().await;
1385 q.get(to_node_id).cloned().unwrap_or_default()
1386 }
1387
1388 pub async fn set_transport_kind(&self, node_id: &str, kind: TransportKind) {
1392 self.transport_kind
1393 .lock()
1394 .await
1395 .insert(node_id.to_string(), kind);
1396 }
1397
1398 pub async fn get_transport_kind(&self, node_id: &str) -> Option<TransportKind> {
1400 self.transport_kind.lock().await.get(node_id).cloned()
1401 }
1402
1403 pub async fn add_udp_listen_addr(&self, node_id: &str, addr: &str) {
1405 self.udp_listen_addrs
1406 .lock()
1407 .await
1408 .insert(node_id.to_string(), addr.to_string());
1409 }
1410
1411 pub async fn udp_listen_addr_for(&self, node_id: &str) -> Option<String> {
1413 self.udp_listen_addrs.lock().await.get(node_id).cloned()
1414 }
1415
1416 pub async fn set_udp_session_id(&self, node_id: &str, session_id: [u8; 8]) {
1418 self.udp_session_ids
1419 .lock()
1420 .await
1421 .insert(node_id.to_string(), session_id);
1422 self.record_peer_activity(node_id, RouteKind::Udp).await;
1423 self.start_liveness_monitor(node_id.to_string(), None).await;
1424 }
1425
1426 pub async fn udp_session_id_for(&self, node_id: &str) -> Option<[u8; 8]> {
1428 self.udp_session_ids.lock().await.get(node_id).copied()
1429 }
1430
1431 #[cfg(feature = "noise")]
1432 pub async fn ensure_udp_session(
1433 &self,
1434 node_id: &str,
1435 local_node_id: &str,
1436 timeout: std::time::Duration,
1437 ) -> Result<[u8; 8], String> {
1438 if let Some(session_id) = self.udp_session_id_for(node_id).await {
1439 return Ok(session_id);
1440 }
1441
1442 let target_addr = self
1443 .udp_listen_addr_for(node_id)
1444 .await
1445 .ok_or_else(|| format!("udp listen addr not found for {node_id}"))?
1446 .parse::<SocketAddr>()
1447 .map_err(|err| format!("invalid udp listen addr for {node_id}: {err}"))?;
1448
1449 let handle = self
1450 .udp_handle
1451 .lock()
1452 .await
1453 .clone()
1454 .ok_or_else(|| "udp handle not initialized".to_string())?;
1455
1456 crate::network::udp_listener::connect_udp(
1457 target_addr,
1458 &handle.socket,
1459 &handle.sessions,
1460 &handle.static_private,
1461 local_node_id,
1462 )
1463 .await
1464 .map_err(|err| err.to_string())?;
1465
1466 let deadline = std::time::Instant::now() + timeout;
1467 loop {
1468 if let Some(session_id) = self.udp_session_id_for(node_id).await {
1469 return Ok(session_id);
1470 }
1471 if std::time::Instant::now() >= deadline {
1472 return Err(format!("timed out establishing udp session for {node_id}"));
1473 }
1474 tokio::time::sleep(std::time::Duration::from_millis(25)).await;
1475 }
1476 }
1477
1478 #[cfg(not(feature = "noise"))]
1479 pub async fn ensure_udp_session(
1480 &self,
1481 node_id: &str,
1482 _local_node_id: &str,
1483 _timeout: std::time::Duration,
1484 ) -> Result<[u8; 8], String> {
1485 Err(format!(
1486 "udp transport is unavailable without the noise feature for {node_id}"
1487 ))
1488 }
1489
1490 #[cfg(feature = "noise")]
1491 pub async fn send_udp_message_to_node(
1492 &self,
1493 node_id: &str,
1494 payload: &[u8],
1495 ) -> Result<(), String> {
1496 let session_id = self
1497 .udp_session_id_for(node_id)
1498 .await
1499 .ok_or_else(|| format!("udp session not found for {node_id}"))?;
1500 let handle = self
1501 .udp_handle
1502 .lock()
1503 .await
1504 .clone()
1505 .ok_or_else(|| "udp handle not initialized".to_string())?;
1506
1507 crate::network::udp_listener::send_udp(
1508 &session_id,
1509 payload,
1510 &handle.socket,
1511 &handle.sessions,
1512 )
1513 .await
1514 .map_err(|err| err.to_string())
1515 }
1516
1517 #[cfg(not(feature = "noise"))]
1518 pub async fn send_udp_message_to_node(
1519 &self,
1520 node_id: &str,
1521 _payload: &[u8],
1522 ) -> Result<(), String> {
1523 Err(format!(
1524 "udp transport is unavailable without the noise feature for {node_id}"
1525 ))
1526 }
1527
1528 pub async fn dispatch_udp_payload(&self, node_id: &str, payload: &[u8]) {
1532 use crate::events::{
1533 dispatcher,
1534 model::{LogEvent, LogLevel, SystemEvent},
1535 };
1536 let mut meta = dispatcher::meta("udp", LogLevel::Debug);
1537 meta.corr_id = Some(dispatcher::correlation_id());
1538 dispatcher::emit(LogEvent::System(SystemEvent {
1539 meta,
1540 action: "udp_payload_received".into(),
1541 detail: Some(format!("node_id={} bytes={}", node_id, payload.len())),
1542 }));
1543 }
1544
1545 pub async fn set_own_observed_addr(
1549 &self,
1550 addr: &str,
1551 observer_node_id: Option<&str>,
1552 request_nonce: Option<[u8; 8]>,
1553 ) {
1554 let record = ObservedUdpAddrRecord {
1555 addr: addr.to_string(),
1556 observed_at: std::time::Instant::now(),
1557 observer_node_id: observer_node_id.map(str::to_string),
1558 request_nonce,
1559 };
1560 *self.own_observed_addr.lock().await = Some(record.clone());
1561 let sample_key = observer_node_id
1562 .map(str::to_string)
1563 .or_else(|| request_nonce.map(hex::encode))
1564 .unwrap_or_else(|| addr.to_string());
1565 self.own_observed_addr_samples
1566 .lock()
1567 .await
1568 .insert(sample_key, record);
1569 }
1570
1571 pub async fn own_udp_observed_addr_if_fresh(&self, max_age_secs: u64) -> Option<String> {
1573 self.own_observed_addr
1574 .lock()
1575 .await
1576 .as_ref()
1577 .filter(|record| record.observed_at.elapsed().as_secs() < max_age_secs)
1578 .map(|record| record.addr.clone())
1579 }
1580
1581 pub async fn own_udp_observed_records_if_fresh(
1583 &self,
1584 max_age_secs: u64,
1585 ) -> Vec<ObservedUdpAddrRecord> {
1586 self.own_observed_addr_samples
1587 .lock()
1588 .await
1589 .values()
1590 .filter(|record| record.observed_at.elapsed().as_secs() < max_age_secs)
1591 .cloned()
1592 .collect()
1593 }
1594
1595 pub async fn nat_observation_refresh_secs(&self) -> u64 {
1597 #[cfg(feature = "noise")]
1598 {
1599 if let Some(state) = self.nat_state.read().await.clone() {
1600 return state.refresh_secs;
1601 }
1602 }
1603 300
1604 }
1605
1606 pub async fn add_udp_observed_addr(
1608 &self,
1609 node_id: &str,
1610 addr: &str,
1611 observer_node_id: Option<&str>,
1612 request_nonce: Option<[u8; 8]>,
1613 ) {
1614 self.udp_observed_addrs.lock().await.insert(
1615 node_id.to_string(),
1616 ObservedUdpAddrRecord {
1617 addr: addr.to_string(),
1618 observed_at: std::time::Instant::now(),
1619 observer_node_id: observer_node_id.map(str::to_string),
1620 request_nonce,
1621 },
1622 );
1623 }
1624
1625 pub async fn udp_observed_addr_for(&self, node_id: &str) -> Option<String> {
1627 self.udp_observed_addrs
1628 .lock()
1629 .await
1630 .get(node_id)
1631 .map(|record| record.addr.clone())
1632 }
1633
1634 pub async fn udp_observed_record_for_if_fresh(
1635 &self,
1636 node_id: &str,
1637 max_age_secs: u64,
1638 ) -> Option<ObservedUdpAddrRecord> {
1639 self.udp_observed_addrs
1640 .lock()
1641 .await
1642 .get(node_id)
1643 .filter(|record| record.observed_at.elapsed().as_secs() < max_age_secs)
1644 .cloned()
1645 }
1646
1647 pub async fn udp_observed_addr_for_if_fresh(
1649 &self,
1650 node_id: &str,
1651 max_age_secs: u64,
1652 ) -> Option<String> {
1653 self.udp_observed_addrs
1654 .lock()
1655 .await
1656 .get(node_id)
1657 .and_then(|record| {
1658 if record.observed_at.elapsed().as_secs() < max_age_secs {
1659 Some(record.addr.clone())
1660 } else {
1661 None
1662 }
1663 })
1664 }
1665
1666 pub async fn node_id_for_addr(&self, addr: &SocketAddr) -> Option<String> {
1668 self.node_ids
1669 .lock()
1670 .await
1671 .iter()
1672 .find_map(|(nid, a)| if a == addr { Some(nid.clone()) } else { None })
1673 }
1674
1675 pub async fn add_pending_punch(
1677 &self,
1678 attempt_id: &str,
1679 responder_node_id: &str,
1680 initiator_node_id: &str,
1681 timeout_ms: u64,
1682 ) {
1683 self.pending_punches.lock().await.insert(
1684 attempt_id.to_string(),
1685 PendingPunch {
1686 attempt_id: attempt_id.to_string(),
1687 initiator_node_id: initiator_node_id.to_string(),
1688 responder_node_id: responder_node_id.to_string(),
1689 timeout_ms,
1690 },
1691 );
1692 }
1693
1694 pub async fn get_pending_punch(&self, attempt_id: &str) -> Option<PendingPunch> {
1696 self.pending_punches.lock().await.get(attempt_id).cloned()
1697 }
1698
1699 pub async fn remove_pending_punch(&self, attempt_id: &str) {
1701 self.pending_punches.lock().await.remove(attempt_id);
1702 }
1703
1704 pub async fn register_pending_observation(
1705 &self,
1706 observer_node_id: &str,
1707 expected_source: SocketAddr,
1708 nonce: [u8; 8],
1709 ) {
1710 self.pending_observations.lock().await.insert(
1711 nonce,
1712 PendingObservation {
1713 observer_node_id: observer_node_id.to_string(),
1714 expected_source,
1715 nonce,
1716 requested_at: std::time::Instant::now(),
1717 },
1718 );
1719 }
1720
1721 pub async fn pending_observation(&self, nonce: &[u8; 8]) -> Option<PendingObservation> {
1722 self.pending_observations.lock().await.get(nonce).cloned()
1723 }
1724
1725 pub async fn remove_pending_observation(&self, nonce: &[u8; 8]) -> Option<PendingObservation> {
1726 self.pending_observations.lock().await.remove(nonce)
1727 }
1728
1729 pub fn set_gateway_external_ip(&self, external_ip: Option<String>) {
1730 *self.gateway_external_ip.write() = external_ip;
1731 }
1732
1733 pub fn gateway_external_ip(&self) -> Option<String> {
1734 self.gateway_external_ip.read().clone()
1735 }
1736
1737 pub fn set_public_tcp_hello_addr(&self, addr: Option<String>) {
1738 *self.public_tcp_hello_addr.write() = addr;
1739 }
1740
1741 pub fn public_tcp_hello_addr(&self) -> Option<String> {
1742 self.public_tcp_hello_addr.read().clone()
1743 }
1744
1745 pub fn set_direct_tcp_hint(&self, enabled: bool) {
1746 self.direct_tcp_hint.store(enabled, Ordering::Relaxed);
1747 }
1748
1749 pub fn direct_tcp_hint(&self) -> bool {
1750 self.direct_tcp_hint.load(Ordering::Relaxed)
1751 }
1752
1753 pub fn replace_nat_diagnostic_summary(&self, summary: Option<String>) -> bool {
1754 let mut guard = self.nat_diagnostic_summary.write();
1755 if *guard == summary {
1756 false
1757 } else {
1758 *guard = summary;
1759 true
1760 }
1761 }
1762
1763 #[cfg(feature = "noise")]
1765 pub async fn set_udp_handle(
1766 &self,
1767 socket: std::sync::Arc<tokio::net::UdpSocket>,
1768 sessions: crate::network::udp_listener::UdpSessions,
1769 static_private: Vec<u8>,
1770 ) {
1771 *self.udp_handle.lock().await = Some(UdpHandle {
1772 socket,
1773 sessions,
1774 static_private,
1775 });
1776 }
1777
1778 #[cfg(feature = "noise")]
1780 pub async fn set_nat_state(
1781 &self,
1782 state: std::sync::Arc<crate::network::nat_traversal::NatState>,
1783 ) {
1784 *self.nat_state.write().await = Some(state);
1785 }
1786
1787 #[cfg(feature = "noise")]
1789 pub async fn handle_punch_coordinate_msg(
1790 &self,
1791 attempt_id: &str,
1792 from_node_id: &str,
1793 target_node_id: &str,
1794 timeout_ms: u64,
1795 ) {
1796 let nat = self.nat_state.read().await.clone();
1797 if let Some(state) = nat {
1798 crate::network::nat_traversal::handle_punch_coordinate(
1799 self,
1800 &state,
1801 attempt_id,
1802 from_node_id,
1803 target_node_id,
1804 timeout_ms,
1805 )
1806 .await;
1807 }
1808 }
1809
1810 #[cfg(feature = "noise")]
1812 pub async fn handle_punch_ready_msg(
1813 &self,
1814 attempt_id: &str,
1815 responder_node_id: &str,
1816 target_node_id: &str,
1817 ok: bool,
1818 ) {
1819 let nat = self.nat_state.read().await.clone();
1820 if let Some(state) = nat {
1821 crate::network::nat_traversal::handle_punch_ready(
1822 self,
1823 &state,
1824 attempt_id,
1825 responder_node_id,
1826 target_node_id,
1827 ok,
1828 )
1829 .await;
1830 }
1831 }
1832
1833 #[cfg(feature = "noise")]
1835 pub async fn handle_punch_go_msg(
1836 &self,
1837 local_node_id: &str,
1838 punch: crate::network::nat_traversal::PunchGoParams,
1839 ) {
1840 let nat = self.nat_state.read().await.clone();
1841 let handle = self.udp_handle.lock().await.clone();
1842 if let (Some(state), Some(h)) = (nat, handle) {
1843 let pm = self.clone();
1844 let local_node_id = local_node_id.to_string();
1845 tokio::spawn(async move {
1846 crate::network::nat_traversal::execute_punch_window(
1847 &pm,
1848 &h.socket,
1849 &h.sessions,
1850 &h.static_private,
1851 &state,
1852 &local_node_id,
1853 punch,
1854 )
1855 .await;
1856 });
1857 }
1858 }
1859}
1860
1861#[derive(Clone, Debug)]
1862struct BindingPrefs {
1863 store_forward: bool,
1864 expires_at: Option<u64>,
1866 qos: Option<String>,
1868}
1869
1870fn current_unix_ts() -> u64 {
1871 use std::time::{SystemTime, UNIX_EPOCH};
1872 SystemTime::now()
1873 .duration_since(UNIX_EPOCH)
1874 .unwrap_or_default()
1875 .as_secs()
1876}