1use std::collections::{HashMap, VecDeque};
32
33#[inline]
38fn xorshift64(state: &mut u64) -> u64 {
39 let mut x = *state;
40 x ^= x << 13;
41 x ^= x >> 7;
42 x ^= x << 17;
43 *state = x;
44 x
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub enum BandwidthClass {
52 High,
54 Normal,
56 Low,
58 Background,
60}
61
62impl BandwidthClass {
63 pub fn weight(self) -> f64 {
65 match self {
66 BandwidthClass::High => 4.0,
67 BandwidthClass::Normal => 2.0,
68 BandwidthClass::Low => 1.0,
69 BandwidthClass::Background => 0.5,
70 }
71 }
72
73 pub fn priority(self) -> u32 {
75 match self {
76 BandwidthClass::High => 4,
77 BandwidthClass::Normal => 3,
78 BandwidthClass::Low => 2,
79 BandwidthClass::Background => 1,
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
89pub enum AllocationPolicy {
90 EqualShare,
92 WeightedFair,
94 MinGuarantee(u64),
96 MaxCapacity(u64),
98 PriorityQueue,
100}
101
102#[derive(Debug, Clone)]
109pub struct BandwidthWindow {
110 samples: VecDeque<u64>,
111 prng_state: u64,
112 capacity: usize,
113}
114
115impl BandwidthWindow {
116 const DEFAULT_CAPACITY: usize = 10;
117
118 pub fn new() -> Self {
120 Self {
121 samples: VecDeque::with_capacity(Self::DEFAULT_CAPACITY),
122 prng_state: 0xcafe_babe_dead_beef,
123 capacity: Self::DEFAULT_CAPACITY,
124 }
125 }
126
127 pub fn push(&mut self, raw_bps: u64) {
130 let jitter_range = (raw_bps / 200).max(1);
132 let jitter = xorshift64(&mut self.prng_state) % jitter_range;
133 let jittered = if xorshift64(&mut self.prng_state) & 1 == 0 {
135 raw_bps.saturating_add(jitter)
136 } else {
137 raw_bps.saturating_sub(jitter)
138 };
139
140 if self.samples.len() == self.capacity {
141 self.samples.pop_front();
142 }
143 self.samples.push_back(jittered);
144 }
145
146 pub fn average_bps(&self) -> u64 {
148 if self.samples.is_empty() {
149 return 0;
150 }
151 let sum: u64 = self.samples.iter().sum();
152 sum / self.samples.len() as u64
153 }
154
155 pub fn peak_bps(&self) -> u64 {
157 self.samples.iter().copied().max().unwrap_or(0)
158 }
159
160 pub fn len(&self) -> usize {
162 self.samples.len()
163 }
164
165 pub fn is_empty(&self) -> bool {
167 self.samples.is_empty()
168 }
169}
170
171impl Default for BandwidthWindow {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177#[derive(Debug, Clone)]
181pub struct PeerBandwidthProfile {
182 pub peer_id: String,
184 pub allocated_bps: u64,
186 pub used_bps: u64,
188 pub latency_ms: u64,
190 pub packet_loss_ppm: u32,
192 pub class: BandwidthClass,
194 pub last_updated: u64,
196 pub(crate) window: BandwidthWindow,
198}
199
200impl PeerBandwidthProfile {
201 fn new(peer_id: String, class: BandwidthClass, initial_bps: u64, now: u64) -> Self {
202 Self {
203 peer_id,
204 allocated_bps: initial_bps,
205 used_bps: 0,
206 latency_ms: 0,
207 packet_loss_ppm: 0,
208 class,
209 last_updated: now,
210 window: BandwidthWindow::new(),
211 }
212 }
213}
214
215#[derive(Debug, Clone)]
219pub enum BandwidthEvent {
220 PeerAdded {
222 peer_id: String,
223 allocated_bps: u64,
224 timestamp: u64,
225 },
226 PeerRemoved { peer_id: String, timestamp: u64 },
228 BandwidthUpdated {
230 peer_id: String,
231 new_bps: u64,
232 timestamp: u64,
233 },
234 CongestionDetected {
236 peer_id: String,
237 loss_ppm: u32,
238 timestamp: u64,
239 },
240 AllocationRevised { timestamp: u64 },
242}
243
244impl BandwidthEvent {
245 pub fn timestamp(&self) -> u64 {
247 match self {
248 BandwidthEvent::PeerAdded { timestamp, .. } => *timestamp,
249 BandwidthEvent::PeerRemoved { timestamp, .. } => *timestamp,
250 BandwidthEvent::BandwidthUpdated { timestamp, .. } => *timestamp,
251 BandwidthEvent::CongestionDetected { timestamp, .. } => *timestamp,
252 BandwidthEvent::AllocationRevised { timestamp } => *timestamp,
253 }
254 }
255}
256
257#[derive(Debug, Clone)]
261pub struct AllocatorConfig {
262 pub total_capacity_bps: u64,
264 pub min_allocation_bps: u64,
266 pub max_allocation_bps: u64,
268 pub policy: AllocationPolicy,
270 pub rebalance_interval_secs: u64,
272 pub congestion_threshold_ppm: u32,
274}
275
276impl AllocatorConfig {
277 pub fn validate(&self) -> Result<(), AllocatorError> {
279 if self.min_allocation_bps > self.max_allocation_bps {
280 return Err(AllocatorError::InvalidConfiguration(
281 "min_allocation_bps must be ≤ max_allocation_bps".to_string(),
282 ));
283 }
284 if self.total_capacity_bps == 0 {
285 return Err(AllocatorError::InvalidConfiguration(
286 "total_capacity_bps must be > 0".to_string(),
287 ));
288 }
289 if self.max_allocation_bps > self.total_capacity_bps {
290 return Err(AllocatorError::InvalidConfiguration(
291 "max_allocation_bps must be ≤ total_capacity_bps".to_string(),
292 ));
293 }
294 Ok(())
295 }
296}
297
298#[derive(Debug, Clone)]
305pub struct BandwidthStats {
306 pub total_allocated_bps: u64,
308 pub total_used_bps: u64,
310 pub utilization_pct: f64,
312 pub congested_peers: usize,
314 pub peer_count: usize,
316 pub fairness_index: f64,
318}
319
320#[derive(Debug, Clone, PartialEq, thiserror::Error)]
324pub enum AllocatorError {
325 #[error("peer not found: {0}")]
326 PeerNotFound(String),
327
328 #[error("allocation of {0} bps exceeds remaining capacity")]
329 AllocationExceedsCapacity(u64),
330
331 #[error("invalid configuration: {0}")]
332 InvalidConfiguration(String),
333
334 #[error("congestion detected on peer {peer_id}: loss_ppm={loss_ppm}")]
335 CongestionDetected { peer_id: String, loss_ppm: u32 },
336}
337
338pub struct AdaptiveBandwidthAllocator {
345 config: AllocatorConfig,
346 peers: HashMap<String, PeerBandwidthProfile>,
348 events: VecDeque<BandwidthEvent>,
350 allocated_total: u64,
352 now_ms: u64,
355}
356
357impl AdaptiveBandwidthAllocator {
358 pub const MAX_EVENT_HISTORY: usize = 200;
360
361 pub fn new(config: AllocatorConfig) -> Self {
366 Self {
368 config,
369 peers: HashMap::new(),
370 events: VecDeque::with_capacity(Self::MAX_EVENT_HISTORY + 1),
371 allocated_total: 0,
372 now_ms: 1_000, }
374 }
375
376 pub fn advance_time(&mut self, delta_ms: u64) {
379 self.now_ms = self.now_ms.saturating_add(delta_ms);
380 }
381
382 pub fn add_peer(
389 &mut self,
390 peer_id: String,
391 class: BandwidthClass,
392 initial_bps: u64,
393 ) -> Result<(), AllocatorError> {
394 let clamped = initial_bps.clamp(
395 self.config.min_allocation_bps,
396 self.config.max_allocation_bps,
397 );
398
399 let new_total = self.allocated_total.saturating_add(clamped);
400 if new_total > self.config.total_capacity_bps {
401 return Err(AllocatorError::AllocationExceedsCapacity(clamped));
402 }
403
404 let profile = PeerBandwidthProfile::new(peer_id.clone(), class, clamped, self.now_ms);
405 self.peers.insert(peer_id.clone(), profile);
406 self.allocated_total = new_total;
407
408 self.push_event(BandwidthEvent::PeerAdded {
409 peer_id,
410 allocated_bps: clamped,
411 timestamp: self.now_ms,
412 });
413 Ok(())
414 }
415
416 pub fn remove_peer(&mut self, peer_id: &str) -> Result<PeerBandwidthProfile, AllocatorError> {
421 let profile = self
422 .peers
423 .remove(peer_id)
424 .ok_or_else(|| AllocatorError::PeerNotFound(peer_id.to_string()))?;
425
426 self.allocated_total = self.allocated_total.saturating_sub(profile.allocated_bps);
427
428 self.push_event(BandwidthEvent::PeerRemoved {
429 peer_id: peer_id.to_string(),
430 timestamp: self.now_ms,
431 });
432 Ok(profile)
433 }
434
435 pub fn update_measurement(
443 &mut self,
444 peer_id: &str,
445 used_bps: u64,
446 latency_ms: u64,
447 packet_loss_ppm: u32,
448 ) -> Result<BandwidthEvent, AllocatorError> {
449 let now = self.now_ms;
450 let profile = self
451 .peers
452 .get_mut(peer_id)
453 .ok_or_else(|| AllocatorError::PeerNotFound(peer_id.to_string()))?;
454
455 profile.used_bps = used_bps;
456 profile.latency_ms = latency_ms;
457 profile.packet_loss_ppm = packet_loss_ppm;
458 profile.last_updated = now;
459 profile.window.push(used_bps);
460
461 let event = if packet_loss_ppm > self.config.congestion_threshold_ppm {
462 BandwidthEvent::CongestionDetected {
463 peer_id: peer_id.to_string(),
464 loss_ppm: packet_loss_ppm,
465 timestamp: now,
466 }
467 } else {
468 BandwidthEvent::BandwidthUpdated {
469 peer_id: peer_id.to_string(),
470 new_bps: used_bps,
471 timestamp: now,
472 }
473 };
474
475 self.push_event(event.clone());
476 Ok(event)
477 }
478
479 pub fn reallocate(&mut self) -> Vec<BandwidthEvent> {
485 let mut out_events: Vec<BandwidthEvent> = Vec::new();
486 let now = self.now_ms;
487
488 if self.peers.is_empty() {
489 self.push_event(BandwidthEvent::AllocationRevised { timestamp: now });
490 out_events.push(BandwidthEvent::AllocationRevised { timestamp: now });
491 return out_events;
492 }
493
494 let new_allocs = self.compute_allocations();
495
496 let mut new_total: u64 = 0;
498 for (peer_id, alloc) in &new_allocs {
499 if let Some(profile) = self.peers.get_mut(peer_id) {
500 profile.allocated_bps = *alloc;
501 new_total = new_total.saturating_add(*alloc);
502 }
503 }
504 self.allocated_total = new_total;
505
506 let threshold = self.config.congestion_threshold_ppm;
508 let congested: Vec<(String, u32)> = self
509 .peers
510 .values()
511 .filter(|p| p.packet_loss_ppm > threshold)
512 .map(|p| (p.peer_id.clone(), p.packet_loss_ppm))
513 .collect();
514
515 for (peer_id, loss_ppm) in congested {
516 let ev = BandwidthEvent::CongestionDetected {
517 peer_id,
518 loss_ppm,
519 timestamp: now,
520 };
521 self.push_event(ev.clone());
522 out_events.push(ev);
523 }
524
525 let revised = BandwidthEvent::AllocationRevised { timestamp: now };
526 self.push_event(revised.clone());
527 out_events.push(revised);
528
529 out_events
530 }
531
532 pub fn get_allocation(&self, peer_id: &str) -> Result<u64, AllocatorError> {
534 self.peers
535 .get(peer_id)
536 .map(|p| p.allocated_bps)
537 .ok_or_else(|| AllocatorError::PeerNotFound(peer_id.to_string()))
538 }
539
540 pub fn stats(&self) -> BandwidthStats {
542 let peer_count = self.peers.len();
543 let total_allocated_bps: u64 = self.peers.values().map(|p| p.allocated_bps).sum();
544 let total_used_bps: u64 = self.peers.values().map(|p| p.used_bps).sum();
545
546 let utilization_pct = if total_allocated_bps == 0 {
547 0.0
548 } else {
549 (total_used_bps as f64 / total_allocated_bps as f64) * 100.0
550 };
551
552 let threshold = self.config.congestion_threshold_ppm;
553 let congested_peers = self
554 .peers
555 .values()
556 .filter(|p| p.packet_loss_ppm > threshold)
557 .count();
558
559 let fairness_index = Self::jain_fairness_index(
560 self.peers.values().map(|p| p.allocated_bps as f64),
561 peer_count,
562 );
563
564 BandwidthStats {
565 total_allocated_bps,
566 total_used_bps,
567 utilization_pct,
568 congested_peers,
569 peer_count,
570 fairness_index,
571 }
572 }
573
574 pub fn congested_peers(&self) -> Vec<String> {
576 let threshold = self.config.congestion_threshold_ppm;
577 self.peers
578 .values()
579 .filter(|p| p.packet_loss_ppm > threshold)
580 .map(|p| p.peer_id.clone())
581 .collect()
582 }
583
584 pub fn events_since(&self, since_ms: u64) -> Vec<BandwidthEvent> {
586 self.events
587 .iter()
588 .filter(|e| e.timestamp() >= since_ms)
589 .cloned()
590 .collect()
591 }
592
593 pub fn drain_events(&mut self) -> Vec<BandwidthEvent> {
595 self.events.drain(..).collect()
596 }
597
598 pub fn peer_profile(&self, peer_id: &str) -> Option<&PeerBandwidthProfile> {
600 self.peers.get(peer_id)
601 }
602
603 pub fn peer_count(&self) -> usize {
605 self.peers.len()
606 }
607
608 pub fn total_capacity_bps(&self) -> u64 {
610 self.config.total_capacity_bps
611 }
612
613 pub fn config(&self) -> &AllocatorConfig {
615 &self.config
616 }
617
618 fn push_event(&mut self, event: BandwidthEvent) {
622 if self.events.len() == Self::MAX_EVENT_HISTORY {
623 self.events.pop_front();
624 }
625 self.events.push_back(event);
626 }
627
628 fn compute_allocations(&self) -> HashMap<String, u64> {
631 match &self.config.policy {
632 AllocationPolicy::EqualShare => self.policy_equal_share(),
633 AllocationPolicy::WeightedFair => self.policy_weighted_fair(),
634 AllocationPolicy::MinGuarantee(min_n) => self.policy_min_guarantee(*min_n),
635 AllocationPolicy::MaxCapacity(max_n) => self.policy_max_capacity(*max_n),
636 AllocationPolicy::PriorityQueue => self.policy_priority_queue(),
637 }
638 }
639
640 fn policy_equal_share(&self) -> HashMap<String, u64> {
642 let n = self.peers.len() as u64;
643 let share = self.config.total_capacity_bps.checked_div(n).unwrap_or(0);
644 let clamped = share.clamp(
645 self.config.min_allocation_bps,
646 self.config.max_allocation_bps,
647 );
648 self.peers.keys().map(|id| (id.clone(), clamped)).collect()
649 }
650
651 fn policy_weighted_fair(&self) -> HashMap<String, u64> {
653 let total_weight: f64 = self.peers.values().map(|p| p.class.weight()).sum();
654 if total_weight == 0.0 {
655 return self.policy_equal_share();
656 }
657 self.peers
658 .values()
659 .map(|p| {
660 let raw = (self.config.total_capacity_bps as f64 * p.class.weight() / total_weight)
661 as u64;
662 let alloc = raw.clamp(
663 self.config.min_allocation_bps,
664 self.config.max_allocation_bps,
665 );
666 (p.peer_id.clone(), alloc)
667 })
668 .collect()
669 }
670
671 fn policy_min_guarantee(&self, min_n: u64) -> HashMap<String, u64> {
673 let peer_count = self.peers.len() as u64;
674 if peer_count == 0 {
675 return HashMap::new();
676 }
677 let effective_min = min_n
679 .max(self.config.min_allocation_bps)
680 .min(self.config.max_allocation_bps);
681
682 let guaranteed_total = effective_min.saturating_mul(peer_count);
683 let remainder = self
684 .config
685 .total_capacity_bps
686 .saturating_sub(guaranteed_total);
687 let bonus_per_peer = remainder / peer_count;
688
689 self.peers
690 .keys()
691 .map(|id| {
692 let alloc = (effective_min + bonus_per_peer).min(self.config.max_allocation_bps);
693 (id.clone(), alloc)
694 })
695 .collect()
696 }
697
698 fn policy_max_capacity(&self, max_n: u64) -> HashMap<String, u64> {
700 let peer_count = self.peers.len() as u64;
701 if peer_count == 0 {
702 return HashMap::new();
703 }
704 let effective_max = max_n.min(self.config.max_allocation_bps);
705 let equal = (self.config.total_capacity_bps / peer_count).min(effective_max);
706 let clamped = equal.clamp(self.config.min_allocation_bps, effective_max);
707 self.peers.keys().map(|id| (id.clone(), clamped)).collect()
708 }
709
710 fn policy_priority_queue(&self) -> HashMap<String, u64> {
712 let mut ordered: Vec<&PeerBandwidthProfile> = self.peers.values().collect();
714 ordered.sort_by(|a, b| {
715 b.class
716 .priority()
717 .cmp(&a.class.priority())
718 .then_with(|| a.peer_id.cmp(&b.peer_id))
719 });
720
721 let mut remaining = self.config.total_capacity_bps;
722 let mut allocs: HashMap<String, u64> = HashMap::new();
723
724 for profile in &ordered {
725 let grant = remaining.min(self.config.max_allocation_bps);
726 let grant = grant.max(self.config.min_allocation_bps);
727 let grant = if grant > remaining { remaining } else { grant };
729 allocs.insert(profile.peer_id.clone(), grant);
730 remaining = remaining.saturating_sub(grant);
731 }
732
733 for profile in &ordered {
735 let entry = allocs.entry(profile.peer_id.clone()).or_insert(0);
736 if *entry == 0 {
737 *entry = self.config.min_allocation_bps;
738 }
739 }
740
741 allocs
742 }
743
744 fn jain_fairness_index(allocations: impl Iterator<Item = f64>, n: usize) -> f64 {
747 if n == 0 {
748 return 1.0;
749 }
750 let (sum, sum_sq) = allocations.fold((0.0_f64, 0.0_f64), |(s, sq), x| (s + x, sq + x * x));
751 if sum_sq == 0.0 {
752 return 1.0;
753 }
754 (sum * sum) / (n as f64 * sum_sq)
755 }
756}
757
758#[cfg(test)]
761mod tests {
762 use super::*;
763
764 fn default_config() -> AllocatorConfig {
767 AllocatorConfig {
768 total_capacity_bps: 100_000_000, min_allocation_bps: 1_000_000, max_allocation_bps: 50_000_000, policy: AllocationPolicy::EqualShare,
772 rebalance_interval_secs: 30,
773 congestion_threshold_ppm: 10_000, }
775 }
776
777 fn alloc_with_policy(policy: AllocationPolicy) -> AdaptiveBandwidthAllocator {
778 let mut cfg = default_config();
779 cfg.policy = policy;
780 AdaptiveBandwidthAllocator::new(cfg)
781 }
782
783 fn populated_allocator(n: usize, policy: AllocationPolicy) -> AdaptiveBandwidthAllocator {
784 let mut a = alloc_with_policy(policy);
785 for i in 0..n {
786 let class = match i % 4 {
787 0 => BandwidthClass::High,
788 1 => BandwidthClass::Normal,
789 2 => BandwidthClass::Low,
790 _ => BandwidthClass::Background,
791 };
792 a.add_peer(format!("peer-{i}"), class, 5_000_000)
793 .expect("test: failed to add peer in populated_allocator");
794 }
795 a
796 }
797
798 #[test]
801 fn test_xorshift64_non_zero_output() {
802 let mut state = 12345u64;
803 let v = xorshift64(&mut state);
804 assert_ne!(v, 0);
805 assert_ne!(state, 12345u64);
806 }
807
808 #[test]
809 fn test_xorshift64_deterministic() {
810 let mut s1 = 99999u64;
811 let mut s2 = 99999u64;
812 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
813 assert_eq!(s1, s2);
814 }
815
816 #[test]
817 fn test_xorshift64_different_states_differ() {
818 let mut s1 = 1u64;
819 let mut s2 = 2u64;
820 assert_ne!(xorshift64(&mut s1), xorshift64(&mut s2));
821 }
822
823 #[test]
826 fn test_bandwidth_class_weights() {
827 assert_eq!(BandwidthClass::High.weight(), 4.0);
828 assert_eq!(BandwidthClass::Normal.weight(), 2.0);
829 assert_eq!(BandwidthClass::Low.weight(), 1.0);
830 assert_eq!(BandwidthClass::Background.weight(), 0.5);
831 }
832
833 #[test]
834 fn test_bandwidth_class_priority_ordering() {
835 assert!(BandwidthClass::High.priority() > BandwidthClass::Normal.priority());
836 assert!(BandwidthClass::Normal.priority() > BandwidthClass::Low.priority());
837 assert!(BandwidthClass::Low.priority() > BandwidthClass::Background.priority());
838 }
839
840 #[test]
843 fn test_window_empty_returns_zero() {
844 let w = BandwidthWindow::new();
845 assert_eq!(w.average_bps(), 0);
846 assert_eq!(w.peak_bps(), 0);
847 assert!(w.is_empty());
848 }
849
850 #[test]
851 fn test_window_single_sample() {
852 let mut w = BandwidthWindow::new();
853 w.push(1_000_000);
854 assert!(!w.is_empty());
855 assert_eq!(w.len(), 1);
856 let avg = w.average_bps();
858 assert!((990_000..=1_010_000).contains(&avg));
859 }
860
861 #[test]
862 fn test_window_evicts_oldest_at_capacity() {
863 let mut w = BandwidthWindow::new();
864 for _ in 0..12 {
865 w.push(5_000_000);
866 }
867 assert_eq!(w.len(), 10);
868 }
869
870 #[test]
871 fn test_window_peak_is_max_sample() {
872 let mut w = BandwidthWindow::new();
873 for v in [1_000_000u64, 2_000_000, 3_000_000] {
874 w.push(v);
875 }
876 assert!(w.peak_bps() >= 2_900_000);
878 }
879
880 #[test]
881 fn test_window_average_convergence() {
882 let mut w = BandwidthWindow::new();
883 let target = 10_000_000u64;
884 for _ in 0..10 {
885 w.push(target);
886 }
887 let avg = w.average_bps();
888 assert!((avg as i64 - target as i64).abs() < 100_001);
890 }
891
892 #[test]
895 fn test_add_peer_success() {
896 let mut a = AdaptiveBandwidthAllocator::new(default_config());
897 assert!(a
898 .add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
899 .is_ok());
900 assert_eq!(a.peer_count(), 1);
901 }
902
903 #[test]
904 fn test_add_peer_clamps_to_max() {
905 let mut a = AdaptiveBandwidthAllocator::new(default_config());
906 a.add_peer("p1".to_string(), BandwidthClass::High, 999_000_000)
907 .expect("test: add high-bandwidth peer");
908 assert_eq!(
909 a.get_allocation("p1").expect("test: get allocation for p1"),
910 50_000_000
911 );
912 }
913
914 #[test]
915 fn test_add_peer_clamps_to_min() {
916 let mut a = AdaptiveBandwidthAllocator::new(default_config());
917 a.add_peer("p1".to_string(), BandwidthClass::Background, 1)
918 .expect("test: add background peer");
919 assert_eq!(
920 a.get_allocation("p1")
921 .expect("test: get allocation for background peer"),
922 1_000_000
923 );
924 }
925
926 #[test]
927 fn test_add_peer_exceeds_capacity() {
928 let mut a = AdaptiveBandwidthAllocator::new(default_config());
929 a.add_peer("p1".to_string(), BandwidthClass::High, 40_000_000)
931 .expect("test: add first high peer");
932 a.add_peer("p2".to_string(), BandwidthClass::High, 40_000_000)
933 .expect("test: add second high peer");
934 let err = a
936 .add_peer("p3".to_string(), BandwidthClass::Normal, 40_000_000)
937 .unwrap_err();
938 assert!(matches!(err, AllocatorError::AllocationExceedsCapacity(_)));
939 }
940
941 #[test]
942 fn test_add_peer_emits_event() {
943 let mut a = AdaptiveBandwidthAllocator::new(default_config());
944 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
945 .expect("test: add normal peer for event test");
946 let events = a.drain_events();
947 assert_eq!(events.len(), 1);
948 assert!(matches!(events[0], BandwidthEvent::PeerAdded { .. }));
949 }
950
951 #[test]
954 fn test_remove_peer_success() {
955 let mut a = AdaptiveBandwidthAllocator::new(default_config());
956 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
957 .expect("test: add peer before remove");
958 let profile = a.remove_peer("p1").expect("test: remove existing peer");
959 assert_eq!(profile.peer_id, "p1");
960 assert_eq!(a.peer_count(), 0);
961 }
962
963 #[test]
964 fn test_remove_peer_releases_capacity() {
965 let mut a = AdaptiveBandwidthAllocator::new(default_config());
966 a.add_peer("p1".to_string(), BandwidthClass::High, 40_000_000)
967 .expect("test: add p1 before release test");
968 a.add_peer("p2".to_string(), BandwidthClass::High, 40_000_000)
969 .expect("test: add p2 before release test");
970 a.remove_peer("p1")
971 .expect("test: remove p1 to release capacity");
972 assert!(a
974 .add_peer("p3".to_string(), BandwidthClass::High, 40_000_000)
975 .is_ok());
976 }
977
978 #[test]
979 fn test_remove_peer_not_found() {
980 let mut a = AdaptiveBandwidthAllocator::new(default_config());
981 let err = a.remove_peer("ghost").unwrap_err();
982 assert!(matches!(err, AllocatorError::PeerNotFound(_)));
983 }
984
985 #[test]
986 fn test_remove_peer_emits_event() {
987 let mut a = AdaptiveBandwidthAllocator::new(default_config());
988 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
989 .expect("test: add peer before remove event test");
990 a.drain_events();
991 a.remove_peer("p1")
992 .expect("test: remove peer to trigger event");
993 let events = a.drain_events();
994 assert!(matches!(events[0], BandwidthEvent::PeerRemoved { .. }));
995 }
996
997 #[test]
1000 fn test_update_measurement_normal() {
1001 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1002 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1003 .expect("test: add peer before measurement update");
1004 let ev = a
1005 .update_measurement("p1", 4_000_000, 20, 100)
1006 .expect("test: update measurement for p1");
1007 assert!(matches!(ev, BandwidthEvent::BandwidthUpdated { .. }));
1008 }
1009
1010 #[test]
1011 fn test_update_measurement_congestion_detected() {
1012 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1013 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1014 .expect("test: add peer before congestion test");
1015 let ev = a
1017 .update_measurement("p1", 2_000_000, 100, 50_000)
1018 .expect("test: update measurement with high loss");
1019 assert!(matches!(ev, BandwidthEvent::CongestionDetected { .. }));
1020 }
1021
1022 #[test]
1023 fn test_update_measurement_not_found() {
1024 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1025 let err = a.update_measurement("ghost", 1_000, 5, 0).unwrap_err();
1026 assert!(matches!(err, AllocatorError::PeerNotFound(_)));
1027 }
1028
1029 #[test]
1030 fn test_update_measurement_updates_window() {
1031 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1032 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1033 .expect("test: add peer before window test");
1034 for _ in 0..5 {
1035 a.update_measurement("p1", 4_000_000, 10, 0)
1036 .expect("test: update measurement in loop");
1037 }
1038 let profile = a
1039 .peer_profile("p1")
1040 .expect("test: get peer profile after window updates");
1041 assert_eq!(profile.window.len(), 5);
1042 }
1043
1044 #[test]
1045 fn test_update_measurement_updates_latency_and_loss() {
1046 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1047 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1048 .expect("test: add peer before latency/loss test");
1049 a.update_measurement("p1", 3_000_000, 42, 500)
1050 .expect("test: update measurement with latency and loss");
1051 let p = a
1052 .peer_profile("p1")
1053 .expect("test: get peer profile for latency/loss check");
1054 assert_eq!(p.latency_ms, 42);
1055 assert_eq!(p.packet_loss_ppm, 500);
1056 }
1057
1058 #[test]
1061 fn test_reallocate_equal_share_basic() {
1062 let mut a = alloc_with_policy(AllocationPolicy::EqualShare);
1063 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1064 .expect("test: add p1 for equal share test");
1065 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1066 .expect("test: add p2 for equal share test");
1067 a.reallocate();
1068 assert_eq!(
1070 a.get_allocation("p1")
1071 .expect("test: get p1 allocation after equal share reallocate"),
1072 50_000_000
1073 );
1074 assert_eq!(
1075 a.get_allocation("p2")
1076 .expect("test: get p2 allocation after equal share reallocate"),
1077 50_000_000
1078 );
1079 }
1080
1081 #[test]
1082 fn test_reallocate_equal_share_zero_peers_is_safe() {
1083 let mut a = alloc_with_policy(AllocationPolicy::EqualShare);
1084 let events = a.reallocate();
1085 assert!(!events.is_empty());
1086 assert!(matches!(
1087 events.last().expect("test: events list must not be empty"),
1088 BandwidthEvent::AllocationRevised { .. }
1089 ));
1090 }
1091
1092 #[test]
1093 fn test_reallocate_equal_share_clamped_to_min() {
1094 let mut cfg = default_config();
1095 cfg.total_capacity_bps = 5_000_000; cfg.min_allocation_bps = 1_000_000;
1097 cfg.max_allocation_bps = 5_000_000;
1098 let mut a = AdaptiveBandwidthAllocator::new(cfg);
1099 for i in 0..10 {
1101 let _ = a.add_peer(format!("p{i}"), BandwidthClass::Low, 1_000_000);
1103 }
1104 a.reallocate();
1105 for i in 0..a.peer_count() {
1107 if let Ok(alloc) = a.get_allocation(&format!("p{i}")) {
1108 assert!(alloc >= 1_000_000);
1109 }
1110 }
1111 }
1112
1113 #[test]
1116 fn test_reallocate_weighted_fair_respects_weight_order() {
1117 let mut a = alloc_with_policy(AllocationPolicy::WeightedFair);
1118 a.add_peer("high".to_string(), BandwidthClass::High, 5_000_000)
1119 .expect("test: add high-priority peer for weight order test");
1120 a.add_peer("low".to_string(), BandwidthClass::Low, 5_000_000)
1121 .expect("test: add low-priority peer for weight order test");
1122 a.reallocate();
1123 let high_alloc = a
1124 .get_allocation("high")
1125 .expect("test: get allocation for high priority peer");
1126 let low_alloc = a
1127 .get_allocation("low")
1128 .expect("test: get allocation for low priority peer");
1129 assert!(
1130 high_alloc > low_alloc,
1131 "high priority must get more than low"
1132 );
1133 }
1134
1135 #[test]
1136 fn test_reallocate_weighted_fair_sum_bounded_by_capacity() {
1137 let mut a = populated_allocator(5, AllocationPolicy::WeightedFair);
1138 a.reallocate();
1139 let total: u64 = (0..5)
1140 .map(|i| {
1141 a.get_allocation(&format!("peer-{i}"))
1142 .expect("test: get allocation for peer in weighted fair sum")
1143 })
1144 .sum();
1145 assert!(total <= 100_000_000);
1146 }
1147
1148 #[test]
1149 fn test_reallocate_weighted_fair_single_peer() {
1150 let mut a = alloc_with_policy(AllocationPolicy::WeightedFair);
1151 a.add_peer("only".to_string(), BandwidthClass::Normal, 5_000_000)
1152 .expect("test: add single peer for weighted fair test");
1153 a.reallocate();
1154 assert_eq!(
1156 a.get_allocation("only")
1157 .expect("test: get allocation for sole weighted fair peer"),
1158 50_000_000
1159 );
1160 }
1161
1162 #[test]
1165 fn test_reallocate_min_guarantee_basic() {
1166 let mut a = alloc_with_policy(AllocationPolicy::MinGuarantee(5_000_000));
1167 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1168 .expect("test: add p1 for min guarantee basic test");
1169 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1170 .expect("test: add p2 for min guarantee basic test");
1171 a.reallocate();
1172 assert!(
1173 a.get_allocation("p1")
1174 .expect("test: get allocation for p1 in min guarantee basic")
1175 >= 5_000_000
1176 );
1177 assert!(
1178 a.get_allocation("p2")
1179 .expect("test: get allocation for p2 in min guarantee basic")
1180 >= 5_000_000
1181 );
1182 }
1183
1184 #[test]
1185 fn test_reallocate_min_guarantee_distributes_surplus() {
1186 let mut a = alloc_with_policy(AllocationPolicy::MinGuarantee(2_000_000));
1187 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1188 .expect("test: add p1 for min guarantee surplus test");
1189 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1190 .expect("test: add p2 for min guarantee surplus test");
1191 a.reallocate();
1192 assert_eq!(
1195 a.get_allocation("p1")
1196 .expect("test: get allocation for p1 after surplus distribution"),
1197 50_000_000
1198 );
1199 }
1200
1201 #[test]
1202 fn test_reallocate_min_guarantee_honors_config_min() {
1203 let mut a = alloc_with_policy(AllocationPolicy::MinGuarantee(500_000));
1204 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1206 .expect("test: add p1 for min guarantee honors config min test");
1207 a.reallocate();
1208 assert!(
1209 a.get_allocation("p1")
1210 .expect("test: get allocation for p1 with config min enforcement")
1211 >= 1_000_000
1212 );
1213 }
1214
1215 #[test]
1218 fn test_reallocate_max_capacity_caps_peers() {
1219 let mut a = alloc_with_policy(AllocationPolicy::MaxCapacity(10_000_000));
1220 a.add_peer("p1".to_string(), BandwidthClass::High, 5_000_000)
1221 .expect("test: add p1 for max capacity caps test");
1222 a.add_peer("p2".to_string(), BandwidthClass::High, 5_000_000)
1223 .expect("test: add p2 for max capacity caps test");
1224 a.reallocate();
1225 assert!(
1226 a.get_allocation("p1")
1227 .expect("test: get allocation for p1 under max capacity cap")
1228 <= 10_000_000
1229 );
1230 assert!(
1231 a.get_allocation("p2")
1232 .expect("test: get allocation for p2 under max capacity cap")
1233 <= 10_000_000
1234 );
1235 }
1236
1237 #[test]
1238 fn test_reallocate_max_capacity_zero_peers_safe() {
1239 let mut a = alloc_with_policy(AllocationPolicy::MaxCapacity(10_000_000));
1240 a.reallocate();
1241 assert_eq!(a.peer_count(), 0);
1242 }
1243
1244 #[test]
1247 fn test_reallocate_priority_queue_high_gets_more() {
1248 let mut a = alloc_with_policy(AllocationPolicy::PriorityQueue);
1249 a.add_peer("bg".to_string(), BandwidthClass::Background, 5_000_000)
1250 .expect("test: add background peer for priority queue high-gets-more test");
1251 a.add_peer("hi".to_string(), BandwidthClass::High, 5_000_000)
1252 .expect("test: add high priority peer for priority queue high-gets-more test");
1253 a.reallocate();
1254 let hi = a
1255 .get_allocation("hi")
1256 .expect("test: get allocation for high priority peer in priority queue");
1257 let bg = a
1258 .get_allocation("bg")
1259 .expect("test: get allocation for background peer in priority queue");
1260 assert!(hi >= bg, "high priority should get >= background");
1261 }
1262
1263 #[test]
1264 fn test_reallocate_priority_queue_no_negative_allocations() {
1265 let mut a = alloc_with_policy(AllocationPolicy::PriorityQueue);
1266 for i in 0..8 {
1267 a.add_peer(format!("p{i}"), BandwidthClass::High, 5_000_000)
1268 .expect("test: add high-priority peer in no-negative-allocations test");
1269 }
1270 a.reallocate();
1271 for i in 0..8 {
1272 assert!(
1273 a.get_allocation(&format!("p{i}"))
1274 .expect("test: get allocation for peer in no-negative-allocations check")
1275 >= 1_000_000
1276 );
1277 }
1278 }
1279
1280 #[test]
1281 fn test_reallocate_priority_queue_emits_revised() {
1282 let mut a = populated_allocator(3, AllocationPolicy::PriorityQueue);
1283 a.drain_events();
1284 let events = a.reallocate();
1285 assert!(events
1286 .iter()
1287 .any(|e| matches!(e, BandwidthEvent::AllocationRevised { .. })));
1288 }
1289
1290 #[test]
1293 fn test_get_allocation_not_found() {
1294 let a = AdaptiveBandwidthAllocator::new(default_config());
1295 let err = a.get_allocation("nobody").unwrap_err();
1296 assert!(matches!(err, AllocatorError::PeerNotFound(_)));
1297 }
1298
1299 #[test]
1302 fn test_stats_empty_allocator() {
1303 let a = AdaptiveBandwidthAllocator::new(default_config());
1304 let s = a.stats();
1305 assert_eq!(s.peer_count, 0);
1306 assert_eq!(s.total_allocated_bps, 0);
1307 assert_eq!(s.fairness_index, 1.0);
1308 }
1309
1310 #[test]
1311 fn test_stats_utilization_pct() {
1312 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1313 a.add_peer("p1".to_string(), BandwidthClass::Normal, 10_000_000)
1314 .expect("test: add p1 for stats utilization test");
1315 a.update_measurement("p1", 5_000_000, 10, 0)
1316 .expect("test: record measurement for p1 in utilization test");
1317 let s = a.stats();
1318 assert!((s.utilization_pct - 50.0).abs() < 1.0);
1320 }
1321
1322 #[test]
1323 fn test_stats_congested_count() {
1324 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1325 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1326 .expect("test: add p1 for stats congested count test");
1327 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1328 .expect("test: add p2 for stats congested count test");
1329 a.update_measurement("p1", 3_000_000, 100, 50_000)
1330 .expect("test: record congested measurement for p1"); a.update_measurement("p2", 4_000_000, 10, 0)
1332 .expect("test: record healthy measurement for p2"); let s = a.stats();
1334 assert_eq!(s.congested_peers, 1);
1335 }
1336
1337 #[test]
1338 fn test_stats_fairness_perfect() {
1339 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1340 a.add_peer("p1".to_string(), BandwidthClass::Normal, 10_000_000)
1341 .expect("test: add p1 for stats fairness perfect test");
1342 a.add_peer("p2".to_string(), BandwidthClass::Normal, 10_000_000)
1343 .expect("test: add p2 for stats fairness perfect test");
1344 let s = a.stats();
1345 assert!((s.fairness_index - 1.0).abs() < 1e-9);
1347 }
1348
1349 #[test]
1350 fn test_stats_fairness_unequal() {
1351 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1352 a.add_peer("p1".to_string(), BandwidthClass::High, 10_000_000)
1353 .expect("test: add p1 High class for stats fairness unequal test");
1354 a.add_peer("p2".to_string(), BandwidthClass::High, 10_000_000)
1355 .expect("test: add p2 High class for stats fairness unequal test");
1356 a.reallocate();
1358 let s = a.stats();
1359 assert!(s.fairness_index > 0.0 && s.fairness_index <= 1.0);
1360 }
1361
1362 #[test]
1365 fn test_congested_peers_empty() {
1366 let a = AdaptiveBandwidthAllocator::new(default_config());
1367 assert!(a.congested_peers().is_empty());
1368 }
1369
1370 #[test]
1371 fn test_congested_peers_detection() {
1372 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1373 a.add_peer("good".to_string(), BandwidthClass::Normal, 5_000_000)
1374 .expect("test: add good peer for congested peers detection test");
1375 a.add_peer("bad".to_string(), BandwidthClass::Normal, 5_000_000)
1376 .expect("test: add bad peer for congested peers detection test");
1377 a.update_measurement("bad", 1_000_000, 200, 100_000)
1378 .expect("test: record high-latency measurement for bad peer");
1379 let congested = a.congested_peers();
1380 assert_eq!(congested.len(), 1);
1381 assert_eq!(congested[0], "bad");
1382 }
1383
1384 #[test]
1385 fn test_congested_peers_at_threshold_not_flagged() {
1386 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1387 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1388 .expect("test: add p1 for congested peers at threshold test");
1389 a.update_measurement("p1", 3_000_000, 50, 10_000)
1391 .expect("test: record measurement at congestion threshold for p1");
1392 assert!(a.congested_peers().is_empty());
1393 }
1394
1395 #[test]
1398 fn test_events_since_filters_by_timestamp() {
1399 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1400 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1401 .expect("test: add p1 for events since timestamp filter test");
1402 a.advance_time(100);
1403 let mid_ts = a.now_ms;
1404 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1405 .expect("test: add p2 after time advance for events since timestamp filter test");
1406 let recent = a.events_since(mid_ts);
1407 assert_eq!(recent.len(), 1);
1409 }
1410
1411 #[test]
1412 fn test_drain_events_clears_history() {
1413 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1414 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1415 .expect("test: add p1 for drain events clears history test");
1416 assert!(!a.drain_events().is_empty());
1417 assert!(a.drain_events().is_empty());
1418 }
1419
1420 #[test]
1421 fn test_event_history_bounded_at_200() {
1422 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1423 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1424 .expect("test: add p1 for event history bounded test");
1425 for _ in 0..300 {
1427 a.update_measurement("p1", 4_000_000, 10, 0)
1428 .expect("test: record repeated measurement for event history bounded test");
1429 }
1430 let events = a.events_since(0);
1432 assert!(events.len() <= AdaptiveBandwidthAllocator::MAX_EVENT_HISTORY);
1433 }
1434
1435 #[test]
1436 fn test_events_since_zero_returns_all() {
1437 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1438 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1439 .expect("test: add p1 for events since zero returns all test");
1440 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1441 .expect("test: add p2 for events since zero returns all test");
1442 let all = a.events_since(0);
1443 assert_eq!(all.len(), 2);
1444 }
1445
1446 #[test]
1449 fn test_config_validate_ok() {
1450 assert!(default_config().validate().is_ok());
1451 }
1452
1453 #[test]
1454 fn test_config_validate_min_gt_max_fails() {
1455 let mut cfg = default_config();
1456 cfg.min_allocation_bps = 60_000_000;
1457 cfg.max_allocation_bps = 10_000_000;
1458 assert!(cfg.validate().is_err());
1459 }
1460
1461 #[test]
1462 fn test_config_validate_zero_capacity_fails() {
1463 let mut cfg = default_config();
1464 cfg.total_capacity_bps = 0;
1465 assert!(cfg.validate().is_err());
1466 }
1467
1468 #[test]
1469 fn test_config_validate_max_gt_capacity_fails() {
1470 let mut cfg = default_config();
1471 cfg.max_allocation_bps = cfg.total_capacity_bps + 1;
1472 assert!(cfg.validate().is_err());
1473 }
1474
1475 #[test]
1478 fn test_jain_fairness_all_equal() {
1479 let fi =
1480 AdaptiveBandwidthAllocator::jain_fairness_index([10.0, 10.0, 10.0].iter().copied(), 3);
1481 assert!((fi - 1.0).abs() < 1e-9);
1482 }
1483
1484 #[test]
1485 fn test_jain_fairness_one_takes_all() {
1486 let fi =
1488 AdaptiveBandwidthAllocator::jain_fairness_index([100.0, 0.0, 0.0].iter().copied(), 3);
1489 assert!((fi - 1.0 / 3.0).abs() < 1e-9);
1491 }
1492
1493 #[test]
1494 fn test_jain_fairness_zero_peers() {
1495 let fi = AdaptiveBandwidthAllocator::jain_fairness_index(std::iter::empty(), 0);
1496 assert_eq!(fi, 1.0);
1497 }
1498
1499 #[test]
1500 fn test_jain_fairness_single_peer() {
1501 let fi = AdaptiveBandwidthAllocator::jain_fairness_index([42.0].iter().copied(), 1);
1502 assert!((fi - 1.0).abs() < 1e-9);
1503 }
1504
1505 #[test]
1508 fn test_reallocate_emits_allocation_revised() {
1509 let mut a = populated_allocator(3, AllocationPolicy::EqualShare);
1510 a.drain_events();
1511 let events = a.reallocate();
1512 assert!(events
1513 .iter()
1514 .any(|e| matches!(e, BandwidthEvent::AllocationRevised { .. })));
1515 }
1516
1517 #[test]
1518 fn test_single_peer_equal_share() {
1519 let mut a = alloc_with_policy(AllocationPolicy::EqualShare);
1520 a.add_peer("only".to_string(), BandwidthClass::Normal, 5_000_000)
1521 .expect("test: add single peer for equal share test");
1522 a.reallocate();
1523 assert_eq!(
1525 a.get_allocation("only")
1526 .expect("test: get allocation for single peer"),
1527 50_000_000
1528 );
1529 }
1530
1531 #[test]
1532 fn test_over_capacity_add_rejected() {
1533 let mut cfg = default_config();
1534 cfg.total_capacity_bps = 10_000_000; cfg.max_allocation_bps = 10_000_000;
1536 let mut a = AdaptiveBandwidthAllocator::new(cfg);
1537 a.add_peer("p1".to_string(), BandwidthClass::Normal, 10_000_000)
1538 .expect("test: add p1 within capacity for over-capacity rejection test");
1539 let err = a
1541 .add_peer("p2".to_string(), BandwidthClass::Normal, 1_000_000)
1542 .unwrap_err();
1543 assert!(matches!(err, AllocatorError::AllocationExceedsCapacity(_)));
1544 }
1545
1546 #[test]
1547 fn test_reallocate_after_remove_rebalances_correctly() {
1548 let mut a = alloc_with_policy(AllocationPolicy::EqualShare);
1549 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1550 .expect("test: add p1 for rebalance after remove test");
1551 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1552 .expect("test: add p2 for rebalance after remove test");
1553 a.add_peer("p3".to_string(), BandwidthClass::Normal, 5_000_000)
1554 .expect("test: add p3 to be removed for rebalance test");
1555 a.remove_peer("p3")
1556 .expect("test: remove p3 to trigger rebalance");
1557 a.reallocate();
1558 assert_eq!(
1560 a.get_allocation("p1")
1561 .expect("test: get allocation for p1 after rebalance"),
1562 50_000_000
1563 );
1564 assert_eq!(
1565 a.get_allocation("p2")
1566 .expect("test: get allocation for p2 after rebalance"),
1567 50_000_000
1568 );
1569 }
1570
1571 #[test]
1572 fn test_congestion_clears_after_update() {
1573 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1574 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1575 .expect("test: add p1 for congestion clear test");
1576 a.update_measurement("p1", 1_000_000, 200, 100_000)
1577 .expect("test: update measurement to mark p1 as congested"); assert_eq!(a.congested_peers().len(), 1);
1579 a.update_measurement("p1", 4_000_000, 10, 0)
1580 .expect("test: update measurement to clear p1 congestion"); assert_eq!(a.congested_peers().len(), 0);
1582 }
1583
1584 #[test]
1585 fn test_advance_time_affects_events() {
1586 let mut a = AdaptiveBandwidthAllocator::new(default_config());
1587 a.add_peer("p1".to_string(), BandwidthClass::Normal, 5_000_000)
1588 .expect("test: add p1 before time advance");
1589 a.advance_time(500);
1590 a.add_peer("p2".to_string(), BandwidthClass::Normal, 5_000_000)
1591 .expect("test: add p2 after time advance");
1592 let after_advance = a.events_since(a.now_ms);
1593 assert_eq!(after_advance.len(), 1);
1595 }
1596
1597 #[test]
1598 fn test_multiple_reallocate_calls_stable() {
1599 let mut a = populated_allocator(4, AllocationPolicy::WeightedFair);
1600 for _ in 0..5 {
1601 a.reallocate();
1602 }
1603 for i in 0..4 {
1605 let alloc = a
1606 .get_allocation(&format!("peer-{i}"))
1607 .expect("test: get allocation for peer in stability test");
1608 let cfg = a.config();
1609 assert!(alloc >= cfg.min_allocation_bps);
1610 assert!(alloc <= cfg.max_allocation_bps);
1611 }
1612 }
1613}