#![allow(dead_code)]
use alloc::vec::Vec;
use core::time::Duration;
const INITIAL_TIMEOUT: Duration = Duration::from_millis(1000);
const MAX_TIMEOUT: Duration = Duration::from_secs(60);
const MAX_RETRANSMITS: u32 = 6;
type Instant = Duration;
#[derive(Default)]
pub(crate) struct Flight {
pub(crate) datagrams: Vec<Vec<u8>>,
}
impl Flight {
pub(crate) fn new() -> Self {
Self {
datagrams: Vec::new(),
}
}
pub(crate) fn push(&mut self, datagram: Vec<u8>) {
self.datagrams.push(datagram);
}
pub(crate) fn is_empty(&self) -> bool {
self.datagrams.is_empty()
}
pub(crate) fn clear(&mut self) {
self.datagrams.clear();
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Action {
Idle,
Retransmit,
GiveUp,
}
pub(crate) struct Retransmit {
last_flight: Flight,
deadline: Option<Instant>,
timeout: Duration,
attempts: u32,
}
impl Retransmit {
pub(crate) fn new() -> Self {
Self {
last_flight: Flight::new(),
deadline: None,
timeout: INITIAL_TIMEOUT,
attempts: 0,
}
}
pub(crate) fn set_flight(&mut self, flight: Flight, now: Instant) {
self.last_flight = flight;
self.timeout = INITIAL_TIMEOUT;
self.attempts = 0;
self.deadline = Some(now + INITIAL_TIMEOUT);
}
pub(crate) fn next_timeout(&self) -> Option<Instant> {
self.deadline
}
pub(crate) fn on_timeout(&mut self, now: Instant) -> Action {
let Some(deadline) = self.deadline else {
return Action::Idle;
};
if now < deadline {
return Action::Idle;
}
if self.attempts >= MAX_RETRANSMITS {
self.deadline = None;
return Action::GiveUp;
}
self.attempts += 1;
self.timeout = (self.timeout * 2).min(MAX_TIMEOUT);
self.deadline = Some(now + self.timeout);
Action::Retransmit
}
pub(crate) fn on_peer_response(&mut self) {
self.last_flight.clear();
self.deadline = None;
self.timeout = INITIAL_TIMEOUT;
self.attempts = 0;
}
pub(crate) fn flight_datagrams(&self) -> &[Vec<u8>] {
&self.last_flight.datagrams
}
}
impl Default for Retransmit {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn flight_of(datagrams: &[&[u8]]) -> Flight {
let mut f = Flight::new();
for d in datagrams {
f.push(d.to_vec());
}
f
}
#[test]
fn initial_deadline_is_one_second_after_set() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"hello"]), Duration::from_secs(0));
assert_eq!(r.next_timeout(), Some(Duration::from_secs(1)));
}
#[test]
fn timeout_doubles_each_attempt() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"a"]), Duration::from_secs(0));
assert_eq!(r.next_timeout(), Some(Duration::from_secs(1)));
assert_eq!(r.on_timeout(Duration::from_secs(1)), Action::Retransmit);
assert_eq!(r.next_timeout(), Some(Duration::from_secs(3)));
assert_eq!(r.on_timeout(Duration::from_secs(3)), Action::Retransmit);
assert_eq!(r.next_timeout(), Some(Duration::from_secs(7)));
}
#[test]
fn caps_at_60_seconds() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"x"]), Duration::from_secs(0));
let mut now = Duration::from_secs(0);
for _ in 0..MAX_RETRANSMITS {
now = r.next_timeout().expect("deadline armed");
assert_eq!(r.on_timeout(now), Action::Retransmit);
}
let final_deadline = r.next_timeout().expect("still armed");
assert_eq!(final_deadline - now, MAX_TIMEOUT);
}
#[test]
fn gives_up_after_max_attempts() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"x"]), Duration::from_secs(0));
for _ in 0..MAX_RETRANSMITS {
let now = r.next_timeout().expect("deadline armed");
assert_eq!(r.on_timeout(now), Action::Retransmit);
}
let now = r.next_timeout().expect("deadline still armed");
assert_eq!(r.on_timeout(now), Action::GiveUp);
assert_eq!(r.next_timeout(), None);
}
#[test]
fn on_peer_response_clears_state() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"x", b"y"]), Duration::from_secs(0));
assert!(r.next_timeout().is_some());
r.on_peer_response();
assert_eq!(r.next_timeout(), None);
assert!(r.flight_datagrams().is_empty());
}
#[test]
fn idle_when_called_early() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"x"]), Duration::from_secs(0));
let before = r.next_timeout();
assert_eq!(r.on_timeout(Duration::from_millis(500)), Action::Idle);
assert_eq!(r.next_timeout(), before);
}
#[test]
fn idle_with_no_flight_pending() {
let mut r = Retransmit::new();
assert_eq!(r.on_timeout(Duration::from_secs(10)), Action::Idle);
assert_eq!(r.next_timeout(), None);
}
#[test]
fn simulate_dropped_first_flight() {
let dg1: Vec<u8> = vec![0x16, 0xfe, 0xfd, 0xaa];
let dg2: Vec<u8> = vec![0x16, 0xfe, 0xfd, 0xbb];
let mut flight = Flight::new();
flight.push(dg1.clone());
flight.push(dg2.clone());
let mut r = Retransmit::new();
r.set_flight(flight, Duration::from_secs(0));
assert_eq!(r.on_timeout(Duration::from_secs(1)), Action::Retransmit);
let out = r.flight_datagrams();
assert_eq!(out.len(), 2);
assert_eq!(&out[0], &dg1);
assert_eq!(&out[1], &dg2);
}
#[test]
fn set_flight_resets_attempts_and_timeout() {
let mut r = Retransmit::new();
r.set_flight(flight_of(&[b"x"]), Duration::from_secs(0));
assert_eq!(r.on_timeout(Duration::from_secs(1)), Action::Retransmit);
r.set_flight(flight_of(&[b"y"]), Duration::from_secs(10));
assert_eq!(r.next_timeout(), Some(Duration::from_secs(11)));
}
#[test]
fn flight_helpers() {
let mut f = Flight::new();
assert!(f.is_empty());
f.push(vec![1, 2, 3]);
assert!(!f.is_empty());
f.clear();
assert!(f.is_empty());
}
}