Skip to main content

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}