use bitfold_core::config::Config;
#[derive(Debug, Clone)]
pub struct FlowControl {
window_size: u32,
reliable_data_in_transit: u32,
}
impl FlowControl {
pub fn new(config: &Config) -> Self {
Self { window_size: config.initial_window_size, reliable_data_in_transit: 0 }
}
pub fn window_size(&self) -> u32 {
self.window_size
}
pub fn reliable_data_in_transit(&self) -> u32 {
self.reliable_data_in_transit
}
pub fn set_window_size(&mut self, config: &Config, window_size: u32) {
self.window_size = window_size.clamp(config.min_window_size, config.max_window_size);
}
pub fn record_reliable_data_sent(&mut self, data_size: u32) {
self.reliable_data_in_transit = self.reliable_data_in_transit.saturating_add(data_size);
}
pub fn record_reliable_data_acked(&mut self, data_size: u32) {
self.reliable_data_in_transit = self.reliable_data_in_transit.saturating_sub(data_size);
}
pub fn can_send_reliable(&self, config: &Config, packets_in_flight: u16) -> bool {
if !config.use_window_flow_control {
return packets_in_flight < config.max_packets_in_flight;
}
let approx_packet_size = config.fragment_size as u32;
let window_bytes = self.window_size * approx_packet_size;
self.reliable_data_in_transit < window_bytes
}
pub fn adjust_window_size(&mut self, config: &Config, loss_rate: f32, rtt_ms: u32) {
if !config.use_window_flow_control {
return;
}
if loss_rate < 0.01 && rtt_ms < 200 {
self.window_size =
(self.window_size + (self.window_size / 32).max(1)).min(config.max_window_size);
}
else if loss_rate > 0.05 || rtt_ms > 500 {
self.window_size =
(self.window_size - (self.window_size / 16).max(1)).max(config.min_window_size);
}
}
}
impl Default for FlowControl {
fn default() -> Self {
Self::new(&Config::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_window_flow_control_enabled() {
let mut config = Config::default();
config.use_window_flow_control = true;
config.initial_window_size = 100;
let flow_control = FlowControl::new(&config);
assert_eq!(flow_control.window_size(), 100);
assert_eq!(flow_control.reliable_data_in_transit(), 0);
assert!(flow_control.can_send_reliable(&config, 0));
}
#[test]
fn test_window_flow_control_tracks_in_transit_data() {
let mut config = Config::default();
config.use_window_flow_control = true;
config.initial_window_size = 10; config.fragment_size = 1024;
let mut flow_control = FlowControl::new(&config);
assert!(flow_control.can_send_reliable(&config, 0));
flow_control.record_reliable_data_sent(5 * 1024);
assert_eq!(flow_control.reliable_data_in_transit(), 5 * 1024);
assert!(flow_control.can_send_reliable(&config, 0));
flow_control.record_reliable_data_sent(6 * 1024);
assert_eq!(flow_control.reliable_data_in_transit(), 11 * 1024);
assert!(!flow_control.can_send_reliable(&config, 0));
flow_control.record_reliable_data_acked(3 * 1024);
assert_eq!(flow_control.reliable_data_in_transit(), 8 * 1024);
assert!(flow_control.can_send_reliable(&config, 0));
}
#[test]
fn test_window_size_negotiation() {
let mut config = Config::default();
config.use_window_flow_control = true;
config.initial_window_size = 1000;
config.min_window_size = 64;
config.max_window_size = 2048;
let mut flow_control = FlowControl::new(&config);
assert_eq!(flow_control.window_size(), 1000);
flow_control.set_window_size(&config, 512);
assert_eq!(flow_control.window_size(), 512);
flow_control.set_window_size(&config, 3000);
assert_eq!(flow_control.window_size(), 2048);
flow_control.set_window_size(&config, 32);
assert_eq!(flow_control.window_size(), 64);
}
#[test]
fn test_window_adjustment_increases_on_good_conditions() {
let mut config = Config::default();
config.use_window_flow_control = true;
config.initial_window_size = 100;
config.max_window_size = 200;
let mut flow_control = FlowControl::new(&config);
let initial_window = flow_control.window_size();
let loss_rate = 0.0;
let rtt_ms = 50;
flow_control.adjust_window_size(&config, loss_rate, rtt_ms);
assert!(flow_control.window_size() > initial_window);
}
#[test]
fn test_window_adjustment_decreases_on_poor_conditions() {
let mut config = Config::default();
config.use_window_flow_control = true;
config.initial_window_size = 200;
config.min_window_size = 64;
let mut flow_control = FlowControl::new(&config);
let initial_window = flow_control.window_size();
let loss_rate = 0.10; let rtt_ms = 100;
flow_control.adjust_window_size(&config, loss_rate, rtt_ms);
assert!(flow_control.window_size() < initial_window);
}
#[test]
fn test_window_flow_control_respects_min_max() {
let mut config = Config::default();
config.use_window_flow_control = true;
config.initial_window_size = 100;
config.min_window_size = 64;
config.max_window_size = 128;
let mut flow_control = FlowControl::new(&config);
for _ in 0..50 {
flow_control.adjust_window_size(&config, 0.0, 50);
}
assert!(flow_control.window_size() <= 128);
for _ in 0..50 {
flow_control.adjust_window_size(&config, 0.10, 600);
}
assert!(flow_control.window_size() >= 64);
}
#[test]
fn test_window_flow_control_disabled_uses_packet_limit() {
let mut config = Config::default();
config.use_window_flow_control = false; config.max_packets_in_flight = 10;
let flow_control = FlowControl::new(&config);
assert!(flow_control.can_send_reliable(&config, 0));
assert!(!flow_control.can_send_reliable(&config, 10));
assert!(flow_control.can_send_reliable(&config, 9));
}
#[test]
fn test_record_data_sent_saturating() {
let config = Config::default();
let mut flow_control = FlowControl::new(&config);
flow_control.record_reliable_data_sent(u32::MAX);
assert_eq!(flow_control.reliable_data_in_transit(), u32::MAX);
flow_control.record_reliable_data_sent(1000);
assert_eq!(flow_control.reliable_data_in_transit(), u32::MAX); }
#[test]
fn test_record_data_acked_saturating() {
let config = Config::default();
let mut flow_control = FlowControl::new(&config);
flow_control.record_reliable_data_sent(1000);
flow_control.record_reliable_data_acked(2000);
assert_eq!(flow_control.reliable_data_in_transit(), 0); }
#[test]
fn test_default_initialization() {
let flow_control = FlowControl::default();
let default_config = Config::default();
assert_eq!(flow_control.window_size(), default_config.initial_window_size);
assert_eq!(flow_control.reliable_data_in_transit(), 0);
}
}