1use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct FlowControlConfig {
12 pub initial_window: u64,
14 pub max_window: u64,
16 pub min_window: u64,
18 pub growth_factor: f64,
20 pub shrink_factor: f64,
22}
23
24impl Default for FlowControlConfig {
25 fn default() -> Self {
26 Self {
27 initial_window: 65_536,
28 max_window: 1_048_576,
29 min_window: 4096,
30 growth_factor: 1.5,
31 shrink_factor: 0.5,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct FlowWindow {
39 pub peer_id: String,
41 pub window_size: u64,
43 pub bytes_in_flight: u64,
45 pub bytes_acked: u64,
47 pub bytes_sent: u64,
49 pub stall_count: u64,
51}
52
53#[derive(Debug, Clone)]
55pub struct FlowControlStats {
56 pub peer_count: usize,
58 pub total_stalls: u64,
60 pub avg_utilization: f64,
62}
63
64pub struct PeerFlowControl {
69 config: FlowControlConfig,
70 windows: HashMap<String, FlowWindow>,
71 total_stalls: u64,
72}
73
74impl PeerFlowControl {
75 pub fn new(config: FlowControlConfig) -> Self {
77 Self {
78 config,
79 windows: HashMap::new(),
80 total_stalls: 0,
81 }
82 }
83
84 pub fn can_send(&self, peer_id: &str, bytes: u64) -> bool {
88 match self.windows.get(peer_id) {
89 Some(w) => w.bytes_in_flight.saturating_add(bytes) <= w.window_size,
90 None => bytes <= self.config.initial_window,
91 }
92 }
93
94 pub fn send(&mut self, peer_id: &str, bytes: u64) -> Result<(), String> {
100 let window = self
101 .windows
102 .entry(peer_id.to_string())
103 .or_insert_with(|| FlowWindow {
104 peer_id: peer_id.to_string(),
105 window_size: self.config.initial_window,
106 bytes_in_flight: 0,
107 bytes_acked: 0,
108 bytes_sent: 0,
109 stall_count: 0,
110 });
111
112 if window.bytes_in_flight.saturating_add(bytes) > window.window_size {
113 window.stall_count += 1;
114 self.total_stalls += 1;
115 return Err(format!(
116 "window full for peer {}: in_flight={} + bytes={} > window={}",
117 peer_id, window.bytes_in_flight, bytes, window.window_size
118 ));
119 }
120
121 window.bytes_in_flight = window.bytes_in_flight.saturating_add(bytes);
122 window.bytes_sent = window.bytes_sent.saturating_add(bytes);
123 Ok(())
124 }
125
126 pub fn ack(&mut self, peer_id: &str, bytes: u64) {
131 if let Some(w) = self.windows.get_mut(peer_id) {
132 w.bytes_in_flight = w.bytes_in_flight.saturating_sub(bytes);
133 w.bytes_acked = w.bytes_acked.saturating_add(bytes);
134 }
135 }
136
137 pub fn grow_window(&mut self, peer_id: &str) {
141 if let Some(w) = self.windows.get_mut(peer_id) {
142 let new_size = (w.window_size as f64 * self.config.growth_factor) as u64;
143 w.window_size = new_size.min(self.config.max_window);
144 }
145 }
146
147 pub fn shrink_window(&mut self, peer_id: &str) {
151 if let Some(w) = self.windows.get_mut(peer_id) {
152 let new_size = (w.window_size as f64 * self.config.shrink_factor) as u64;
153 w.window_size = new_size.max(self.config.min_window);
154 }
155 }
156
157 pub fn get_window(&self, peer_id: &str) -> Option<&FlowWindow> {
159 self.windows.get(peer_id)
160 }
161
162 pub fn utilization(&self, peer_id: &str) -> Option<f64> {
165 self.windows.get(peer_id).map(|w| {
166 if w.window_size == 0 {
167 0.0
168 } else {
169 w.bytes_in_flight as f64 / w.window_size as f64
170 }
171 })
172 }
173
174 pub fn remove_peer(&mut self, peer_id: &str) -> bool {
177 self.windows.remove(peer_id).is_some()
178 }
179
180 pub fn peer_count(&self) -> usize {
182 self.windows.len()
183 }
184
185 pub fn stats(&self) -> FlowControlStats {
187 let peer_count = self.windows.len();
188 let avg_utilization = if peer_count == 0 {
189 0.0
190 } else {
191 let total: f64 = self
192 .windows
193 .values()
194 .map(|w| {
195 if w.window_size == 0 {
196 0.0
197 } else {
198 w.bytes_in_flight as f64 / w.window_size as f64
199 }
200 })
201 .sum();
202 total / peer_count as f64
203 };
204
205 FlowControlStats {
206 peer_count,
207 total_stalls: self.total_stalls,
208 avg_utilization,
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn default_ctrl() -> PeerFlowControl {
218 PeerFlowControl::new(FlowControlConfig::default())
219 }
220
221 #[test]
224 fn test_send_ack_lifecycle() {
225 let mut fc = default_ctrl();
226 fc.send("peer1", 1000).expect("send should succeed");
227 let w = fc.get_window("peer1").expect("window should exist");
228 assert_eq!(w.bytes_in_flight, 1000);
229 assert_eq!(w.bytes_sent, 1000);
230
231 fc.ack("peer1", 500);
232 let w = fc.get_window("peer1").expect("window should exist");
233 assert_eq!(w.bytes_in_flight, 500);
234 assert_eq!(w.bytes_acked, 500);
235 }
236
237 #[test]
238 fn test_send_ack_full_cycle() {
239 let mut fc = default_ctrl();
240 fc.send("p", 2000).expect("ok");
241 fc.ack("p", 2000);
242 let w = fc.get_window("p").expect("exists");
243 assert_eq!(w.bytes_in_flight, 0);
244 assert_eq!(w.bytes_acked, 2000);
245 assert_eq!(w.bytes_sent, 2000);
246 }
247
248 #[test]
251 fn test_can_send_new_peer() {
252 let fc = default_ctrl();
253 assert!(fc.can_send("unknown", 65_536));
254 assert!(!fc.can_send("unknown", 65_537));
255 }
256
257 #[test]
258 fn test_can_send_existing_peer() {
259 let mut fc = default_ctrl();
260 fc.send("p", 60_000).expect("ok");
261 assert!(fc.can_send("p", 5_536));
262 assert!(!fc.can_send("p", 5_537));
263 }
264
265 #[test]
266 fn test_can_send_zero_bytes() {
267 let fc = default_ctrl();
268 assert!(fc.can_send("any", 0));
269 }
270
271 #[test]
274 fn test_window_full_stall() {
275 let mut fc = default_ctrl();
276 fc.send("p", 65_536).expect("ok");
277 let res = fc.send("p", 1);
278 assert!(res.is_err());
279 let w = fc.get_window("p").expect("exists");
280 assert_eq!(w.stall_count, 1);
281 assert_eq!(fc.total_stalls, 1);
282 }
283
284 #[test]
285 fn test_multiple_stalls() {
286 let mut fc = default_ctrl();
287 fc.send("p", 65_536).expect("ok");
288 for _ in 0..5 {
289 let _ = fc.send("p", 1);
290 }
291 let w = fc.get_window("p").expect("exists");
292 assert_eq!(w.stall_count, 5);
293 assert_eq!(fc.total_stalls, 5);
294 }
295
296 #[test]
299 fn test_grow_window() {
300 let mut fc = default_ctrl();
301 fc.send("p", 0).expect("ok");
302 fc.grow_window("p");
303 let w = fc.get_window("p").expect("exists");
304 assert_eq!(w.window_size, 98_304); }
306
307 #[test]
308 fn test_grow_window_clamp_max() {
309 let mut fc = PeerFlowControl::new(FlowControlConfig {
310 initial_window: 900_000,
311 max_window: 1_000_000,
312 ..Default::default()
313 });
314 fc.send("p", 0).expect("ok");
315 fc.grow_window("p"); let w = fc.get_window("p").expect("exists");
317 assert_eq!(w.window_size, 1_000_000);
318 }
319
320 #[test]
321 fn test_grow_window_nonexistent_peer_noop() {
322 let mut fc = default_ctrl();
323 fc.grow_window("ghost"); assert!(fc.get_window("ghost").is_none());
325 }
326
327 #[test]
330 fn test_shrink_window() {
331 let mut fc = default_ctrl();
332 fc.send("p", 0).expect("ok");
333 fc.shrink_window("p");
334 let w = fc.get_window("p").expect("exists");
335 assert_eq!(w.window_size, 32_768); }
337
338 #[test]
339 fn test_shrink_window_clamp_min() {
340 let mut fc = PeerFlowControl::new(FlowControlConfig {
341 initial_window: 5_000,
342 min_window: 4_096,
343 ..Default::default()
344 });
345 fc.send("p", 0).expect("ok");
346 fc.shrink_window("p"); let w = fc.get_window("p").expect("exists");
348 assert_eq!(w.window_size, 4_096);
349 }
350
351 #[test]
352 fn test_shrink_window_nonexistent_peer_noop() {
353 let mut fc = default_ctrl();
354 fc.shrink_window("ghost");
355 assert!(fc.get_window("ghost").is_none());
356 }
357
358 #[test]
361 fn test_utilization_no_peer() {
362 let fc = default_ctrl();
363 assert!(fc.utilization("nope").is_none());
364 }
365
366 #[test]
367 fn test_utilization_zero_in_flight() {
368 let mut fc = default_ctrl();
369 fc.send("p", 0).expect("ok");
370 let u = fc.utilization("p").expect("exists");
371 assert!((u - 0.0).abs() < f64::EPSILON);
372 }
373
374 #[test]
375 fn test_utilization_half() {
376 let mut fc = default_ctrl();
377 fc.send("p", 32_768).expect("ok"); let u = fc.utilization("p").expect("exists");
379 assert!((u - 0.5).abs() < f64::EPSILON);
380 }
381
382 #[test]
383 fn test_utilization_full() {
384 let mut fc = default_ctrl();
385 fc.send("p", 65_536).expect("ok");
386 let u = fc.utilization("p").expect("exists");
387 assert!((u - 1.0).abs() < f64::EPSILON);
388 }
389
390 #[test]
393 fn test_remove_peer_present() {
394 let mut fc = default_ctrl();
395 fc.send("p", 100).expect("ok");
396 assert!(fc.remove_peer("p"));
397 assert!(fc.get_window("p").is_none());
398 assert_eq!(fc.peer_count(), 0);
399 }
400
401 #[test]
402 fn test_remove_peer_absent() {
403 let mut fc = default_ctrl();
404 assert!(!fc.remove_peer("ghost"));
405 }
406
407 #[test]
410 fn test_auto_create_window_on_send() {
411 let mut fc = default_ctrl();
412 assert!(fc.get_window("p").is_none());
413 fc.send("p", 10).expect("ok");
414 let w = fc.get_window("p").expect("exists");
415 assert_eq!(w.window_size, 65_536);
416 assert_eq!(w.peer_id, "p");
417 }
418
419 #[test]
422 fn test_peer_count_empty() {
423 let fc = default_ctrl();
424 assert_eq!(fc.peer_count(), 0);
425 }
426
427 #[test]
428 fn test_peer_count_multiple() {
429 let mut fc = default_ctrl();
430 fc.send("a", 1).expect("ok");
431 fc.send("b", 1).expect("ok");
432 fc.send("c", 1).expect("ok");
433 assert_eq!(fc.peer_count(), 3);
434 }
435
436 #[test]
439 fn test_stats_empty() {
440 let fc = default_ctrl();
441 let s = fc.stats();
442 assert_eq!(s.peer_count, 0);
443 assert_eq!(s.total_stalls, 0);
444 assert!((s.avg_utilization - 0.0).abs() < f64::EPSILON);
445 }
446
447 #[test]
448 fn test_stats_with_peers() {
449 let mut fc = default_ctrl();
450 fc.send("a", 65_536).expect("ok"); fc.send("b", 0).expect("ok"); let _ = fc.send("a", 1); let s = fc.stats();
454 assert_eq!(s.peer_count, 2);
455 assert_eq!(s.total_stalls, 1);
456 assert!((s.avg_utilization - 0.5).abs() < f64::EPSILON);
457 }
458
459 #[test]
462 fn test_ack_nonexistent_peer_noop() {
463 let mut fc = default_ctrl();
464 fc.ack("ghost", 1000); }
466
467 #[test]
470 fn test_ack_more_than_in_flight() {
471 let mut fc = default_ctrl();
472 fc.send("p", 100).expect("ok");
473 fc.ack("p", 500); let w = fc.get_window("p").expect("exists");
475 assert_eq!(w.bytes_in_flight, 0);
476 assert_eq!(w.bytes_acked, 500);
477 }
478
479 #[test]
482 fn test_multiple_sends_accumulate() {
483 let mut fc = default_ctrl();
484 fc.send("p", 10_000).expect("ok");
485 fc.send("p", 20_000).expect("ok");
486 fc.send("p", 30_000).expect("ok");
487 let w = fc.get_window("p").expect("exists");
488 assert_eq!(w.bytes_in_flight, 60_000);
489 assert_eq!(w.bytes_sent, 60_000);
490 }
491
492 #[test]
495 fn test_grow_allows_more_sends() {
496 let mut fc = default_ctrl();
497 fc.send("p", 65_536).expect("fill window");
498 assert!(fc.send("p", 1).is_err());
499 fc.ack("p", 65_536);
500 fc.grow_window("p"); fc.send("p", 98_304).expect("ok with grown window");
502 let w = fc.get_window("p").expect("exists");
503 assert_eq!(w.bytes_in_flight, 98_304);
504 }
505
506 #[test]
509 fn test_shrink_reduces_capacity() {
510 let mut fc = default_ctrl();
511 fc.send("p", 0).expect("ok");
512 fc.shrink_window("p"); assert!(fc.can_send("p", 32_768));
514 assert!(!fc.can_send("p", 32_769));
515 }
516
517 #[test]
520 fn test_custom_config() {
521 let cfg = FlowControlConfig {
522 initial_window: 1000,
523 max_window: 5000,
524 min_window: 100,
525 growth_factor: 2.0,
526 shrink_factor: 0.25,
527 };
528 let mut fc = PeerFlowControl::new(cfg);
529 fc.send("p", 0).expect("ok");
530 assert_eq!(fc.get_window("p").expect("e").window_size, 1000);
531
532 fc.grow_window("p");
533 assert_eq!(fc.get_window("p").expect("e").window_size, 2000);
534
535 fc.grow_window("p");
536 assert_eq!(fc.get_window("p").expect("e").window_size, 4000);
537
538 fc.grow_window("p"); assert_eq!(fc.get_window("p").expect("e").window_size, 5000);
540
541 fc.shrink_window("p"); assert_eq!(fc.get_window("p").expect("e").window_size, 1250);
543
544 fc.shrink_window("p"); fc.shrink_window("p"); assert_eq!(fc.get_window("p").expect("e").window_size, 100);
547 }
548
549 #[test]
552 fn test_stats_avg_utilization_three_peers() {
553 let mut fc = default_ctrl();
554 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();
559 let expected = (1.0 + 0.5 + 0.25) / 3.0;
560 assert!((s.avg_utilization - expected).abs() < 1e-10);
561 }
562
563 #[test]
566 fn test_stalls_across_multiple_peers() {
567 let mut fc = default_ctrl();
568 fc.send("a", 65_536).expect("ok");
569 fc.send("b", 65_536).expect("ok");
570 let _ = fc.send("a", 1);
571 let _ = fc.send("b", 1);
572 let _ = fc.send("a", 1);
573 assert_eq!(fc.total_stalls, 3);
574 assert_eq!(fc.stats().total_stalls, 3);
575 }
576
577 #[test]
580 fn test_default_config() {
581 let cfg = FlowControlConfig::default();
582 assert_eq!(cfg.initial_window, 65_536);
583 assert_eq!(cfg.max_window, 1_048_576);
584 assert_eq!(cfg.min_window, 4096);
585 assert!((cfg.growth_factor - 1.5).abs() < f64::EPSILON);
586 assert!((cfg.shrink_factor - 0.5).abs() < f64::EPSILON);
587 }
588
589 #[test]
592 fn test_remove_and_readd_peer() {
593 let mut fc = default_ctrl();
594 fc.send("p", 100).expect("ok");
595 fc.grow_window("p");
596 fc.remove_peer("p");
597
598 fc.send("p", 50).expect("ok");
599 let w = fc.get_window("p").expect("exists");
600 assert_eq!(w.window_size, 65_536);
602 assert_eq!(w.bytes_in_flight, 50);
603 assert_eq!(w.bytes_sent, 50);
604 }
605}