1use std::net::SocketAddr;
29use std::time::Duration;
30
31use crate::nat_traversal_api::PeerId;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
38pub enum NatType {
39 None,
43
44 FullCone,
49
50 AddressRestricted,
55
56 PortRestricted,
61
62 Symmetric,
67
68 #[default]
72 Unknown,
73}
74
75impl std::fmt::Display for NatType {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 Self::None => write!(f, "None (Public IP)"),
79 Self::FullCone => write!(f, "Full Cone"),
80 Self::AddressRestricted => write!(f, "Address Restricted"),
81 Self::PortRestricted => write!(f, "Port Restricted"),
82 Self::Symmetric => write!(f, "Symmetric"),
83 Self::Unknown => write!(f, "Unknown"),
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
103pub struct NodeStatus {
104 pub peer_id: PeerId,
107
108 pub local_addr: SocketAddr,
110
111 pub external_addrs: Vec<SocketAddr>,
116
117 pub nat_type: NatType,
120
121 pub can_receive_direct: bool,
125
126 pub has_public_ip: bool,
130
131 pub connected_peers: usize,
134
135 pub active_connections: usize,
137
138 pub pending_connections: usize,
140
141 pub direct_connections: u64,
144
145 pub relayed_connections: u64,
147
148 pub hole_punch_success_rate: f64,
152
153 pub is_relaying: bool,
159
160 pub relay_sessions: usize,
162
163 pub relay_bytes_forwarded: u64,
165
166 pub is_coordinating: bool,
172
173 pub coordination_sessions: usize,
175
176 pub avg_rtt: Duration,
179
180 pub uptime: Duration,
182}
183
184impl Default for NodeStatus {
185 fn default() -> Self {
186 Self {
187 peer_id: PeerId([0u8; 32]),
188 local_addr: "0.0.0.0:0".parse().unwrap_or_else(|_| {
189 SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0)
190 }),
191 external_addrs: Vec::new(),
192 nat_type: NatType::Unknown,
193 can_receive_direct: false,
194 has_public_ip: false,
195 connected_peers: 0,
196 active_connections: 0,
197 pending_connections: 0,
198 direct_connections: 0,
199 relayed_connections: 0,
200 hole_punch_success_rate: 0.0,
201 is_relaying: false,
202 relay_sessions: 0,
203 relay_bytes_forwarded: 0,
204 is_coordinating: false,
205 coordination_sessions: 0,
206 avg_rtt: Duration::ZERO,
207 uptime: Duration::ZERO,
208 }
209 }
210}
211
212impl NodeStatus {
213 pub fn is_connected(&self) -> bool {
215 self.connected_peers > 0
216 }
217
218 pub fn can_help_traversal(&self) -> bool {
223 self.has_public_ip || self.can_receive_direct
224 }
225
226 pub fn total_connections(&self) -> u64 {
228 self.direct_connections + self.relayed_connections
229 }
230
231 pub fn direct_rate(&self) -> f64 {
235 let total = self.total_connections();
236 if total == 0 {
237 0.0
238 } else {
239 self.direct_connections as f64 / total as f64
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_nat_type_display() {
250 assert_eq!(format!("{}", NatType::None), "None (Public IP)");
251 assert_eq!(format!("{}", NatType::FullCone), "Full Cone");
252 assert_eq!(
253 format!("{}", NatType::AddressRestricted),
254 "Address Restricted"
255 );
256 assert_eq!(format!("{}", NatType::PortRestricted), "Port Restricted");
257 assert_eq!(format!("{}", NatType::Symmetric), "Symmetric");
258 assert_eq!(format!("{}", NatType::Unknown), "Unknown");
259 }
260
261 #[test]
262 fn test_nat_type_default() {
263 assert_eq!(NatType::default(), NatType::Unknown);
264 }
265
266 #[test]
267 fn test_node_status_default() {
268 let status = NodeStatus::default();
269 assert_eq!(status.nat_type, NatType::Unknown);
270 assert!(!status.can_receive_direct);
271 assert!(!status.has_public_ip);
272 assert_eq!(status.connected_peers, 0);
273 assert!(!status.is_relaying);
274 assert!(!status.is_coordinating);
275 }
276
277 #[test]
278 fn test_is_connected() {
279 let mut status = NodeStatus::default();
280 assert!(!status.is_connected());
281
282 status.connected_peers = 1;
283 assert!(status.is_connected());
284 }
285
286 #[test]
287 fn test_can_help_traversal() {
288 let mut status = NodeStatus::default();
289 assert!(!status.can_help_traversal());
290
291 status.has_public_ip = true;
292 assert!(status.can_help_traversal());
293
294 status.has_public_ip = false;
295 status.can_receive_direct = true;
296 assert!(status.can_help_traversal());
297 }
298
299 #[test]
300 fn test_total_connections() {
301 let mut status = NodeStatus::default();
302 status.direct_connections = 5;
303 status.relayed_connections = 3;
304 assert_eq!(status.total_connections(), 8);
305 }
306
307 #[test]
308 fn test_direct_rate() {
309 let mut status = NodeStatus::default();
310 assert_eq!(status.direct_rate(), 0.0);
311
312 status.direct_connections = 8;
313 status.relayed_connections = 2;
314 assert!((status.direct_rate() - 0.8).abs() < 0.001);
315 }
316
317 #[test]
318 fn test_status_is_debug() {
319 let status = NodeStatus::default();
320 let debug_str = format!("{:?}", status);
321 assert!(debug_str.contains("NodeStatus"));
322 assert!(debug_str.contains("nat_type"));
323 assert!(debug_str.contains("is_relaying"));
324 }
325
326 #[test]
327 fn test_status_is_clone() {
328 let mut status = NodeStatus::default();
329 status.connected_peers = 5;
330 status.is_relaying = true;
331
332 let cloned = status.clone();
333 assert_eq!(status.connected_peers, cloned.connected_peers);
334 assert_eq!(status.is_relaying, cloned.is_relaying);
335 }
336
337 #[test]
338 fn test_nat_type_equality() {
339 assert_eq!(NatType::FullCone, NatType::FullCone);
340 assert_ne!(NatType::FullCone, NatType::Symmetric);
341 }
342
343 #[test]
344 fn test_status_with_relay() {
345 let mut status = NodeStatus::default();
346 status.is_relaying = true;
347 status.relay_sessions = 3;
348 status.relay_bytes_forwarded = 1024 * 1024; assert!(status.is_relaying);
351 assert_eq!(status.relay_sessions, 3);
352 assert_eq!(status.relay_bytes_forwarded, 1024 * 1024);
353 }
354
355 #[test]
356 fn test_status_with_coordinator() {
357 let mut status = NodeStatus::default();
358 status.is_coordinating = true;
359 status.coordination_sessions = 5;
360
361 assert!(status.is_coordinating);
362 assert_eq!(status.coordination_sessions, 5);
363 }
364
365 #[test]
366 fn test_external_addrs() {
367 let mut status = NodeStatus::default();
368 let addr1: SocketAddr = "1.2.3.4:9000".parse().unwrap();
369 let addr2: SocketAddr = "5.6.7.8:9001".parse().unwrap();
370
371 status.external_addrs.push(addr1);
372 status.external_addrs.push(addr2);
373
374 assert_eq!(status.external_addrs.len(), 2);
375 assert!(status.external_addrs.contains(&addr1));
376 assert!(status.external_addrs.contains(&addr2));
377 }
378}