use std::collections::BTreeMap;
use std::ops::Range;
use std::time::{Duration, Instant};
use crate::constants;
use super::frame::Ack;
use super::stream_id::Dir;
use super::streams::StreamRef;
#[derive(Debug, Clone)]
pub(crate) struct SentPacket {
pub(crate) counter: u64,
pub(crate) time_sent: Instant,
pub(crate) size: u64,
pub(crate) app_limited: bool,
pub(crate) path_gen: u32,
pub(crate) frames: Vec<SentFrame>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum SentFrame {
Stream {
r: StreamRef,
range: Range<u64>,
fin: bool,
},
ResetStream {
r: StreamRef,
},
MaxData,
MaxStreamData {
r: StreamRef,
},
MaxStreams {
dir: Dir,
},
}
pub(crate) struct Recovery {
sent: BTreeMap<u64, SentPacket>,
rtt: RttEstimator,
largest_acked: Option<u64>,
loss_time: Option<Instant>,
last_ack_eliciting: Option<Instant>,
pto_count: u32,
bytes_in_flight: u64,
path_gen: u32,
}
#[derive(Debug, Default)]
pub(crate) struct AckOutcome {
pub(crate) acked: Vec<SentFrame>,
pub(crate) lost: Vec<SentFrame>,
pub(crate) ack_events: Vec<(Instant, u64, bool)>,
pub(crate) congestion: Option<CongestionEvent>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CongestionEvent {
pub(crate) sent_time: Instant,
pub(crate) is_persistent: bool,
pub(crate) lost_bytes: u64,
}
const PTO_MAX_EXPONENT: u32 = constants::PTO_BACKOFF_CAP.trailing_zeros();
const _: () = assert!(constants::PTO_BACKOFF_CAP.is_power_of_two());
const _: () = assert!(PTO_MAX_EXPONENT == 3);
impl Recovery {
pub(crate) fn new() -> Self {
Self {
sent: BTreeMap::new(),
rtt: RttEstimator::new(),
largest_acked: None,
loss_time: None,
last_ack_eliciting: None,
pto_count: 0,
bytes_in_flight: 0,
path_gen: 0,
}
}
pub(crate) fn path_gen(&self) -> u32 {
self.path_gen
}
pub(crate) fn on_sent(&mut self, packet: SentPacket) {
self.last_ack_eliciting = Some(packet.time_sent);
self.bytes_in_flight += packet.size;
let counter = packet.counter;
let previous = self.sent.insert(counter, packet);
debug_assert!(
previous.is_none(),
"§7.1: a counter is sealed once, so the map cannot collide at {counter}"
);
self.debug_check_in_flight();
}
pub(crate) fn on_ack(&mut self, now: Instant, ack: &Ack, highest_sealed: u64) -> AckOutcome {
if ack.largest > highest_sealed {
tracing::debug!(
target: "slither::frames",
largest = ack.largest,
highest_sealed,
"an ACK above the highest counter sealed is ignored whole"
);
return AckOutcome::default();
}
let mut newly: Vec<u64> = Vec::new();
for block in ack.ranges_desc() {
newly.extend(self.sent.range(block).map(|(counter, _)| *counter));
}
newly.sort_unstable();
if newly.is_empty() {
return AckOutcome::default();
}
let sample_from = newly
.last()
.filter(|counter| **counter == ack.largest)
.copied();
let mut outcome = AckOutcome::default();
let mut sample_path_gen = None;
for counter in &newly {
let packet = self
.sent
.remove(counter)
.expect("the counter came from this map and nothing removed it since");
self.bytes_in_flight -= packet.size;
if sample_from == Some(*counter) {
sample_path_gen = Some(packet.path_gen);
}
outcome
.ack_events
.push((packet.time_sent, packet.size, packet.app_limited));
outcome.acked.extend(packet.frames);
}
if let Some(counter) = sample_from {
let (sent_at, _, _) = *outcome
.ack_events
.last()
.expect("newly is non-empty, so at least one event was pushed");
debug_assert_eq!(counter, ack.largest);
if sample_path_gen == Some(self.path_gen) {
self.rtt.sample(
now.saturating_duration_since(sent_at),
Duration::from_micros(ack.ack_delay),
);
}
}
self.pto_count = 0;
self.largest_acked = Some(match self.largest_acked {
Some(previous) => previous.max(ack.largest),
None => ack.largest,
});
let (lost, congestion) = self.detect_lost(now, &newly);
outcome.lost = lost;
outcome.congestion = congestion;
self.debug_check_in_flight();
outcome
}
pub(crate) fn on_loss_timeout(&mut self, now: Instant) -> AckOutcome {
let (lost, congestion) = self.detect_lost(now, &[]);
self.debug_check_in_flight();
AckOutcome {
lost,
congestion,
..AckOutcome::default()
}
}
pub(crate) fn on_pto_timeout(&mut self) {
self.pto_count = self.pto_count.saturating_add(1).min(PTO_MAX_EXPONENT);
}
pub(crate) fn loss_deadline(&self) -> Option<Instant> {
self.loss_time
}
pub(crate) fn pto_deadline(&self) -> Option<Instant> {
if self.sent.is_empty() {
return None;
}
let anchor = self.last_ack_eliciting?;
let multiplier = 1u32 << self.pto_count.min(PTO_MAX_EXPONENT);
debug_assert!(multiplier <= constants::PTO_BACKOFF_CAP);
let interval = self
.rtt
.pto_interval()
.checked_mul(multiplier)
.unwrap_or(Duration::MAX);
anchor.checked_add(interval)
}
pub(crate) fn bytes_in_flight(&self) -> u64 {
self.bytes_in_flight
}
pub(crate) fn is_empty(&self) -> bool {
self.sent.is_empty()
}
pub(crate) fn rtt(&self) -> &RttEstimator {
&self.rtt
}
pub(crate) fn on_roam(&mut self, now: Instant) {
let _ = now;
self.path_gen = self.path_gen.saturating_add(1);
self.rtt.reseed_min_rtt();
}
fn detect_lost(
&mut self,
now: Instant,
newly_acked: &[u64],
) -> (Vec<SentFrame>, Option<CongestionEvent>) {
self.loss_time = None;
let Some(largest_acked) = self.largest_acked else {
return (Vec::new(), None);
};
let loss_delay = self.rtt.loss_delay();
let mut lost: Vec<u64> = Vec::new();
for (counter, packet) in self.sent.range(..=largest_acked) {
let by_count = largest_acked - counter >= constants::K_PACKET_THRESHOLD;
let by_time = now.saturating_duration_since(packet.time_sent) >= loss_delay;
if by_count || by_time {
lost.push(*counter);
} else if let Some(at) = packet.time_sent.checked_add(loss_delay) {
self.loss_time = Some(match self.loss_time {
Some(current) => current.min(at),
None => at,
});
}
}
if lost.is_empty() {
return (Vec::new(), None);
}
let is_persistent = self.persistent_congestion(&lost, newly_acked);
let mut frames = Vec::new();
let mut lost_bytes = 0u64;
let mut earliest: Option<Instant> = None;
for counter in &lost {
let packet = self
.sent
.remove(counter)
.expect("the counter came from this map and nothing removed it since");
self.bytes_in_flight -= packet.size;
lost_bytes += packet.size;
earliest = Some(match earliest {
Some(current) => current.min(packet.time_sent),
None => packet.time_sent,
});
frames.extend(packet.frames);
}
let event = CongestionEvent {
sent_time: earliest.expect("lost is non-empty"),
is_persistent,
lost_bytes,
};
(frames, Some(event))
}
fn persistent_congestion(&self, lost: &[u64], newly_acked: &[u64]) -> bool {
if !self.rtt.has_sample() {
return false;
}
let Some(period) = self
.rtt
.pto_interval()
.checked_mul(constants::PERSISTENT_CONGESTION_THRESHOLD)
else {
return false;
};
let mut run_start: Option<Instant> = None;
let mut previous: Option<u64> = None;
for counter in lost {
let packet = &self.sent[counter];
if packet.path_gen != self.path_gen {
continue;
}
let sent_at = packet.time_sent;
let broken = match previous {
None => true,
Some(previous) => newly_acked
.iter()
.any(|acked| *acked > previous && *acked < *counter),
};
if broken {
run_start = Some(sent_at);
}
if let Some(first) = run_start
&& sent_at.saturating_duration_since(first) > period
{
return true;
}
previous = Some(*counter);
}
false
}
fn debug_check_in_flight(&self) {
debug_assert_eq!(
self.bytes_in_flight,
self.sent.values().map(|p| p.size).sum::<u64>(),
"§14.5: bytes_in_flight is the sum of the map's sizes"
);
}
}
#[derive(Debug, Clone)]
pub(crate) struct RttEstimator {
latest: Duration,
smoothed: Option<Duration>,
rttvar: Duration,
min_rtt: Option<Duration>,
}
impl Default for RttEstimator {
fn default() -> Self {
Self::new()
}
}
impl RttEstimator {
pub(crate) fn new() -> Self {
Self {
latest: Duration::ZERO,
smoothed: None,
rttvar: constants::K_INITIAL_RTT / 2,
min_rtt: None,
}
}
pub(crate) fn sample(&mut self, latest: Duration, ack_delay: Duration) {
self.latest = latest;
let Some(smoothed) = self.smoothed else {
self.smoothed = Some(latest);
self.rttvar = latest / 2;
self.min_rtt = Some(latest);
return;
};
let min_rtt = match self.min_rtt {
Some(current) => current.min(latest),
None => latest,
};
self.min_rtt = Some(min_rtt);
let capped = ack_delay.min(constants::MAX_ACK_DELAY);
let adjusted = if latest >= min_rtt + capped {
latest - capped
} else {
latest
};
let deviation = smoothed.abs_diff(adjusted);
self.rttvar = self.rttvar * 3 / 4 + deviation / 4;
self.smoothed = Some(smoothed * 7 / 8 + adjusted / 8);
}
pub(crate) fn smoothed_rtt(&self) -> Duration {
self.smoothed.unwrap_or(constants::K_INITIAL_RTT)
}
pub(crate) fn rttvar(&self) -> Duration {
self.rttvar
}
pub(crate) fn min_rtt(&self) -> Option<Duration> {
self.min_rtt
}
pub(crate) fn has_sample(&self) -> bool {
self.smoothed.is_some()
}
pub(crate) fn loss_delay(&self) -> Duration {
let base = self.smoothed_rtt().max(self.latest);
(base * 9 / 8).max(constants::K_GRANULARITY)
}
pub(crate) fn pto_interval(&self) -> Duration {
self.smoothed_rtt()
+ (self.rttvar * 4).max(constants::K_GRANULARITY)
+ constants::MAX_ACK_DELAY
}
pub(crate) fn reseed_min_rtt(&mut self) {
self.min_rtt = None;
}
}