1use std::collections::{HashMap, VecDeque};
16
17pub type CccConnId = u64;
21pub type CccCongestionController = CongestionController;
23pub type CccDecision = Decision;
25
26#[inline]
29fn xorshift64(state: &mut u64) -> u64 {
30 let mut x = *state;
31 x ^= x << 13;
32 x ^= x >> 7;
33 x ^= x << 17;
34 *state = x;
35 x
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
42pub enum CccAlgorithm {
43 #[default]
45 Reno,
46 Cubic,
48 Bbr,
50 Vegas,
52 Westwood,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
60pub enum CccState {
61 SlowStart,
63 #[default]
65 CongestionAvoidance,
66 FastRecovery,
68 Idle,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum CccEventType {
77 AckReceived,
79 PacketLost,
81 Timeout,
83 FastRetransmit,
85 SlowStartEnter,
87 CaEnter,
89}
90
91#[derive(Clone, Debug)]
95pub struct CccEvent {
96 pub ts: u64,
98 pub conn_id: CccConnId,
100 pub event_type: CccEventType,
102 pub cwnd_before: u64,
104 pub cwnd_after: u64,
106}
107
108#[derive(Clone, Debug, PartialEq)]
112pub struct Decision {
113 pub new_cwnd: u64,
115 pub new_ssthresh: u64,
117 pub new_state: CccState,
119 pub sending_rate: Option<f64>,
121}
122
123#[derive(Clone, Debug)]
127pub struct CccConnection {
128 pub id: CccConnId,
130 pub cwnd: u64,
132 pub ssthresh: u64,
134 pub state: CccState,
136 pub rtt_ms: f64,
138 pub rtt_var: f64,
140 pub in_flight: u64,
142 pub bytes_acked: u64,
144 pub bytes_lost: u64,
146 pub last_ts: u64,
148 cubic_k: f64,
151 cubic_w_max: f64,
153 bbr_bw: f64,
156 bbr_min_rtt: f64,
158 vegas_base_rtt: f64,
161 westwood_bw: f64,
164 prng_state: u64,
166}
167
168impl CccConnection {
169 fn new(id: CccConnId, config: &CccControllerConfig) -> Self {
170 Self {
171 id,
172 cwnd: config.initial_cwnd,
173 ssthresh: config.ssthresh,
174 state: CccState::SlowStart,
175 rtt_ms: 0.0,
176 rtt_var: 0.0,
177 in_flight: 0,
178 bytes_acked: 0,
179 bytes_lost: 0,
180 last_ts: 0,
181 cubic_k: 0.0,
182 cubic_w_max: config.initial_cwnd as f64,
183 bbr_bw: 0.0,
184 bbr_min_rtt: f64::MAX,
185 vegas_base_rtt: 0.0,
186 westwood_bw: 0.0,
187 prng_state: id.wrapping_add(1).max(1),
188 }
189 }
190
191 fn update_rtt(&mut self, new_rtt_ms: f64, alpha: f64) {
193 if self.rtt_ms == 0.0 {
194 self.rtt_ms = new_rtt_ms;
195 self.rtt_var = new_rtt_ms / 2.0;
196 } else {
197 let diff = (new_rtt_ms - self.rtt_ms).abs();
198 self.rtt_var = (1.0 - 0.25) * self.rtt_var + 0.25 * diff;
199 self.rtt_ms = (1.0 - alpha) * self.rtt_ms + alpha * new_rtt_ms;
200 }
201 if new_rtt_ms < self.bbr_min_rtt {
202 self.bbr_min_rtt = new_rtt_ms;
203 }
204 if self.vegas_base_rtt == 0.0 || new_rtt_ms < self.vegas_base_rtt {
205 self.vegas_base_rtt = new_rtt_ms;
206 }
207 }
208
209 fn rate(&self) -> Option<f64> {
211 if self.rtt_ms > 0.0 {
212 Some(self.cwnd as f64 / (self.rtt_ms / 1000.0))
213 } else {
214 None
215 }
216 }
217
218 pub fn apply_cwnd_jitter(&mut self, min_cwnd: u64, max_cwnd: u64) {
223 let r = xorshift64(&mut self.prng_state);
224 let pct = (r % 201) as i64 - 100; let delta = (self.cwnd as i64 * pct / 10_000).unsigned_abs();
227 if pct >= 0 {
228 self.cwnd = self.cwnd.saturating_add(delta).min(max_cwnd);
229 } else {
230 self.cwnd = self.cwnd.saturating_sub(delta).max(min_cwnd);
231 }
232 }
233}
234
235#[derive(Clone, Debug)]
239pub struct CccControllerConfig {
240 pub algorithm: CccAlgorithm,
242 pub initial_cwnd: u64,
244 pub min_cwnd: u64,
246 pub max_cwnd: u64,
248 pub ssthresh: u64,
250 pub rtt_alpha: f64,
252}
253
254impl Default for CccControllerConfig {
255 fn default() -> Self {
256 Self {
257 algorithm: CccAlgorithm::Reno,
258 initial_cwnd: 65_536,
259 min_cwnd: 1_448,
260 max_cwnd: 16_777_216,
261 ssthresh: 1_048_576,
262 rtt_alpha: 0.125,
263 }
264 }
265}
266
267#[derive(Clone, Debug, Default)]
271pub struct CccControllerStats {
272 pub total_acks: u64,
274 pub total_losses: u64,
276 pub avg_cwnd: f64,
278 pub avg_rtt: f64,
280 pub active_connections: usize,
282}
283
284pub struct CongestionController {
291 connections: HashMap<CccConnId, CccConnection>,
293 events: VecDeque<CccEvent>,
295 config: CccControllerConfig,
297 total_acks: u64,
299 total_losses: u64,
301 tick: u64,
303}
304
305impl CongestionController {
306 pub fn new(config: CccControllerConfig) -> Self {
308 Self {
309 connections: HashMap::new(),
310 events: VecDeque::with_capacity(1_000),
311 config,
312 total_acks: 0,
313 total_losses: 0,
314 tick: 0,
315 }
316 }
317
318 pub fn with_defaults() -> Self {
320 Self::new(CccControllerConfig::default())
321 }
322
323 pub fn add_connection(&mut self, id: CccConnId) {
327 self.connections
328 .entry(id)
329 .or_insert_with(|| CccConnection::new(id, &self.config));
330 }
331
332 pub fn remove_connection(&mut self, id: CccConnId) -> bool {
334 self.connections.remove(&id).is_some()
335 }
336
337 pub fn reset_connection(&mut self, id: CccConnId) -> Result<(), &'static str> {
341 let config = &self.config;
342 let conn = self
343 .connections
344 .get_mut(&id)
345 .ok_or("connection not found")?;
346 *conn = CccConnection::new(id, config);
347 Ok(())
348 }
349
350 pub fn on_ack(
356 &mut self,
357 conn_id: CccConnId,
358 bytes_acked: u64,
359 rtt_ms: f64,
360 ) -> Result<CccDecision, &'static str> {
361 self.tick = self.tick.wrapping_add(1);
362 let ts = self.tick;
363
364 let conn = self
365 .connections
366 .get_mut(&conn_id)
367 .ok_or("connection not found")?;
368
369 let cwnd_before = conn.cwnd;
370 conn.update_rtt(rtt_ms, self.config.rtt_alpha);
371 conn.bytes_acked = conn.bytes_acked.saturating_add(bytes_acked);
372 conn.last_ts = ts;
373
374 if rtt_ms > 0.0 {
376 let sample_bw = bytes_acked as f64 / (rtt_ms / 1_000.0);
377 if conn.westwood_bw == 0.0 {
378 conn.westwood_bw = sample_bw;
379 } else {
380 conn.westwood_bw = 0.875 * conn.westwood_bw + 0.125 * sample_bw;
381 }
382 if conn.bbr_bw == 0.0 {
383 conn.bbr_bw = sample_bw;
384 } else {
385 conn.bbr_bw = conn.bbr_bw.max(sample_bw);
386 }
387 }
388
389 let min_cwnd = self.config.min_cwnd;
390 let max_cwnd = self.config.max_cwnd;
391
392 let new_cwnd = match self.config.algorithm {
393 CccAlgorithm::Reno => reno_on_ack(conn, bytes_acked, min_cwnd, max_cwnd),
394 CccAlgorithm::Cubic => cubic_on_ack(conn, bytes_acked, rtt_ms, min_cwnd, max_cwnd),
395 CccAlgorithm::Bbr => bbr_on_ack(conn, min_cwnd, max_cwnd),
396 CccAlgorithm::Vegas => vegas_on_ack(conn, min_cwnd, max_cwnd),
397 CccAlgorithm::Westwood => westwood_on_ack(conn, bytes_acked, min_cwnd, max_cwnd),
398 };
399
400 conn.cwnd = new_cwnd.clamp(min_cwnd, max_cwnd);
401 self.total_acks += 1;
402
403 let cwnd_after = conn.cwnd;
405 let new_ssthresh = conn.ssthresh;
406 let new_state = conn.state;
407 let sending_rate = conn.rate();
408
409 self.push_event(CccEvent {
410 ts,
411 conn_id,
412 event_type: CccEventType::AckReceived,
413 cwnd_before,
414 cwnd_after,
415 });
416
417 Ok(Decision {
418 new_cwnd: cwnd_after,
419 new_ssthresh,
420 new_state,
421 sending_rate,
422 })
423 }
424
425 pub fn on_loss(
429 &mut self,
430 conn_id: CccConnId,
431 lost_bytes: u64,
432 ) -> Result<CccDecision, &'static str> {
433 self.tick = self.tick.wrapping_add(1);
434 let ts = self.tick;
435
436 let conn = self
437 .connections
438 .get_mut(&conn_id)
439 .ok_or("connection not found")?;
440
441 let cwnd_before = conn.cwnd;
442 conn.bytes_lost = conn.bytes_lost.saturating_add(lost_bytes);
443 conn.last_ts = ts;
444
445 let min_cwnd = self.config.min_cwnd;
446 let max_cwnd = self.config.max_cwnd;
447
448 let new_cwnd = match self.config.algorithm {
449 CccAlgorithm::Reno => reno_on_loss(conn, min_cwnd),
450 CccAlgorithm::Cubic => cubic_on_loss(conn, min_cwnd),
451 CccAlgorithm::Bbr => bbr_on_loss(conn, min_cwnd),
452 CccAlgorithm::Vegas => vegas_on_loss(conn, min_cwnd),
453 CccAlgorithm::Westwood => westwood_on_loss(conn, min_cwnd),
454 };
455
456 conn.cwnd = new_cwnd.clamp(min_cwnd, max_cwnd);
457 conn.state = CccState::FastRecovery;
458 self.total_losses += 1;
459
460 let cwnd_after = conn.cwnd;
462 let new_ssthresh = conn.ssthresh;
463 let sending_rate = conn.rate();
464
465 self.push_event(CccEvent {
466 ts,
467 conn_id,
468 event_type: CccEventType::PacketLost,
469 cwnd_before,
470 cwnd_after,
471 });
472
473 Ok(Decision {
474 new_cwnd: cwnd_after,
475 new_ssthresh,
476 new_state: CccState::FastRecovery,
477 sending_rate,
478 })
479 }
480
481 pub fn on_timeout(&mut self, conn_id: CccConnId) -> Result<CccDecision, &'static str> {
485 self.tick = self.tick.wrapping_add(1);
486 let ts = self.tick;
487
488 let conn = self
489 .connections
490 .get_mut(&conn_id)
491 .ok_or("connection not found")?;
492
493 let cwnd_before = conn.cwnd;
494 conn.last_ts = ts;
495
496 let min_cwnd = self.config.min_cwnd;
497
498 conn.ssthresh = (conn.cwnd / 2).max(min_cwnd);
499 conn.cwnd = min_cwnd;
500 conn.state = CccState::SlowStart;
501 conn.cubic_k = 0.0;
503 conn.cubic_w_max = min_cwnd as f64;
504 self.total_losses += 1;
505
506 let cwnd_after = conn.cwnd;
508 let new_ssthresh = conn.ssthresh;
509 let sending_rate = conn.rate();
510
511 self.push_event(CccEvent {
512 ts,
513 conn_id,
514 event_type: CccEventType::Timeout,
515 cwnd_before,
516 cwnd_after,
517 });
518 self.push_event(CccEvent {
519 ts,
520 conn_id,
521 event_type: CccEventType::SlowStartEnter,
522 cwnd_before: cwnd_after,
523 cwnd_after,
524 });
525
526 Ok(Decision {
527 new_cwnd: cwnd_after,
528 new_ssthresh,
529 new_state: CccState::SlowStart,
530 sending_rate,
531 })
532 }
533
534 pub fn sending_rate(&self, conn_id: CccConnId) -> Option<f64> {
540 self.connections.get(&conn_id).and_then(|c| c.rate())
541 }
542
543 pub fn controller_stats(&self) -> CccControllerStats {
545 let n = self.connections.len();
546 if n == 0 {
547 return CccControllerStats {
548 total_acks: self.total_acks,
549 total_losses: self.total_losses,
550 avg_cwnd: 0.0,
551 avg_rtt: 0.0,
552 active_connections: 0,
553 };
554 }
555 let sum_cwnd: u64 = self.connections.values().map(|c| c.cwnd).sum();
556 let sum_rtt: f64 = self.connections.values().map(|c| c.rtt_ms).sum();
557 CccControllerStats {
558 total_acks: self.total_acks,
559 total_losses: self.total_losses,
560 avg_cwnd: sum_cwnd as f64 / n as f64,
561 avg_rtt: sum_rtt / n as f64,
562 active_connections: n,
563 }
564 }
565
566 pub fn connection(&self, conn_id: CccConnId) -> Option<&CccConnection> {
568 self.connections.get(&conn_id)
569 }
570
571 pub fn events(&self) -> &VecDeque<CccEvent> {
573 &self.events
574 }
575
576 fn push_event(&mut self, event: CccEvent) {
579 if self.events.len() >= 1_000 {
580 self.events.pop_front();
581 }
582 self.events.push_back(event);
583 }
584}
585
586fn reno_on_ack(conn: &mut CccConnection, bytes_acked: u64, min_cwnd: u64, max_cwnd: u64) -> u64 {
590 match conn.state {
591 CccState::SlowStart | CccState::Idle => {
592 let new_cwnd = conn.cwnd.saturating_add(bytes_acked).min(max_cwnd);
593 if new_cwnd >= conn.ssthresh {
594 conn.state = CccState::CongestionAvoidance;
595 }
596 new_cwnd
597 }
598 CccState::CongestionAvoidance => {
599 let mss: u64 = 1_448;
601 let increase = if conn.cwnd > 0 {
602 (mss.saturating_mul(mss)).saturating_div(conn.cwnd).max(1)
603 } else {
604 mss
605 };
606 conn.cwnd.saturating_add(increase).min(max_cwnd)
607 }
608 CccState::FastRecovery => {
609 let new_cwnd = conn.cwnd.saturating_add(bytes_acked / 2).min(max_cwnd);
611 if new_cwnd >= conn.ssthresh {
612 conn.state = CccState::CongestionAvoidance;
613 }
614 new_cwnd
615 }
616 }
617 .max(min_cwnd)
618}
619
620fn reno_on_loss(conn: &mut CccConnection, min_cwnd: u64) -> u64 {
622 conn.ssthresh = (conn.cwnd / 2).max(min_cwnd);
623 conn.ssthresh
624}
625
626const CUBIC_C: f64 = 0.4;
630const CUBIC_BETA: f64 = 0.7;
632
633fn cubic_on_ack(
635 conn: &mut CccConnection,
636 bytes_acked: u64,
637 rtt_ms: f64,
638 min_cwnd: u64,
639 max_cwnd: u64,
640) -> u64 {
641 match conn.state {
642 CccState::SlowStart | CccState::Idle => {
643 let new_cwnd = conn.cwnd.saturating_add(bytes_acked).min(max_cwnd);
644 if new_cwnd >= conn.ssthresh {
645 conn.state = CccState::CongestionAvoidance;
646 let w_max = conn.cubic_w_max;
648 conn.cubic_k = ((w_max * (1.0 - CUBIC_BETA)) / CUBIC_C).cbrt();
649 }
650 new_cwnd.max(min_cwnd)
651 }
652 CccState::CongestionAvoidance => {
653 let rtt_s = (rtt_ms / 1_000.0).max(0.001);
654 let t = rtt_s;
656 let k = conn.cubic_k;
657 let w_max = conn.cubic_w_max;
658 let delta = t - k;
660 let w_cubic = CUBIC_C * delta * delta * delta + w_max;
661 let target = w_cubic.max(conn.cwnd as f64);
662 let new_cwnd = (target as u64).clamp(min_cwnd, max_cwnd);
663 if new_cwnd >= conn.ssthresh {
664 conn.state = CccState::CongestionAvoidance; }
666 new_cwnd
667 }
668 CccState::FastRecovery => {
669 let new_cwnd = conn.cwnd.saturating_add(bytes_acked / 2).min(max_cwnd);
670 if new_cwnd >= conn.ssthresh {
671 conn.state = CccState::CongestionAvoidance;
672 }
673 new_cwnd.max(min_cwnd)
674 }
675 }
676}
677
678fn cubic_on_loss(conn: &mut CccConnection, min_cwnd: u64) -> u64 {
680 conn.cubic_w_max = conn.cwnd as f64;
681 let new_cwnd = ((conn.cwnd as f64 * CUBIC_BETA) as u64).max(min_cwnd);
682 conn.ssthresh = new_cwnd;
683 conn.cubic_k = 0.0;
685 new_cwnd
686}
687
688fn bbr_on_ack(conn: &mut CccConnection, min_cwnd: u64, max_cwnd: u64) -> u64 {
692 if conn.bbr_bw > 0.0 && conn.bbr_min_rtt < f64::MAX && conn.bbr_min_rtt > 0.0 {
693 let bdp = conn.bbr_bw * (conn.bbr_min_rtt / 1_000.0);
694 let target = (bdp * 1.25) as u64;
696 let new_cwnd = target.max(conn.cwnd).clamp(min_cwnd, max_cwnd);
698 if conn.state == CccState::SlowStart && new_cwnd >= conn.ssthresh {
699 conn.state = CccState::CongestionAvoidance;
700 }
701 new_cwnd
702 } else {
703 let new_cwnd = conn.cwnd.saturating_add(1_448).min(max_cwnd);
705 if new_cwnd >= conn.ssthresh {
706 conn.state = CccState::CongestionAvoidance;
707 }
708 new_cwnd.max(min_cwnd)
709 }
710}
711
712fn bbr_on_loss(conn: &mut CccConnection, min_cwnd: u64) -> u64 {
715 conn.ssthresh = (conn.cwnd * 9 / 10).max(min_cwnd);
716 conn.ssthresh
717}
718
719const VEGAS_ALPHA: f64 = 2.0;
723const VEGAS_BETA: f64 = 4.0;
724
725fn vegas_on_ack(conn: &mut CccConnection, min_cwnd: u64, max_cwnd: u64) -> u64 {
727 if conn.vegas_base_rtt == 0.0 || conn.rtt_ms == 0.0 {
728 let new_cwnd = conn.cwnd.saturating_add(1_448).min(max_cwnd);
730 if new_cwnd >= conn.ssthresh {
731 conn.state = CccState::CongestionAvoidance;
732 }
733 return new_cwnd.max(min_cwnd);
734 }
735
736 let expected = conn.cwnd as f64 / conn.vegas_base_rtt;
737 let actual = conn.cwnd as f64 / conn.rtt_ms;
738 let diff = expected - actual;
739 let mss: f64 = 1_448.0;
740
741 let new_cwnd: u64 = match conn.state {
742 CccState::SlowStart | CccState::Idle => {
743 let grown = conn.cwnd.saturating_add(1_448).min(max_cwnd);
744 if grown >= conn.ssthresh {
745 conn.state = CccState::CongestionAvoidance;
746 }
747 grown
748 }
749 CccState::CongestionAvoidance => {
750 if diff < VEGAS_ALPHA {
751 (conn.cwnd as f64 + mss * mss / conn.cwnd as f64) as u64
753 } else if diff > VEGAS_BETA {
754 conn.cwnd
756 .saturating_sub((mss * mss / conn.cwnd as f64) as u64)
757 } else {
758 conn.cwnd
759 }
760 }
761 CccState::FastRecovery => {
762 let grown = conn.cwnd.saturating_add(1_448 / 2).min(max_cwnd);
763 if grown >= conn.ssthresh {
764 conn.state = CccState::CongestionAvoidance;
765 }
766 grown
767 }
768 };
769 new_cwnd.clamp(min_cwnd, max_cwnd)
770}
771
772fn vegas_on_loss(conn: &mut CccConnection, min_cwnd: u64) -> u64 {
774 conn.ssthresh = (conn.cwnd / 2).max(min_cwnd);
775 conn.ssthresh
776}
777
778fn westwood_on_ack(
782 conn: &mut CccConnection,
783 bytes_acked: u64,
784 min_cwnd: u64,
785 max_cwnd: u64,
786) -> u64 {
787 match conn.state {
788 CccState::SlowStart | CccState::Idle => {
789 let new_cwnd = conn.cwnd.saturating_add(bytes_acked).min(max_cwnd);
790 if new_cwnd >= conn.ssthresh {
791 conn.state = CccState::CongestionAvoidance;
792 }
793 new_cwnd.max(min_cwnd)
794 }
795 CccState::CongestionAvoidance => {
796 let mss: u64 = 1_448;
797 let increase = if conn.cwnd > 0 {
798 (mss.saturating_mul(mss)).saturating_div(conn.cwnd).max(1)
799 } else {
800 mss
801 };
802 conn.cwnd
803 .saturating_add(increase)
804 .min(max_cwnd)
805 .max(min_cwnd)
806 }
807 CccState::FastRecovery => {
808 let new_cwnd = conn.cwnd.saturating_add(bytes_acked / 2).min(max_cwnd);
809 if new_cwnd >= conn.ssthresh {
810 conn.state = CccState::CongestionAvoidance;
811 }
812 new_cwnd.max(min_cwnd)
813 }
814 }
815}
816
817fn westwood_on_loss(conn: &mut CccConnection, min_cwnd: u64) -> u64 {
819 if conn.westwood_bw > 0.0 && conn.rtt_ms > 0.0 {
820 let bdp = (conn.westwood_bw * conn.rtt_ms / 1_000.0) as u64;
821 conn.ssthresh = bdp.max(min_cwnd);
822 } else {
823 conn.ssthresh = (conn.cwnd / 2).max(min_cwnd);
824 }
825 conn.ssthresh
826}
827
828#[derive(Clone, Copy, Debug, PartialEq, Eq)]
834pub enum CongestionState {
835 SlowStart,
837 CongestionAvoidance,
839 FastRecovery,
841}
842
843#[derive(Clone, Copy, Debug, PartialEq, Eq)]
845pub enum CongestionEvent {
846 AckReceived {
848 bytes: u64,
850 },
851 PacketLoss,
853 Timeout,
855 EcnMark,
857}
858
859#[derive(Clone, Debug)]
861pub struct WindowStats {
862 pub current_window: u64,
864 pub slow_start_threshold: u64,
866 pub state: CongestionState,
868 pub total_acks: u64,
870 pub total_losses: u64,
872}
873
874impl WindowStats {
875 pub fn utilization(&self) -> f64 {
879 let denom = self.current_window + self.slow_start_threshold;
880 if denom == 0 {
881 return 0.0;
882 }
883 self.current_window as f64 / denom as f64
884 }
885}
886
887#[derive(Clone, Debug)]
889pub struct CongestionConfig {
890 pub initial_window: u64,
892 pub max_window: u64,
894 pub min_window: u64,
896 pub slow_start_threshold: u64,
898}
899
900impl Default for CongestionConfig {
901 fn default() -> Self {
902 Self {
903 initial_window: 65_536,
904 max_window: 16_777_216,
905 min_window: 1_448,
906 slow_start_threshold: 1_048_576,
907 }
908 }
909}
910
911pub struct PeerCongestionController {
913 pub peer_id: String,
915 pub window: u64,
917 pub ssthresh: u64,
919 pub state: CongestionState,
921 pub config: CongestionConfig,
923 pub total_acks: u64,
925 pub total_losses: u64,
927}
928
929impl PeerCongestionController {
930 pub fn new(peer_id: String, config: CongestionConfig) -> Self {
932 let window = config.initial_window;
933 let ssthresh = config.slow_start_threshold;
934 Self {
935 peer_id,
936 window,
937 ssthresh,
938 state: CongestionState::SlowStart,
939 config,
940 total_acks: 0,
941 total_losses: 0,
942 }
943 }
944
945 pub fn on_event(&mut self, event: CongestionEvent) {
947 match event {
948 CongestionEvent::AckReceived { bytes } => self.handle_ack(bytes),
949 CongestionEvent::PacketLoss => self.handle_packet_loss(),
950 CongestionEvent::Timeout => self.handle_timeout(),
951 CongestionEvent::EcnMark => self.handle_ecn(),
952 }
953 }
954
955 fn handle_ack(&mut self, bytes: u64) {
956 match self.state {
957 CongestionState::SlowStart => {
958 self.window = self
959 .window
960 .saturating_add(bytes)
961 .min(self.config.max_window);
962 if self.window >= self.ssthresh {
963 self.state = CongestionState::CongestionAvoidance;
964 }
965 self.total_acks += 1;
966 }
967 CongestionState::CongestionAvoidance => {
968 let increase = if self.window > 0 {
969 (bytes.saturating_mul(bytes)).saturating_div(self.window)
970 } else {
971 bytes
972 };
973 self.window = self
974 .window
975 .saturating_add(increase)
976 .min(self.config.max_window);
977 self.total_acks += 1;
978 }
979 CongestionState::FastRecovery => {
980 self.window = self
981 .window
982 .saturating_add(bytes / 2)
983 .min(self.config.max_window);
984 if self.window >= self.ssthresh {
985 self.state = CongestionState::CongestionAvoidance;
986 }
987 self.total_acks += 1;
988 }
989 }
990 }
991
992 fn handle_packet_loss(&mut self) {
993 self.ssthresh = (self.window / 2).max(self.config.min_window);
994 self.window = self.ssthresh;
995 self.state = CongestionState::FastRecovery;
996 self.total_losses += 1;
997 }
998
999 fn handle_timeout(&mut self) {
1000 self.ssthresh = (self.window / 2).max(self.config.min_window);
1001 self.window = self.config.initial_window.max(self.config.min_window);
1002 self.state = CongestionState::SlowStart;
1003 self.total_losses += 1;
1004 }
1005
1006 fn handle_ecn(&mut self) {
1007 self.ssthresh = ((self.window * 7) / 8).max(self.config.min_window);
1008 self.window = self.ssthresh;
1009 self.state = CongestionState::CongestionAvoidance;
1010 self.total_losses += 1;
1011 }
1012
1013 pub fn window_stats(&self) -> WindowStats {
1015 WindowStats {
1016 current_window: self.window,
1017 slow_start_threshold: self.ssthresh,
1018 state: self.state,
1019 total_acks: self.total_acks,
1020 total_losses: self.total_losses,
1021 }
1022 }
1023
1024 pub fn can_send(&self, bytes: u64) -> bool {
1026 bytes <= self.window
1027 }
1028}
1029
1030pub struct MultiPeerCongestionManager {
1032 pub controllers: HashMap<String, PeerCongestionController>,
1034 pub config: CongestionConfig,
1036}
1037
1038impl MultiPeerCongestionManager {
1039 pub fn new(config: CongestionConfig) -> Self {
1041 Self {
1042 controllers: HashMap::new(),
1043 config,
1044 }
1045 }
1046
1047 pub fn get_or_create(&mut self, peer_id: &str) -> &mut PeerCongestionController {
1049 self.controllers
1050 .entry(peer_id.to_owned())
1051 .or_insert_with(|| {
1052 PeerCongestionController::new(peer_id.to_owned(), self.config.clone())
1053 })
1054 }
1055
1056 pub fn on_event(&mut self, peer_id: &str, event: CongestionEvent) {
1058 let ctrl = self.get_or_create(peer_id);
1059 ctrl.on_event(event);
1060 }
1061
1062 pub fn remove_peer(&mut self, peer_id: &str) -> bool {
1064 self.controllers.remove(peer_id).is_some()
1065 }
1066
1067 pub fn total_window(&self) -> u64 {
1069 self.controllers.values().map(|c| c.window).sum()
1070 }
1071}
1072
1073#[cfg(test)]
1078mod tests {
1079 use super::*;
1080
1081 fn default_config() -> CongestionConfig {
1084 CongestionConfig::default()
1085 }
1086
1087 fn legacy_ctrl(peer: &str) -> PeerCongestionController {
1088 PeerCongestionController::new(peer.to_owned(), default_config())
1089 }
1090
1091 fn make_ctrl(algo: CccAlgorithm) -> CongestionController {
1092 CongestionController::new(CccControllerConfig {
1093 algorithm: algo,
1094 ..CccControllerConfig::default()
1095 })
1096 }
1097
1098 #[test]
1101 fn test_new_starts_in_slow_start() {
1102 let ctrl = legacy_ctrl("peer-a");
1103 assert_eq!(ctrl.state, CongestionState::SlowStart);
1104 assert_eq!(ctrl.window, 65_536);
1105 assert_eq!(ctrl.ssthresh, 1_048_576);
1106 }
1107
1108 #[test]
1109 fn test_slow_start_ack_grows_window() {
1110 let mut ctrl = legacy_ctrl("p");
1111 let before = ctrl.window;
1112 ctrl.on_event(CongestionEvent::AckReceived { bytes: 1_000 });
1113 assert_eq!(ctrl.window, before + 1_000);
1114 }
1115
1116 #[test]
1117 fn test_slow_start_caps_at_max() {
1118 let cfg = CongestionConfig {
1119 initial_window: 16_777_000,
1120 ..Default::default()
1121 };
1122 let mut ctrl = PeerCongestionController::new("p".into(), cfg.clone());
1123 ctrl.on_event(CongestionEvent::AckReceived { bytes: 1_000_000 });
1124 assert_eq!(ctrl.window, cfg.max_window);
1125 }
1126
1127 #[test]
1128 fn test_slow_start_transitions_at_ssthresh() {
1129 let cfg = CongestionConfig {
1130 initial_window: 500_000,
1131 slow_start_threshold: 600_000,
1132 ..Default::default()
1133 };
1134 let mut ctrl = PeerCongestionController::new("p".into(), cfg);
1135 ctrl.on_event(CongestionEvent::AckReceived { bytes: 200_000 });
1136 assert_eq!(ctrl.state, CongestionState::CongestionAvoidance);
1137 }
1138
1139 #[test]
1140 fn test_congestion_avoidance_increase_smaller() {
1141 let cfg = CongestionConfig {
1142 initial_window: 100_000,
1143 slow_start_threshold: 50_000,
1144 ..Default::default()
1145 };
1146 let mut ctrl = PeerCongestionController::new("p".into(), cfg);
1147 ctrl.state = CongestionState::CongestionAvoidance;
1148 let bytes: u64 = 1_000;
1149 let before = ctrl.window;
1150 ctrl.on_event(CongestionEvent::AckReceived { bytes });
1151 let ca_increase = ctrl.window - before;
1152 assert!(ca_increase < bytes);
1153 assert_eq!(ca_increase, (bytes * bytes) / 100_000);
1154 }
1155
1156 #[test]
1157 fn test_fast_recovery_ack_grows_half() {
1158 let mut ctrl = legacy_ctrl("p");
1159 ctrl.state = CongestionState::FastRecovery;
1160 ctrl.ssthresh = ctrl.window + 100_000;
1161 let before = ctrl.window;
1162 ctrl.on_event(CongestionEvent::AckReceived { bytes: 2_000 });
1163 assert_eq!(ctrl.window, before + 1_000);
1164 }
1165
1166 #[test]
1167 fn test_fast_recovery_transitions_to_ca() {
1168 let cfg = CongestionConfig {
1169 initial_window: 50_000,
1170 slow_start_threshold: 60_000,
1171 ..Default::default()
1172 };
1173 let mut ctrl = PeerCongestionController::new("p".into(), cfg);
1174 ctrl.state = CongestionState::FastRecovery;
1175 ctrl.ssthresh = 51_000;
1176 ctrl.on_event(CongestionEvent::AckReceived { bytes: 10_000 });
1177 assert_eq!(ctrl.state, CongestionState::CongestionAvoidance);
1178 }
1179
1180 #[test]
1181 fn test_packet_loss_sets_ssthresh_and_state() {
1182 let mut ctrl = legacy_ctrl("p");
1183 let orig_window = ctrl.window;
1184 ctrl.on_event(CongestionEvent::PacketLoss);
1185 assert_eq!(ctrl.ssthresh, orig_window / 2);
1186 assert_eq!(ctrl.window, orig_window / 2);
1187 assert_eq!(ctrl.state, CongestionState::FastRecovery);
1188 }
1189
1190 #[test]
1191 fn test_packet_loss_floors_at_min() {
1192 let cfg = CongestionConfig {
1193 initial_window: 2_000,
1194 min_window: 1_448,
1195 ..Default::default()
1196 };
1197 let mut ctrl = PeerCongestionController::new("p".into(), cfg.clone());
1198 ctrl.on_event(CongestionEvent::PacketLoss);
1199 assert_eq!(ctrl.window, cfg.min_window);
1200 assert_eq!(ctrl.ssthresh, cfg.min_window);
1201 }
1202
1203 #[test]
1204 fn test_timeout_resets_window_and_state() {
1205 let mut ctrl = legacy_ctrl("p");
1206 ctrl.window = 500_000;
1207 ctrl.on_event(CongestionEvent::Timeout);
1208 assert_eq!(ctrl.window, 65_536);
1209 assert_eq!(ctrl.state, CongestionState::SlowStart);
1210 }
1211
1212 #[test]
1213 fn test_ecn_mark() {
1214 let mut ctrl = legacy_ctrl("p");
1215 ctrl.window = 800_000;
1216 ctrl.on_event(CongestionEvent::EcnMark);
1217 assert_eq!(ctrl.ssthresh, (800_000u64 * 7) / 8);
1218 assert_eq!(ctrl.window, ctrl.ssthresh);
1219 assert_eq!(ctrl.state, CongestionState::CongestionAvoidance);
1220 }
1221
1222 #[test]
1223 fn test_total_acks_increments() {
1224 let mut ctrl = legacy_ctrl("p");
1225 assert_eq!(ctrl.total_acks, 0);
1226 ctrl.on_event(CongestionEvent::AckReceived { bytes: 100 });
1227 ctrl.on_event(CongestionEvent::AckReceived { bytes: 100 });
1228 assert_eq!(ctrl.total_acks, 2);
1229 }
1230
1231 #[test]
1232 fn test_total_losses_increments() {
1233 let mut ctrl = legacy_ctrl("p");
1234 assert_eq!(ctrl.total_losses, 0);
1235 ctrl.on_event(CongestionEvent::PacketLoss);
1236 ctrl.on_event(CongestionEvent::EcnMark);
1237 ctrl.on_event(CongestionEvent::Timeout);
1238 assert_eq!(ctrl.total_losses, 3);
1239 }
1240
1241 #[test]
1242 fn test_can_send_within_window() {
1243 let ctrl = legacy_ctrl("p");
1244 assert!(ctrl.can_send(ctrl.window));
1245 assert!(ctrl.can_send(1));
1246 }
1247
1248 #[test]
1249 fn test_can_send_exceeds_window() {
1250 let ctrl = legacy_ctrl("p");
1251 assert!(!ctrl.can_send(ctrl.window + 1));
1252 }
1253
1254 #[test]
1255 fn test_utilization() {
1256 let ctrl = legacy_ctrl("p");
1257 let stats = ctrl.window_stats();
1258 let expected = stats.current_window as f64
1259 / (stats.current_window + stats.slow_start_threshold) as f64;
1260 let diff = (stats.utilization() - expected).abs();
1261 assert!(diff < 1e-12);
1262 }
1263
1264 #[test]
1265 fn test_utilization_zero() {
1266 let stats = WindowStats {
1267 current_window: 0,
1268 slow_start_threshold: 0,
1269 state: CongestionState::SlowStart,
1270 total_acks: 0,
1271 total_losses: 0,
1272 };
1273 assert_eq!(stats.utilization(), 0.0);
1274 }
1275
1276 #[test]
1277 fn test_manager_creates_on_first_access() {
1278 let mut mgr = MultiPeerCongestionManager::new(default_config());
1279 let ctrl = mgr.get_or_create("peer-1");
1280 assert_eq!(ctrl.state, CongestionState::SlowStart);
1281 assert_eq!(ctrl.window, 65_536);
1282 }
1283
1284 #[test]
1285 fn test_manager_routes_event() {
1286 let mut mgr = MultiPeerCongestionManager::new(default_config());
1287 mgr.get_or_create("a");
1288 mgr.get_or_create("b");
1289 let initial_a = mgr.controllers["a"].window;
1290 let initial_b = mgr.controllers["b"].window;
1291 mgr.on_event("a", CongestionEvent::AckReceived { bytes: 5_000 });
1292 assert_eq!(mgr.controllers["a"].window, initial_a + 5_000);
1293 assert_eq!(mgr.controllers["b"].window, initial_b);
1294 }
1295
1296 #[test]
1297 fn test_remove_peer() {
1298 let mut mgr = MultiPeerCongestionManager::new(default_config());
1299 mgr.get_or_create("x");
1300 assert!(mgr.remove_peer("x"));
1301 assert!(!mgr.remove_peer("x"));
1302 }
1303
1304 #[test]
1305 fn test_total_window() {
1306 let mut mgr = MultiPeerCongestionManager::new(default_config());
1307 mgr.get_or_create("a");
1308 mgr.get_or_create("b");
1309 let expected = mgr.controllers["a"].window + mgr.controllers["b"].window;
1310 assert_eq!(mgr.total_window(), expected);
1311 }
1312
1313 #[test]
1314 fn test_ssthresh_floor_on_loss() {
1315 let cfg = CongestionConfig {
1316 initial_window: 1_500,
1317 min_window: 1_448,
1318 ..Default::default()
1319 };
1320 let mut ctrl = PeerCongestionController::new("p".into(), cfg.clone());
1321 ctrl.on_event(CongestionEvent::PacketLoss);
1322 assert!(ctrl.ssthresh >= cfg.min_window);
1323 assert!(ctrl.window >= cfg.min_window);
1324 }
1325
1326 #[test]
1327 fn test_state_machine_full_cycle() {
1328 let mut ctrl = legacy_ctrl("p");
1329 assert_eq!(ctrl.state, CongestionState::SlowStart);
1330 for _ in 0..20 {
1331 ctrl.on_event(CongestionEvent::AckReceived { bytes: 100_000 });
1332 }
1333 assert_eq!(ctrl.state, CongestionState::CongestionAvoidance);
1334 ctrl.on_event(CongestionEvent::PacketLoss);
1335 assert_eq!(ctrl.state, CongestionState::FastRecovery);
1336 for _ in 0..30 {
1337 ctrl.on_event(CongestionEvent::AckReceived { bytes: 100_000 });
1338 }
1339 assert_eq!(ctrl.state, CongestionState::CongestionAvoidance);
1340 ctrl.on_event(CongestionEvent::Timeout);
1341 assert_eq!(ctrl.state, CongestionState::SlowStart);
1342 }
1343
1344 #[test]
1345 fn test_ca_aimd_formula() {
1346 let cfg = CongestionConfig {
1347 initial_window: 200_000,
1348 slow_start_threshold: 100_000,
1349 ..Default::default()
1350 };
1351 let mut ctrl = PeerCongestionController::new("p".into(), cfg);
1352 ctrl.state = CongestionState::CongestionAvoidance;
1353 let window_before = ctrl.window;
1354 let bytes: u64 = 4_000;
1355 ctrl.on_event(CongestionEvent::AckReceived { bytes });
1356 let expected_increase = (bytes * bytes) / window_before;
1357 assert_eq!(ctrl.window, window_before + expected_increase);
1358 }
1359
1360 #[test]
1365 fn test_add_connection_creates_entry() {
1366 let mut cc = make_ctrl(CccAlgorithm::Reno);
1367 cc.add_connection(1);
1368 assert!(cc.connection(1).is_some());
1369 }
1370
1371 #[test]
1372 fn test_add_connection_idempotent() {
1373 let mut cc = make_ctrl(CccAlgorithm::Reno);
1374 cc.add_connection(1);
1375 let cwnd1 = cc.connection(1).map(|c| c.cwnd);
1376 cc.add_connection(1); let cwnd2 = cc.connection(1).map(|c| c.cwnd);
1378 assert_eq!(cwnd1, cwnd2);
1379 }
1380
1381 #[test]
1382 fn test_remove_connection_returns_true() {
1383 let mut cc = make_ctrl(CccAlgorithm::Reno);
1384 cc.add_connection(10);
1385 assert!(cc.remove_connection(10));
1386 }
1387
1388 #[test]
1389 fn test_remove_connection_missing_returns_false() {
1390 let mut cc = make_ctrl(CccAlgorithm::Reno);
1391 assert!(!cc.remove_connection(99));
1392 }
1393
1394 #[test]
1395 fn test_reset_connection_restores_defaults() {
1396 let mut cc = make_ctrl(CccAlgorithm::Reno);
1397 cc.add_connection(5);
1398 cc.on_ack(5, 500_000, 10.0).expect("ack ok");
1400 cc.reset_connection(5).expect("reset ok");
1401 let conn = cc.connection(5).expect("still exists");
1402 assert_eq!(conn.cwnd, 65_536);
1403 assert_eq!(conn.state, CccState::SlowStart);
1404 }
1405
1406 #[test]
1407 fn test_reset_connection_unknown_errors() {
1408 let mut cc = make_ctrl(CccAlgorithm::Reno);
1409 assert!(cc.reset_connection(999).is_err());
1410 }
1411
1412 #[test]
1415 fn test_on_ack_unknown_errors() {
1416 let mut cc = make_ctrl(CccAlgorithm::Reno);
1417 assert!(cc.on_ack(42, 1_000, 10.0).is_err());
1418 }
1419
1420 #[test]
1421 fn test_reno_slow_start_ack_increases_cwnd() {
1422 let mut cc = make_ctrl(CccAlgorithm::Reno);
1423 cc.add_connection(1);
1424 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1425 cc.on_ack(1, 1_000, 20.0).expect("ok");
1426 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1427 assert!(after > before);
1428 }
1429
1430 #[test]
1431 fn test_reno_transitions_to_ca_after_ssthresh() {
1432 let mut cc = CongestionController::new(CccControllerConfig {
1433 algorithm: CccAlgorithm::Reno,
1434 initial_cwnd: 900_000,
1435 ssthresh: 1_000_000,
1436 ..Default::default()
1437 });
1438 cc.add_connection(1);
1439 cc.on_ack(1, 200_000, 10.0).expect("ok");
1440 let state = cc
1441 .connection(1)
1442 .map(|c| c.state)
1443 .expect("test: connection 1 should exist after add_connection");
1444 assert_eq!(state, CccState::CongestionAvoidance);
1445 }
1446
1447 #[test]
1448 fn test_reno_ca_smaller_increase_than_ss() {
1449 let mut cc = CongestionController::new(CccControllerConfig {
1450 algorithm: CccAlgorithm::Reno,
1451 initial_cwnd: 200_000,
1452 ssthresh: 100_000,
1453 ..Default::default()
1454 });
1455 cc.add_connection(1);
1456 if let Some(c) = cc.connections.get_mut(&1) {
1458 c.state = CccState::CongestionAvoidance;
1459 }
1460 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1461 cc.on_ack(1, 5_000, 10.0).expect("ok");
1462 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1463 let delta = after - before;
1465 assert!(delta < 5_000, "delta={delta}");
1466 }
1467
1468 #[test]
1469 fn test_reno_cwnd_capped_at_max() {
1470 let mut cc = CongestionController::new(CccControllerConfig {
1471 algorithm: CccAlgorithm::Reno,
1472 initial_cwnd: 16_776_000,
1473 max_cwnd: 16_777_216,
1474 ..Default::default()
1475 });
1476 cc.add_connection(1);
1477 cc.on_ack(1, 500_000, 10.0).expect("ok");
1478 let cwnd = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1479 assert!(cwnd <= 16_777_216);
1480 }
1481
1482 #[test]
1485 fn test_on_loss_unknown_errors() {
1486 let mut cc = make_ctrl(CccAlgorithm::Reno);
1487 assert!(cc.on_loss(42, 1_000).is_err());
1488 }
1489
1490 #[test]
1491 fn test_reno_on_loss_halves_cwnd() {
1492 let mut cc = make_ctrl(CccAlgorithm::Reno);
1493 cc.add_connection(1);
1494 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1495 cc.on_loss(1, 1_000).expect("ok");
1496 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1497 assert!(after <= before / 2 + 1); }
1499
1500 #[test]
1501 fn test_on_loss_enters_fast_recovery() {
1502 let mut cc = make_ctrl(CccAlgorithm::Reno);
1503 cc.add_connection(1);
1504 cc.on_loss(1, 100).expect("ok");
1505 assert_eq!(
1506 cc.connection(1).map(|c| c.state),
1507 Some(CccState::FastRecovery)
1508 );
1509 }
1510
1511 #[test]
1512 fn test_on_loss_increments_total_losses() {
1513 let mut cc = make_ctrl(CccAlgorithm::Reno);
1514 cc.add_connection(1);
1515 cc.on_loss(1, 100).expect("ok");
1516 cc.on_loss(1, 100).expect("ok");
1517 assert_eq!(cc.controller_stats().total_losses, 2);
1518 }
1519
1520 #[test]
1521 fn test_on_loss_cwnd_never_below_min() {
1522 let mut cc = CongestionController::new(CccControllerConfig {
1523 algorithm: CccAlgorithm::Reno,
1524 initial_cwnd: 1_448,
1525 min_cwnd: 1_448,
1526 ..Default::default()
1527 });
1528 cc.add_connection(1);
1529 cc.on_loss(1, 100).expect("ok");
1530 let cwnd = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1531 assert!(cwnd >= 1_448);
1532 }
1533
1534 #[test]
1537 fn test_on_timeout_unknown_errors() {
1538 let mut cc = make_ctrl(CccAlgorithm::Reno);
1539 assert!(cc.on_timeout(42).is_err());
1540 }
1541
1542 #[test]
1543 fn test_on_timeout_resets_to_slow_start() {
1544 let mut cc = make_ctrl(CccAlgorithm::Reno);
1545 cc.add_connection(1);
1546 cc.on_ack(1, 500_000, 10.0).expect("ok");
1547 cc.on_timeout(1).expect("ok");
1548 assert_eq!(cc.connection(1).map(|c| c.state), Some(CccState::SlowStart));
1549 }
1550
1551 #[test]
1552 fn test_on_timeout_resets_cwnd_to_min() {
1553 let mut cc = make_ctrl(CccAlgorithm::Reno);
1554 cc.add_connection(1);
1555 cc.on_timeout(1).expect("ok");
1556 let cwnd = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1557 assert_eq!(cwnd, 1_448); }
1559
1560 #[test]
1561 fn test_on_timeout_halves_ssthresh() {
1562 let mut cc = make_ctrl(CccAlgorithm::Reno);
1563 cc.add_connection(1);
1564 let before_ssthresh = cc.connection(1).map(|c| c.ssthresh).unwrap_or(0);
1565 let before_cwnd = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1566 cc.on_timeout(1).expect("ok");
1567 let after_ssthresh = cc.connection(1).map(|c| c.ssthresh).unwrap_or(0);
1568 assert!(after_ssthresh <= (before_cwnd / 2).max(1_448));
1569 assert!(after_ssthresh <= before_ssthresh);
1571 }
1572
1573 #[test]
1574 fn test_on_timeout_increments_total_losses() {
1575 let mut cc = make_ctrl(CccAlgorithm::Reno);
1576 cc.add_connection(1);
1577 cc.on_timeout(1).expect("ok");
1578 assert_eq!(cc.controller_stats().total_losses, 1);
1579 }
1580
1581 #[test]
1584 fn test_sending_rate_none_before_rtt() {
1585 let mut cc = make_ctrl(CccAlgorithm::Reno);
1586 cc.add_connection(1);
1587 assert_eq!(cc.sending_rate(1), None);
1588 }
1589
1590 #[test]
1591 fn test_sending_rate_some_after_ack() {
1592 let mut cc = make_ctrl(CccAlgorithm::Reno);
1593 cc.add_connection(1);
1594 cc.on_ack(1, 1_000, 20.0).expect("ok");
1595 assert!(cc.sending_rate(1).is_some());
1596 }
1597
1598 #[test]
1599 fn test_sending_rate_unknown_none() {
1600 let cc = make_ctrl(CccAlgorithm::Reno);
1601 assert_eq!(cc.sending_rate(999), None);
1602 }
1603
1604 #[test]
1605 fn test_sending_rate_positive() {
1606 let mut cc = make_ctrl(CccAlgorithm::Reno);
1607 cc.add_connection(1);
1608 cc.on_ack(1, 10_000, 100.0).expect("ok");
1609 let rate = cc.sending_rate(1).unwrap_or(0.0);
1610 assert!(rate > 0.0);
1611 }
1612
1613 #[test]
1616 fn test_stats_empty_controller() {
1617 let cc = make_ctrl(CccAlgorithm::Reno);
1618 let stats = cc.controller_stats();
1619 assert_eq!(stats.active_connections, 0);
1620 assert_eq!(stats.total_acks, 0);
1621 assert_eq!(stats.total_losses, 0);
1622 assert_eq!(stats.avg_cwnd, 0.0);
1623 assert_eq!(stats.avg_rtt, 0.0);
1624 }
1625
1626 #[test]
1627 fn test_stats_counts_connections() {
1628 let mut cc = make_ctrl(CccAlgorithm::Reno);
1629 cc.add_connection(1);
1630 cc.add_connection(2);
1631 assert_eq!(cc.controller_stats().active_connections, 2);
1632 }
1633
1634 #[test]
1635 fn test_stats_total_acks_aggregated() {
1636 let mut cc = make_ctrl(CccAlgorithm::Reno);
1637 cc.add_connection(1);
1638 cc.add_connection(2);
1639 cc.on_ack(1, 1_000, 10.0).expect("ok");
1640 cc.on_ack(2, 1_000, 10.0).expect("ok");
1641 assert_eq!(cc.controller_stats().total_acks, 2);
1642 }
1643
1644 #[test]
1645 fn test_stats_avg_cwnd_reasonable() {
1646 let mut cc = make_ctrl(CccAlgorithm::Reno);
1647 cc.add_connection(1);
1648 let stats = cc.controller_stats();
1649 assert!(stats.avg_cwnd > 0.0);
1650 }
1651
1652 #[test]
1655 fn test_events_populated_on_ack() {
1656 let mut cc = make_ctrl(CccAlgorithm::Reno);
1657 cc.add_connection(1);
1658 cc.on_ack(1, 1_000, 10.0).expect("ok");
1659 assert!(!cc.events().is_empty());
1660 }
1661
1662 #[test]
1663 fn test_events_populated_on_loss() {
1664 let mut cc = make_ctrl(CccAlgorithm::Reno);
1665 cc.add_connection(1);
1666 cc.on_loss(1, 100).expect("ok");
1667 let has_loss = cc
1668 .events()
1669 .iter()
1670 .any(|e| e.event_type == CccEventType::PacketLost);
1671 assert!(has_loss);
1672 }
1673
1674 #[test]
1675 fn test_events_bounded_at_1000() {
1676 let mut cc = make_ctrl(CccAlgorithm::Reno);
1677 cc.add_connection(1);
1678 for _ in 0..1_100u32 {
1679 cc.on_ack(1, 100, 5.0).expect("ok");
1680 }
1681 assert!(cc.events().len() <= 1_000);
1682 }
1683
1684 #[test]
1685 fn test_events_timeout_type() {
1686 let mut cc = make_ctrl(CccAlgorithm::Reno);
1687 cc.add_connection(1);
1688 cc.on_timeout(1).expect("ok");
1689 let has_timeout = cc
1690 .events()
1691 .iter()
1692 .any(|e| e.event_type == CccEventType::Timeout);
1693 assert!(has_timeout);
1694 }
1695
1696 #[test]
1697 fn test_events_record_cwnd_change() {
1698 let mut cc = make_ctrl(CccAlgorithm::Reno);
1699 cc.add_connection(1);
1700 cc.on_ack(1, 10_000, 10.0).expect("ok");
1701 let event = cc.events().iter().last().expect("event");
1702 assert!(event.cwnd_after > 0);
1704 }
1705
1706 #[test]
1709 fn test_cubic_slow_start_grows() {
1710 let mut cc = make_ctrl(CccAlgorithm::Cubic);
1711 cc.add_connection(1);
1712 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1713 cc.on_ack(1, 10_000, 20.0).expect("ok");
1714 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1715 assert!(after > before);
1716 }
1717
1718 #[test]
1719 fn test_cubic_loss_reduces_cwnd() {
1720 let mut cc = make_ctrl(CccAlgorithm::Cubic);
1721 cc.add_connection(1);
1722 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1723 cc.on_loss(1, 1_000).expect("ok");
1724 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1725 assert!(after <= before);
1726 }
1727
1728 #[test]
1729 fn test_cubic_timeout_resets_to_slowstart() {
1730 let mut cc = make_ctrl(CccAlgorithm::Cubic);
1731 cc.add_connection(1);
1732 cc.on_timeout(1).expect("ok");
1733 assert_eq!(cc.connection(1).map(|c| c.state), Some(CccState::SlowStart));
1734 }
1735
1736 #[test]
1739 fn test_bbr_grows_cwnd() {
1740 let mut cc = make_ctrl(CccAlgorithm::Bbr);
1741 cc.add_connection(1);
1742 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1743 cc.on_ack(1, 10_000, 20.0).expect("ok");
1744 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1745 assert!(after >= before);
1746 }
1747
1748 #[test]
1749 fn test_bbr_loss_mild_reduction() {
1750 let mut cc = make_ctrl(CccAlgorithm::Bbr);
1751 cc.add_connection(1);
1752 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1753 cc.on_loss(1, 1_000).expect("ok");
1754 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1755 assert!(after > 0 && after <= before);
1757 }
1758
1759 #[test]
1760 fn test_bbr_has_sending_rate_after_ack() {
1761 let mut cc = make_ctrl(CccAlgorithm::Bbr);
1762 cc.add_connection(1);
1763 cc.on_ack(1, 50_000, 15.0).expect("ok");
1764 assert!(cc.sending_rate(1).is_some());
1765 }
1766
1767 #[test]
1770 fn test_vegas_grows_cwnd_without_rtt() {
1771 let mut cc = make_ctrl(CccAlgorithm::Vegas);
1772 cc.add_connection(1);
1773 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1774 cc.on_ack(1, 1_448, 0.0).expect("ok");
1775 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1776 assert!(after >= before);
1777 }
1778
1779 #[test]
1780 fn test_vegas_loss_reduces_cwnd() {
1781 let mut cc = make_ctrl(CccAlgorithm::Vegas);
1782 cc.add_connection(1);
1783 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1784 cc.on_loss(1, 1_000).expect("ok");
1785 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1786 assert!(after <= before);
1787 }
1788
1789 #[test]
1792 fn test_westwood_slow_start_grows() {
1793 let mut cc = make_ctrl(CccAlgorithm::Westwood);
1794 cc.add_connection(1);
1795 let before = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1796 cc.on_ack(1, 5_000, 10.0).expect("ok");
1797 let after = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1798 assert!(after > before);
1799 }
1800
1801 #[test]
1802 fn test_westwood_loss_uses_bw_estimate() {
1803 let mut cc = make_ctrl(CccAlgorithm::Westwood);
1804 cc.add_connection(1);
1805 cc.on_ack(1, 50_000, 20.0).expect("ok");
1807 cc.on_loss(1, 1_000).expect("ok");
1808 let ssthresh = cc.connection(1).map(|c| c.ssthresh).unwrap_or(0);
1810 assert!(ssthresh >= 1_448);
1811 }
1812
1813 #[test]
1816 fn test_xorshift64_produces_nonzero() {
1817 let mut state: u64 = 12345;
1818 let v = xorshift64(&mut state);
1819 assert_ne!(v, 0);
1820 }
1821
1822 #[test]
1823 fn test_xorshift64_changes_state() {
1824 let mut state: u64 = 9999;
1825 let v1 = xorshift64(&mut state);
1826 let v2 = xorshift64(&mut state);
1827 assert_ne!(v1, v2);
1828 }
1829
1830 #[test]
1831 fn test_xorshift64_deterministic() {
1832 let mut s1: u64 = 42;
1833 let mut s2: u64 = 42;
1834 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
1835 }
1836
1837 #[test]
1840 fn test_decision_new_cwnd_matches_connection() {
1841 let mut cc = make_ctrl(CccAlgorithm::Reno);
1842 cc.add_connection(1);
1843 let d = cc.on_ack(1, 1_000, 10.0).expect("ok");
1844 let actual = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1845 assert_eq!(d.new_cwnd, actual);
1846 }
1847
1848 #[test]
1849 fn test_decision_state_matches_connection() {
1850 let mut cc = make_ctrl(CccAlgorithm::Reno);
1851 cc.add_connection(1);
1852 let d = cc.on_loss(1, 100).expect("ok");
1853 assert_eq!(d.new_state, CccState::FastRecovery);
1854 }
1855
1856 #[test]
1857 fn test_decision_sending_rate_consistency() {
1858 let mut cc = make_ctrl(CccAlgorithm::Reno);
1859 cc.add_connection(1);
1860 let d = cc.on_ack(1, 1_000, 50.0).expect("ok");
1861 assert_eq!(d.sending_rate, cc.sending_rate(1));
1863 }
1864
1865 #[test]
1868 fn test_rtt_updated_on_ack() {
1869 let mut cc = make_ctrl(CccAlgorithm::Reno);
1870 cc.add_connection(1);
1871 cc.on_ack(1, 1_000, 30.0).expect("ok");
1872 let rtt = cc.connection(1).map(|c| c.rtt_ms).unwrap_or(0.0);
1873 assert!(rtt > 0.0);
1874 }
1875
1876 #[test]
1877 fn test_rtt_ewma_converges() {
1878 let mut cc = make_ctrl(CccAlgorithm::Reno);
1879 cc.add_connection(1);
1880 for _ in 0..20 {
1881 cc.on_ack(1, 1_000, 100.0).expect("ok");
1882 }
1883 let rtt = cc.connection(1).map(|c| c.rtt_ms).unwrap_or(0.0);
1884 assert!((rtt - 100.0).abs() < 20.0, "rtt={rtt}");
1886 }
1887
1888 #[test]
1891 fn test_bytes_acked_accumulates() {
1892 let mut cc = make_ctrl(CccAlgorithm::Reno);
1893 cc.add_connection(1);
1894 cc.on_ack(1, 5_000, 10.0).expect("ok");
1895 cc.on_ack(1, 3_000, 10.0).expect("ok");
1896 let ba = cc.connection(1).map(|c| c.bytes_acked).unwrap_or(0);
1897 assert_eq!(ba, 8_000);
1898 }
1899
1900 #[test]
1901 fn test_bytes_lost_accumulates() {
1902 let mut cc = make_ctrl(CccAlgorithm::Reno);
1903 cc.add_connection(1);
1904 cc.on_loss(1, 1_000).expect("ok");
1905 cc.on_loss(1, 500).expect("ok");
1906 let bl = cc.connection(1).map(|c| c.bytes_lost).unwrap_or(0);
1907 assert_eq!(bl, 1_500);
1908 }
1909
1910 #[test]
1913 fn test_cwnd_never_below_min_after_many_losses() {
1914 let mut cc = make_ctrl(CccAlgorithm::Reno);
1915 cc.add_connection(1);
1916 for _ in 0..50 {
1917 cc.on_loss(1, 100_000).expect("ok");
1918 }
1919 let cwnd = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1920 assert!(cwnd >= 1_448);
1921 }
1922
1923 #[test]
1926 fn test_cwnd_never_above_max_after_many_acks() {
1927 let mut cc = make_ctrl(CccAlgorithm::Reno);
1928 cc.add_connection(1);
1929 for _ in 0..5_000u32 {
1930 cc.on_ack(1, 100_000, 5.0).expect("ok");
1931 }
1932 let cwnd = cc.connection(1).map(|c| c.cwnd).unwrap_or(0);
1933 assert!(cwnd <= 16_777_216);
1934 }
1935
1936 #[test]
1939 fn test_connections_isolated() {
1940 let mut cc = make_ctrl(CccAlgorithm::Reno);
1941 cc.add_connection(1);
1942 cc.add_connection(2);
1943 let before_2 = cc.connection(2).map(|c| c.cwnd).unwrap_or(0);
1944 cc.on_loss(1, 10_000).expect("ok");
1945 let after_2 = cc.connection(2).map(|c| c.cwnd).unwrap_or(0);
1946 assert_eq!(before_2, after_2);
1947 }
1948
1949 #[test]
1952 fn test_algorithm_default_is_reno() {
1953 assert_eq!(CccAlgorithm::default(), CccAlgorithm::Reno);
1954 }
1955
1956 #[test]
1959 fn test_state_default_is_ca() {
1960 assert_eq!(CccState::default(), CccState::CongestionAvoidance);
1961 }
1962
1963 #[test]
1966 fn test_type_aliases_usable() {
1967 let mut cc: CccCongestionController = CccCongestionController::with_defaults();
1968 cc.add_connection(1);
1969 let d: CccDecision = cc.on_ack(1, 1_000, 10.0).expect("ok");
1970 assert!(d.new_cwnd > 0);
1971 }
1972}