1use serde::{Deserialize, Serialize};
37use std::collections::{HashMap, VecDeque};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45pub enum BandwidthDirection {
46 Upload,
48 Download,
50 Both,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
64pub struct BandwidthLimit {
65 pub bytes_per_second: u64,
67 pub burst_bytes: u64,
69}
70
71impl BandwidthLimit {
72 pub fn new(bytes_per_second: u64, burst_bytes: u64) -> Self {
74 Self {
75 bytes_per_second,
76 burst_bytes,
77 }
78 }
79
80 pub fn is_unlimited(&self) -> bool {
82 self.bytes_per_second == 0
83 }
84}
85
86impl Default for BandwidthLimit {
87 fn default() -> Self {
88 Self {
90 bytes_per_second: 1_048_576,
91 burst_bytes: 2_097_152,
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct PeerBandwidthState {
103 pub peer_id: String,
105 pub upload_tokens: f64,
107 pub download_tokens: f64,
109 pub upload_limit: BandwidthLimit,
111 pub download_limit: BandwidthLimit,
113 pub total_bytes_sent: u64,
115 pub total_bytes_recv: u64,
117 pub last_refill_ms: u64,
119}
120
121impl PeerBandwidthState {
122 pub fn new(
124 peer_id: String,
125 upload_limit: BandwidthLimit,
126 download_limit: BandwidthLimit,
127 now_ms: u64,
128 ) -> Self {
129 Self {
130 peer_id,
131 upload_tokens: upload_limit.burst_bytes as f64,
132 download_tokens: download_limit.burst_bytes as f64,
133 upload_limit,
134 download_limit,
135 total_bytes_sent: 0,
136 total_bytes_recv: 0,
137 last_refill_ms: now_ms,
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148pub struct BandwidthUsage {
149 pub peer_id: String,
151 pub bytes_sent: u64,
153 pub bytes_recv: u64,
155 pub upload_rate_bps: f64,
157 pub download_rate_bps: f64,
159 pub window_ms: u64,
161}
162
163#[derive(Debug, Clone, Default, Serialize, Deserialize)]
169pub enum FairnessPolicy {
170 MaxMinFairness,
174 WeightedFair {
177 weights: HashMap<String, f64>,
179 },
180 #[default]
182 Unrestricted,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct BandwidthManagerConfig {
192 pub default_upload_limit: BandwidthLimit,
194 pub default_download_limit: BandwidthLimit,
196 pub max_peers: usize,
199 pub fairness_policy: FairnessPolicy,
201 pub usage_window_ms: u64,
203}
204
205impl Default for BandwidthManagerConfig {
206 fn default() -> Self {
207 Self {
208 default_upload_limit: BandwidthLimit {
209 bytes_per_second: 1_048_576, burst_bytes: 2_097_152, },
212 default_download_limit: BandwidthLimit {
213 bytes_per_second: 2_097_152, burst_bytes: 4_194_304, },
216 max_peers: 1_000,
217 fairness_policy: FairnessPolicy::Unrestricted,
218 usage_window_ms: 5_000,
219 }
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct BandwidthManagerStats {
230 pub registered_peers: usize,
232 pub total_bytes_sent: u64,
234 pub total_bytes_recv: u64,
236 pub avg_upload_rate_bps: f64,
238 pub avg_download_rate_bps: f64,
240}
241
242pub struct PeerBandwidthManager {
252 pub config: BandwidthManagerConfig,
254 pub peers: HashMap<String, PeerBandwidthState>,
256 pub usage_snapshots: HashMap<String, VecDeque<(u64, u64, u64)>>,
258 pub total_bytes_sent: u64,
260 pub total_bytes_recv: u64,
262 registration_order: VecDeque<String>,
264}
265
266impl PeerBandwidthManager {
267 pub fn new(config: BandwidthManagerConfig) -> Self {
273 Self {
274 peers: HashMap::new(),
275 usage_snapshots: HashMap::new(),
276 total_bytes_sent: 0,
277 total_bytes_recv: 0,
278 registration_order: VecDeque::new(),
279 config,
280 }
281 }
282
283 pub fn register_peer(&mut self, peer_id: String, now: u64) {
292 if self.peers.contains_key(&peer_id) {
293 return;
294 }
295
296 if self.peers.len() >= self.config.max_peers {
298 if let Some(oldest) = self.registration_order.pop_front() {
299 self.peers.remove(&oldest);
300 self.usage_snapshots.remove(&oldest);
301 }
302 }
303
304 let state = PeerBandwidthState::new(
305 peer_id.clone(),
306 self.config.default_upload_limit,
307 self.config.default_download_limit,
308 now,
309 );
310 self.peers.insert(peer_id.clone(), state);
311 self.usage_snapshots
312 .insert(peer_id.clone(), VecDeque::new());
313 self.registration_order.push_back(peer_id);
314 }
315
316 pub fn set_peer_limits(
320 &mut self,
321 peer_id: &str,
322 upload: BandwidthLimit,
323 download: BandwidthLimit,
324 ) -> bool {
325 if let Some(state) = self.peers.get_mut(peer_id) {
326 state.upload_limit = upload;
327 state.download_limit = download;
328 state.upload_tokens = state.upload_tokens.min(upload.burst_bytes as f64);
330 state.download_tokens = state.download_tokens.min(download.burst_bytes as f64);
331 true
332 } else {
333 false
334 }
335 }
336
337 pub fn remove_peer(&mut self, peer_id: &str) -> bool {
341 if self.peers.remove(peer_id).is_some() {
342 self.usage_snapshots.remove(peer_id);
343 self.registration_order.retain(|id| id != peer_id);
344 true
345 } else {
346 false
347 }
348 }
349
350 pub fn refill_tokens(state: &mut PeerBandwidthState, now: u64) {
362 if now <= state.last_refill_ms {
363 return;
364 }
365 let elapsed_ms = now - state.last_refill_ms;
366 let elapsed_s = elapsed_ms as f64 / 1_000.0;
367
368 if !state.upload_limit.is_unlimited() {
369 let new_up = state.upload_limit.bytes_per_second as f64 * elapsed_s;
370 state.upload_tokens =
371 (state.upload_tokens + new_up).min(state.upload_limit.burst_bytes as f64);
372 }
373
374 if !state.download_limit.is_unlimited() {
375 let new_dn = state.download_limit.bytes_per_second as f64 * elapsed_s;
376 state.download_tokens =
377 (state.download_tokens + new_dn).min(state.download_limit.burst_bytes as f64);
378 }
379
380 state.last_refill_ms = now;
381 }
382
383 pub fn try_consume(
389 &mut self,
390 peer_id: &str,
391 bytes: u64,
392 direction: BandwidthDirection,
393 now: u64,
394 ) -> bool {
395 let state = match self.peers.get_mut(peer_id) {
396 Some(s) => s,
397 None => return false,
398 };
399
400 Self::refill_tokens(state, now);
401
402 let bytes_f = bytes as f64;
403
404 match direction {
405 BandwidthDirection::Upload => {
406 if state.upload_limit.is_unlimited() || state.upload_tokens >= bytes_f {
407 if !state.upload_limit.is_unlimited() {
408 state.upload_tokens -= bytes_f;
409 }
410 true
411 } else {
412 false
413 }
414 }
415 BandwidthDirection::Download => {
416 if state.download_limit.is_unlimited() || state.download_tokens >= bytes_f {
417 if !state.download_limit.is_unlimited() {
418 state.download_tokens -= bytes_f;
419 }
420 true
421 } else {
422 false
423 }
424 }
425 BandwidthDirection::Both => {
426 let up_ok = state.upload_limit.is_unlimited() || state.upload_tokens >= bytes_f;
427 let dn_ok = state.download_limit.is_unlimited() || state.download_tokens >= bytes_f;
428 if up_ok && dn_ok {
429 if !state.upload_limit.is_unlimited() {
430 state.upload_tokens -= bytes_f;
431 }
432 if !state.download_limit.is_unlimited() {
433 state.download_tokens -= bytes_f;
434 }
435 true
436 } else {
437 false
438 }
439 }
440 }
441 }
442
443 pub fn record_transfer(&mut self, peer_id: &str, bytes_sent: u64, bytes_recv: u64, now: u64) {
453 if let Some(state) = self.peers.get_mut(peer_id) {
455 state.total_bytes_sent = state.total_bytes_sent.saturating_add(bytes_sent);
456 state.total_bytes_recv = state.total_bytes_recv.saturating_add(bytes_recv);
457 }
458
459 if let Some(snapshots) = self.usage_snapshots.get_mut(peer_id) {
461 snapshots.push_back((now, bytes_sent, bytes_recv));
462
463 let window = self.config.usage_window_ms;
464 let cutoff = now.saturating_sub(window);
465 while snapshots
466 .front()
467 .map(|(ts, _, _)| *ts < cutoff)
468 .unwrap_or(false)
469 {
470 snapshots.pop_front();
471 }
472 }
473
474 self.total_bytes_sent = self.total_bytes_sent.saturating_add(bytes_sent);
476 self.total_bytes_recv = self.total_bytes_recv.saturating_add(bytes_recv);
477 }
478
479 pub fn peer_usage(&self, peer_id: &str, now: u64) -> Option<BandwidthUsage> {
488 if !self.peers.contains_key(peer_id) {
489 return None;
490 }
491
492 let snapshots = self.usage_snapshots.get(peer_id)?;
493 let window = self.config.usage_window_ms;
494 let cutoff = now.saturating_sub(window);
495
496 let mut bytes_sent: u64 = 0;
497 let mut bytes_recv: u64 = 0;
498 for (ts, sent, recv) in snapshots.iter() {
499 if *ts >= cutoff {
500 bytes_sent = bytes_sent.saturating_add(*sent);
501 bytes_recv = bytes_recv.saturating_add(*recv);
502 }
503 }
504
505 let window_s = window as f64 / 1_000.0;
506 let upload_rate_bps = if window_s > 0.0 {
507 bytes_sent as f64 / window_s
508 } else {
509 0.0
510 };
511 let download_rate_bps = if window_s > 0.0 {
512 bytes_recv as f64 / window_s
513 } else {
514 0.0
515 };
516
517 Some(BandwidthUsage {
518 peer_id: peer_id.to_string(),
519 bytes_sent,
520 bytes_recv,
521 upload_rate_bps,
522 download_rate_bps,
523 window_ms: window,
524 })
525 }
526
527 pub fn top_uploaders(&self, k: usize, now: u64) -> Vec<BandwidthUsage> {
529 let mut usages: Vec<BandwidthUsage> = self
530 .peers
531 .keys()
532 .filter_map(|id| self.peer_usage(id, now))
533 .collect();
534 usages.sort_by(|a, b| {
535 b.upload_rate_bps
536 .partial_cmp(&a.upload_rate_bps)
537 .unwrap_or(std::cmp::Ordering::Equal)
538 });
539 usages.truncate(k);
540 usages
541 }
542
543 pub fn top_downloaders(&self, k: usize, now: u64) -> Vec<BandwidthUsage> {
545 let mut usages: Vec<BandwidthUsage> = self
546 .peers
547 .keys()
548 .filter_map(|id| self.peer_usage(id, now))
549 .collect();
550 usages.sort_by(|a, b| {
551 b.download_rate_bps
552 .partial_cmp(&a.download_rate_bps)
553 .unwrap_or(std::cmp::Ordering::Equal)
554 });
555 usages.truncate(k);
556 usages
557 }
558
559 pub fn apply_fairness(&mut self, now: u64) {
572 let peer_ids: Vec<String> = self.peers.keys().cloned().collect();
574 for id in &peer_ids {
575 if let Some(state) = self.peers.get_mut(id) {
576 Self::refill_tokens(state, now);
577 }
578 }
579
580 let n = self.peers.len();
581 if n == 0 {
582 return;
583 }
584
585 match &self.config.fairness_policy.clone() {
586 FairnessPolicy::Unrestricted => {}
587
588 FairnessPolicy::MaxMinFairness => {
589 let total_upload: f64 = self.peers.values().map(|s| s.upload_tokens).sum();
590 let total_download: f64 = self.peers.values().map(|s| s.download_tokens).sum();
591 let fair_up = total_upload / n as f64;
592 let fair_dn = total_download / n as f64;
593
594 for state in self.peers.values_mut() {
595 state.upload_tokens = state
596 .upload_tokens
597 .min(fair_up)
598 .min(state.upload_limit.burst_bytes as f64);
599 state.download_tokens = state
600 .download_tokens
601 .min(fair_dn)
602 .min(state.download_limit.burst_bytes as f64);
603 }
604 }
605
606 FairnessPolicy::WeightedFair { weights } => {
607 let weighted: Vec<(String, f64)> = peer_ids
609 .iter()
610 .map(|id| {
611 let w = weights.get(id).copied().unwrap_or(1.0).max(0.0);
612 (id.clone(), w)
613 })
614 .collect();
615
616 let total_weight: f64 = weighted.iter().map(|(_, w)| w).sum();
617 if total_weight <= 0.0 {
618 return;
619 }
620
621 for (id, weight) in &weighted {
622 let norm = weight / total_weight;
623 if let Some(state) = self.peers.get_mut(id) {
624 state.upload_tokens =
625 (state.upload_tokens * norm).min(state.upload_limit.burst_bytes as f64);
626 state.download_tokens = (state.download_tokens * norm)
627 .min(state.download_limit.burst_bytes as f64);
628 }
629 }
630 }
631 }
632 }
633
634 pub fn evict_idle_peers(&mut self, now: u64, idle_threshold_ms: u64) -> usize {
642 let cutoff = now.saturating_sub(idle_threshold_ms);
643 let idle: Vec<String> = self
644 .usage_snapshots
645 .iter()
646 .filter(|(_, snaps)| {
647 !snaps.iter().any(|(ts, _, _)| *ts >= cutoff)
649 })
650 .map(|(id, _)| id.clone())
651 .collect();
652
653 let count = idle.len();
654 for id in &idle {
655 self.peers.remove(id);
656 self.usage_snapshots.remove(id);
657 self.registration_order.retain(|r| r != id);
658 }
659 count
660 }
661
662 pub fn manager_stats(&self) -> BandwidthManagerStats {
672 let n = self.peers.len();
675 let (sum_up, sum_dn) = if n > 0 {
676 let window_s = self.config.usage_window_ms as f64 / 1_000.0;
679 let mut up = 0.0f64;
680 let mut dn = 0.0f64;
681 for snaps in self.usage_snapshots.values() {
682 let sent: u64 = snaps.iter().map(|(_, s, _)| s).sum();
683 let recv: u64 = snaps.iter().map(|(_, _, r)| r).sum();
684 if window_s > 0.0 {
685 up += sent as f64 / window_s;
686 dn += recv as f64 / window_s;
687 }
688 }
689 (up, dn)
690 } else {
691 (0.0, 0.0)
692 };
693
694 let avg_up = if n > 0 { sum_up / n as f64 } else { 0.0 };
695 let avg_dn = if n > 0 { sum_dn / n as f64 } else { 0.0 };
696
697 BandwidthManagerStats {
698 registered_peers: n,
699 total_bytes_sent: self.total_bytes_sent,
700 total_bytes_recv: self.total_bytes_recv,
701 avg_upload_rate_bps: avg_up,
702 avg_download_rate_bps: avg_dn,
703 }
704 }
705}
706
707#[cfg(test)]
712mod tests {
713 use super::{
714 BandwidthDirection, BandwidthLimit, BandwidthManagerConfig, BandwidthUsage, FairnessPolicy,
715 PeerBandwidthManager, PeerBandwidthState,
716 };
717 use std::collections::HashMap;
718
719 fn make_manager() -> PeerBandwidthManager {
725 PeerBandwidthManager::new(BandwidthManagerConfig::default())
726 }
727
728 fn make_tight_manager() -> PeerBandwidthManager {
730 let config = BandwidthManagerConfig {
731 default_upload_limit: BandwidthLimit {
732 bytes_per_second: 1_024,
733 burst_bytes: 2_048,
734 },
735 default_download_limit: BandwidthLimit {
736 bytes_per_second: 1_024,
737 burst_bytes: 2_048,
738 },
739 ..Default::default()
740 };
741 PeerBandwidthManager::new(config)
742 }
743
744 #[test]
749 fn test_register_peer_basic() {
750 let mut mgr = make_manager();
751 mgr.register_peer("alice".to_string(), 0);
752 assert!(mgr.peers.contains_key("alice"));
753 }
754
755 #[test]
756 fn test_register_peer_idempotent() {
757 let mut mgr = make_manager();
758 mgr.register_peer("alice".to_string(), 0);
759 mgr.register_peer("alice".to_string(), 1_000);
760 assert_eq!(mgr.peers.len(), 1);
762 }
763
764 #[test]
765 fn test_register_peer_initial_tokens_full() {
766 let mut mgr = make_tight_manager();
767 mgr.register_peer("peer-a".to_string(), 0);
768 let state = mgr.peers.get("peer-a").expect("peer not found");
769 assert_eq!(state.upload_tokens, 2_048.0);
770 assert_eq!(state.download_tokens, 2_048.0);
771 }
772
773 #[test]
774 fn test_register_peer_evicts_oldest_when_full() {
775 let config = BandwidthManagerConfig {
776 max_peers: 2,
777 ..Default::default()
778 };
779 let mut mgr = PeerBandwidthManager::new(config);
780 mgr.register_peer("p1".to_string(), 0);
781 mgr.register_peer("p2".to_string(), 0);
782 mgr.register_peer("p3".to_string(), 0);
783 assert!(!mgr.peers.contains_key("p1"));
785 assert!(mgr.peers.contains_key("p2"));
786 assert!(mgr.peers.contains_key("p3"));
787 }
788
789 #[test]
794 fn test_remove_peer_existing() {
795 let mut mgr = make_manager();
796 mgr.register_peer("bob".to_string(), 0);
797 assert!(mgr.remove_peer("bob"));
798 assert!(!mgr.peers.contains_key("bob"));
799 }
800
801 #[test]
802 fn test_remove_peer_nonexistent() {
803 let mut mgr = make_manager();
804 assert!(!mgr.remove_peer("ghost"));
805 }
806
807 #[test]
808 fn test_remove_peer_clears_snapshots() {
809 let mut mgr = make_manager();
810 mgr.register_peer("carol".to_string(), 0);
811 mgr.record_transfer("carol", 100, 200, 1_000);
812 mgr.remove_peer("carol");
813 assert!(!mgr.usage_snapshots.contains_key("carol"));
814 }
815
816 #[test]
821 fn test_set_peer_limits_success() {
822 let mut mgr = make_tight_manager();
823 mgr.register_peer("dave".to_string(), 0);
824 let new_up = BandwidthLimit::new(4_096, 8_192);
825 let new_dn = BandwidthLimit::new(4_096, 8_192);
826 assert!(mgr.set_peer_limits("dave", new_up, new_dn));
827 let state = mgr.peers.get("dave").expect("peer not found");
828 assert_eq!(state.upload_limit.bytes_per_second, 4_096);
829 }
830
831 #[test]
832 fn test_set_peer_limits_nonexistent() {
833 let mut mgr = make_manager();
834 let limit = BandwidthLimit::default();
835 assert!(!mgr.set_peer_limits("nobody", limit, limit));
836 }
837
838 #[test]
839 fn test_set_peer_limits_clamps_tokens() {
840 let mut mgr = make_tight_manager();
841 mgr.register_peer("eve".to_string(), 0);
842 let tight = BandwidthLimit::new(1_024, 512);
844 mgr.set_peer_limits("eve", tight, tight);
845 let state = mgr.peers.get("eve").expect("peer not found");
846 assert!(state.upload_tokens <= 512.0);
847 assert!(state.download_tokens <= 512.0);
848 }
849
850 #[test]
855 fn test_refill_tokens_adds_correct_amount() {
856 let limit = BandwidthLimit::new(1_000, 10_000);
857 let mut state = PeerBandwidthState::new("p".to_string(), limit, limit, 0);
858 state.upload_tokens = 0.0;
860 state.download_tokens = 0.0;
861 PeerBandwidthManager::refill_tokens(&mut state, 1_000);
863 assert!((state.upload_tokens - 1_000.0).abs() < 1.0);
864 assert!((state.download_tokens - 1_000.0).abs() < 1.0);
865 }
866
867 #[test]
868 fn test_refill_tokens_clamps_to_burst() {
869 let limit = BandwidthLimit::new(10_000, 5_000);
870 let mut state = PeerBandwidthState::new("p".to_string(), limit, limit, 0);
871 state.upload_tokens = 0.0;
872 state.download_tokens = 0.0;
873 PeerBandwidthManager::refill_tokens(&mut state, 1_000);
875 assert_eq!(state.upload_tokens, 5_000.0);
876 assert_eq!(state.download_tokens, 5_000.0);
877 }
878
879 #[test]
880 fn test_refill_tokens_noop_when_time_not_advanced() {
881 let limit = BandwidthLimit::new(1_000, 10_000);
882 let mut state = PeerBandwidthState::new("p".to_string(), limit, limit, 1_000);
883 state.upload_tokens = 500.0;
884 PeerBandwidthManager::refill_tokens(&mut state, 1_000); assert_eq!(state.upload_tokens, 500.0);
886 }
887
888 #[test]
893 fn test_try_consume_upload_allowed() {
894 let mut mgr = make_tight_manager();
895 mgr.register_peer("frank".to_string(), 0);
896 assert!(mgr.try_consume("frank", 1_000, BandwidthDirection::Upload, 0));
897 }
898
899 #[test]
900 fn test_try_consume_upload_denied_insufficient_tokens() {
901 let mut mgr = make_tight_manager();
902 mgr.register_peer("grace".to_string(), 0);
903 assert!(mgr.try_consume("grace", 2_048, BandwidthDirection::Upload, 0));
905 assert!(!mgr.try_consume("grace", 1, BandwidthDirection::Upload, 0));
907 }
908
909 #[test]
910 fn test_try_consume_download_allowed() {
911 let mut mgr = make_tight_manager();
912 mgr.register_peer("hank".to_string(), 0);
913 assert!(mgr.try_consume("hank", 1_024, BandwidthDirection::Download, 0));
914 }
915
916 #[test]
917 fn test_try_consume_both_directions() {
918 let mut mgr = make_tight_manager();
919 mgr.register_peer("ivan".to_string(), 0);
920 assert!(mgr.try_consume("ivan", 1_000, BandwidthDirection::Both, 0));
921 let state = mgr.peers.get("ivan").expect("peer not found");
922 assert!((state.upload_tokens - 1_048.0).abs() < 2.0);
923 assert!((state.download_tokens - 1_048.0).abs() < 2.0);
924 }
925
926 #[test]
927 fn test_try_consume_both_denied_if_one_direction_insufficient() {
928 let mut mgr = make_tight_manager();
929 mgr.register_peer("judy".to_string(), 0);
930 assert!(mgr.try_consume("judy", 2_048, BandwidthDirection::Upload, 0));
932 assert!(!mgr.try_consume("judy", 100, BandwidthDirection::Both, 0));
934 }
935
936 #[test]
937 fn test_try_consume_unknown_peer_returns_false() {
938 let mut mgr = make_manager();
939 assert!(!mgr.try_consume("unknown", 1, BandwidthDirection::Upload, 0));
940 }
941
942 #[test]
943 fn test_try_consume_refills_before_check() {
944 let mut mgr = make_tight_manager();
945 mgr.register_peer("karl".to_string(), 0);
946 assert!(mgr.try_consume("karl", 2_048, BandwidthDirection::Upload, 0));
948 assert!(mgr.try_consume("karl", 2_048, BandwidthDirection::Upload, 2_000));
950 }
951
952 #[test]
957 fn test_record_transfer_updates_global_totals() {
958 let mut mgr = make_manager();
959 mgr.register_peer("liam".to_string(), 0);
960 mgr.record_transfer("liam", 1_000, 500, 1_000);
961 assert_eq!(mgr.total_bytes_sent, 1_000);
962 assert_eq!(mgr.total_bytes_recv, 500);
963 }
964
965 #[test]
966 fn test_record_transfer_updates_peer_totals() {
967 let mut mgr = make_manager();
968 mgr.register_peer("mia".to_string(), 0);
969 mgr.record_transfer("mia", 300, 150, 1_000);
970 mgr.record_transfer("mia", 200, 100, 2_000);
971 let state = mgr.peers.get("mia").expect("peer not found");
972 assert_eq!(state.total_bytes_sent, 500);
973 assert_eq!(state.total_bytes_recv, 250);
974 }
975
976 #[test]
977 fn test_record_transfer_pushes_snapshot() {
978 let mut mgr = make_manager();
979 mgr.register_peer("noah".to_string(), 0);
980 mgr.record_transfer("noah", 100, 50, 1_000);
981 let snaps = mgr.usage_snapshots.get("noah").expect("no snapshots");
982 assert_eq!(snaps.len(), 1);
983 }
984
985 #[test]
986 fn test_record_transfer_prunes_old_snapshots() {
987 let mut mgr = make_manager(); mgr.register_peer("olivia".to_string(), 0);
989 mgr.record_transfer("olivia", 100, 50, 0);
990 mgr.record_transfer("olivia", 100, 50, 4_999);
991 mgr.record_transfer("olivia", 100, 50, 5_001);
993 let snaps = mgr.usage_snapshots.get("olivia").expect("no snapshots");
994 for (ts, _, _) in snaps.iter() {
996 assert!(*ts >= 5_001u64.saturating_sub(5_000));
997 }
998 }
999
1000 #[test]
1001 fn test_record_transfer_noop_for_unknown_peer_global_counts_still_updated() {
1002 let mut mgr = make_manager();
1003 mgr.record_transfer("unknown", 999, 111, 1_000);
1005 assert_eq!(mgr.total_bytes_sent, 999);
1006 assert_eq!(mgr.total_bytes_recv, 111);
1007 }
1008
1009 #[test]
1014 fn test_peer_usage_returns_none_for_unregistered() {
1015 let mgr = make_manager();
1016 assert!(mgr.peer_usage("ghost", 5_000).is_none());
1017 }
1018
1019 #[test]
1020 fn test_peer_usage_empty_snapshots() {
1021 let mut mgr = make_manager();
1022 mgr.register_peer("petra".to_string(), 0);
1023 let usage = mgr.peer_usage("petra", 5_000).expect("should return usage");
1024 assert_eq!(usage.bytes_sent, 0);
1025 assert_eq!(usage.bytes_recv, 0);
1026 assert_eq!(usage.upload_rate_bps, 0.0);
1027 }
1028
1029 #[test]
1030 fn test_peer_usage_rate_calculation() {
1031 let mut mgr = make_manager(); mgr.register_peer("quinn".to_string(), 0);
1033 mgr.record_transfer("quinn", 5_000, 0, 1_000);
1035 let usage = mgr.peer_usage("quinn", 6_000).expect("should return usage");
1036 assert!((usage.upload_rate_bps - 1_000.0).abs() < 1.0);
1038 }
1039
1040 #[test]
1041 fn test_peer_usage_excludes_old_snapshots() {
1042 let mut mgr = make_manager(); mgr.register_peer("rachel".to_string(), 0);
1044 mgr.record_transfer("rachel", 10_000, 0, 0);
1046 mgr.record_transfer("rachel", 1_000, 0, 5_001);
1047 let usage = mgr
1048 .peer_usage("rachel", 10_001)
1049 .expect("should return usage");
1050 assert_eq!(usage.bytes_sent, 1_000);
1052 }
1053
1054 #[test]
1055 fn test_peer_usage_window_ms_matches_config() {
1056 let mut mgr = make_manager();
1057 mgr.register_peer("sam".to_string(), 0);
1058 let usage = mgr.peer_usage("sam", 0).expect("should return usage");
1059 assert_eq!(usage.window_ms, 5_000);
1060 }
1061
1062 #[test]
1067 fn test_top_uploaders_ordering() {
1068 let mut mgr = make_manager();
1069 for id in &["a", "b", "c"] {
1070 mgr.register_peer(id.to_string(), 0);
1071 }
1072 mgr.record_transfer("a", 300, 0, 1_000);
1073 mgr.record_transfer("b", 100, 0, 1_000);
1074 mgr.record_transfer("c", 200, 0, 1_000);
1075 let top = mgr.top_uploaders(3, 5_000);
1076 assert_eq!(top[0].peer_id, "a");
1077 assert_eq!(top[1].peer_id, "c");
1078 assert_eq!(top[2].peer_id, "b");
1079 }
1080
1081 #[test]
1082 fn test_top_uploaders_k_limits_results() {
1083 let mut mgr = make_manager();
1084 for i in 0..5u32 {
1085 let id = format!("peer-{i}");
1086 mgr.register_peer(id.clone(), 0);
1087 mgr.record_transfer(&id, (i + 1) as u64 * 100, 0, 1_000);
1088 }
1089 let top = mgr.top_uploaders(2, 5_000);
1090 assert_eq!(top.len(), 2);
1091 }
1092
1093 #[test]
1094 fn test_top_downloaders_ordering() {
1095 let mut mgr = make_manager();
1096 for id in &["x", "y", "z"] {
1097 mgr.register_peer(id.to_string(), 0);
1098 }
1099 mgr.record_transfer("x", 0, 500, 1_000);
1100 mgr.record_transfer("y", 0, 200, 1_000);
1101 mgr.record_transfer("z", 0, 800, 1_000);
1102 let top = mgr.top_downloaders(3, 5_000);
1103 assert_eq!(top[0].peer_id, "z");
1104 assert_eq!(top[1].peer_id, "x");
1105 assert_eq!(top[2].peer_id, "y");
1106 }
1107
1108 #[test]
1109 fn test_top_downloaders_returns_all_if_k_exceeds_peers() {
1110 let mut mgr = make_manager();
1111 mgr.register_peer("only".to_string(), 0);
1112 mgr.record_transfer("only", 0, 100, 1_000);
1113 let top = mgr.top_downloaders(10, 5_000);
1114 assert_eq!(top.len(), 1);
1115 }
1116
1117 #[test]
1122 fn test_fairness_maxmin_equalises_tokens() {
1123 let config = BandwidthManagerConfig {
1124 fairness_policy: FairnessPolicy::MaxMinFairness,
1125 ..Default::default()
1126 };
1127 let mut mgr = PeerBandwidthManager::new(config);
1128 mgr.register_peer("p1".to_string(), 0);
1129 mgr.register_peer("p2".to_string(), 0);
1130 mgr.peers.get_mut("p1").expect("p1").upload_tokens = 2_000.0;
1132 mgr.peers.get_mut("p2").expect("p2").upload_tokens = 1_000.0;
1133 mgr.apply_fairness(0);
1134 let fair_share = 3_000.0 / 2.0; let t1 = mgr.peers.get("p1").expect("p1").upload_tokens;
1136 let t2 = mgr.peers.get("p2").expect("p2").upload_tokens;
1137 assert!(t1 <= fair_share + 0.01);
1139 assert!(t2 <= fair_share + 0.01);
1140 }
1141
1142 #[test]
1143 fn test_fairness_maxmin_noop_when_no_peers() {
1144 let config = BandwidthManagerConfig {
1145 fairness_policy: FairnessPolicy::MaxMinFairness,
1146 ..Default::default()
1147 };
1148 let mut mgr = PeerBandwidthManager::new(config);
1149 mgr.apply_fairness(0);
1151 }
1152
1153 #[test]
1158 fn test_fairness_weighted_scales_tokens() {
1159 let mut weights = HashMap::new();
1160 weights.insert("heavy".to_string(), 3.0);
1161 weights.insert("light".to_string(), 1.0);
1162 let config = BandwidthManagerConfig {
1163 fairness_policy: FairnessPolicy::WeightedFair { weights },
1164 default_upload_limit: BandwidthLimit {
1165 bytes_per_second: 100_000,
1166 burst_bytes: 1_000_000,
1167 },
1168 default_download_limit: BandwidthLimit {
1169 bytes_per_second: 100_000,
1170 burst_bytes: 1_000_000,
1171 },
1172 ..Default::default()
1173 };
1174 let mut mgr = PeerBandwidthManager::new(config);
1175 mgr.register_peer("heavy".to_string(), 0);
1176 mgr.register_peer("light".to_string(), 0);
1177 mgr.peers.get_mut("heavy").expect("heavy").upload_tokens = 1_000_000.0;
1179 mgr.peers.get_mut("light").expect("light").upload_tokens = 1_000_000.0;
1180 mgr.apply_fairness(0);
1181 let heavy_tokens = mgr.peers.get("heavy").expect("heavy").upload_tokens;
1182 let light_tokens = mgr.peers.get("light").expect("light").upload_tokens;
1183 assert!(
1186 heavy_tokens > light_tokens,
1187 "heavy={heavy_tokens} light={light_tokens}"
1188 );
1189 }
1190
1191 #[test]
1192 fn test_fairness_unrestricted_noop() {
1193 let mut mgr = make_manager(); mgr.register_peer("q1".to_string(), 0);
1195 mgr.peers.get_mut("q1").expect("q1").upload_tokens = 777.0;
1196 mgr.apply_fairness(0);
1197 let tokens = mgr.peers.get("q1").expect("q1").upload_tokens;
1199 assert!(tokens >= 777.0);
1201 }
1202
1203 #[test]
1208 fn test_evict_idle_peers_removes_stale() {
1209 let mut mgr = make_manager();
1210 mgr.register_peer("active".to_string(), 0);
1211 mgr.register_peer("idle".to_string(), 0);
1212 mgr.record_transfer("active", 100, 0, 10_000);
1213 let evicted = mgr.evict_idle_peers(10_000, 5_000);
1215 assert_eq!(evicted, 1);
1216 assert!(!mgr.peers.contains_key("idle"));
1217 assert!(mgr.peers.contains_key("active"));
1218 }
1219
1220 #[test]
1221 fn test_evict_idle_peers_returns_zero_when_all_active() {
1222 let mut mgr = make_manager();
1223 mgr.register_peer("p1".to_string(), 0);
1224 mgr.record_transfer("p1", 1, 0, 5_000);
1225 let evicted = mgr.evict_idle_peers(5_000, 5_000);
1226 assert_eq!(evicted, 0);
1227 }
1228
1229 #[test]
1230 fn test_evict_idle_peers_clears_snapshots() {
1231 let mut mgr = make_manager();
1232 mgr.register_peer("stale".to_string(), 0);
1233 mgr.record_transfer("stale", 100, 0, 0);
1235 let evicted = mgr.evict_idle_peers(10_000, 5_000);
1237 assert_eq!(evicted, 1);
1238 assert!(!mgr.usage_snapshots.contains_key("stale"));
1239 }
1240
1241 #[test]
1246 fn test_manager_stats_registered_peers() {
1247 let mut mgr = make_manager();
1248 mgr.register_peer("s1".to_string(), 0);
1249 mgr.register_peer("s2".to_string(), 0);
1250 let stats = mgr.manager_stats();
1251 assert_eq!(stats.registered_peers, 2);
1252 }
1253
1254 #[test]
1255 fn test_manager_stats_global_byte_counts() {
1256 let mut mgr = make_manager();
1257 mgr.register_peer("t1".to_string(), 0);
1258 mgr.record_transfer("t1", 1_000, 500, 1_000);
1259 let stats = mgr.manager_stats();
1260 assert_eq!(stats.total_bytes_sent, 1_000);
1261 assert_eq!(stats.total_bytes_recv, 500);
1262 }
1263
1264 #[test]
1265 fn test_manager_stats_avg_rates() {
1266 let mut mgr = make_manager(); mgr.register_peer("u1".to_string(), 0);
1268 mgr.register_peer("u2".to_string(), 0);
1269 mgr.record_transfer("u1", 5_000, 0, 1_000);
1270 mgr.record_transfer("u2", 10_000, 0, 1_000);
1271 let stats = mgr.manager_stats();
1272 assert!((stats.avg_upload_rate_bps - 1_500.0).abs() < 1.0);
1274 }
1275
1276 #[test]
1277 fn test_manager_stats_empty() {
1278 let mgr = make_manager();
1279 let stats = mgr.manager_stats();
1280 assert_eq!(stats.registered_peers, 0);
1281 assert_eq!(stats.total_bytes_sent, 0);
1282 assert_eq!(stats.total_bytes_recv, 0);
1283 assert_eq!(stats.avg_upload_rate_bps, 0.0);
1284 }
1285
1286 #[test]
1291 fn test_bandwidth_limit_is_unlimited() {
1292 let unlimited = BandwidthLimit::new(0, 0);
1293 assert!(unlimited.is_unlimited());
1294 }
1295
1296 #[test]
1297 fn test_bandwidth_limit_is_not_unlimited() {
1298 let limited = BandwidthLimit::new(1_000, 2_000);
1299 assert!(!limited.is_unlimited());
1300 }
1301
1302 #[test]
1303 fn test_bandwidth_limit_default_values() {
1304 let limit = BandwidthLimit::default();
1305 assert_eq!(limit.bytes_per_second, 1_048_576);
1306 assert_eq!(limit.burst_bytes, 2_097_152);
1307 }
1308
1309 #[test]
1314 fn test_config_default_values() {
1315 let config = BandwidthManagerConfig::default();
1316 assert_eq!(config.max_peers, 1_000);
1317 assert_eq!(config.usage_window_ms, 5_000);
1318 assert_eq!(config.default_upload_limit.bytes_per_second, 1_048_576);
1319 assert_eq!(config.default_download_limit.bytes_per_second, 2_097_152);
1320 }
1321
1322 #[test]
1327 fn test_bandwidth_usage_fields() {
1328 let usage = BandwidthUsage {
1329 peer_id: "test".to_string(),
1330 bytes_sent: 1_000,
1331 bytes_recv: 500,
1332 upload_rate_bps: 200.0,
1333 download_rate_bps: 100.0,
1334 window_ms: 5_000,
1335 };
1336 assert_eq!(usage.peer_id, "test");
1337 assert_eq!(usage.bytes_sent, 1_000);
1338 assert_eq!(usage.bytes_recv, 500);
1339 assert!((usage.upload_rate_bps - 200.0).abs() < f64::EPSILON);
1340 }
1341
1342 #[test]
1347 fn test_unlimited_upload_always_allowed() {
1348 let config = BandwidthManagerConfig {
1349 default_upload_limit: BandwidthLimit::new(0, 0), default_download_limit: BandwidthLimit::new(1_024, 2_048),
1351 ..Default::default()
1352 };
1353 let mut mgr = PeerBandwidthManager::new(config);
1354 mgr.register_peer("unlimited".to_string(), 0);
1355 assert!(mgr.try_consume("unlimited", u64::MAX / 2, BandwidthDirection::Upload, 0));
1357 }
1358
1359 #[test]
1360 fn test_saturating_add_prevents_overflow() {
1361 let mut mgr = make_manager();
1362 mgr.register_peer("big".to_string(), 0);
1363 mgr.record_transfer("big", u64::MAX - 10, 0, 1_000);
1364 mgr.record_transfer("big", 100, 0, 2_000);
1365 assert_eq!(mgr.total_bytes_sent, u64::MAX);
1367 }
1368
1369 #[test]
1370 fn test_multiple_peers_independent_token_buckets() {
1371 let mut mgr = make_tight_manager();
1372 mgr.register_peer("p1".to_string(), 0);
1373 mgr.register_peer("p2".to_string(), 0);
1374 assert!(mgr.try_consume("p1", 2_048, BandwidthDirection::Upload, 0));
1376 assert!(mgr.try_consume("p2", 2_048, BandwidthDirection::Upload, 0));
1378 }
1379
1380 #[test]
1381 fn test_top_uploaders_empty_manager() {
1382 let mgr = make_manager();
1383 let top = mgr.top_uploaders(5, 0);
1384 assert!(top.is_empty());
1385 }
1386
1387 #[test]
1388 fn test_top_downloaders_empty_manager() {
1389 let mgr = make_manager();
1390 let top = mgr.top_downloaders(5, 0);
1391 assert!(top.is_empty());
1392 }
1393
1394 #[test]
1395 fn test_registration_order_maintained_for_eviction() {
1396 let config = BandwidthManagerConfig {
1397 max_peers: 3,
1398 ..Default::default()
1399 };
1400 let mut mgr = PeerBandwidthManager::new(config);
1401 for id in &["r1", "r2", "r3", "r4", "r5"] {
1402 mgr.register_peer(id.to_string(), 0);
1403 }
1404 assert!(!mgr.peers.contains_key("r1"));
1406 assert!(!mgr.peers.contains_key("r2"));
1407 assert!(mgr.peers.contains_key("r3"));
1408 assert!(mgr.peers.contains_key("r4"));
1409 assert!(mgr.peers.contains_key("r5"));
1410 }
1411
1412 #[test]
1413 fn test_try_consume_after_refill_succeeds() {
1414 let mut mgr = make_tight_manager();
1415 mgr.register_peer("walter".to_string(), 0);
1416 assert!(mgr.try_consume("walter", 2_048, BandwidthDirection::Upload, 0));
1418 assert!(mgr.try_consume("walter", 1_024, BandwidthDirection::Upload, 1_000));
1421 }
1422}