bitfold_peer/flow_control.rs
1//! Window-based flow control for reliable data transmission.
2//!
3//! This module implements a sliding window flow control mechanism that prevents overwhelming
4//! the receiver with too much data at once. The window size represents the maximum amount of
5//! unacknowledged data (in packets) that can be in flight at any given time.
6//!
7//! # Flow Control Strategy
8//!
9//! The flow control system uses a dynamic window that adapts to network conditions:
10//!
11//! - **Window Size**: Measured in packets, controls how much data can be sent without acknowledgment
12//! - **In-Transit Tracking**: Monitors bytes of reliable data waiting for ACK
13//! - **Dynamic Adjustment**: Window grows during good conditions (low loss, low RTT) and shrinks
14//! during poor conditions (high loss, high RTT)
15//!
16//! # Window Adjustment Algorithm
17//!
18//! The window size adjusts based on:
19//! - Packet loss rate (lower is better)
20//! - Round-trip time (RTT) (lower is better)
21//!
22//! **Increase conditions**: Loss rate < 1% and RTT < 200ms
23//! - Window grows by ~3% (1/32) per adjustment
24//!
25//! **Decrease conditions**: Loss rate > 5% or RTT > 500ms
26//! - Window shrinks by ~6% (1/16) per adjustment
27//!
28//! # Example
29//!
30//! ```
31//! use bitfold_peer::flow_control::FlowControl;
32//! use bitfold_core::config::Config;
33//!
34//! let config = Config::default();
35//! let mut flow_control = FlowControl::new(&config);
36//!
37//! // Check if we can send data
38//! if flow_control.can_send_reliable(&config, 0) {
39//! // Send data and record it
40//! flow_control.record_reliable_data_sent(1024);
41//! }
42//!
43//! // Later, when data is acknowledged
44//! flow_control.record_reliable_data_acked(1024);
45//!
46//! // Periodically adjust window based on network conditions
47//! let loss_rate = 0.02; // 2% loss
48//! let rtt_ms = 150;
49//! flow_control.adjust_window_size(&config, loss_rate, rtt_ms);
50//! ```
51
52use bitfold_core::config::Config;
53
54/// Window-based flow control state for managing reliable data transmission.
55///
56/// This struct encapsulates the state needed for sliding window flow control,
57/// including the current window size and tracking of in-transit reliable data.
58#[derive(Debug, Clone)]
59pub struct FlowControl {
60 /// Current window size for flow control (in packets)
61 window_size: u32,
62 /// Reliable data currently in transit (waiting for ACK), in bytes
63 reliable_data_in_transit: u32,
64}
65
66impl FlowControl {
67 /// Creates a new flow control instance with configuration defaults.
68 ///
69 /// # Arguments
70 ///
71 /// * `config` - Configuration containing initial window size and limits
72 ///
73 /// # Example
74 ///
75 /// ```
76 /// use bitfold_peer::flow_control::FlowControl;
77 /// use bitfold_core::config::Config;
78 ///
79 /// let config = Config::default();
80 /// let flow_control = FlowControl::new(&config);
81 /// ```
82 pub fn new(config: &Config) -> Self {
83 Self { window_size: config.initial_window_size, reliable_data_in_transit: 0 }
84 }
85
86 /// Returns the current window size (in packets).
87 ///
88 /// The window size represents the maximum number of packets that can be
89 /// in flight (unacknowledged) at any given time.
90 pub fn window_size(&self) -> u32 {
91 self.window_size
92 }
93
94 /// Returns the amount of reliable data currently in transit (in bytes).
95 ///
96 /// This tracks the total bytes of reliable data that have been sent but
97 /// not yet acknowledged by the receiver.
98 pub fn reliable_data_in_transit(&self) -> u32 {
99 self.reliable_data_in_transit
100 }
101
102 /// Sets the window size (for negotiation during handshake).
103 ///
104 /// The provided window size will be clamped to the configured min/max bounds.
105 ///
106 /// # Arguments
107 ///
108 /// * `config` - Configuration containing min and max window size limits
109 /// * `window_size` - Desired window size (will be clamped to min/max)
110 pub fn set_window_size(&mut self, config: &Config, window_size: u32) {
111 self.window_size = window_size.clamp(config.min_window_size, config.max_window_size);
112 }
113
114 /// Records reliable data being sent (adds to in-transit counter).
115 ///
116 /// Call this method whenever reliable data is transmitted to track how much
117 /// data is waiting for acknowledgment.
118 ///
119 /// # Arguments
120 ///
121 /// * `data_size` - Size of the data being sent, in bytes
122 pub fn record_reliable_data_sent(&mut self, data_size: u32) {
123 self.reliable_data_in_transit = self.reliable_data_in_transit.saturating_add(data_size);
124 }
125
126 /// Records reliable data being acknowledged (removes from in-transit counter).
127 ///
128 /// Call this method when an acknowledgment is received to reduce the amount
129 /// of tracked in-transit data.
130 ///
131 /// # Arguments
132 ///
133 /// * `data_size` - Size of the data being acknowledged, in bytes
134 pub fn record_reliable_data_acked(&mut self, data_size: u32) {
135 self.reliable_data_in_transit = self.reliable_data_in_transit.saturating_sub(data_size);
136 }
137
138 /// Checks if we can send more data based on window-based flow control.
139 ///
140 /// Returns `true` if we have room in the window, `false` if the window is full.
141 /// When window flow control is disabled, this falls back to checking a simple
142 /// packet count limit.
143 ///
144 /// # Arguments
145 ///
146 /// * `config` - Configuration containing window control settings
147 /// * `packets_in_flight` - Current number of unacknowledged packets (used when
148 /// window flow control is disabled)
149 ///
150 /// # Returns
151 ///
152 /// `true` if more reliable data can be sent, `false` if the window is full
153 pub fn can_send_reliable(&self, config: &Config, packets_in_flight: u16) -> bool {
154 if !config.use_window_flow_control {
155 // Fall back to simple packet count limit
156 return packets_in_flight < config.max_packets_in_flight;
157 }
158
159 // Window-based: check if in-transit data is within window size
160 // Approximate packet size for window calculation (MTU-based)
161 let approx_packet_size = config.fragment_size as u32;
162 let window_bytes = self.window_size * approx_packet_size;
163
164 self.reliable_data_in_transit < window_bytes
165 }
166
167 /// Dynamically adjusts the window size based on network conditions.
168 ///
169 /// This should be called periodically to adapt the window size to changing
170 /// network conditions. The window grows during good conditions and shrinks
171 /// during poor conditions.
172 ///
173 /// # Adjustment Logic
174 ///
175 /// - **Increase**: When loss rate < 1% and RTT < 200ms, grow window by ~3% (1/32)
176 /// - **Decrease**: When loss rate > 5% or RTT > 500ms, shrink window by ~6% (1/16)
177 /// - **No change**: Otherwise maintain current window size
178 ///
179 /// The window size is always clamped to the configured min/max bounds.
180 ///
181 /// # Arguments
182 ///
183 /// * `config` - Configuration containing window control settings and limits
184 /// * `loss_rate` - Current packet loss rate (0.0 to 1.0)
185 /// * `rtt_ms` - Current round-trip time in milliseconds
186 pub fn adjust_window_size(&mut self, config: &Config, loss_rate: f32, rtt_ms: u32) {
187 if !config.use_window_flow_control {
188 return;
189 }
190
191 // Increase window if conditions are good (low loss, reasonable RTT)
192 if loss_rate < 0.01 && rtt_ms < 200 {
193 // Less than 1% loss and RTT < 200ms
194 self.window_size =
195 (self.window_size + (self.window_size / 32).max(1)).min(config.max_window_size);
196 }
197 // Decrease window if conditions are poor (high loss or high RTT)
198 else if loss_rate > 0.05 || rtt_ms > 500 {
199 // More than 5% loss or RTT > 500ms
200 self.window_size =
201 (self.window_size - (self.window_size / 16).max(1)).max(config.min_window_size);
202 }
203 }
204}
205
206impl Default for FlowControl {
207 fn default() -> Self {
208 Self::new(&Config::default())
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_window_flow_control_enabled() {
218 let mut config = Config::default();
219 config.use_window_flow_control = true;
220 config.initial_window_size = 100;
221 let flow_control = FlowControl::new(&config);
222
223 assert_eq!(flow_control.window_size(), 100);
224 assert_eq!(flow_control.reliable_data_in_transit(), 0);
225 assert!(flow_control.can_send_reliable(&config, 0));
226 }
227
228 #[test]
229 fn test_window_flow_control_tracks_in_transit_data() {
230 let mut config = Config::default();
231 config.use_window_flow_control = true;
232 config.initial_window_size = 10; // Small window
233 config.fragment_size = 1024;
234 let mut flow_control = FlowControl::new(&config);
235
236 // Initially can send
237 assert!(flow_control.can_send_reliable(&config, 0));
238
239 // Record sending 5KB of data
240 flow_control.record_reliable_data_sent(5 * 1024);
241 assert_eq!(flow_control.reliable_data_in_transit(), 5 * 1024);
242
243 // Can still send (5KB < 10 packets * 1024 bytes = 10KB window)
244 assert!(flow_control.can_send_reliable(&config, 0));
245
246 // Record sending another 6KB (total 11KB, exceeds 10KB window)
247 flow_control.record_reliable_data_sent(6 * 1024);
248 assert_eq!(flow_control.reliable_data_in_transit(), 11 * 1024);
249
250 // Now cannot send (exceeds window)
251 assert!(!flow_control.can_send_reliable(&config, 0));
252
253 // ACK some data (3KB)
254 flow_control.record_reliable_data_acked(3 * 1024);
255 assert_eq!(flow_control.reliable_data_in_transit(), 8 * 1024);
256
257 // Now can send again (8KB < 10KB window)
258 assert!(flow_control.can_send_reliable(&config, 0));
259 }
260
261 #[test]
262 fn test_window_size_negotiation() {
263 let mut config = Config::default();
264 config.use_window_flow_control = true;
265 config.initial_window_size = 1000;
266 config.min_window_size = 64;
267 config.max_window_size = 2048;
268 let mut flow_control = FlowControl::new(&config);
269
270 assert_eq!(flow_control.window_size(), 1000);
271
272 // Set to a value within range
273 flow_control.set_window_size(&config, 512);
274 assert_eq!(flow_control.window_size(), 512);
275
276 // Set to value above max - should clamp
277 flow_control.set_window_size(&config, 3000);
278 assert_eq!(flow_control.window_size(), 2048);
279
280 // Set to value below min - should clamp
281 flow_control.set_window_size(&config, 32);
282 assert_eq!(flow_control.window_size(), 64);
283 }
284
285 #[test]
286 fn test_window_adjustment_increases_on_good_conditions() {
287 let mut config = Config::default();
288 config.use_window_flow_control = true;
289 config.initial_window_size = 100;
290 config.max_window_size = 200;
291 let mut flow_control = FlowControl::new(&config);
292
293 let initial_window = flow_control.window_size();
294
295 // Simulate good conditions (no loss, low RTT)
296 let loss_rate = 0.0;
297 let rtt_ms = 50;
298
299 flow_control.adjust_window_size(&config, loss_rate, rtt_ms);
300
301 // Window should increase
302 assert!(flow_control.window_size() > initial_window);
303 }
304
305 #[test]
306 fn test_window_adjustment_decreases_on_poor_conditions() {
307 let mut config = Config::default();
308 config.use_window_flow_control = true;
309 config.initial_window_size = 200;
310 config.min_window_size = 64;
311 let mut flow_control = FlowControl::new(&config);
312
313 let initial_window = flow_control.window_size();
314
315 // Simulate poor conditions (high packet loss)
316 let loss_rate = 0.10; // 10% loss
317 let rtt_ms = 100;
318
319 flow_control.adjust_window_size(&config, loss_rate, rtt_ms);
320
321 // Window should decrease
322 assert!(flow_control.window_size() < initial_window);
323 }
324
325 #[test]
326 fn test_window_flow_control_respects_min_max() {
327 let mut config = Config::default();
328 config.use_window_flow_control = true;
329 config.initial_window_size = 100;
330 config.min_window_size = 64;
331 config.max_window_size = 128;
332 let mut flow_control = FlowControl::new(&config);
333
334 // Repeatedly adjust upward
335 for _ in 0..50 {
336 flow_control.adjust_window_size(&config, 0.0, 50);
337 }
338
339 // Should not exceed max
340 assert!(flow_control.window_size() <= 128);
341
342 // Simulate sustained poor conditions
343 for _ in 0..50 {
344 flow_control.adjust_window_size(&config, 0.10, 600);
345 }
346
347 // Should not go below min
348 assert!(flow_control.window_size() >= 64);
349 }
350
351 #[test]
352 fn test_window_flow_control_disabled_uses_packet_limit() {
353 let mut config = Config::default();
354 config.use_window_flow_control = false; // Disabled
355 config.max_packets_in_flight = 10;
356 let flow_control = FlowControl::new(&config);
357
358 // When disabled, should use packets_in_flight limit
359 // Initially 0 packets in flight, so can send
360 assert!(flow_control.can_send_reliable(&config, 0));
361
362 // With 10 packets in flight (at limit), should not be able to send
363 assert!(!flow_control.can_send_reliable(&config, 10));
364
365 // With 9 packets in flight (under limit), should be able to send
366 assert!(flow_control.can_send_reliable(&config, 9));
367 }
368
369 #[test]
370 fn test_record_data_sent_saturating() {
371 let config = Config::default();
372 let mut flow_control = FlowControl::new(&config);
373
374 // Test that adding doesn't overflow
375 flow_control.record_reliable_data_sent(u32::MAX);
376 assert_eq!(flow_control.reliable_data_in_transit(), u32::MAX);
377
378 flow_control.record_reliable_data_sent(1000);
379 assert_eq!(flow_control.reliable_data_in_transit(), u32::MAX); // Should saturate
380 }
381
382 #[test]
383 fn test_record_data_acked_saturating() {
384 let config = Config::default();
385 let mut flow_control = FlowControl::new(&config);
386
387 // Test that subtracting doesn't underflow
388 flow_control.record_reliable_data_sent(1000);
389 flow_control.record_reliable_data_acked(2000);
390 assert_eq!(flow_control.reliable_data_in_transit(), 0); // Should saturate at 0
391 }
392
393 #[test]
394 fn test_default_initialization() {
395 let flow_control = FlowControl::default();
396 let default_config = Config::default();
397
398 assert_eq!(flow_control.window_size(), default_config.initial_window_size);
399 assert_eq!(flow_control.reliable_data_in_transit(), 0);
400 }
401}