use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct FlowControlConfig {
pub initial_window: u64,
pub max_window: u64,
pub min_window: u64,
pub growth_factor: f64,
pub shrink_factor: f64,
}
impl Default for FlowControlConfig {
fn default() -> Self {
Self {
initial_window: 65_536,
max_window: 1_048_576,
min_window: 4096,
growth_factor: 1.5,
shrink_factor: 0.5,
}
}
}
#[derive(Debug, Clone)]
pub struct FlowWindow {
pub peer_id: String,
pub window_size: u64,
pub bytes_in_flight: u64,
pub bytes_acked: u64,
pub bytes_sent: u64,
pub stall_count: u64,
}
#[derive(Debug, Clone)]
pub struct FlowControlStats {
pub peer_count: usize,
pub total_stalls: u64,
pub avg_utilization: f64,
}
pub struct PeerFlowControl {
config: FlowControlConfig,
windows: HashMap<String, FlowWindow>,
total_stalls: u64,
}
impl PeerFlowControl {
pub fn new(config: FlowControlConfig) -> Self {
Self {
config,
windows: HashMap::new(),
total_stalls: 0,
}
}
pub fn can_send(&self, peer_id: &str, bytes: u64) -> bool {
match self.windows.get(peer_id) {
Some(w) => w.bytes_in_flight.saturating_add(bytes) <= w.window_size,
None => bytes <= self.config.initial_window,
}
}
pub fn send(&mut self, peer_id: &str, bytes: u64) -> Result<(), String> {
let window = self
.windows
.entry(peer_id.to_string())
.or_insert_with(|| FlowWindow {
peer_id: peer_id.to_string(),
window_size: self.config.initial_window,
bytes_in_flight: 0,
bytes_acked: 0,
bytes_sent: 0,
stall_count: 0,
});
if window.bytes_in_flight.saturating_add(bytes) > window.window_size {
window.stall_count += 1;
self.total_stalls += 1;
return Err(format!(
"window full for peer {}: in_flight={} + bytes={} > window={}",
peer_id, window.bytes_in_flight, bytes, window.window_size
));
}
window.bytes_in_flight = window.bytes_in_flight.saturating_add(bytes);
window.bytes_sent = window.bytes_sent.saturating_add(bytes);
Ok(())
}
pub fn ack(&mut self, peer_id: &str, bytes: u64) {
if let Some(w) = self.windows.get_mut(peer_id) {
w.bytes_in_flight = w.bytes_in_flight.saturating_sub(bytes);
w.bytes_acked = w.bytes_acked.saturating_add(bytes);
}
}
pub fn grow_window(&mut self, peer_id: &str) {
if let Some(w) = self.windows.get_mut(peer_id) {
let new_size = (w.window_size as f64 * self.config.growth_factor) as u64;
w.window_size = new_size.min(self.config.max_window);
}
}
pub fn shrink_window(&mut self, peer_id: &str) {
if let Some(w) = self.windows.get_mut(peer_id) {
let new_size = (w.window_size as f64 * self.config.shrink_factor) as u64;
w.window_size = new_size.max(self.config.min_window);
}
}
pub fn get_window(&self, peer_id: &str) -> Option<&FlowWindow> {
self.windows.get(peer_id)
}
pub fn utilization(&self, peer_id: &str) -> Option<f64> {
self.windows.get(peer_id).map(|w| {
if w.window_size == 0 {
0.0
} else {
w.bytes_in_flight as f64 / w.window_size as f64
}
})
}
pub fn remove_peer(&mut self, peer_id: &str) -> bool {
self.windows.remove(peer_id).is_some()
}
pub fn peer_count(&self) -> usize {
self.windows.len()
}
pub fn stats(&self) -> FlowControlStats {
let peer_count = self.windows.len();
let avg_utilization = if peer_count == 0 {
0.0
} else {
let total: f64 = self
.windows
.values()
.map(|w| {
if w.window_size == 0 {
0.0
} else {
w.bytes_in_flight as f64 / w.window_size as f64
}
})
.sum();
total / peer_count as f64
};
FlowControlStats {
peer_count,
total_stalls: self.total_stalls,
avg_utilization,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_ctrl() -> PeerFlowControl {
PeerFlowControl::new(FlowControlConfig::default())
}
#[test]
fn test_send_ack_lifecycle() {
let mut fc = default_ctrl();
fc.send("peer1", 1000).expect("send should succeed");
let w = fc.get_window("peer1").expect("window should exist");
assert_eq!(w.bytes_in_flight, 1000);
assert_eq!(w.bytes_sent, 1000);
fc.ack("peer1", 500);
let w = fc.get_window("peer1").expect("window should exist");
assert_eq!(w.bytes_in_flight, 500);
assert_eq!(w.bytes_acked, 500);
}
#[test]
fn test_send_ack_full_cycle() {
let mut fc = default_ctrl();
fc.send("p", 2000).expect("ok");
fc.ack("p", 2000);
let w = fc.get_window("p").expect("exists");
assert_eq!(w.bytes_in_flight, 0);
assert_eq!(w.bytes_acked, 2000);
assert_eq!(w.bytes_sent, 2000);
}
#[test]
fn test_can_send_new_peer() {
let fc = default_ctrl();
assert!(fc.can_send("unknown", 65_536));
assert!(!fc.can_send("unknown", 65_537));
}
#[test]
fn test_can_send_existing_peer() {
let mut fc = default_ctrl();
fc.send("p", 60_000).expect("ok");
assert!(fc.can_send("p", 5_536));
assert!(!fc.can_send("p", 5_537));
}
#[test]
fn test_can_send_zero_bytes() {
let fc = default_ctrl();
assert!(fc.can_send("any", 0));
}
#[test]
fn test_window_full_stall() {
let mut fc = default_ctrl();
fc.send("p", 65_536).expect("ok");
let res = fc.send("p", 1);
assert!(res.is_err());
let w = fc.get_window("p").expect("exists");
assert_eq!(w.stall_count, 1);
assert_eq!(fc.total_stalls, 1);
}
#[test]
fn test_multiple_stalls() {
let mut fc = default_ctrl();
fc.send("p", 65_536).expect("ok");
for _ in 0..5 {
let _ = fc.send("p", 1);
}
let w = fc.get_window("p").expect("exists");
assert_eq!(w.stall_count, 5);
assert_eq!(fc.total_stalls, 5);
}
#[test]
fn test_grow_window() {
let mut fc = default_ctrl();
fc.send("p", 0).expect("ok");
fc.grow_window("p");
let w = fc.get_window("p").expect("exists");
assert_eq!(w.window_size, 98_304); }
#[test]
fn test_grow_window_clamp_max() {
let mut fc = PeerFlowControl::new(FlowControlConfig {
initial_window: 900_000,
max_window: 1_000_000,
..Default::default()
});
fc.send("p", 0).expect("ok");
fc.grow_window("p"); let w = fc.get_window("p").expect("exists");
assert_eq!(w.window_size, 1_000_000);
}
#[test]
fn test_grow_window_nonexistent_peer_noop() {
let mut fc = default_ctrl();
fc.grow_window("ghost"); assert!(fc.get_window("ghost").is_none());
}
#[test]
fn test_shrink_window() {
let mut fc = default_ctrl();
fc.send("p", 0).expect("ok");
fc.shrink_window("p");
let w = fc.get_window("p").expect("exists");
assert_eq!(w.window_size, 32_768); }
#[test]
fn test_shrink_window_clamp_min() {
let mut fc = PeerFlowControl::new(FlowControlConfig {
initial_window: 5_000,
min_window: 4_096,
..Default::default()
});
fc.send("p", 0).expect("ok");
fc.shrink_window("p"); let w = fc.get_window("p").expect("exists");
assert_eq!(w.window_size, 4_096);
}
#[test]
fn test_shrink_window_nonexistent_peer_noop() {
let mut fc = default_ctrl();
fc.shrink_window("ghost");
assert!(fc.get_window("ghost").is_none());
}
#[test]
fn test_utilization_no_peer() {
let fc = default_ctrl();
assert!(fc.utilization("nope").is_none());
}
#[test]
fn test_utilization_zero_in_flight() {
let mut fc = default_ctrl();
fc.send("p", 0).expect("ok");
let u = fc.utilization("p").expect("exists");
assert!((u - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_utilization_half() {
let mut fc = default_ctrl();
fc.send("p", 32_768).expect("ok"); let u = fc.utilization("p").expect("exists");
assert!((u - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_utilization_full() {
let mut fc = default_ctrl();
fc.send("p", 65_536).expect("ok");
let u = fc.utilization("p").expect("exists");
assert!((u - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_remove_peer_present() {
let mut fc = default_ctrl();
fc.send("p", 100).expect("ok");
assert!(fc.remove_peer("p"));
assert!(fc.get_window("p").is_none());
assert_eq!(fc.peer_count(), 0);
}
#[test]
fn test_remove_peer_absent() {
let mut fc = default_ctrl();
assert!(!fc.remove_peer("ghost"));
}
#[test]
fn test_auto_create_window_on_send() {
let mut fc = default_ctrl();
assert!(fc.get_window("p").is_none());
fc.send("p", 10).expect("ok");
let w = fc.get_window("p").expect("exists");
assert_eq!(w.window_size, 65_536);
assert_eq!(w.peer_id, "p");
}
#[test]
fn test_peer_count_empty() {
let fc = default_ctrl();
assert_eq!(fc.peer_count(), 0);
}
#[test]
fn test_peer_count_multiple() {
let mut fc = default_ctrl();
fc.send("a", 1).expect("ok");
fc.send("b", 1).expect("ok");
fc.send("c", 1).expect("ok");
assert_eq!(fc.peer_count(), 3);
}
#[test]
fn test_stats_empty() {
let fc = default_ctrl();
let s = fc.stats();
assert_eq!(s.peer_count, 0);
assert_eq!(s.total_stalls, 0);
assert!((s.avg_utilization - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_stats_with_peers() {
let mut fc = default_ctrl();
fc.send("a", 65_536).expect("ok"); fc.send("b", 0).expect("ok"); let _ = fc.send("a", 1); let s = fc.stats();
assert_eq!(s.peer_count, 2);
assert_eq!(s.total_stalls, 1);
assert!((s.avg_utilization - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_ack_nonexistent_peer_noop() {
let mut fc = default_ctrl();
fc.ack("ghost", 1000); }
#[test]
fn test_ack_more_than_in_flight() {
let mut fc = default_ctrl();
fc.send("p", 100).expect("ok");
fc.ack("p", 500); let w = fc.get_window("p").expect("exists");
assert_eq!(w.bytes_in_flight, 0);
assert_eq!(w.bytes_acked, 500);
}
#[test]
fn test_multiple_sends_accumulate() {
let mut fc = default_ctrl();
fc.send("p", 10_000).expect("ok");
fc.send("p", 20_000).expect("ok");
fc.send("p", 30_000).expect("ok");
let w = fc.get_window("p").expect("exists");
assert_eq!(w.bytes_in_flight, 60_000);
assert_eq!(w.bytes_sent, 60_000);
}
#[test]
fn test_grow_allows_more_sends() {
let mut fc = default_ctrl();
fc.send("p", 65_536).expect("fill window");
assert!(fc.send("p", 1).is_err());
fc.ack("p", 65_536);
fc.grow_window("p"); fc.send("p", 98_304).expect("ok with grown window");
let w = fc.get_window("p").expect("exists");
assert_eq!(w.bytes_in_flight, 98_304);
}
#[test]
fn test_shrink_reduces_capacity() {
let mut fc = default_ctrl();
fc.send("p", 0).expect("ok");
fc.shrink_window("p"); assert!(fc.can_send("p", 32_768));
assert!(!fc.can_send("p", 32_769));
}
#[test]
fn test_custom_config() {
let cfg = FlowControlConfig {
initial_window: 1000,
max_window: 5000,
min_window: 100,
growth_factor: 2.0,
shrink_factor: 0.25,
};
let mut fc = PeerFlowControl::new(cfg);
fc.send("p", 0).expect("ok");
assert_eq!(fc.get_window("p").expect("e").window_size, 1000);
fc.grow_window("p");
assert_eq!(fc.get_window("p").expect("e").window_size, 2000);
fc.grow_window("p");
assert_eq!(fc.get_window("p").expect("e").window_size, 4000);
fc.grow_window("p"); assert_eq!(fc.get_window("p").expect("e").window_size, 5000);
fc.shrink_window("p"); assert_eq!(fc.get_window("p").expect("e").window_size, 1250);
fc.shrink_window("p"); fc.shrink_window("p"); assert_eq!(fc.get_window("p").expect("e").window_size, 100);
}
#[test]
fn test_stats_avg_utilization_three_peers() {
let mut fc = default_ctrl();
fc.send("a", 65_536).expect("ok"); fc.send("b", 32_768).expect("ok"); fc.send("c", 16_384).expect("ok"); let s = fc.stats();
let expected = (1.0 + 0.5 + 0.25) / 3.0;
assert!((s.avg_utilization - expected).abs() < 1e-10);
}
#[test]
fn test_stalls_across_multiple_peers() {
let mut fc = default_ctrl();
fc.send("a", 65_536).expect("ok");
fc.send("b", 65_536).expect("ok");
let _ = fc.send("a", 1);
let _ = fc.send("b", 1);
let _ = fc.send("a", 1);
assert_eq!(fc.total_stalls, 3);
assert_eq!(fc.stats().total_stalls, 3);
}
#[test]
fn test_default_config() {
let cfg = FlowControlConfig::default();
assert_eq!(cfg.initial_window, 65_536);
assert_eq!(cfg.max_window, 1_048_576);
assert_eq!(cfg.min_window, 4096);
assert!((cfg.growth_factor - 1.5).abs() < f64::EPSILON);
assert!((cfg.shrink_factor - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_remove_and_readd_peer() {
let mut fc = default_ctrl();
fc.send("p", 100).expect("ok");
fc.grow_window("p");
fc.remove_peer("p");
fc.send("p", 50).expect("ok");
let w = fc.get_window("p").expect("exists");
assert_eq!(w.window_size, 65_536);
assert_eq!(w.bytes_in_flight, 50);
assert_eq!(w.bytes_sent, 50);
}
}