1use std::net::SocketAddr;
30use std::time::Duration;
31
32use crate::nat_traversal_api::PeerId;
33pub use crate::reachability::ReachabilityScope;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
46pub enum NatType {
47 None,
52
53 FullCone,
58
59 AddressRestricted,
64
65 PortRestricted,
70
71 Symmetric,
76
77 #[default]
81 Unknown,
82}
83
84impl std::fmt::Display for NatType {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 Self::None => write!(f, "None (No NAT detected)"),
88 Self::FullCone => write!(f, "Full Cone"),
89 Self::AddressRestricted => write!(f, "Address Restricted"),
90 Self::PortRestricted => write!(f, "Port Restricted"),
91 Self::Symmetric => write!(f, "Symmetric"),
92 Self::Unknown => write!(f, "Unknown"),
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
113pub struct NodeStatus {
114 pub peer_id: PeerId,
117
118 pub local_addr: SocketAddr,
120
121 pub external_addrs: Vec<SocketAddr>,
126
127 pub nat_type: NatType,
133
134 pub can_receive_direct: bool,
139
140 pub direct_reachability_scope: Option<ReachabilityScope>,
142
143 pub has_global_address: bool,
147
148 pub port_mapping_active: bool,
150
151 pub port_mapping_addr: Option<SocketAddr>,
153
154 pub mdns_browsing: bool,
156
157 pub mdns_advertising: bool,
159
160 pub mdns_discovered_peers: usize,
162
163 pub relay_service_enabled: bool,
169
170 pub coordinator_service_enabled: bool,
175
176 pub bootstrap_service_enabled: bool,
180
181 pub connected_peers: usize,
184
185 pub active_connections: usize,
187
188 pub pending_connections: usize,
190
191 pub direct_connections: u64,
194
195 pub relayed_connections: u64,
197
198 pub hole_punch_success_rate: f64,
202
203 pub is_relaying: bool,
209
210 pub relay_sessions: usize,
214
215 pub relay_bytes_forwarded: u64,
217
218 pub is_coordinating: bool,
225
226 pub coordination_sessions: usize,
231
232 pub avg_rtt: Duration,
235
236 pub uptime: Duration,
238}
239
240impl Default for NodeStatus {
241 fn default() -> Self {
242 Self {
243 peer_id: PeerId([0u8; 32]),
244 local_addr: "0.0.0.0:0".parse().unwrap_or_else(|_| {
245 SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0)
246 }),
247 external_addrs: Vec::new(),
248 nat_type: NatType::Unknown,
249 can_receive_direct: false,
250 direct_reachability_scope: None,
251 has_global_address: false,
252 port_mapping_active: false,
253 port_mapping_addr: None,
254 mdns_browsing: false,
255 mdns_advertising: false,
256 mdns_discovered_peers: 0,
257 relay_service_enabled: false,
258 coordinator_service_enabled: false,
259 bootstrap_service_enabled: false,
260 connected_peers: 0,
261 active_connections: 0,
262 pending_connections: 0,
263 direct_connections: 0,
264 relayed_connections: 0,
265 hole_punch_success_rate: 0.0,
266 is_relaying: false,
267 relay_sessions: 0,
268 relay_bytes_forwarded: 0,
269 is_coordinating: false,
270 coordination_sessions: 0,
271 avg_rtt: Duration::ZERO,
272 uptime: Duration::ZERO,
273 }
274 }
275}
276
277impl NodeStatus {
278 pub fn is_connected(&self) -> bool {
280 self.connected_peers > 0
281 }
282
283 pub fn can_help_traversal(&self) -> bool {
288 self.relay_service_enabled || self.coordinator_service_enabled || self.can_receive_direct
289 }
290
291 pub fn total_connections(&self) -> u64 {
293 self.direct_connections + self.relayed_connections
294 }
295
296 pub fn direct_rate(&self) -> f64 {
300 let total = self.total_connections();
301 if total == 0 {
302 0.0
303 } else {
304 self.direct_connections as f64 / total as f64
305 }
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_nat_type_display() {
315 assert_eq!(format!("{}", NatType::None), "None (No NAT detected)");
316 assert_eq!(format!("{}", NatType::FullCone), "Full Cone");
317 assert_eq!(
318 format!("{}", NatType::AddressRestricted),
319 "Address Restricted"
320 );
321 assert_eq!(format!("{}", NatType::PortRestricted), "Port Restricted");
322 assert_eq!(format!("{}", NatType::Symmetric), "Symmetric");
323 assert_eq!(format!("{}", NatType::Unknown), "Unknown");
324 }
325
326 #[test]
327 fn test_nat_type_default() {
328 assert_eq!(NatType::default(), NatType::Unknown);
329 }
330
331 #[test]
332 fn test_node_status_default() {
333 let status = NodeStatus::default();
334 assert_eq!(status.nat_type, NatType::Unknown);
335 assert!(!status.can_receive_direct);
336 assert_eq!(status.direct_reachability_scope, None);
337 assert!(!status.has_global_address);
338 assert!(!status.port_mapping_active);
339 assert_eq!(status.port_mapping_addr, None);
340 assert!(!status.mdns_browsing);
341 assert!(!status.mdns_advertising);
342 assert_eq!(status.mdns_discovered_peers, 0);
343 assert!(!status.relay_service_enabled);
344 assert!(!status.coordinator_service_enabled);
345 assert!(!status.bootstrap_service_enabled);
346 assert_eq!(status.connected_peers, 0);
347 assert!(!status.is_relaying);
348 assert!(!status.is_coordinating);
349 }
350
351 #[test]
352 fn test_is_connected() {
353 let mut status = NodeStatus::default();
354 assert!(!status.is_connected());
355
356 status.connected_peers = 1;
357 assert!(status.is_connected());
358 }
359
360 #[test]
361 fn test_can_help_traversal() {
362 let mut status = NodeStatus::default();
363 assert!(!status.can_help_traversal());
364
365 status.has_global_address = true;
366 assert!(
367 !status.can_help_traversal(),
368 "Global address alone must not imply direct reachability"
369 );
370
371 status.relay_service_enabled = true;
372 assert!(status.can_help_traversal());
373
374 status.relay_service_enabled = false;
375 status.coordinator_service_enabled = true;
376 assert!(status.can_help_traversal());
377
378 status.coordinator_service_enabled = false;
379 status.can_receive_direct = true;
380 status.direct_reachability_scope = Some(ReachabilityScope::Global);
381 assert!(status.can_help_traversal());
382 }
383
384 #[test]
385 fn test_total_connections() {
386 let mut status = NodeStatus::default();
387 status.direct_connections = 5;
388 status.relayed_connections = 3;
389 assert_eq!(status.total_connections(), 8);
390 }
391
392 #[test]
393 fn test_direct_rate() {
394 let mut status = NodeStatus::default();
395 assert_eq!(status.direct_rate(), 0.0);
396
397 status.direct_connections = 8;
398 status.relayed_connections = 2;
399 assert!((status.direct_rate() - 0.8).abs() < 0.001);
400 }
401
402 #[test]
403 fn test_status_is_debug() {
404 let status = NodeStatus::default();
405 let debug_str = format!("{:?}", status);
406 assert!(debug_str.contains("NodeStatus"));
407 assert!(debug_str.contains("nat_type"));
408 assert!(debug_str.contains("is_relaying"));
409 }
410
411 #[test]
412 fn test_status_is_clone() {
413 let mut status = NodeStatus::default();
414 status.connected_peers = 5;
415 status.is_relaying = true;
416
417 let cloned = status.clone();
418 assert_eq!(status.connected_peers, cloned.connected_peers);
419 assert_eq!(status.is_relaying, cloned.is_relaying);
420 }
421
422 #[test]
423 fn test_nat_type_equality() {
424 assert_eq!(NatType::FullCone, NatType::FullCone);
425 assert_ne!(NatType::FullCone, NatType::Symmetric);
426 }
427
428 #[test]
429 fn test_status_with_relay() {
430 let mut status = NodeStatus::default();
431 status.is_relaying = true;
432 status.relay_sessions = 3;
433 status.relay_bytes_forwarded = 1024 * 1024; assert!(status.is_relaying);
436 assert_eq!(status.relay_sessions, 3);
437 assert_eq!(status.relay_bytes_forwarded, 1024 * 1024);
438 }
439
440 #[test]
441 fn test_status_with_coordinator() {
442 let mut status = NodeStatus::default();
443 status.is_coordinating = true;
444 status.coordination_sessions = 5;
445
446 assert!(status.is_coordinating);
447 assert_eq!(status.coordination_sessions, 5);
448 }
449
450 #[test]
451 fn test_external_addrs() {
452 let mut status = NodeStatus::default();
453 let addr1: SocketAddr = "1.2.3.4:9000".parse().unwrap();
454 let addr2: SocketAddr = "5.6.7.8:9001".parse().unwrap();
455
456 status.external_addrs.push(addr1);
457 status.external_addrs.push(addr2);
458
459 assert_eq!(status.external_addrs.len(), 2);
460 assert!(status.external_addrs.contains(&addr1));
461 assert!(status.external_addrs.contains(&addr2));
462 }
463
464 #[test]
465 fn test_direct_reachability_scope_tracks_observer_scope() {
466 let mut status = NodeStatus::default();
467 status.can_receive_direct = true;
468 status.direct_reachability_scope = Some(ReachabilityScope::LocalNetwork);
469
470 assert_eq!(
471 status.direct_reachability_scope,
472 Some(ReachabilityScope::LocalNetwork)
473 );
474 }
475
476 #[test]
477 fn test_port_mapping_status_fields() {
478 let mut status = NodeStatus::default();
479 let mapped_addr: SocketAddr = "198.51.100.77:41000".parse().unwrap();
480
481 status.port_mapping_active = true;
482 status.port_mapping_addr = Some(mapped_addr);
483
484 assert!(status.port_mapping_active);
485 assert_eq!(status.port_mapping_addr, Some(mapped_addr));
486 }
487
488 #[test]
489 fn test_mdns_status_fields() {
490 let mut status = NodeStatus::default();
491 status.mdns_browsing = true;
492 status.mdns_advertising = true;
493 status.mdns_discovered_peers = 2;
494
495 assert!(status.mdns_browsing);
496 assert!(status.mdns_advertising);
497 assert_eq!(status.mdns_discovered_peers, 2);
498 }
499
500 #[test]
501 fn test_assist_service_status_fields() {
502 let mut status = NodeStatus::default();
503 status.relay_service_enabled = true;
504 status.coordinator_service_enabled = true;
505 status.bootstrap_service_enabled = true;
506
507 assert!(status.relay_service_enabled);
508 assert!(status.coordinator_service_enabled);
509 assert!(status.bootstrap_service_enabled);
510 }
511}