1
//! Define the congestion control parameters needed for the algorithms.
2
//!
3
//! All of these values are taken from the consensus. And so the details of these values can be
4
//! found in section 6.5.1. of proposal 324.
5

            
6
use caret::caret_int;
7
use derive_builder::Builder;
8

            
9
use tor_config::{ConfigBuildError, impl_standard_builder};
10
use tor_units::Percentage;
11

            
12
/// Fixed window parameters that are for the SENDME v0 world of fixed congestion window.
13
#[non_exhaustive]
14
#[derive(Builder, Copy, Clone, Debug, amplify::Getters)]
15
#[builder(build_fn(error = "ConfigBuildError"))]
16
pub struct FixedWindowParams {
17
    /// Circuit window starting point. From the "circwindow" param.
18
    #[getter(as_copy)]
19
    circ_window_start: u16,
20
    /// Circuit window minimum value.
21
    #[getter(as_copy)]
22
    circ_window_min: u16,
23
    /// Circuit window maximum value.
24
    #[getter(as_copy)]
25
    circ_window_max: u16,
26
}
27
impl_standard_builder! { FixedWindowParams: !Deserialize + !Default }
28

            
29
impl FixedWindowParams {
30
    #[cfg(test)]
31
    // These have been copied from C-tor.
32
22
    pub(crate) fn defaults_for_tests() -> Self {
33
22
        Self {
34
22
            circ_window_start: 1000,
35
22
            circ_window_min: 100,
36
22
            circ_window_max: 1000,
37
22
        }
38
22
    }
39
}
40

            
41
/// Vegas queuing parameters taken from the consensus only which are different depending if the
42
/// circuit is an onion service one, an exit or used for SBWS.
43
#[non_exhaustive]
44
#[derive(Copy, Clone, Debug, amplify::Getters)]
45
pub struct VegasQueueParams {
46
    /// Alpha parameter is used to know when to increase the window.
47
    #[getter(as_copy)]
48
    alpha: u32,
49
    /// Beta parameter is used to know when to decrease the window
50
    #[getter(as_copy)]
51
    beta: u32,
52
    /// Delta parameter is used as an indicator to drop the window to this considering the current
53
    /// BDP value and increment.
54
    #[getter(as_copy)]
55
    delta: u32,
56
    /// Gamma parameter is only used in slow start and used to know when to increase or adjust the
57
    /// window with the BDP.
58
    #[getter(as_copy)]
59
    gamma: u32,
60
    /// Parameter describe the RFC3742 'cap', after which congestion window increments are reduced.
61
    /// INT32_MAX disables
62
    #[getter(as_copy)]
63
    ss_cwnd_cap: u32,
64
}
65

            
66
/// Used when we parse at once all the specific circuit type vegas queue parameters. They are
67
/// bundled in a 5-tuple and transformed with this.
68
impl From<(u32, u32, u32, u32, u32)> for VegasQueueParams {
69
640
    fn from(v: (u32, u32, u32, u32, u32)) -> Self {
70
640
        Self {
71
640
            alpha: v.0,
72
640
            beta: v.1,
73
640
            delta: v.2,
74
640
            gamma: v.3,
75
640
            ss_cwnd_cap: v.4,
76
640
        }
77
640
    }
78
}
79

            
80
/// Vegas algorithm parameters taken from the consensus.
81
#[non_exhaustive]
82
#[derive(Builder, Copy, Clone, Debug, amplify::Getters)]
83
#[builder(build_fn(error = "ConfigBuildError"))]
84
pub struct VegasParams {
85
    /// The amount of queued cells that Vegas can tolerate before reacting.
86
    cell_in_queue_params: VegasQueueParams,
87
    /// A hard-max on the congestion window in Slow Start.
88
    #[getter(as_copy)]
89
    ss_cwnd_max: u32,
90
    /// This parameter defines the integer number of 'cc_sendme_inc' multiples
91
    /// of gap allowed between inflight and cwnd, to still declare the cwnd full.
92
    #[getter(as_copy)]
93
    cwnd_full_gap: u32,
94
    /// This parameter defines a low watermark in percent.
95
    cwnd_full_min_pct: Percentage<u32>,
96
    /// This parameter governs how often a cwnd must be full.
97
    #[getter(as_copy)]
98
    cwnd_full_per_cwnd: u32,
99
}
100
impl_standard_builder! { VegasParams: !Deserialize + !Default }
101

            
102
impl VegasParams {
103
    #[cfg(test)]
104
    // These have been copied from spec (prop324).
105
22
    pub(crate) fn defaults_for_tests() -> Self {
106
        // The OUTBUF_CELLS size from prop324.
107
        const OC: u32 = 62;
108
22
        Self {
109
22
            cell_in_queue_params: (3 * OC, 4 * OC, 5 * OC, 3 * OC, 600).into(),
110
22
            ss_cwnd_max: 5000,
111
22
            cwnd_full_gap: 4,
112
22
            cwnd_full_min_pct: Percentage::new(25),
113
22
            cwnd_full_per_cwnd: 1,
114
22
        }
115
22
    }
116
}
117

            
118
/// The different congestion control algorithms. Each contain their parameters taken from the
119
/// consensus.
120
#[non_exhaustive]
121
#[derive(Clone, Debug, strum::EnumDiscriminants)]
122
// No need for the discriminants to be public at the moment,
123
// and we might want to rename it if it does become public.
124
#[strum_discriminants(vis(pub(crate)))]
125
pub enum Algorithm {
126
    /// Fixed window algorithm.
127
    FixedWindow(FixedWindowParams),
128
    /// Vegas algorithm.
129
    Vegas(VegasParams),
130
}
131

            
132
impl Algorithm {
133
    /// Return true if this algorithm can be used along with CGO.
134
    ///
135
    /// CGO requires the V1 relay cell format, where every relay command
136
    /// implies either the presence or absence of a StreamID.
137
    /// But that format is not compatible with (legacy) stream-level SENDME messages
138
    /// for flow control.
139
1000
    pub(crate) fn compatible_with_cgo(&self) -> bool {
140
1000
        match self {
141
636
            Algorithm::FixedWindow(_) => false,
142
364
            Algorithm::Vegas(_) => true,
143
        }
144
1000
    }
145
}
146

            
147
caret_int! {
148
    /// Congestion control algorithm types defined by numerical values. See "cc_alg" in proposal
149
    /// 324 section 6.5.1 for the supported values.
150
    ///
151
    /// This is a i32 so it is the same type as the consensus supported value type.
152
    pub struct AlgorithmType(i32) {
153
        /// Fixed window algorithm.
154
        FIXED_WINDOW = 0,
155
        /// Vegas algorithm.
156
        VEGAS = 2,
157
    }
158
}
159

            
160
/// The round trip estimator parameters taken from consensus and used to estimate the round trip
161
/// time on a circuit.
162
#[non_exhaustive]
163
#[derive(Builder, Clone, Debug, amplify::Getters)]
164
#[builder(build_fn(error = "ConfigBuildError"))]
165
pub struct RoundTripEstimatorParams {
166
    /// The "N" parameter in N-EWMA smoothing of RTT and/or bandwidth estimation, specified as a
167
    /// percentage of the number of SENDME acks in a congestion window.
168
    ///
169
    /// A percentage over 100% indicates smoothing with more than one congestion window's worth
170
    /// of SENDMEs.
171
    ewma_cwnd_pct: Percentage<u32>,
172
    /// The maximum value of the "N" parameter in N-EWMA smoothing of RTT and/or bandwidth
173
    /// estimation.
174
    #[getter(as_copy)]
175
    ewma_max: u32,
176
    /// The maximum value of the "N" parameter in N-EWMA smoothing of RTT and/or bandwidth
177
    /// estimation but in Slow Start.
178
    #[getter(as_copy)]
179
    ewma_ss_max: u32,
180
    /// Describes a percentile average between min and current ewma, for use to reset RTT_min, when
181
    /// the congestion window hits cwnd_min.
182
    rtt_reset_pct: Percentage<u32>,
183
}
184
impl_standard_builder! { RoundTripEstimatorParams: !Deserialize + !Default }
185

            
186
impl RoundTripEstimatorParams {
187
    #[cfg(test)]
188
    // These have been copied from spec (prop324).
189
22
    pub(crate) fn defaults_for_tests() -> Self {
190
22
        Self {
191
22
            ewma_cwnd_pct: Percentage::new(50),
192
22
            ewma_max: 10,
193
22
            ewma_ss_max: 2,
194
22
            rtt_reset_pct: Percentage::new(100),
195
22
        }
196
22
    }
197
}
198

            
199
/// The parameters of what constitute a congestion window. This is used by all congestion control
200
/// algorithms as in it is not specific to an algorithm.
201
#[non_exhaustive]
202
#[derive(Builder, Clone, Copy, Debug, amplify::Getters)]
203
#[builder(build_fn(error = "ConfigBuildError"))]
204
pub struct CongestionWindowParams {
205
    /// Initial size of the congestion window.
206
    #[getter(as_copy)]
207
    cwnd_init: u32,
208
    /// Percent of cwnd to increment by during slow start.
209
    cwnd_inc_pct_ss: Percentage<u32>,
210
    /// Number of cells to increment cwnd by during steady state.
211
    #[getter(as_copy)]
212
    cwnd_inc: u32,
213
    /// Number of times per congestion window to update based on congestion signals.
214
    #[getter(as_copy)]
215
    cwnd_inc_rate: u32,
216
    /// Minimum congestion window (must be at least sendme_inc)
217
    #[getter(as_copy)]
218
    cwnd_min: u32,
219
    /// Maximum congestion window
220
    #[getter(as_copy)]
221
    cwnd_max: u32,
222
    /// The SENDME increment as in the number of cells to ACK with every SENDME. This is coming
223
    /// from the consensus and negotiated during circuit setup.
224
    #[getter(as_copy)]
225
    sendme_inc: u32,
226
}
227
impl_standard_builder! { CongestionWindowParams: !Deserialize + !Default}
228

            
229
impl CongestionWindowParams {
230
    /// Set the `sendme_inc` value.
231
    ///
232
    /// This is used to override the default increment value from when this was constructed with a
233
    /// [`CongestionWindowParamsBuilder`].
234
    /// Typically the default when built should be from the network parameters from the consensus.
235
12
    pub(crate) fn set_sendme_inc(&mut self, inc: u8) {
236
12
        self.sendme_inc = u32::from(inc);
237
12
    }
238

            
239
    #[cfg(test)]
240
    // These have been copied from spec (prop324).
241
22
    pub(crate) fn defaults_for_tests() -> Self {
242
22
        Self {
243
22
            cwnd_init: 4 * 31,
244
22
            cwnd_inc_pct_ss: Percentage::new(50),
245
22
            cwnd_inc: 31,
246
22
            cwnd_inc_rate: 1,
247
22
            cwnd_min: 31,
248
22
            cwnd_max: u32::MAX,
249
22
            sendme_inc: 31,
250
22
        }
251
22
    }
252
}
253

            
254
/// Global congestion control parameters taken from consensus. These are per-circuit.
255
#[non_exhaustive]
256
#[derive(Builder, Clone, Debug, amplify::Getters)]
257
#[builder(build_fn(error = "ConfigBuildError"))]
258
pub struct CongestionControlParams {
259
    /// The congestion control algorithm to use.
260
    alg: Algorithm,
261
    /// Parameters to the fallback fixed-window algorithm, which we use
262
    /// when the one in `alg` is not supported by a given relay.
263
    ///
264
    /// It is put in here because by the time we do path selection, we don't have access to the
265
    /// consensus and so we have to keep our fallback ready.
266
    fixed_window_params: FixedWindowParams,
267
    /// Congestion window parameters.
268
    #[getter(as_mut)]
269
    #[getter(as_copy)]
270
    cwnd_params: CongestionWindowParams,
271
    /// RTT calculation parameters.
272
    rtt_params: RoundTripEstimatorParams,
273
}
274
impl_standard_builder! { CongestionControlParams: !Deserialize + !Default }
275

            
276
impl CongestionControlParams {
277
    /// Return true iff congestion control is enabled that is the algorithm is anything other than
278
    /// the fixed window SENDMEs.
279
    ///
280
    /// C-tor ref: congestion_control_enabled()
281
48
    pub(crate) fn is_enabled(&self) -> bool {
282
48
        !matches!(self.alg(), Algorithm::FixedWindow(_))
283
48
    }
284

            
285
    /// Make these parameters to use the fallback algorithm. This can't be reversed.
286
84
    pub(crate) fn use_fallback_alg(&mut self) {
287
84
        self.alg = Algorithm::FixedWindow(self.fixed_window_params);
288
84
    }
289
}
290

            
291
/// Return true iff the given sendme increment is valid with regards to the value in the circuit
292
/// parameters that is taken from the consensus.
293
24
pub(crate) fn is_sendme_inc_valid(inc: u8, params: &CongestionControlParams) -> bool {
294
    // Ease our lives a bit because the consensus value is u32.
295
24
    let inc_u32 = u32::from(inc);
296
    // A consensus value of 1 would allow this sendme increment to be 0 and thus
297
    // we have to special case it before evaluating.
298
24
    if inc == 0 {
299
2
        return false;
300
22
    }
301
22
    let inc_consensus = params.cwnd_params().sendme_inc();
302
    // See prop324 section 10.3
303
22
    if inc_u32 > (inc_consensus.saturating_add(1)) || inc_u32 < (inc_consensus.saturating_sub(1)) {
304
4
        return false;
305
18
    }
306
18
    true
307
24
}
308

            
309
#[cfg(test)]
310
mod test {
311
    use crate::{
312
        ccparams::is_sendme_inc_valid, congestion::test_utils::params::build_cc_vegas_params,
313
    };
314

            
315
    #[test]
316
    fn test_sendme_inc_valid() {
317
        let params = build_cc_vegas_params();
318
        let ref_inc = params.cwnd_params().sendme_inc() as u8;
319

            
320
        // In range.
321
        assert!(is_sendme_inc_valid(ref_inc, &params));
322
        assert!(is_sendme_inc_valid(ref_inc + 1, &params));
323
        assert!(is_sendme_inc_valid(ref_inc - 1, &params));
324
        // Out of range.
325
        assert!(!is_sendme_inc_valid(0, &params));
326
        assert!(!is_sendme_inc_valid(ref_inc + 2, &params));
327
        assert!(!is_sendme_inc_valid(ref_inc - 2, &params));
328
    }
329
}