1
//! Circuit reactor's stream XON/XOFF flow control.
2
//!
3
//! ## Notes on consensus parameters
4
//!
5
//! ### `cc_xoff_client`
6
//!
7
//! This is the number of bytes that we buffer within a [`DataStream`]. The actual total number of
8
//! bytes buffered can be *much* larger. For example there will be additional buffering:
9
//!
10
//! - Within the arti socks/http proxy: Arti's proxy code needs to read some bytes from the stream, store
11
//!   it in a temporary buffer, then write the buffer to the socket. If the socket would block, the
12
//!   data would remain in that temporary buffer. In practice arti uses only a small byte buffer (APP_STREAM_BUF_LEN) at
13
//!   the time of writing, which is hopefully negligible. See `arti::socks::copy_interactive()`.
14
//! - Within the kernel: There are two additional buffers that will store stream data before the
15
//!   application connected over socks will see the data: Arti's socket send buffer and the
16
//!   application's socket receive buffer. If the application were to stop reading from its socket,
17
//!   stream data would accumulate first in the socket's receive buffer. Once full, stream data
18
//!   would accumulate in arti's socket's send buffer. This can become relatively large, especially
19
//!   with buffer autotuning enabled. On a Linux 6.15 system with curl downloading a large file and
20
//!   stopping mid-download, the receive buffer was 6,116,738 bytes and the send buffer was
21
//!   2,631,062 bytes. This sums to around 8.7 MB of stream data buffered in the kernel, which is
22
//!   significantly higher than the current consensus value of `cc_xoff_client`.
23
//!   NOTE: Arti's proxy sockets now use fixed-size `DEFAULT_{SEND,RECV}_BUF_SIZE` kernel buffers.
24
//!
25
//! This means that the total number of bytes buffered before an XOFF is sent can be much larger
26
//! than `cc_xoff_client`.
27
//!
28
//! While we should take into account the kernel and arti socks buffering above, we also need to
29
//! keep in mind that arti-client is a library that can be used by others. These library users might
30
//! not do any kernel or socks buffering, for example if they write a rust program that handles the
31
//! stream data entirely within their program. We don't want to set `cc_xoff_client` too low that it
32
//! harms the performance for these users, even if it's fine for the arti socks proxy case.
33

            
34
use std::num::Saturating;
35
use std::sync::Arc;
36

            
37
use postage::watch;
38
use tor_cell::relaycell::flow_ctrl::{FlowCtrlVersion, Xoff, Xon, XonKBpsEwma};
39
use tor_cell::relaycell::msg::AnyRelayMsg;
40
use tor_cell::relaycell::{RelayCmd, RelayMsg, UnparsedRelayMsg};
41
use tracing::trace;
42

            
43
use super::reader::DrainRateRequest;
44

            
45
use crate::stream::flow_ctrl::params::{CellCount, FlowCtrlParameters};
46
use crate::stream::flow_ctrl::state::{
47
    FlowCtrlHooks, HalfStreamFlowCtrlHooks, StreamRateLimit, WithSidechannelMitigations,
48
};
49
use crate::util::notify::NotifySender;
50
use crate::{Error, Result};
51

            
52
#[cfg(doc)]
53
use {crate::client::stream::DataStream, crate::stream::flow_ctrl::state::StreamFlowCtrl};
54

            
55
/// State for XON/XOFF flow control.
56
#[derive(Debug)]
57
pub(crate) struct XonXoffFlowCtrl {
58
    /// Consensus parameters.
59
    params: Arc<FlowCtrlParameters>,
60
    /// How we communicate rate limit updates to the
61
    /// [`DataWriter`](crate::client::stream::DataWriter).
62
    rate_limit_updater: watch::Sender<StreamRateLimit>,
63
    /// How we communicate requests for new drain rate updates to the
64
    /// [`XonXoffReader`](crate::stream::flow_ctrl::xon_xoff::reader::XonXoffReader).
65
    drain_rate_requester: NotifySender<DrainRateRequest>,
66
    /// The last rate limit we sent.
67
    last_sent_xon_xoff: Option<XonXoffMsg>,
68
    /// The buffer limit at which we should send an XOFF.
69
    ///
70
    /// In prop324 it says that this will be either `cc_xoff_client` or `cc_xoff_exit` depending on
71
    /// whether we're a client/hs or exit, but we deviate from the spec here (see how it is set
72
    /// below).
73
    xoff_limit: CellCount<{ tor_cell::relaycell::PAYLOAD_MAX_SIZE_ALL as u32 }>,
74
    /// DropMark sidechannel mitigations.
75
    ///
76
    /// This is only enabled if we are a client (including an onion service).
77
    //
78
    // We could use a `Box` here so that this only takes up space if sidechannel mitigations are
79
    // enabled. But `SidechannelMitigation` is (at the time of writing) only 16 bytes. We could
80
    // reconsider in the future if we add more functionality to `SidechannelMitigation`.
81
    sidechannel_mitigation: Option<SidechannelMitigation>,
82
}
83

            
84
impl XonXoffFlowCtrl {
85
    /// Returns a new xon/xoff-based state.
86
36
    pub(crate) fn new(
87
36
        params: Arc<FlowCtrlParameters>,
88
36
        with_sidechannel_mitigations: WithSidechannelMitigations,
89
36
        rate_limit_updater: watch::Sender<StreamRateLimit>,
90
36
        drain_rate_requester: NotifySender<DrainRateRequest>,
91
36
    ) -> Self {
92
36
        let sidechannel_mitigation = match with_sidechannel_mitigations {
93
20
            WithSidechannelMitigations::Enabled => Some(SidechannelMitigation::new()),
94
16
            WithSidechannelMitigations::Disabled => None,
95
        };
96

            
97
        // We use the same XOFF limit regardless of if we're a client or exit.
98
        // See https://gitlab.torproject.org/tpo/core/torspec/-/issues/371#note_3260658
99
36
        let xoff_limit = std::cmp::max(params.cc_xoff_client, params.cc_xoff_exit);
100

            
101
36
        Self {
102
36
            params,
103
36
            rate_limit_updater,
104
36
            drain_rate_requester,
105
36
            last_sent_xon_xoff: None,
106
36
            xoff_limit,
107
36
            sidechannel_mitigation,
108
36
        }
109
36
    }
110
}
111

            
112
impl FlowCtrlHooks for XonXoffFlowCtrl {
113
6016
    fn can_send<M: RelayMsg>(&self, _msg: &M) -> bool {
114
        // we perform rate-limiting in the `DataWriter`,
115
        // so we send any messages that made it past the `DataWriter`
116
6016
        true
117
6016
    }
118

            
119
1200
    fn about_to_send(&mut self, msg: &AnyRelayMsg) -> Result<()> {
120
        // if sidechannel mitigations are enabled and this is a RELAY_DATA message,
121
        // notify that we sent a data message
122
1200
        if let Some(ref mut sidechannel_mitigation) = self.sidechannel_mitigation {
123
1200
            if let AnyRelayMsg::Data(data_msg) = msg {
124
1200
                sidechannel_mitigation.sent_stream_data(data_msg.as_ref().len());
125
1200
            }
126
        }
127

            
128
1200
        Ok(())
129
1200
    }
130

            
131
    fn put_for_incoming_sendme(&mut self, _msg: UnparsedRelayMsg) -> Result<()> {
132
        let msg = "Stream level SENDME not allowed due to congestion control";
133
        Err(Error::CircProto(msg.into()))
134
    }
135

            
136
    fn handle_incoming_xon(&mut self, msg: UnparsedRelayMsg) -> Result<()> {
137
        let xon = msg
138
            .decode::<Xon>()
139
            .map_err(|e| Error::from_bytes_err(e, "failed to decode XON message"))?
140
            .into_msg();
141

            
142
        // > Parties SHOULD treat XON or XOFF cells with unrecognized versions as a protocol
143
        // > violation.
144
        if *xon.version() != 0 {
145
            return Err(Error::CircProto("Unrecognized XON version".into()));
146
        }
147

            
148
        // if sidechannel mitigations are enabled, notify that an XON was received
149
        if let Some(ref mut sidechannel_mitigation) = self.sidechannel_mitigation {
150
            sidechannel_mitigation.received_xon(&self.params)?;
151
        }
152

            
153
        trace!("Received an XON with rate {}", xon.kbytes_per_sec_ewma());
154

            
155
        let rate = match xon.kbytes_per_sec_ewma() {
156
            XonKBpsEwma::Limited(rate_kbytes_per_sec) => {
157
                let rate_kbytes_per_sec = u64::from(rate_kbytes_per_sec.get());
158
                // convert from kilobytes/s to bytes/s
159
                StreamRateLimit::new_bytes_per_sec(rate_kbytes_per_sec * 1000)
160
            }
161
            XonKBpsEwma::Unlimited => StreamRateLimit::MAX,
162
        };
163

            
164
        *self.rate_limit_updater.borrow_mut() = rate;
165
        Ok(())
166
    }
167

            
168
    fn handle_incoming_xoff(&mut self, msg: UnparsedRelayMsg) -> Result<()> {
169
        let xoff = msg
170
            .decode::<Xoff>()
171
            .map_err(|e| Error::from_bytes_err(e, "failed to decode XOFF message"))?
172
            .into_msg();
173

            
174
        // > Parties SHOULD treat XON or XOFF cells with unrecognized versions as a protocol
175
        // > violation.
176
        if *xoff.version() != 0 {
177
            return Err(Error::CircProto("Unrecognized XOFF version".into()));
178
        }
179

            
180
        // if sidechannel mitigations are enabled, notify that an XOFF was received
181
        if let Some(ref mut sidechannel_mitigation) = self.sidechannel_mitigation {
182
            sidechannel_mitigation.received_xoff(&self.params)?;
183
        }
184

            
185
        trace!("Received an XOFF");
186

            
187
        // update the rate limit and notify the `DataWriter`
188
        *self.rate_limit_updater.borrow_mut() = StreamRateLimit::ZERO;
189

            
190
        Ok(())
191
    }
192

            
193
8
    fn maybe_send_xon(&mut self, rate: XonKBpsEwma, buffer_len: usize) -> Result<Option<Xon>> {
194
8
        if buffer_len as u64 > self.xoff_limit.as_bytes() {
195
            // we can't send an XON, and we should have already sent an XOFF when the queue first
196
            // exceeded the limit (see `maybe_send_xoff()`)
197
4
            debug_assert!(matches!(self.last_sent_xon_xoff, Some(XonXoffMsg::Xoff)));
198

            
199
            // inform the stream reader that we need a new drain rate
200
4
            self.drain_rate_requester.notify();
201
4
            return Ok(None);
202
4
        }
203

            
204
4
        self.last_sent_xon_xoff = Some(XonXoffMsg::Xon(rate));
205

            
206
4
        trace!("Want to send an XON with rate {rate}");
207

            
208
4
        Ok(Some(Xon::new(FlowCtrlVersion::V0, rate)))
209
8
    }
210

            
211
168
    fn maybe_send_xoff(&mut self, buffer_len: usize) -> Result<Option<Xoff>> {
212
        // if the last XON/XOFF we sent was an XOFF, no need to send another
213
96
        if matches!(self.last_sent_xon_xoff, Some(XonXoffMsg::Xoff)) {
214
72
            return Ok(None);
215
96
        }
216

            
217
96
        if buffer_len as u64 <= self.xoff_limit.as_bytes() {
218
88
            return Ok(None);
219
8
        }
220

            
221
        // either we have never sent an XOFF or XON, or we last sent an XON
222

            
223
        // remember that we last sent an XOFF
224
8
        self.last_sent_xon_xoff = Some(XonXoffMsg::Xoff);
225

            
226
        // inform the stream reader that we need a new drain rate
227
8
        self.drain_rate_requester.notify();
228

            
229
8
        trace!("Want to send an XOFF");
230

            
231
8
        Ok(Some(Xoff::new(FlowCtrlVersion::V0)))
232
168
    }
233

            
234
28
    fn inbound_queue_max_len(&self) -> usize {
235
        // Congestion control doesn't have an upper limit for the number of in-flight
236
        // cells that the other end might send,
237
        // so we need to expect any number of cells on this stream.
238
        //
239
        // Since dealing with mpsc queues that may be bounded or unbounded is a pain (requires a
240
        // bunch of enum wrappers), we'll set a very high bound.
241
        // This bound should be high enough that we'll never reach it in practice
242
        // (and if we do, it's surely a bug or an attack),
243
        // but not too high as to cause `futures_channel::mpsc::channel()` to panic.
244
        //
245
        // Here we choose a max of 2_000_000 messages,
246
        // which is approx 1000 MB of stream data (assuming packed cells).
247
        //
248
        // TODO(arti#2540): We should use an unbounded queue for XON/XOFF flow control,
249
        // and should return `None` here.
250
28
        2_000_000
251
28
    }
252
}
253

            
254
/// State for XON/XOFF flow control on a half-stream.
255
#[derive(Debug)]
256
pub(crate) struct HalfStreamXonXoffFlowCtrl {
257
    /// The original [`XonXoffFlowCtrl`] from the full stream.
258
    ///
259
    /// We keep this since we need to continue validating any incoming messages
260
    /// and continue applying the sidechannel mitigations.
261
    inner: XonXoffFlowCtrl,
262
}
263

            
264
impl HalfStreamXonXoffFlowCtrl {
265
    /// Returns a new xon/xoff-based state for a half-stream.
266
12
    pub(crate) fn new(flow_ctrl: XonXoffFlowCtrl) -> Self {
267
12
        Self { inner: flow_ctrl }
268
12
    }
269
}
270

            
271
impl HalfStreamFlowCtrlHooks for HalfStreamXonXoffFlowCtrl {
272
12
    fn handle_incoming_dropped(&mut self, _msg_count: u16) -> Result<()> {
273
        // Nothing to do here.
274
12
        Ok(())
275
12
    }
276

            
277
    fn handle_incoming_msg(&mut self, msg: UnparsedRelayMsg) -> Result<Option<UnparsedRelayMsg>> {
278
        match msg.cmd() {
279
            RelayCmd::SENDME => {
280
                self.inner.put_for_incoming_sendme(msg)?;
281
                Ok(None)
282
            }
283
            RelayCmd::XON => {
284
                self.inner.handle_incoming_xon(msg)?;
285
                Ok(None)
286
            }
287
            RelayCmd::XOFF => {
288
                self.inner.handle_incoming_xoff(msg)?;
289
                Ok(None)
290
            }
291
            // Nothing to do here.
292
            _ => Ok(Some(msg)),
293
        }
294
    }
295
}
296

            
297
/// An XON or XOFF message with no associated data.
298
#[derive(Debug, PartialEq, Eq)]
299
enum XonXoff {
300
    /// XON message.
301
    Xon,
302
    /// XOFF message.
303
    Xoff,
304
}
305

            
306
/// An XON or XOFF message with associated data.
307
#[derive(Debug)]
308
enum XonXoffMsg {
309
    /// XON message with a rate.
310
    // TODO: I'm expecting that we'll want the `XonKBpsEwma` in the future.
311
    // If that doesn't end up being the case, then we should remove it.
312
    #[expect(dead_code)]
313
    Xon(XonKBpsEwma),
314
    /// XOFF message.
315
    Xoff,
316
}
317

            
318
/// Sidechannel mitigations for DropMark attacks.
319
///
320
/// > In order to mitigate DropMark attacks, both XOFF and advisory XON transmission must be
321
/// > restricted.
322
///
323
/// These restrictions should be implemented for clients (OPs and onion services).
324
#[derive(Debug)]
325
struct SidechannelMitigation {
326
    /// The last rate limit update we received.
327
    last_recvd_xon_xoff: Option<XonXoff>,
328
    /// Number of sent stream bytes.
329
    ///
330
    /// C-tor has some logic to try to fit this into a 32-bit integer,
331
    /// but lets not do that unless we need to as it will make bugs more likely.
332
    bytes_sent_total: Saturating<u64>,
333
    /// The number of advisory XON messages we've received.
334
    ///
335
    /// Note: Advisory XONs are XON->XON messages, and not XOFF->XON messages.
336
    num_advisory_xon_recvd: Saturating<u64>,
337
    /// The number of XOFF messages we've received.
338
    num_xoff_recvd: Saturating<u64>,
339
}
340

            
341
impl SidechannelMitigation {
342
    /// A new [`SidechannelMitigation`].
343
56
    fn new() -> Self {
344
56
        Self {
345
56
            last_recvd_xon_xoff: None,
346
56
            bytes_sent_total: Saturating(0),
347
56
            num_advisory_xon_recvd: Saturating(0),
348
56
            num_xoff_recvd: Saturating(0),
349
56
        }
350
56
    }
351

            
352
    /// A (likely underestimated) guess of the XOFF limit that the other endpoint is using.
353
92
    fn peer_xoff_limit_bytes(params: &FlowCtrlParameters) -> u64 {
354
        // We need to consider that `xoff_client` and `xoff_exit` may be different, we don't know
355
        // here exactly what kind of peer we're connected to, and that we may have a different view
356
        // of the consensus than the peer.
357
        // We deviate from prop324 here and use a more relaxed threshold.
358
        // See https://gitlab.torproject.org/tpo/core/torspec/-/issues/371#note_3260658
359
92
        let min = std::cmp::min(
360
92
            params.cc_xoff_client.as_bytes(),
361
92
            params.cc_xoff_exit.as_bytes(),
362
        );
363
92
        min / 2
364
92
    }
365

            
366
    /// A (likely underestimated) guess of the advisory XON limit that the other endpoint is using.
367
42
    fn peer_xon_limit_bytes(params: &FlowCtrlParameters) -> u64 {
368
        // We need to consider that we may have a different view of the consensus than the peer.
369
        // We deviate from prop324 here and use a more relaxed threshold.
370
        // See https://gitlab.torproject.org/tpo/core/torspec/-/issues/371#note_3260658
371
42
        params.cc_xon_rate.as_bytes() / 2
372
42
    }
373

            
374
    /// Notify that we have sent stream data.
375
1236
    fn sent_stream_data(&mut self, stream_bytes: usize) {
376
        // perform a saturating conversion to u64
377
1236
        let stream_bytes: u64 = stream_bytes.try_into().unwrap_or(u64::MAX);
378
1236
        self.bytes_sent_total += stream_bytes;
379
1236
    }
380

            
381
    /// Notify that we have received an XON message.
382
40
    fn received_xon(&mut self, params: &FlowCtrlParameters) -> Result<()> {
383
        // Check to make sure that XON is not sent too early, for dropmark attacks. The main
384
        // sidechannel risk is early cells, but we also check to see that we did not get more XONs
385
        // than make sense for the number of bytes we sent.
386
        //
387
        // The ordering is important here. For example we first want to check if we received an
388
        // advisory XON that was too early, before we check if we received the advisory XON too
389
        // frequently.
390

            
391
        // Ensure that we have sent some bytes. This might be covered by other checks below, but this
392
        // is the most important check so we do it explicitly here first.
393
40
        if self.bytes_sent_total.0 == 0 {
394
            const MSG: &str = "Received XON before sending any data";
395
4
            return Err(Error::CircProto(MSG.into()));
396
36
        }
397

            
398
        // is this an advisory XON?
399
36
        let is_advisory = match self.last_recvd_xon_xoff {
400
            // if we last received an XON, then this is advisory since we are already sending data
401
12
            Some(XonXoff::Xon) => true,
402
            // if we last received an XOFF, then this isn't advisory since we're being asked to
403
            // resume sending data
404
16
            Some(XonXoff::Xoff) => false,
405
            // if we never received an XON nor XOFF, then this is advisory since we are already
406
            // sending data
407
8
            None => true,
408
        };
409

            
410
        // set this before we possibly return early below, since this must be set regardless of if
411
        // it's an advisory XON or not
412
36
        self.last_recvd_xon_xoff = Some(XonXoff::Xon);
413

            
414
        // we only restrict advisory XON messages
415
36
        if !is_advisory {
416
16
            return Ok(());
417
20
        }
418

            
419
20
        self.num_advisory_xon_recvd += 1;
420

            
421
        // > Clients also SHOULD ensure that advisory XONs do not arrive before the minimum of the
422
        // > XOFF limit and 'cc_xon_rate' full cells worth of bytes have been transmitted.
423
        //
424
        // NOTE: We use a more relaxed threshold for the XON and XOFF limits than in prop324.
425
20
        let advisory_not_expected_before = std::cmp::min(
426
20
            Self::peer_xoff_limit_bytes(params),
427
20
            Self::peer_xon_limit_bytes(params),
428
        );
429
20
        if self.bytes_sent_total.0 < advisory_not_expected_before {
430
            const MSG: &str = "Received advisory XON too early";
431
2
            return Err(Error::CircProto(MSG.into()));
432
18
        }
433

            
434
        // > Clients SHOULD ensure that advisory XONs do not arrive more frequently than every
435
        // > 'cc_xon_rate' cells worth of sent data.
436
        //
437
        // It should be an error if
438
        //   XON frequency > 1/peer_xon_limit_bytes
439
        // where
440
        //   XON frequency = num_advisory_xon_recvd/bytes_sent_total
441
        //
442
        // so
443
        //   num_advisory_xon_recvd/bytes_sent_total > 1/peer_xon_limit_bytes
444
        //
445
        // or to better work with integers
446
        //   num_advisory_xon_recvd > bytes_sent_total/peer_xon_limit_bytes
447
        //
448
        // NOTE: We use a more relaxed threshold for the XON limit than in prop324.
449
18
        let peer_xon_limit_bytes = Self::peer_xon_limit_bytes(params);
450
18
        if peer_xon_limit_bytes != 0
451
18
            && self.num_advisory_xon_recvd.0 > self.bytes_sent_total.0 / peer_xon_limit_bytes
452
        {
453
            const MSG: &str = "Received advisory XON too frequently";
454
6
            return Err(Error::CircProto(MSG.into()));
455
12
        }
456

            
457
12
        Ok(())
458
40
    }
459

            
460
    /// Notify that we have received an XOFF message.
461
48
    fn received_xoff(&mut self, params: &FlowCtrlParameters) -> Result<()> {
462
        // Check to make sure that XOFF is not sent too early, for dropmark attacks. The
463
        // main sidechannel risk is early cells, but we also check to make sure that we have not
464
        // received more XOFFs than could have been generated by the bytes we sent.
465
        //
466
        // The ordering is important here. For example we first want to disallow consecutive XOFFs,
467
        // then check if we received an XOFF that was too early, and finally check if we received
468
        // the XOFF too frequently.
469

            
470
48
        self.num_xoff_recvd += 1;
471

            
472
        // Ensure that we have sent some bytes. This might be covered by other checks below, but this
473
        // is the most important check so we do it explicitly here first.
474
48
        if self.bytes_sent_total.0 == 0 {
475
            const MSG: &str = "Received XOFF before sending any data";
476
4
            return Err(Error::CircProto(MSG.into()));
477
44
        }
478

            
479
        // disallow consecutive XOFF messages
480
44
        if self.last_recvd_xon_xoff == Some(XonXoff::Xoff) {
481
            const MSG: &str = "Received consecutive XOFF messages";
482
8
            return Err(Error::CircProto(MSG.into()));
483
36
        }
484

            
485
        // > clients MUST ensure that an XOFF does not arrive before it has sent the appropriate
486
        // > XOFF limit of bytes on a stream ('cc_xoff_exit' for exits, 'cc_xoff_client' for
487
        // > onions).
488
        //
489
        // NOTE: We use a more relaxed threshold for the XOFF limit than in prop324.
490
36
        if self.bytes_sent_total.0 < Self::peer_xoff_limit_bytes(params) {
491
            const MSG: &str = "Received XOFF too early";
492
4
            return Err(Error::CircProto(MSG.into()));
493
32
        }
494

            
495
        // > Clients also SHOULD ensure than XOFFs do not arrive more frequently than every XOFF
496
        // > limit worth of sent data.
497
        //
498
        // It should be an error if
499
        //   XOFF frequency > 1/peer_xoff_limit_bytes
500
        // where
501
        //   XOFF frequency = num_xoff_recvd/bytes_sent_total
502
        //
503
        // so
504
        //   num_xoff_recvd/bytes_sent_total > 1/peer_xoff_limit_bytes
505
        //
506
        // or to better work with integers
507
        //   num_xoff_recvd > bytes_sent_total/peer_xoff_limit_bytes
508
        //
509
        // NOTE: We use a more relaxed threshold for the XOFF limit than in prop324.
510
32
        let peer_xoff_limit_bytes = Self::peer_xoff_limit_bytes(params);
511
32
        if peer_xoff_limit_bytes != 0
512
32
            && self.num_xoff_recvd.0 > self.bytes_sent_total.0 / peer_xoff_limit_bytes
513
        {
514
4
            return Err(Error::CircProto("Received XOFF too frequently".into()));
515
28
        }
516

            
517
28
        self.last_recvd_xon_xoff = Some(XonXoff::Xoff);
518

            
519
28
        Ok(())
520
48
    }
521
}
522

            
523
#[cfg(test)]
524
mod test {
525
    use super::*;
526

            
527
    use crate::stream::flow_ctrl::params::CellCount;
528

            
529
    #[test]
530
    fn sidechannel_mitigation() {
531
        let params = [
532
            FlowCtrlParameters {
533
                cc_xoff_client: CellCount::new(2),
534
                cc_xoff_exit: CellCount::new(4),
535
                cc_xon_rate: CellCount::new(8),
536
                cc_xon_change_pct: 1,
537
                cc_xon_ewma_cnt: 1,
538
            },
539
            FlowCtrlParameters {
540
                cc_xoff_client: CellCount::new(8),
541
                cc_xoff_exit: CellCount::new(4),
542
                cc_xon_rate: CellCount::new(2),
543
                cc_xon_change_pct: 1,
544
                cc_xon_ewma_cnt: 1,
545
            },
546
        ];
547

            
548
        for params in params {
549
            let xon_limit = SidechannelMitigation::peer_xon_limit_bytes(&params);
550
            let xoff_limit = SidechannelMitigation::peer_xoff_limit_bytes(&params);
551

            
552
            let mut x = SidechannelMitigation::new();
553
            // cannot receive XON as first message
554
            assert!(x.received_xon(&params).is_err());
555

            
556
            let mut x = SidechannelMitigation::new();
557
            // cannot receive XOFF as first message
558
            assert!(x.received_xoff(&params).is_err());
559

            
560
            let mut x = SidechannelMitigation::new();
561
            // cannot receive XOFF after sending fewer than `xoff_limit` bytes
562
            x.sent_stream_data(xoff_limit as usize - 1);
563
            assert!(x.received_xoff(&params).is_err());
564

            
565
            let mut x = SidechannelMitigation::new();
566
            // can receive XOFF after sending `xoff_limit` bytes
567
            x.sent_stream_data(xoff_limit as usize);
568
            assert!(x.received_xoff(&params).is_ok());
569
            // but cannot receive another XOFF immediately after
570
            assert!(x.received_xoff(&params).is_err());
571

            
572
            let mut x = SidechannelMitigation::new();
573
            // can receive XOFF after sending `xoff_limit` bytes
574
            x.sent_stream_data(xoff_limit as usize);
575
            assert!(x.received_xoff(&params).is_ok());
576
            // but cannot receive another XOFF even after sending another `xoff_limit` bytes
577
            x.sent_stream_data(xoff_limit as usize);
578
            assert!(x.received_xoff(&params).is_err());
579

            
580
            let mut x = SidechannelMitigation::new();
581
            // can receive XOFF after sending `xoff_limit` bytes
582
            x.sent_stream_data(xoff_limit as usize);
583
            assert!(x.received_xoff(&params).is_ok());
584
            // and can immediately receive an XON
585
            assert!(x.received_xon(&params).is_ok());
586
            // and can receive another XOFF after sending another `xoff_limit` bytes
587
            x.sent_stream_data(xoff_limit as usize);
588
            assert!(x.received_xoff(&params).is_ok());
589

            
590
            let mut x = SidechannelMitigation::new();
591
            // cannot receive XON after sending fewer than `xon_limit` bytes
592
            x.sent_stream_data(xon_limit as usize - 1);
593
            assert!(x.received_xon(&params).is_err());
594

            
595
            let mut x = SidechannelMitigation::new();
596
            // can receive XON after sending a large number of bytes
597
            x.sent_stream_data(xon_limit as usize * 3);
598
            assert!(x.received_xon(&params).is_ok());
599
            // and can immediately receive another XON
600
            assert!(x.received_xon(&params).is_ok());
601
            // and can immediately receive another XON
602
            assert!(x.received_xon(&params).is_ok());
603
            // but cannot receive another XON immediately after
604
            assert!(x.received_xon(&params).is_err());
605

            
606
            let mut x = SidechannelMitigation::new();
607
            // can receive XOFF after sending a large number of bytes
608
            x.sent_stream_data(xoff_limit as usize * 3);
609
            assert!(x.received_xoff(&params).is_ok());
610
            // and can immediately receive an XON
611
            assert!(x.received_xon(&params).is_ok());
612
            // and can immediately receive an XOFF
613
            assert!(x.received_xoff(&params).is_ok());
614
            // and can immediately receive an XON
615
            assert!(x.received_xon(&params).is_ok());
616
            // and can immediately receive an XOFF
617
            assert!(x.received_xoff(&params).is_ok());
618
            // and can immediately receive an XON
619
            assert!(x.received_xon(&params).is_ok());
620
            // but cannot immediately receive an XOFF
621
            assert!(x.received_xoff(&params).is_err());
622
        }
623
    }
624
}