Skip to content
File

Blob: firmware/vendor/str0m/src/bwe/link_capacity_estimator.rs

rust182 lines
1use std::time::{Duration, Instant};
2 
3use crate::rtp_::Bitrate;
4 
5/// Estimates link capacity based on successful probe results during ALR.
6///
7/// This tracks the link's proven capacity from probes sent when the application
8/// is in Application Limited Region (ALR), meaning the app is sending less than
9/// network capacity. During ALR, probes can discover the true available bandwidth
10/// without being constrained by application send rate.
11///
12/// The estimator only accepts probe results obtained while in ALR, as these
13/// represent genuine capacity measurements. Probes during non-ALR periods
14/// may be artificially limited by application sending patterns.
15///
16/// Capacity estimates decay over time (default 60s) since network conditions
17/// can change, and old measurements become less reliable.
18#[derive(Default)]
19pub struct LinkCapacityEstimator {
20 /// Current estimate of link capacity, if available
21 capacity_estimate: Option<Bitrate>,
22 
23 /// Time when the capacity estimate was last updated
24 last_estimate_time: Option<Instant>,
25}
26 
27impl LinkCapacityEstimator {
28 /// Default duration before capacity estimate resets (60 seconds)
29 const DEFAULT_RESET_WINDOW: Duration = Duration::from_secs(60);
30 
31 /// Create a new LinkCapacityEstimator with default settings
32 pub fn new() -> Self {
33 Self::default()
34 }
35 
36 /// Update the capacity estimate from a successful probe result.
37 ///
38 /// This should only be called for probes that were sent during ALR.
39 /// The estimate is stored along with the current time for decay tracking.
40 pub fn update_from_probe(&mut self, probe_estimate: Bitrate, now: Instant) {
41 // Only accept valid probe estimates
42 if !probe_estimate.is_valid() {
43 return;
44 }
45 
46 // Update or set the capacity estimate
47 // If we already have an estimate, take the max (capacity shouldn't decrease
48 // from successful probes, only from decay/timeout)
49 let current = self.capacity_estimate.get_or_insert(probe_estimate);
50 *current = (*current).max(probe_estimate);
51 
52 self.last_estimate_time = Some(now);
53 
54 trace!(
55 "Link capacity estimate updated to {} from probe",
56 probe_estimate
57 );
58 }
59 
60 /// Get the current capacity estimate, if available and not expired.
61 pub fn capacity_estimate(&self, now: Instant) -> Option<Bitrate> {
62 let estimate = self.capacity_estimate?;
63 let last_time = self.last_estimate_time?;
64 
65 // Check if estimate has expired (defensive against clock skew)
66 if now.saturating_duration_since(last_time) > Self::DEFAULT_RESET_WINDOW {
67 trace!("Link capacity estimate expired");
68 return None;
69 }
70 
71 Some(estimate)
72 }
73 
74 /// Reset the capacity estimate.
75 #[cfg(test)]
76 pub fn reset(&mut self) {
77 if self.capacity_estimate.is_some() {
78 trace!("Link capacity estimate reset");
79 }
80 self.capacity_estimate = None;
81 self.last_estimate_time = None;
82 }
83 
84 /// Check if we currently have a valid capacity estimate
85 #[cfg(test)]
86 pub fn has_estimate(&self) -> bool {
87 self.capacity_estimate.is_some() && self.last_estimate_time.is_some()
88 }
89}
90 
91#[cfg(test)]
92mod tests {
93 use super::*;
94 
95 #[test]
96 fn starts_with_no_estimate() {
97 let estimator = LinkCapacityEstimator::new();
98 let now = Instant::now();
99 
100 assert_eq!(estimator.capacity_estimate(now), None);
101 assert!(!estimator.has_estimate());
102 }
103 
104 #[test]
105 fn stores_probe_result() {
106 let mut estimator = LinkCapacityEstimator::new();
107 let now = Instant::now();
108 let probe_result = Bitrate::mbps(10);
109 
110 estimator.update_from_probe(probe_result, now);
111 
112 assert_eq!(estimator.capacity_estimate(now), Some(probe_result));
113 assert!(estimator.has_estimate());
114 }
115 
116 #[test]
117 fn takes_maximum_of_multiple_probes() {
118 let mut estimator = LinkCapacityEstimator::new();
119 let now = Instant::now();
120 
121 estimator.update_from_probe(Bitrate::mbps(10), now);
122 estimator.update_from_probe(Bitrate::mbps(5), now);
123 
124 // Should keep the higher estimate
125 assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(10)));
126 
127 estimator.update_from_probe(Bitrate::mbps(15), now);
128 
129 // Should update to higher estimate
130 assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(15)));
131 }
132 
133 #[test]
134 fn estimate_expires_after_reset_window() {
135 let mut estimator = LinkCapacityEstimator::new();
136 let now = Instant::now();
137 
138 estimator.update_from_probe(Bitrate::mbps(10), now);
139 assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(10)));
140 
141 // Check just before expiration
142 let almost_expired = now + Duration::from_secs(59);
143 assert_eq!(
144 estimator.capacity_estimate(almost_expired),
145 Some(Bitrate::mbps(10))
146 );
147 
148 // Check after expiration
149 let expired = now + Duration::from_secs(61);
150 assert_eq!(estimator.capacity_estimate(expired), None);
151 }
152 
153 #[test]
154 fn reset_clears_estimate() {
155 let mut estimator = LinkCapacityEstimator::new();
156 let now = Instant::now();
157 
158 estimator.update_from_probe(Bitrate::mbps(10), now);
159 assert!(estimator.has_estimate());
160 
161 estimator.reset();
162 
163 assert!(!estimator.has_estimate());
164 assert_eq!(estimator.capacity_estimate(now), None);
165 }
166 
167 #[test]
168 fn ignores_invalid_probes() {
169 let mut estimator = LinkCapacityEstimator::new();
170 let now = Instant::now();
171 
172 // Set a valid estimate first
173 estimator.update_from_probe(Bitrate::mbps(10), now);
174 
175 // Try to update with invalid bitrate
176 estimator.update_from_probe(Bitrate::NEG_INFINITY, now);
177 
178 // Should keep the valid estimate
179 assert_eq!(estimator.capacity_estimate(now), Some(Bitrate::mbps(10)));
180 }
181}