1use std::collections::VecDeque;
40use std::time::{Duration, Instant};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BbrState {
45 Startup,
47 ProbeBW,
49 Drain,
51 ProbeRTT,
53 FastRecovery,
55}
56
57#[derive(Debug, Clone, Copy)]
59pub struct DeliverySample {
60 pub delivered_bytes: u64,
62 pub sent_at: Instant,
64 pub acked_at: Instant,
66 pub packet_bytes: u64,
68 pub is_app_limited: bool,
70 pub ack_delay_us: u64,
74}
75
76#[derive(Debug)]
78struct WindowFilter {
79 window: VecDeque<(Instant, u64)>,
80 window_size: Duration,
81}
82
83impl WindowFilter {
84 fn new(window_size: Duration) -> Self {
85 Self {
86 window: VecDeque::new(),
87 window_size,
88 }
89 }
90
91 fn update_max(&mut self, now: Instant, value: u64) -> u64 {
92 while let Some(&(ts, _)) = self.window.front() {
94 if now.duration_since(ts) > self.window_size {
95 self.window.pop_front();
96 } else {
97 break;
98 }
99 }
100 while let Some(&(_, v)) = self.window.back() {
102 if v <= value {
103 self.window.pop_back();
104 } else {
105 break;
106 }
107 }
108 self.window.push_back((now, value));
109 self.window.front().map(|&(_, v)| v).unwrap_or(value)
111 }
112
113 fn update_min(&mut self, now: Instant, value: u64) -> u64 {
114 while let Some(&(ts, _)) = self.window.front() {
115 if now.duration_since(ts) > self.window_size {
116 self.window.pop_front();
117 } else {
118 break;
119 }
120 }
121 while let Some(&(_, v)) = self.window.back() {
122 if v >= value {
123 self.window.pop_back();
124 } else {
125 break;
126 }
127 }
128 self.window.push_back((now, value));
129 self.window.front().map(|&(_, v)| v).unwrap_or(value)
130 }
131}
132
133const PROBE_BW_GAINS: [f64; 4] = [1.25, 0.75, 1.0, 1.0];
137
138const STARTUP_GROWTH_THRESHOLD: f64 = 0.25;
140
141const STARTUP_ROUNDS_LIMIT: u32 = 3;
143
144const PROBE_RTT_INTERVAL: Duration = Duration::from_secs(10);
146
147const PROBE_RTT_DURATION: Duration = Duration::from_millis(200);
149
150const PROBE_RTT_CWND_PACKETS: u64 = 4;
152
153const MIN_PACKET_SIZE: u64 = 1400;
155
156const FAST_RECOVERY_PACING_GAIN: f64 = 0.5;
158
159const FAST_RECOVERY_EXIT_FRACTION: f64 = 1.0;
161
162pub struct BandwidthEstimator {
166 state: BbrState,
168 btl_bw: u64,
170 min_rtt: Duration,
172 bw_filter: WindowFilter,
174 rtt_filter: WindowFilter,
176 delivered_bytes: u64,
178 last_delivery: Instant,
180 pacing_gain: f64,
182 cwnd_gain: f64,
184 round_count: u32,
186 filled_pipe: bool,
188 prev_bw: u64,
190 rounds_without_growth: u32,
192
193 inflight_bytes: u64,
196
197 last_probe_rtt_time: Instant,
200 probe_rtt_entered: Option<Instant>,
202 prior_state: BbrState,
204
205 app_limited: bool,
208 app_limited_at_delivered: u64,
210
211 fast_recovery_entered: Option<Instant>,
214 recovery_lost_bytes: u64,
216}
217
218impl BandwidthEstimator {
219 pub fn new() -> Self {
221 let now = Instant::now();
222 Self {
223 state: BbrState::Startup,
224 btl_bw: 0,
225 min_rtt: Duration::from_millis(100), bw_filter: WindowFilter::new(Duration::from_secs(10)),
227 rtt_filter: WindowFilter::new(Duration::from_secs(10)),
228 delivered_bytes: 0,
229 last_delivery: now,
230 pacing_gain: 2.0, cwnd_gain: 2.0,
232 round_count: 0,
233 filled_pipe: false,
234 prev_bw: 0,
235 rounds_without_growth: 0,
236 inflight_bytes: 0,
237 last_probe_rtt_time: now,
238 probe_rtt_entered: None,
239 prior_state: BbrState::ProbeBW,
240 app_limited: false,
241 app_limited_at_delivered: 0,
242 fast_recovery_entered: None,
243 recovery_lost_bytes: 0,
244 }
245 }
246
247 pub fn on_send(&mut self, bytes: u64) {
251 self.inflight_bytes = self.inflight_bytes.saturating_add(bytes);
252 }
253
254 pub fn on_ack(&mut self, sample: DeliverySample) -> u64 {
258 let now = sample.acked_at;
259
260 self.inflight_bytes = self.inflight_bytes.saturating_sub(sample.packet_bytes);
262
263 self.delivered_bytes += sample.packet_bytes;
265 self.last_delivery = now;
266
267 let send_elapsed = sample.acked_at.duration_since(sample.sent_at);
271 let ack_delay = Duration::from_micros(sample.ack_delay_us);
272 let rtt_propagation = send_elapsed.saturating_sub(ack_delay);
273
274 let rtt_us = rtt_propagation.as_micros() as u64;
276 if rtt_us > 0 {
277 let min_rtt_us = self.rtt_filter.update_min(now, rtt_us);
278 self.min_rtt = Duration::from_micros(min_rtt_us);
279 }
280
281 let delivery_rate = if !send_elapsed.is_zero() {
283 (sample.packet_bytes as f64 / send_elapsed.as_secs_f64()) as u64
284 } else {
285 0
286 };
287
288 if delivery_rate > 0 && !sample.is_app_limited {
292 self.btl_bw = self.bw_filter.update_max(now, delivery_rate);
293 }
294
295 if self.app_limited && self.delivered_bytes > self.app_limited_at_delivered {
297 self.app_limited = false;
298 }
299
300 self.update_state(now);
302
303 self.pacing_rate()
305 }
306
307 pub fn on_loss(&mut self, bytes: u64) {
312 self.inflight_bytes = self.inflight_bytes.saturating_sub(bytes);
313 self.recovery_lost_bytes = self.recovery_lost_bytes.saturating_add(bytes);
314
315 if self.state != BbrState::FastRecovery && self.state != BbrState::ProbeRTT {
317 self.prior_state = self.state;
318 self.fast_recovery_entered = Some(Instant::now());
319 self.transition_to(BbrState::FastRecovery);
320 }
321 }
322
323 pub fn set_app_limited(&mut self) {
329 self.app_limited = true;
330 self.app_limited_at_delivered = self.delivered_bytes;
331 }
332
333 pub fn is_app_limited(&self) -> bool {
335 self.app_limited
336 }
337
338 pub fn pacing_rate(&self) -> u64 {
340 let base = self.btl_bw.max(1);
341 (base as f64 * self.pacing_gain) as u64
342 }
343
344 pub fn cwnd(&self) -> u64 {
346 if self.state == BbrState::ProbeRTT {
347 return PROBE_RTT_CWND_PACKETS * MIN_PACKET_SIZE;
349 }
350 let bdp = self.bdp();
351 (bdp as f64 * self.cwnd_gain).max((PROBE_RTT_CWND_PACKETS * MIN_PACKET_SIZE) as f64) as u64
352 }
353
354 pub fn bdp(&self) -> u64 {
356 (self.btl_bw as f64 * self.min_rtt.as_secs_f64()) as u64
357 }
358
359 pub fn inflight_bytes(&self) -> u64 {
361 self.inflight_bytes
362 }
363
364 pub fn bottleneck_bandwidth(&self) -> u64 {
366 self.btl_bw
367 }
368
369 pub fn min_rtt(&self) -> Duration {
371 self.min_rtt
372 }
373
374 pub fn state(&self) -> BbrState {
376 self.state
377 }
378
379 pub fn delivered_bytes(&self) -> u64 {
381 self.delivered_bytes
382 }
383
384 pub fn round_count(&self) -> u32 {
386 self.round_count
387 }
388
389 fn update_state(&mut self, now: Instant) {
393 if self.state != BbrState::ProbeRTT
395 && self.state != BbrState::Startup
396 && self.state != BbrState::FastRecovery
397 && now.duration_since(self.last_probe_rtt_time) >= PROBE_RTT_INTERVAL
398 {
399 self.prior_state = self.state;
400 self.transition_to(BbrState::ProbeRTT);
401 self.probe_rtt_entered = Some(now);
402 return;
403 }
404
405 match self.state {
406 BbrState::Startup => {
407 self.round_count += 1;
408
409 if self.prev_bw > 0 {
411 let growth = (self.btl_bw as f64 - self.prev_bw as f64) / self.prev_bw as f64;
412
413 if growth < STARTUP_GROWTH_THRESHOLD {
414 self.rounds_without_growth += 1;
415 } else {
416 self.rounds_without_growth = 0;
417 }
418
419 if self.rounds_without_growth >= STARTUP_ROUNDS_LIMIT {
420 self.filled_pipe = true;
421 self.transition_to(BbrState::Drain);
422 }
423 }
424 self.prev_bw = self.btl_bw;
425 }
426 BbrState::Drain => {
427 let bdp = self.bdp();
429 if self.inflight_bytes <= bdp || bdp == 0 {
430 self.transition_to(BbrState::ProbeBW);
431 }
432 }
433 BbrState::ProbeBW => {
434 let cycle_idx = (self.round_count as usize) % PROBE_BW_GAINS.len();
436 self.pacing_gain = PROBE_BW_GAINS[cycle_idx];
437 self.cwnd_gain = 2.0;
438 self.round_count += 1;
439 }
440 BbrState::ProbeRTT => {
441 if let Some(entered) = self.probe_rtt_entered {
443 if now.duration_since(entered) >= PROBE_RTT_DURATION {
444 self.last_probe_rtt_time = now;
445 self.probe_rtt_entered = None;
446 self.transition_to(self.prior_state);
447 }
448 } else {
449 self.transition_to(BbrState::ProbeBW);
451 }
452 }
453 BbrState::FastRecovery => {
454 let bdp = self.bdp();
459 let should_exit = self.inflight_bytes
460 <= (bdp as f64 * FAST_RECOVERY_EXIT_FRACTION) as u64
461 || bdp == 0;
462
463 if should_exit {
464 self.recovery_lost_bytes = 0;
465 self.fast_recovery_entered = None;
466 self.transition_to(self.prior_state);
467 }
468 }
469 }
470 }
471
472 fn transition_to(&mut self, new_state: BbrState) {
474 match new_state {
475 BbrState::Startup => {
476 self.pacing_gain = 2.0;
477 self.cwnd_gain = 2.0;
478 }
479 BbrState::Drain => {
480 self.pacing_gain = 0.75;
481 self.cwnd_gain = 2.0;
482 }
483 BbrState::ProbeBW => {
484 self.pacing_gain = 1.0;
485 self.cwnd_gain = 2.0;
486 }
487 BbrState::ProbeRTT => {
488 self.pacing_gain = 1.0;
489 self.cwnd_gain = 1.0;
490 }
491 BbrState::FastRecovery => {
492 self.pacing_gain = FAST_RECOVERY_PACING_GAIN;
495 self.cwnd_gain = 1.0;
496 }
497 }
498 self.state = new_state;
499 }
500}
501
502impl Default for BandwidthEstimator {
503 fn default() -> Self {
504 Self::new()
505 }
506}
507
508impl std::fmt::Debug for BandwidthEstimator {
509 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510 f.debug_struct("BandwidthEstimator")
511 .field("state", &self.state)
512 .field("btl_bw_kbps", &(self.btl_bw / 1024))
513 .field("min_rtt_ms", &self.min_rtt.as_millis())
514 .field("pacing_gain", &self.pacing_gain)
515 .field("inflight_bytes", &self.inflight_bytes)
516 .field("delivered_bytes", &self.delivered_bytes)
517 .field("app_limited", &self.app_limited)
518 .finish()
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 fn make_sample(sent_at: Instant, rtt_ms: u64, packet_bytes: u64) -> DeliverySample {
527 DeliverySample {
528 delivered_bytes: 0,
529 sent_at,
530 acked_at: sent_at + Duration::from_millis(rtt_ms),
531 packet_bytes,
532 is_app_limited: false,
533 ack_delay_us: 0, }
535 }
536
537 fn make_app_limited_sample(sent_at: Instant, rtt_ms: u64, packet_bytes: u64) -> DeliverySample {
538 DeliverySample {
539 delivered_bytes: 0,
540 sent_at,
541 acked_at: sent_at + Duration::from_millis(rtt_ms),
542 packet_bytes,
543 is_app_limited: true,
544 ack_delay_us: 0,
545 }
546 }
547
548 #[test]
549 fn test_estimator_starts_in_startup() {
550 let est = BandwidthEstimator::new();
551 assert_eq!(est.state(), BbrState::Startup);
552 assert_eq!(est.delivered_bytes(), 0);
553 assert_eq!(est.inflight_bytes(), 0);
554 assert!(!est.is_app_limited());
555 }
556
557 #[test]
558 fn test_bandwidth_increases_with_acks() {
559 let mut est = BandwidthEstimator::new();
560 let now = Instant::now();
561
562 for i in 0..10 {
564 let sent = now + Duration::from_millis(i * 10);
565 est.on_send(1400);
566 let sample = make_sample(sent, 10, 1400);
567 est.on_ack(sample);
568 }
569
570 assert!(
572 est.bottleneck_bandwidth() > 0,
573 "btl_bw = {} should be > 0",
574 est.bottleneck_bandwidth()
575 );
576 assert_eq!(est.delivered_bytes(), 14_000);
577 }
578
579 #[test]
580 fn test_min_rtt_tracking() {
581 let mut est = BandwidthEstimator::new();
582 let now = Instant::now();
583
584 let s1 = make_sample(now, 100, 1400);
586 est.on_ack(s1);
587 assert!(est.min_rtt() <= Duration::from_millis(101));
588
589 let s2 = make_sample(now + Duration::from_millis(200), 5, 1400);
590 est.on_ack(s2);
591 assert!(
592 est.min_rtt() <= Duration::from_millis(6),
593 "min_rtt = {:?}",
594 est.min_rtt()
595 );
596 }
597
598 #[test]
599 fn test_pacing_rate_positive() {
600 let mut est = BandwidthEstimator::new();
601 let now = Instant::now();
602
603 let sample = make_sample(now, 20, 1400);
604 est.on_ack(sample);
605
606 assert!(est.pacing_rate() > 0);
608 }
609
610 #[test]
611 fn test_cwnd_at_least_minimum() {
612 let est = BandwidthEstimator::new();
613 let cwnd = est.cwnd();
615 assert!(
616 cwnd >= 4 * 1400,
617 "cwnd = {} should be >= {}",
618 cwnd,
619 4 * 1400
620 );
621 }
622
623 #[test]
624 fn test_startup_to_drain_transition() {
625 let mut est = BandwidthEstimator::new();
626 let now = Instant::now();
627
628 for i in 0..20 {
630 let sent = now + Duration::from_millis(i * 10);
631 est.on_send(1400);
632 let sample = make_sample(sent, 10, 1400);
633 est.on_ack(sample);
634 }
635
636 assert!(
638 est.state() != BbrState::Startup || est.round_count < 20,
639 "expected startup exit, state = {:?}, rounds = {}",
640 est.state(),
641 est.round_count
642 );
643 }
644
645 #[test]
648 fn test_inflight_tracking() {
649 let mut est = BandwidthEstimator::new();
650
651 est.on_send(1400);
653 est.on_send(1400);
654 est.on_send(1400);
655 assert_eq!(est.inflight_bytes(), 4200);
656
657 let now = Instant::now();
659 est.on_ack(make_sample(now, 10, 1400));
660 assert_eq!(est.inflight_bytes(), 2800);
661
662 est.on_loss(1400);
664 assert_eq!(est.inflight_bytes(), 1400);
665
666 est.on_ack(make_sample(now + Duration::from_millis(10), 10, 1400));
668 assert_eq!(est.inflight_bytes(), 0);
669 }
670
671 #[test]
672 fn test_inflight_cant_go_negative() {
673 let mut est = BandwidthEstimator::new();
674 est.on_loss(5000);
675 assert_eq!(est.inflight_bytes(), 0); }
677
678 #[test]
679 fn test_app_limited_filtering() {
680 let mut est = BandwidthEstimator::new();
681 let now = Instant::now();
682
683 for i in 0..5 {
685 let sent = now + Duration::from_millis(i * 10);
686 est.on_send(1400);
687 est.on_ack(make_sample(sent, 10, 1400));
688 }
689 let real_bw = est.bottleneck_bandwidth();
690 assert!(real_bw > 0);
691
692 est.set_app_limited();
695 assert!(est.is_app_limited());
696
697 for i in 5..10 {
698 let sent = now + Duration::from_millis(i * 1000);
699 est.on_ack(make_app_limited_sample(sent, 1000, 100)); }
701
702 assert!(
704 est.bottleneck_bandwidth() >= real_bw,
705 "BW should not decrease from app-limited samples: {} < {}",
706 est.bottleneck_bandwidth(),
707 real_bw
708 );
709 }
710
711 #[test]
712 fn test_drain_waits_for_bdp() {
713 let mut est = BandwidthEstimator::new();
714 let now = Instant::now();
715
716 for i in 0..20 {
718 let sent = now + Duration::from_millis(i * 10);
719 est.on_send(1400);
720 est.on_ack(make_sample(sent, 10, 1400));
721 }
722
723 if est.state() == BbrState::Drain {
725 est.inflight_bytes = est.bdp() * 3;
727 let sent = now + Duration::from_millis(300);
728 est.on_ack(make_sample(sent, 10, 1400));
729 if est.inflight_bytes > est.bdp() {
731 assert_eq!(
732 est.state(),
733 BbrState::Drain,
734 "should stay in Drain while inflight ({}) > BDP ({})",
735 est.inflight_bytes,
736 est.bdp()
737 );
738 }
739 }
740 }
741
742 #[test]
743 fn test_bdp_calculation() {
744 let mut est = BandwidthEstimator::new();
745 let now = Instant::now();
746
747 for i in 0..5 {
749 let sent = now + Duration::from_millis(i * 10);
750 est.on_send(1400);
751 est.on_ack(make_sample(sent, 10, 1400));
752 }
753
754 let bdp = est.bdp();
755 assert!(bdp > 0, "BDP should be positive, got {}", bdp);
759 }
760
761 #[test]
762 fn test_cwnd_minimum_in_probe_rtt() {
763 let mut est = BandwidthEstimator::new();
764 est.state = BbrState::ProbeRTT;
766 let cwnd = est.cwnd();
767 assert_eq!(
768 cwnd,
769 PROBE_RTT_CWND_PACKETS * MIN_PACKET_SIZE,
770 "ProbeRTT CWND should be {} (4 packets), got {}",
771 PROBE_RTT_CWND_PACKETS * MIN_PACKET_SIZE,
772 cwnd
773 );
774 }
775}