1use libp2p::{Multiaddr, PeerId};
6use std::time::Duration;
7
8pub fn format_bytes(bytes: usize) -> String {
20 const KB: usize = 1024;
21 const MB: usize = KB * 1024;
22 const GB: usize = MB * 1024;
23 const TB: usize = GB * 1024;
24
25 if bytes >= TB {
26 format!("{:.2} TB", bytes as f64 / TB as f64)
27 } else if bytes >= GB {
28 format!("{:.2} GB", bytes as f64 / GB as f64)
29 } else if bytes >= MB {
30 format!("{:.2} MB", bytes as f64 / MB as f64)
31 } else if bytes >= KB {
32 format!("{:.2} KB", bytes as f64 / KB as f64)
33 } else {
34 format!("{} B", bytes)
35 }
36}
37
38pub fn format_bandwidth(bytes_per_sec: usize) -> String {
49 format!("{}/s", format_bytes(bytes_per_sec))
50}
51
52pub fn format_duration(duration: Duration) -> String {
65 let total_secs = duration.as_secs();
66 let millis = duration.subsec_millis();
67
68 if total_secs == 0 {
69 if millis == 0 {
70 return format!("{}µs", duration.subsec_micros());
71 }
72 return format!("{}ms", millis);
73 }
74
75 let hours = total_secs / 3600;
76 let minutes = (total_secs % 3600) / 60;
77 let seconds = total_secs % 60;
78
79 let mut parts = Vec::new();
80 if hours > 0 {
81 parts.push(format!("{}h", hours));
82 }
83 if minutes > 0 {
84 parts.push(format!("{}m", minutes));
85 }
86 if seconds > 0 || parts.is_empty() {
87 parts.push(format!("{}s", seconds));
88 }
89
90 parts.join(" ")
91}
92
93pub fn parse_multiaddr(addr: &str) -> Result<Multiaddr, String> {
107 addr.parse::<Multiaddr>()
108 .map_err(|e| format!("Failed to parse multiaddress: {}", e))
109}
110
111pub fn parse_multiaddrs(addrs: &[String]) -> Result<Vec<Multiaddr>, String> {
129 addrs.iter().map(|s| parse_multiaddr(s)).collect()
130}
131
132pub fn is_local_addr(addr: &Multiaddr) -> bool {
146 use libp2p::multiaddr::Protocol;
147
148 for proto in addr.iter() {
149 match proto {
150 Protocol::Ip4(ip) => {
151 return ip.is_loopback() || ip.is_link_local() || ip.is_private();
152 }
153 Protocol::Ip6(ip) => {
154 return ip.is_loopback() || ip.is_unicast_link_local();
155 }
156 _ => continue,
157 }
158 }
159 false
160}
161
162pub fn is_public_addr(addr: &Multiaddr) -> bool {
176 !is_local_addr(addr)
177}
178
179pub fn exponential_backoff(attempt: u32, base: Duration, max: Duration) -> Duration {
195 let backoff = base.saturating_mul(2_u32.saturating_pow(attempt));
196 backoff.min(max)
197}
198
199pub fn jittered_backoff(attempt: u32, base: Duration, max: Duration) -> Duration {
215 use rand::RngCore;
216 let backoff = exponential_backoff(attempt, base, max);
217 let mut rng = rand::rng();
218 let random_value = rng.next_u64() as f64 / u64::MAX as f64;
219 let jitter = 0.75 + (random_value * 0.5); Duration::from_secs_f64(backoff.as_secs_f64() * jitter)
221}
222
223pub fn truncate_peer_id(peer_id: &PeerId, length: usize) -> String {
236 let s = peer_id.to_string();
237 if s.len() <= length + 3 {
238 s
239 } else {
240 format!("{}...{}", &s[..length / 2], &s[s.len() - length / 2..])
241 }
242}
243
244pub fn percentage(value: usize, total: usize) -> f64 {
256 if total == 0 {
257 0.0
258 } else {
259 ((value as f64 / total as f64) * 10000.0).round() / 100.0
260 }
261}
262
263pub fn moving_average(current: f64, new_value: f64, alpha: f64) -> f64 {
277 alpha * new_value + (1.0 - alpha) * current
278}
279
280pub fn validate_alpha(alpha: f64) {
302 assert!(
303 (0.0..=1.0).contains(&alpha),
304 "Alpha must be in range [0.0, 1.0], got {}",
305 alpha
306 );
307}
308
309pub fn peers_match(peer1: &PeerId, peer2: &PeerId) -> bool {
325 peer1 == peer2
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_format_bytes() {
334 assert_eq!(format_bytes(0), "0 B");
335 assert_eq!(format_bytes(500), "500 B");
336 assert_eq!(format_bytes(1024), "1.00 KB");
337 assert_eq!(format_bytes(1_048_576), "1.00 MB");
338 assert_eq!(format_bytes(1_073_741_824), "1.00 GB");
339 assert_eq!(format_bytes(1_099_511_627_776), "1.00 TB");
340 }
341
342 #[test]
343 fn test_format_bandwidth() {
344 assert_eq!(format_bandwidth(1024), "1.00 KB/s");
345 assert_eq!(format_bandwidth(1_048_576), "1.00 MB/s");
346 }
347
348 #[test]
349 fn test_format_duration() {
350 assert_eq!(format_duration(Duration::from_millis(500)), "500ms");
351 assert_eq!(format_duration(Duration::from_secs(30)), "30s");
352 assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
353 assert_eq!(format_duration(Duration::from_secs(3665)), "1h 1m 5s");
354 assert_eq!(format_duration(Duration::from_secs(7200)), "2h");
355 }
356
357 #[test]
358 fn test_parse_multiaddr() {
359 let addr = parse_multiaddr("/ip4/127.0.0.1/tcp/4001").unwrap();
360 assert!(addr.to_string().contains("127.0.0.1"));
361 }
362
363 #[test]
364 fn test_parse_multiaddrs() {
365 let addrs = parse_multiaddrs(&[
366 "/ip4/127.0.0.1/tcp/4001".to_string(),
367 "/ip6/::1/tcp/4001".to_string(),
368 ])
369 .unwrap();
370 assert_eq!(addrs.len(), 2);
371 }
372
373 #[test]
374 fn test_is_local_addr() {
375 let local = parse_multiaddr("/ip4/127.0.0.1/tcp/4001").unwrap();
376 assert!(is_local_addr(&local));
377
378 let local_ipv6 = parse_multiaddr("/ip6/::1/tcp/4001").unwrap();
379 assert!(is_local_addr(&local_ipv6));
380
381 let private = parse_multiaddr("/ip4/192.168.1.1/tcp/4001").unwrap();
382 assert!(is_local_addr(&private));
383
384 let public = parse_multiaddr("/ip4/8.8.8.8/tcp/4001").unwrap();
385 assert!(!is_local_addr(&public));
386 }
387
388 #[test]
389 fn test_is_public_addr() {
390 let public = parse_multiaddr("/ip4/8.8.8.8/tcp/4001").unwrap();
391 assert!(is_public_addr(&public));
392
393 let local = parse_multiaddr("/ip4/127.0.0.1/tcp/4001").unwrap();
394 assert!(!is_public_addr(&local));
395 }
396
397 #[test]
398 fn test_exponential_backoff() {
399 let base = Duration::from_secs(1);
400 let max = Duration::from_secs(60);
401
402 assert_eq!(exponential_backoff(0, base, max), Duration::from_secs(1));
403 assert_eq!(exponential_backoff(1, base, max), Duration::from_secs(2));
404 assert_eq!(exponential_backoff(2, base, max), Duration::from_secs(4));
405 assert_eq!(exponential_backoff(3, base, max), Duration::from_secs(8));
406 assert_eq!(exponential_backoff(10, base, max), Duration::from_secs(60));
407 }
409
410 #[test]
411 fn test_jittered_backoff() {
412 let base = Duration::from_secs(1);
413 let max = Duration::from_secs(60);
414
415 for attempt in 0..5 {
416 let backoff = jittered_backoff(attempt, base, max);
417 let expected = exponential_backoff(attempt, base, max);
418 assert!(backoff.as_secs_f64() >= expected.as_secs_f64() * 0.75);
420 assert!(backoff.as_secs_f64() <= expected.as_secs_f64() * 1.25);
421 }
422 }
423
424 #[test]
425 fn test_truncate_peer_id() {
426 let peer_id = PeerId::random();
427 let truncated = truncate_peer_id(&peer_id, 8);
428 assert!(truncated.len() <= peer_id.to_string().len());
429 assert!(truncated.contains("..."));
430 }
431
432 #[test]
433 fn test_percentage() {
434 assert_eq!(percentage(25, 100), 25.0);
435 assert_eq!(percentage(1, 3), 33.33);
436 assert_eq!(percentage(2, 3), 66.67);
437 assert_eq!(percentage(0, 0), 0.0);
438 assert_eq!(percentage(5, 0), 0.0);
439 }
440
441 #[test]
442 fn test_moving_average() {
443 assert_eq!(moving_average(10.0, 20.0, 0.5), 15.0);
444 assert_eq!(moving_average(10.0, 20.0, 0.0), 10.0);
445 assert_eq!(moving_average(10.0, 20.0, 1.0), 20.0);
446 }
447
448 #[test]
449 fn test_validate_alpha() {
450 validate_alpha(0.0);
451 validate_alpha(0.5);
452 validate_alpha(1.0);
453 }
454
455 #[test]
456 #[should_panic(expected = "Alpha must be in range")]
457 fn test_validate_alpha_too_high() {
458 validate_alpha(1.5);
459 }
460
461 #[test]
462 #[should_panic(expected = "Alpha must be in range")]
463 fn test_validate_alpha_negative() {
464 validate_alpha(-0.1);
465 }
466
467 #[test]
468 fn test_peers_match() {
469 let peer1 = PeerId::random();
470 let peer2 = peer1;
471 let peer3 = PeerId::random();
472
473 assert!(peers_match(&peer1, &peer2));
474 assert!(!peers_match(&peer1, &peer3));
475 }
476}