238 lines
6.8 KiB
Rust
238 lines
6.8 KiB
Rust
// Copyright (C) 2021, Cloudflare, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
//
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
//! Proportional Rate Reduction
|
|
//!
|
|
//! This implementation is based on the following RFC:
|
|
//!
|
|
//! <https://datatracker.ietf.org/doc/html/rfc6937>
|
|
|
|
use std::cmp;
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct PRR {
|
|
// Total bytes delivered during recovery.
|
|
prr_delivered: usize,
|
|
|
|
// FlightSize at the start of recovery.
|
|
recoverfs: usize,
|
|
|
|
// Total bytes sent during recovery.
|
|
prr_out: usize,
|
|
|
|
// Total additional bytes can be sent for retransmit during recovery.
|
|
pub snd_cnt: usize,
|
|
}
|
|
|
|
impl PRR {
|
|
pub fn on_packet_sent(&mut self, sent_bytes: usize) {
|
|
self.prr_out += sent_bytes;
|
|
|
|
self.snd_cnt = self.snd_cnt.saturating_sub(sent_bytes);
|
|
}
|
|
|
|
pub fn congestion_event(&mut self, bytes_in_flight: usize) {
|
|
self.prr_delivered = 0;
|
|
|
|
self.recoverfs = bytes_in_flight;
|
|
|
|
self.prr_out = 0;
|
|
|
|
self.snd_cnt = 0;
|
|
}
|
|
|
|
pub fn on_packet_acked(
|
|
&mut self, delivered_data: usize, pipe: usize, ssthresh: usize,
|
|
max_datagram_size: usize,
|
|
) {
|
|
self.prr_delivered += delivered_data;
|
|
|
|
self.snd_cnt = if pipe > ssthresh {
|
|
// Proportional Rate Reduction.
|
|
if self.recoverfs > 0 {
|
|
((self.prr_delivered * ssthresh + self.recoverfs - 1) /
|
|
self.recoverfs)
|
|
.saturating_sub(self.prr_out)
|
|
} else {
|
|
0
|
|
}
|
|
} else {
|
|
// PRR-SSRB.
|
|
let limit = cmp::max(
|
|
self.prr_delivered.saturating_sub(self.prr_out),
|
|
delivered_data,
|
|
) + max_datagram_size;
|
|
|
|
// Attempt to catch up, as permitted by limit
|
|
cmp::min(ssthresh - pipe, limit)
|
|
};
|
|
|
|
// snd_cnt should be a positive number.
|
|
self.snd_cnt = cmp::max(self.snd_cnt, 0);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn congestion_event() {
|
|
let mut prr = PRR::default();
|
|
let bytes_in_flight = 1000;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
assert_eq!(prr.recoverfs, bytes_in_flight);
|
|
assert_eq!(prr.snd_cnt, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn on_packet_sent() {
|
|
let mut prr = PRR::default();
|
|
let bytes_in_flight = 1000;
|
|
let bytes_sent = 500;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
prr.on_packet_sent(bytes_sent);
|
|
|
|
assert_eq!(prr.prr_out, bytes_sent);
|
|
assert_eq!(prr.snd_cnt, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn on_packet_acked_prr() {
|
|
let mut prr = PRR::default();
|
|
let max_datagram_size = 1000;
|
|
let bytes_in_flight = max_datagram_size * 10;
|
|
let ssthresh = bytes_in_flight / 2;
|
|
let acked = 1000;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
// pipe > ssthresh uses PRR algorithm.
|
|
let pipe = bytes_in_flight;
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 500);
|
|
|
|
let snd_cnt = prr.snd_cnt;
|
|
|
|
// send one more allowed by snd_cnt
|
|
prr.on_packet_sent(snd_cnt);
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 500);
|
|
}
|
|
|
|
#[test]
|
|
fn on_packet_acked_prr_overflow() {
|
|
let mut prr = PRR::default();
|
|
let max_datagram_size = 1000;
|
|
let bytes_in_flight = max_datagram_size * 10;
|
|
let ssthresh = bytes_in_flight / 2;
|
|
let acked = 1000;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
prr.on_packet_sent(max_datagram_size);
|
|
|
|
// pipe > ssthresh uses PRR algorithm.
|
|
let pipe = bytes_in_flight + max_datagram_size;
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn on_packet_acked_prr_zero_in_flight() {
|
|
let mut prr = PRR::default();
|
|
let max_datagram_size = 1000;
|
|
let bytes_in_flight = 0;
|
|
let ssthresh = 3000;
|
|
let acked = 1000;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
// pipe > ssthresh uses PRR algorithm.
|
|
let pipe = ssthresh + 1000;
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn on_packet_acked_prr_ssrb() {
|
|
let mut prr = PRR::default();
|
|
let max_datagram_size = 1000;
|
|
let bytes_in_flight = max_datagram_size * 10;
|
|
let ssthresh = bytes_in_flight / 2;
|
|
let acked = 1000;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
// pipe <= ssthresh uses PRR-SSRB algorithm.
|
|
let pipe = max_datagram_size;
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 2000);
|
|
|
|
let snd_cnt = prr.snd_cnt;
|
|
|
|
// send one more allowed by snd_cnt
|
|
prr.on_packet_sent(snd_cnt);
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 2000);
|
|
}
|
|
|
|
#[test]
|
|
fn on_packet_acked_prr_ssrb_overflow() {
|
|
let mut prr = PRR::default();
|
|
let max_datagram_size = 1000;
|
|
let bytes_in_flight = max_datagram_size * 10;
|
|
let ssthresh = bytes_in_flight / 2;
|
|
let acked = 500;
|
|
|
|
prr.congestion_event(bytes_in_flight);
|
|
|
|
// pipe <= ssthresh uses PRR-SSRB algorithm.
|
|
let pipe = max_datagram_size;
|
|
|
|
prr.on_packet_sent(max_datagram_size);
|
|
|
|
prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);
|
|
|
|
assert_eq!(prr.snd_cnt, 1500);
|
|
}
|
|
}
|