use crate::seq::Seq;
pub const MAX_FAST_REQUESTS: u32 = 8;
pub const MAX_RANGES: usize = 1024;
const NEAR_DEADLINE_RTTS: i64 = 3;
const MIN_INTERVAL: i64 = 10_000;
const MIN_FAST_INTERVAL: i64 = 2_500;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Loss {
pub first: Seq,
pub last: Seq,
pub deadline: i64,
pub last_request: i64,
pub requests: u32,
pub fast_requests: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timing {
pub rtt: i64,
pub retry: i64,
}
impl Timing {
pub const fn rtt(rtt: i64) -> Self {
Self { rtt, retry: rtt }
}
}
#[derive(Debug, Default, Clone)]
pub struct LossList {
entries: Vec<Loss>,
}
impl LossList {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn ranges(&self) -> usize {
self.entries.len()
}
pub fn count(&self) -> u64 {
self.entries
.iter()
.map(|e| e.first.distance_to(e.last) as u64 + 1)
.sum()
}
pub fn contains(&self, s: Seq) -> bool {
self.entries.iter().any(|e| e.first <= s && s <= e.last)
}
pub fn first(&self) -> Option<Seq> {
self.entries.first().map(|e| e.first)
}
pub fn add(&mut self, first: Seq, last: Seq, now: i64, deadline: i64) {
if last < first || self.entries.len() >= MAX_RANGES {
return;
}
let e = Loss {
first,
last,
deadline,
last_request: now,
requests: 1,
fast_requests: 0,
};
let at = self
.entries
.iter()
.rposition(|x| x.last < first)
.map_or(0, |i| i + 1);
if self.entries.get(at).is_some_and(|x| x.first <= last) {
return;
}
self.entries.insert(at, e);
}
pub fn remove(&mut self, s: Seq) -> bool {
let Some(i) = self
.entries
.iter()
.position(|e| e.first <= s && s <= e.last)
else {
return false;
};
let Some(e) = self.entries.get_mut(i) else {
return false;
};
match (e.first == s, e.last == s) {
(true, true) => {
self.entries.remove(i);
}
(true, false) => e.first = s.next(),
(false, true) => e.last = s.prev(),
(false, false) => {
let mut tail = *e;
e.last = s.prev();
tail.first = s.next();
self.entries.insert(i + 1, tail);
self.entries.truncate(MAX_RANGES);
}
}
true
}
pub fn remove_before(&mut self, s: Seq) {
self.entries.retain_mut(|e| {
if e.last < s {
return false;
}
if e.first < s {
e.first = s;
}
true
});
}
pub fn remove_range(&mut self, first: Seq, last: Seq) {
if last < first {
return;
}
let mut kept = Vec::with_capacity(self.entries.len() + 1);
for e in self.entries.drain(..) {
if e.last < first || last < e.first {
kept.push(e);
continue;
}
if e.first < first {
kept.push(Loss {
last: first.prev(),
..e
});
}
if last < e.last {
kept.push(Loss {
first: last.next(),
..e
});
}
}
kept.truncate(MAX_RANGES);
self.entries = kept;
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn due(&mut self, now: i64, t: Timing, limit: usize) -> Vec<(Seq, Seq)> {
let rtt = t.rtt.max(1);
let mut out = Vec::new();
for e in &mut self.entries {
if out.len() >= limit {
break;
}
if !matches!(e.next_request(now, t), Some(at) if at <= now) {
continue;
}
if e.near(now, rtt) && e.fast_requests < MAX_FAST_REQUESTS {
e.fast_requests += 1;
}
e.last_request = now;
e.requests = e.requests.saturating_add(1);
out.push((e.first, e.last));
}
out
}
pub fn next_due(&self, now: i64, t: Timing) -> Option<i64> {
self.entries
.iter()
.filter_map(|e| e.next_request(now, t))
.map(|at| at.max(now))
.min()
}
}
impl Loss {
fn near(&self, t: i64, rtt: i64) -> bool {
self.deadline.saturating_sub(t) < NEAR_DEADLINE_RTTS * rtt
}
fn next_request(&self, now: i64, t: Timing) -> Option<i64> {
let rtt = t.rtt.max(1);
let ordinary = self
.last_request
.saturating_add(t.retry.max(rtt).max(MIN_INTERVAL));
let at = if self.fast_requests < MAX_FAST_REQUESTS {
let near_from = self.deadline.saturating_sub(NEAR_DEADLINE_RTTS * rtt) + 1;
let fast = self
.last_request
.saturating_add((rtt / 4).max(MIN_FAST_INTERVAL))
.max(near_from);
ordinary.min(fast)
} else {
ordinary
};
let useful = |t: i64| t.saturating_add(rtt) <= self.deadline;
(useful(now) && useful(at)).then_some(at)
}
}
#[cfg(test)]
mod tests {
use super::*;
const RTT: i64 = 60_000;
fn s(v: u32) -> Seq {
Seq::new(v)
}
#[test]
fn add_remove_and_split() {
let mut l = LossList::new();
l.add(s(10), s(14), 0, 1_000_000);
l.add(s(20), s(20), 0, 1_000_000);
assert_eq!(l.count(), 6);
assert!(l.remove(s(12)));
assert!(!l.remove(s(12)));
assert!(l.remove(s(10)));
assert!(l.remove(s(14)));
assert_eq!(l.count(), 3);
assert!(l.contains(s(11)) && l.contains(s(13)) && l.contains(s(20)));
assert!(!l.contains(s(12)));
l.remove_before(s(13));
assert_eq!(l.first(), Some(s(13)));
l.remove_before(s(21));
assert!(l.is_empty());
}
#[test]
fn ranges_across_the_wrap() {
let mut l = LossList::new();
l.add(s(Seq::MAX - 1), s(1), 0, 1_000_000);
assert_eq!(l.count(), 4);
assert!(l.remove(s(0)));
assert_eq!(l.ranges(), 2);
l.remove_before(s(1));
assert_eq!(l.count(), 1);
assert_eq!(l.first(), Some(s(1)));
}
#[test]
fn the_list_is_bounded() {
let mut l = LossList::new();
for i in 0..(MAX_RANGES as u32 + 10) {
l.add(s(i * 2), s(i * 2), 0, 1_000_000);
}
assert_eq!(l.ranges(), MAX_RANGES);
let mut l = LossList::new();
l.add(s(0), s(10_000), 0, 1_000_000);
for i in 0..5000 {
l.remove(s(i * 2 + 1));
}
assert!(l.ranges() <= MAX_RANGES);
}
#[test]
fn remove_a_range() {
let mut l = LossList::new();
l.add(s(10), s(20), 0, 1_000_000);
l.add(s(30), s(30), 0, 1_000_000);
l.add(s(40), s(45), 0, 1_000_000);
l.remove_range(s(15), s(42));
assert_eq!(l.count(), 5 + 3);
assert!(l.contains(s(14)) && !l.contains(s(15)));
assert!(!l.contains(s(30)) && !l.contains(s(42)) && l.contains(s(43)));
l.remove_range(s(12), s(11));
assert_eq!(l.count(), 8, "an empty range removes nothing");
l.remove_range(s(11), s(11));
assert_eq!(l.ranges(), 3, "a hole splits a range");
}
#[test]
fn overlapping_adds_are_ignored() {
let mut l = LossList::new();
l.add(s(5), s(9), 0, 10);
l.add(s(7), s(12), 0, 10);
assert_eq!(l.count(), 5);
l.add(s(3), s(1), 0, 10);
assert_eq!(l.count(), 5, "an empty range is nothing");
l.add(s(1), s(2), 0, 10);
assert_eq!(l.first(), Some(s(1)), "sorted");
}
#[test]
fn asks_again_every_round_trip() {
let mut l = LossList::new();
let deadline = 2_000_000;
l.add(s(1), s(1), 0, deadline);
assert!(l.due(RTT - 1, Timing::rtt(RTT), 10).is_empty());
assert_eq!(l.next_due(RTT - 1, Timing::rtt(RTT)), Some(RTT));
assert_eq!(l.due(RTT, Timing::rtt(RTT), 10), vec![(s(1), s(1))]);
assert!(l.due(RTT + 1, Timing::rtt(RTT), 10).is_empty());
assert_eq!(l.due(2 * RTT, Timing::rtt(RTT), 10).len(), 1);
}
#[test]
fn asks_every_quarter_round_trip_near_the_deadline_up_to_a_cap() {
let mut l = LossList::new();
let deadline = 1_000_000;
l.add(s(1), s(1), 0, deadline);
let near = deadline - 3 * RTT + 1;
assert_eq!(l.due(near, Timing::rtt(RTT), 10).len(), 1);
let mut t = near;
let mut asked = 1;
loop {
t += RTT / 4;
if t + RTT > deadline {
break;
}
asked += l.due(t, Timing::rtt(RTT), 10).len();
}
assert!(asked <= 1 + MAX_FAST_REQUESTS as usize, "{asked}");
assert!(asked >= 7, "{asked}");
assert!(l.due(deadline - RTT + 1, Timing::rtt(RTT), 10).is_empty());
assert_eq!(l.next_due(deadline - RTT + 1, Timing::rtt(RTT)), None);
}
#[test]
fn never_asks_when_a_retransmission_would_be_too_late() {
let mut l = LossList::new();
l.add(s(1), s(3), 0, RTT / 2);
assert!(l.due(RTT, Timing::rtt(RTT), 10).is_empty());
assert_eq!(l.next_due(RTT, Timing::rtt(RTT)), None);
}
#[test]
fn the_next_request_is_never_in_the_past_nor_useless() {
let mut l = LossList::new();
let deadline = 1_000_000;
l.add(s(1), s(1), 0, deadline);
assert_eq!(l.next_due(500_000, Timing::rtt(RTT)), Some(500_000));
assert_eq!(l.next_due(deadline - RTT + 1, Timing::rtt(RTT)), None);
let mut l = LossList::new();
l.add(s(1), s(1), 0, RTT + RTT / 8);
assert_eq!(l.next_due(0, Timing::rtt(RTT)), None);
}
#[test]
fn tiny_round_trips_do_not_flood() {
let mut l = LossList::new();
l.add(s(1), s(1), 0, 10_000_000);
assert_eq!(l.next_due(0, Timing::rtt(3)), Some(MIN_INTERVAL));
let mut asked = 0;
for t in 0..=100_000 {
asked += l.due(t, Timing::rtt(3), 10).len();
}
assert_eq!(asked, 10, "every 10 ms over 100 ms");
}
#[test]
fn limit_keeps_the_earliest() {
let mut l = LossList::new();
for i in 0..10 {
l.add(s(i * 2), s(i * 2), 0, 10_000_000);
}
let d = l.due(RTT, Timing::rtt(RTT), 3);
assert_eq!(d, vec![(s(0), s(0)), (s(2), s(2)), (s(4), s(4))]);
assert_eq!(l.due(RTT, Timing::rtt(RTT), 100).len(), 7);
}
}