Skip to main content

rtc_sctp/association/
timer.rs

1use std::time::{Duration, Instant};
2
3pub(crate) const ACK_INTERVAL: u64 = 200;
4const MAX_INIT_RETRANS: usize = 8;
5const PATH_MAX_RETRANS: usize = 5;
6const NO_MAX_RETRANS: usize = usize::MAX;
7const TIMER_COUNT: usize = 6;
8
9#[derive(Debug, Copy, Clone)]
10/// Retransmission limits for the association's timers.
11///
12/// Each field caps how many times the corresponding timer may fire before the association is
13/// abandoned. `Default` follows the RFC 4960 recommendations.
14pub struct TimerConfig {
15    /// How many times INIT may be retransmitted (T1-init) before the association fails.
16    pub max_t1_init_retrans: usize,
17    /// How many times COOKIE-ECHO may be retransmitted (T1-cookie).
18    pub max_t1_cookie_retrans: usize,
19    /// How many times SHUTDOWN may be retransmitted (T2-shutdown).
20    pub max_t2_shutdown_retrans: usize,
21    /// How many times a DATA chunk may be retransmitted on T3-rtx expiry.
22    ///
23    /// Defaults to unlimited, leaving reliability to the per-stream partial-reliability settings.
24    pub max_t3_rtx_retrans: usize,
25    /// How many times a RE-CONFIG chunk (stream reset) may be retransmitted.
26    pub max_reconfig_retrans: usize,
27    /// How many times a delayed SACK may be retransmitted.
28    pub max_ack_retrans: usize,
29}
30
31impl Default for TimerConfig {
32    fn default() -> Self {
33        Self {
34            max_t1_init_retrans: MAX_INIT_RETRANS,
35            max_t1_cookie_retrans: MAX_INIT_RETRANS,
36            max_t2_shutdown_retrans: NO_MAX_RETRANS,
37            max_t3_rtx_retrans: PATH_MAX_RETRANS,
38            max_reconfig_retrans: PATH_MAX_RETRANS,
39            max_ack_retrans: PATH_MAX_RETRANS,
40        }
41    }
42}
43
44#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
45pub(crate) enum Timer {
46    T1Init = 0,
47    T1Cookie = 1,
48    T2Shutdown = 2,
49    T3RTX = 3,
50    Reconfig = 4,
51    Ack = 5,
52}
53
54impl Timer {
55    pub(crate) const VALUES: [Self; TIMER_COUNT] = [
56        Timer::T1Init,
57        Timer::T1Cookie,
58        Timer::T2Shutdown,
59        Timer::T3RTX,
60        Timer::Reconfig,
61        Timer::Ack,
62    ];
63}
64
65/// A table of data associated with each distinct kind of `Timer`
66#[derive(Debug, Copy, Clone, Default)]
67pub(crate) struct TimerTable {
68    data: [Option<Instant>; TIMER_COUNT],
69    retrans: [usize; TIMER_COUNT],
70    max_retrans: [usize; TIMER_COUNT],
71}
72
73impl TimerTable {
74    pub fn new(time_config: TimerConfig) -> Self {
75        TimerTable {
76            max_retrans: [
77                time_config.max_t1_init_retrans,     //T1Init
78                time_config.max_t1_cookie_retrans,   //T1Cookie
79                time_config.max_t2_shutdown_retrans, //T2Shutdown
80                time_config.max_t3_rtx_retrans,      //T3RTX
81                time_config.max_reconfig_retrans,    //Reconfig
82                time_config.max_ack_retrans,         //Ack
83            ],
84            ..Default::default()
85        }
86    }
87
88    pub fn set(&mut self, timer: Timer, time: Option<Instant>) {
89        self.data[timer as usize] = time;
90    }
91
92    pub fn get(&self, timer: Timer) -> Option<Instant> {
93        self.data[timer as usize]
94    }
95
96    pub fn next_timeout(&self) -> Option<Instant> {
97        self.data.iter().filter_map(|&x| x).min()
98    }
99
100    pub fn start(&mut self, timer: Timer, now: Instant, interval: u64) {
101        let interval = if timer == Timer::Ack {
102            interval
103        } else {
104            calculate_next_timeout(interval, self.retrans[timer as usize])
105        };
106
107        let time = now + Duration::from_millis(interval);
108        self.data[timer as usize] = Some(time);
109    }
110
111    /// Restarts the timer if the current instant is none or elapsed.
112    pub fn restart_if_stale(&mut self, timer: Timer, now: Instant, interval: u64) {
113        if let Some(current) = self.data[timer as usize]
114            && current >= now
115        {
116            return;
117        }
118
119        self.start(timer, now, interval);
120    }
121
122    pub fn stop(&mut self, timer: Timer) {
123        self.data[timer as usize] = None;
124        self.retrans[timer as usize] = 0;
125    }
126
127    pub fn is_expired(&mut self, timer: Timer, after: Instant) -> (bool, bool, usize) {
128        let expired = self.data[timer as usize].is_some_and(|x| x <= after);
129        let mut failure = false;
130        if expired {
131            self.retrans[timer as usize] += 1;
132            if self.retrans[timer as usize] > self.max_retrans[timer as usize] {
133                failure = true;
134            }
135        }
136
137        (expired, failure, self.retrans[timer as usize])
138    }
139}
140
141const RTO_INITIAL: u64 = 3000; // msec
142const RTO_MIN: u64 = 1000; // msec
143const RTO_MAX: u64 = 60000; // msec
144const RTO_ALPHA: u64 = 1;
145const RTO_BETA: u64 = 2;
146const RTO_BASE: u64 = 8;
147
148/// rtoManager manages Rtx timeout values.
149/// This is an implementation of RFC 4960 sec 6.3.1.
150#[derive(Default, Debug)]
151pub(crate) struct RtoManager {
152    pub(crate) srtt: u64,
153    pub(crate) rttvar: f64,
154    pub(crate) rto: u64,
155    pub(crate) no_update: bool,
156}
157
158impl RtoManager {
159    /// newRTOManager creates a new rtoManager.
160    pub(crate) fn new() -> Self {
161        RtoManager {
162            rto: RTO_INITIAL,
163            ..Default::default()
164        }
165    }
166
167    /// set_new_rtt takes a newly measured RTT then adjust the RTO in msec.
168    pub(crate) fn set_new_rtt(&mut self, rtt: u64) -> u64 {
169        if self.no_update {
170            return self.srtt;
171        }
172
173        if self.srtt == 0 {
174            // First measurement
175            self.srtt = rtt;
176            self.rttvar = rtt as f64 / 2.0;
177        } else {
178            // Subsequent rtt measurement
179            self.rttvar = ((RTO_BASE - RTO_BETA) as f64 * self.rttvar
180                + RTO_BETA as f64 * (self.srtt as i64 - rtt as i64).abs() as f64)
181                / RTO_BASE as f64;
182            self.srtt = ((RTO_BASE - RTO_ALPHA) * self.srtt + RTO_ALPHA * rtt) / RTO_BASE;
183        }
184
185        self.rto = (self.srtt + (4.0 * self.rttvar) as u64).clamp(RTO_MIN, RTO_MAX);
186
187        self.srtt
188    }
189
190    /// get_rto simply returns the current RTO in msec.
191    pub(crate) fn get_rto(&self) -> u64 {
192        self.rto
193    }
194
195    /// reset resets the RTO variables to the initial values.
196    pub(crate) fn reset(&mut self) {
197        if self.no_update {
198            return;
199        }
200
201        self.srtt = 0;
202        self.rttvar = 0.0;
203        self.rto = RTO_INITIAL;
204    }
205
206    /// set RTO value for testing
207    pub(crate) fn set_rto(&mut self, rto: u64, no_update: bool) {
208        self.rto = rto;
209        self.no_update = no_update;
210    }
211}
212
213fn calculate_next_timeout(rto: u64, n_rtos: usize) -> u64 {
214    // RFC 4096 sec 6.3.3.  Handle T3-rtx Expiration
215    //   E2)  For the destination address for which the timer expires, set RTO
216    //        <- RTO * 2 ("back off the timer").  The maximum value discussed
217    //        in rule C7 above (RTO.max) may be used to provide an upper bound
218    //        to this doubling operation.
219    if n_rtos < 31 {
220        std::cmp::min(rto << n_rtos, RTO_MAX)
221    } else {
222        RTO_MAX
223    }
224}