Skip to main content

bitfold_peer/
bandwidth_throttle.rs

1//! Bandwidth throttling and utilization tracking.
2//!
3//! This module provides functionality for monitoring and limiting network bandwidth usage.
4//! It tracks bytes sent and received within sliding time windows and provides methods to
5//! check whether operations can proceed within configured bandwidth limits.
6//!
7//! # Bandwidth Limiting
8//!
9//! Bandwidth limits are enforced per connection on a per-second basis:
10//! - **Outgoing bandwidth**: Limits the number of bytes sent per second
11//! - **Incoming bandwidth**: Limits the number of bytes received per second
12//!
13//! A limit of `0` means unlimited bandwidth.
14//!
15//! # Utilization Tracking
16//!
17//! The module calculates bandwidth utilization as a ratio of bytes transferred to the
18//! configured limit. A utilization of:
19//! - `0.0` = No bandwidth used (or unlimited)
20//! - `0.5` = 50% of limit used
21//! - `1.0` = At the limit
22//! - `>1.0` = Over the limit (throttling should occur)
23
24use std::time::{Duration, Instant};
25
26/// Duration of the bandwidth tracking window (1 second).
27const BANDWIDTH_WINDOW: Duration = Duration::from_secs(1);
28
29/// Tracks bandwidth usage and enforces bandwidth limits.
30///
31/// This structure maintains a sliding window of bandwidth consumption,
32/// resetting counters every second to provide accurate per-second limiting.
33pub struct BandwidthThrottle {
34    /// Bytes sent in the current bandwidth window
35    bytes_sent_this_window: u32,
36    /// Bytes received in the current bandwidth window
37    bytes_received_this_window: u32,
38    /// Start time of the current bandwidth tracking window
39    bandwidth_window_start: Instant,
40    /// Outgoing bandwidth limit in bytes per second (0 = unlimited)
41    outgoing_bandwidth_limit: u32,
42    /// Incoming bandwidth limit in bytes per second (0 = unlimited)
43    incoming_bandwidth_limit: u32,
44}
45
46impl BandwidthThrottle {
47    /// Creates a new bandwidth throttle with the specified limits.
48    ///
49    /// # Arguments
50    ///
51    /// * `outgoing_limit` - Maximum bytes per second for outgoing traffic (0 = unlimited)
52    /// * `incoming_limit` - Maximum bytes per second for incoming traffic (0 = unlimited)
53    /// * `start_time` - Initial timestamp for the bandwidth window
54    pub fn new(outgoing_limit: u32, incoming_limit: u32, start_time: Instant) -> Self {
55        Self {
56            bytes_sent_this_window: 0,
57            bytes_received_this_window: 0,
58            bandwidth_window_start: start_time,
59            outgoing_bandwidth_limit: outgoing_limit,
60            incoming_bandwidth_limit: incoming_limit,
61        }
62    }
63
64    /// Updates bandwidth tracking window, resetting counters if the window has expired.
65    ///
66    /// This should be called periodically (e.g., before each send/receive operation)
67    /// to ensure accurate bandwidth tracking.
68    ///
69    /// # Returns
70    ///
71    /// Returns `true` if the window was reset, `false` otherwise.
72    pub fn update_bandwidth_window(&mut self, time: Instant) -> bool {
73        if time.duration_since(self.bandwidth_window_start) >= BANDWIDTH_WINDOW {
74            self.bytes_sent_this_window = 0;
75            self.bytes_received_this_window = 0;
76            self.bandwidth_window_start = time;
77            true
78        } else {
79            false
80        }
81    }
82
83    /// Records bytes sent for bandwidth tracking.
84    ///
85    /// This increments the send counter using saturating addition to prevent overflow.
86    pub fn record_bytes_sent(&mut self, bytes: u32) {
87        self.bytes_sent_this_window = self.bytes_sent_this_window.saturating_add(bytes);
88    }
89
90    /// Records bytes received for bandwidth tracking.
91    ///
92    /// This increments the receive counter using saturating addition to prevent overflow.
93    pub fn record_bytes_received(&mut self, bytes: u32) {
94        self.bytes_received_this_window = self.bytes_received_this_window.saturating_add(bytes);
95    }
96
97    /// Checks if we can send based on the outgoing bandwidth limit.
98    ///
99    /// # Returns
100    ///
101    /// Returns `true` if we're under the limit or if throttling is disabled (limit == 0).
102    pub fn can_send_within_bandwidth(&self) -> bool {
103        if self.outgoing_bandwidth_limit == 0 {
104            return true; // Unlimited
105        }
106        self.bytes_sent_this_window < self.outgoing_bandwidth_limit
107    }
108
109    /// Returns current outgoing bandwidth utilization (0.0 to 1.0+).
110    ///
111    /// # Returns
112    ///
113    /// - `0.0` if bandwidth limiting is disabled
114    /// - `0.5` if at 50% of the limit
115    /// - `1.0` if at the limit
116    /// - `>1.0` if over the limit
117    pub fn bandwidth_utilization(&self) -> f32 {
118        if self.outgoing_bandwidth_limit == 0 {
119            return 0.0; // Unlimited, no utilization tracking
120        }
121        self.bytes_sent_this_window as f32 / self.outgoing_bandwidth_limit as f32
122    }
123
124    /// Checks if we can receive based on the incoming bandwidth limit.
125    ///
126    /// # Returns
127    ///
128    /// Returns `true` if we're under the limit or if throttling is disabled (limit == 0).
129    pub fn can_receive_within_bandwidth(&self) -> bool {
130        if self.incoming_bandwidth_limit == 0 {
131            return true; // Unlimited
132        }
133        self.bytes_received_this_window < self.incoming_bandwidth_limit
134    }
135
136    /// Returns current incoming bandwidth utilization (0.0 to 1.0+).
137    ///
138    /// # Returns
139    ///
140    /// - `0.0` if bandwidth limiting is disabled
141    /// - `0.5` if at 50% of the limit
142    /// - `1.0` if at the limit
143    /// - `>1.0` if over the limit
144    pub fn incoming_bandwidth_utilization(&self) -> f32 {
145        if self.incoming_bandwidth_limit == 0 {
146            return 0.0; // Unlimited, no utilization tracking
147        }
148        self.bytes_received_this_window as f32 / self.incoming_bandwidth_limit as f32
149    }
150
151    /// Updates the outgoing bandwidth limit.
152    ///
153    /// # Arguments
154    ///
155    /// * `limit` - New outgoing bandwidth limit in bytes per second (0 = unlimited)
156    pub fn set_outgoing_bandwidth_limit(&mut self, limit: u32) {
157        self.outgoing_bandwidth_limit = limit;
158    }
159
160    /// Updates the incoming bandwidth limit.
161    ///
162    /// # Arguments
163    ///
164    /// * `limit` - New incoming bandwidth limit in bytes per second (0 = unlimited)
165    pub fn set_incoming_bandwidth_limit(&mut self, limit: u32) {
166        self.incoming_bandwidth_limit = limit;
167    }
168
169    /// Returns the current outgoing bandwidth limit.
170    pub fn outgoing_bandwidth_limit(&self) -> u32 {
171        self.outgoing_bandwidth_limit
172    }
173
174    /// Returns the current incoming bandwidth limit.
175    pub fn incoming_bandwidth_limit(&self) -> u32 {
176        self.incoming_bandwidth_limit
177    }
178
179    /// Returns the bytes sent in the current window.
180    pub fn bytes_sent_this_window(&self) -> u32 {
181        self.bytes_sent_this_window
182    }
183
184    /// Returns the bytes received in the current window.
185    pub fn bytes_received_this_window(&self) -> u32 {
186        self.bytes_received_this_window
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_bandwidth_unlimited_by_default() {
196        let time = Instant::now();
197        let mut throttle = BandwidthThrottle::new(0, 0, time);
198
199        // Should always be able to send with unlimited bandwidth
200        assert!(throttle.can_send_within_bandwidth());
201
202        // Even after recording lots of bytes
203        throttle.record_bytes_sent(1_000_000);
204        assert!(throttle.can_send_within_bandwidth());
205
206        // Utilization should be 0.0 for unlimited
207        assert_eq!(throttle.bandwidth_utilization(), 0.0);
208    }
209
210    #[test]
211    fn test_bandwidth_throttling_when_limited() {
212        let time = Instant::now();
213        let mut throttle = BandwidthThrottle::new(1000, 0, time); // 1000 bytes/sec outgoing
214
215        // Should be able to send initially
216        assert!(throttle.can_send_within_bandwidth());
217        assert_eq!(throttle.bandwidth_utilization(), 0.0);
218
219        // Send 500 bytes (within limit)
220        throttle.record_bytes_sent(500);
221        assert!(throttle.can_send_within_bandwidth());
222        assert_eq!(throttle.bandwidth_utilization(), 0.5);
223
224        // Send another 500 bytes (at limit)
225        throttle.record_bytes_sent(500);
226        assert!(!throttle.can_send_within_bandwidth()); // Now at limit
227        assert_eq!(throttle.bandwidth_utilization(), 1.0);
228
229        // Try to send more (over limit)
230        throttle.record_bytes_sent(100);
231        assert!(!throttle.can_send_within_bandwidth());
232        assert!(throttle.bandwidth_utilization() > 1.0);
233
234        // After window reset, should be able to send again
235        let time_plus_1sec = time + Duration::from_secs(1);
236        throttle.update_bandwidth_window(time_plus_1sec);
237        assert!(throttle.can_send_within_bandwidth());
238        assert_eq!(throttle.bandwidth_utilization(), 0.0);
239    }
240
241    #[test]
242    fn test_bandwidth_limit_updates() {
243        let time = Instant::now();
244        let mut throttle = BandwidthThrottle::new(0, 0, time);
245
246        // Initially unlimited
247        assert_eq!(throttle.outgoing_bandwidth_limit(), 0);
248        assert_eq!(throttle.incoming_bandwidth_limit(), 0);
249
250        // Update limits
251        throttle.set_incoming_bandwidth_limit(1234);
252        throttle.set_outgoing_bandwidth_limit(5678);
253
254        assert_eq!(throttle.incoming_bandwidth_limit(), 1234);
255        assert_eq!(throttle.outgoing_bandwidth_limit(), 5678);
256    }
257
258    #[test]
259    fn test_incoming_bandwidth_throttling() {
260        let time = Instant::now();
261        let mut throttle = BandwidthThrottle::new(0, 2000, time); // 2000 bytes/sec incoming
262
263        // Should be able to receive initially
264        assert!(throttle.can_receive_within_bandwidth());
265        assert_eq!(throttle.incoming_bandwidth_utilization(), 0.0);
266
267        // Receive 1000 bytes (within limit)
268        throttle.record_bytes_received(1000);
269        assert!(throttle.can_receive_within_bandwidth());
270        assert_eq!(throttle.incoming_bandwidth_utilization(), 0.5);
271
272        // Receive another 1000 bytes (at limit)
273        throttle.record_bytes_received(1000);
274        assert!(!throttle.can_receive_within_bandwidth()); // Now at limit
275        assert_eq!(throttle.incoming_bandwidth_utilization(), 1.0);
276
277        // After window reset, should be able to receive again
278        let time_plus_1sec = time + Duration::from_secs(1);
279        throttle.update_bandwidth_window(time_plus_1sec);
280        assert!(throttle.can_receive_within_bandwidth());
281        assert_eq!(throttle.incoming_bandwidth_utilization(), 0.0);
282    }
283
284    #[test]
285    fn test_bytes_tracking() {
286        let time = Instant::now();
287        let mut throttle = BandwidthThrottle::new(1000, 2000, time);
288
289        assert_eq!(throttle.bytes_sent_this_window(), 0);
290        assert_eq!(throttle.bytes_received_this_window(), 0);
291
292        throttle.record_bytes_sent(100);
293        throttle.record_bytes_received(200);
294
295        assert_eq!(throttle.bytes_sent_this_window(), 100);
296        assert_eq!(throttle.bytes_received_this_window(), 200);
297
298        // After window reset, counters should be zero
299        let time_plus_1sec = time + Duration::from_secs(1);
300        throttle.update_bandwidth_window(time_plus_1sec);
301
302        assert_eq!(throttle.bytes_sent_this_window(), 0);
303        assert_eq!(throttle.bytes_received_this_window(), 0);
304    }
305
306    #[test]
307    fn test_saturating_addition() {
308        let time = Instant::now();
309        let mut throttle = BandwidthThrottle::new(1000, 2000, time);
310
311        // Send maximum u32 bytes (should saturate, not overflow)
312        throttle.record_bytes_sent(u32::MAX);
313        throttle.record_bytes_sent(100); // Should saturate at u32::MAX
314        assert_eq!(throttle.bytes_sent_this_window(), u32::MAX);
315
316        // Receive maximum u32 bytes (should saturate, not overflow)
317        throttle.record_bytes_received(u32::MAX);
318        throttle.record_bytes_received(100); // Should saturate at u32::MAX
319        assert_eq!(throttle.bytes_received_this_window(), u32::MAX);
320    }
321}