blob: 312b2602281ec187b4c28a2b49a90ec5013d1f5a [file] [log] [blame]
// 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);
}
}