1
0
Fork 0
Univerxel/deps/picoquic/cubic.c

686 lines
30 KiB
C

/*
* Author: Christian Huitema
* Copyright (c) 2019, Private Octopus, Inc.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* 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 Private Octopus, Inc. 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.
*/
#include "picoquic_internal.h"
#include <stdlib.h>
#include <string.h>
#include "cc_common.h"
typedef enum {
picoquic_cubic_alg_slow_start = 0,
picoquic_cubic_alg_recovery,
picoquic_cubic_alg_congestion_avoidance
} picoquic_cubic_alg_state_t;
typedef struct st_picoquic_cubic_state_t {
picoquic_cubic_alg_state_t alg_state;
uint64_t recovery_sequence;
uint64_t start_of_epoch;
uint64_t previous_start_of_epoch;
double K;
double W_max;
double W_last_max;
double C;
double beta;
double W_reno;
uint64_t ssthresh;
picoquic_min_max_rtt_t rtt_filter;
} picoquic_cubic_state_t;
static void picoquic_cubic_reset(picoquic_cubic_state_t* cubic_state, picoquic_path_t* path_x, uint64_t current_time) {
memset(&cubic_state->rtt_filter, 0, sizeof(picoquic_min_max_rtt_t));
memset(cubic_state, 0, sizeof(picoquic_cubic_state_t));
cubic_state->alg_state = picoquic_cubic_alg_slow_start;
cubic_state->ssthresh = UINT64_MAX;
cubic_state->W_last_max = (double)cubic_state->ssthresh / (double)path_x->send_mtu;
cubic_state->W_max = cubic_state->W_last_max;
cubic_state->C = 0.4;
cubic_state->beta = 7.0 / 8.0;
cubic_state->start_of_epoch = current_time;
cubic_state->previous_start_of_epoch = 0;
cubic_state->W_reno = PICOQUIC_CWIN_INITIAL;
cubic_state->recovery_sequence = 0;
path_x->cwin = PICOQUIC_CWIN_INITIAL;
}
static void picoquic_cubic_init(picoquic_path_t* path_x, uint64_t current_time)
{
/* Initialize the state of the congestion control algorithm */
picoquic_cubic_state_t* cubic_state = (picoquic_cubic_state_t*)malloc(sizeof(picoquic_cubic_state_t));
path_x->congestion_alg_state = (void*)cubic_state;
if (cubic_state != NULL) {
picoquic_cubic_reset(cubic_state, path_x, current_time);
}
}
static double picoquic_cubic_root(double x)
{
/* First find an approximation */
double v = 1;
double y = 1.0;
double y2;
double y3;
while (v > x * 8) {
v /= 8;
y /= 2;
}
while (v < x) {
v *= 8;
y *= 2;
}
for (int i = 0; i < 3; i++) {
y2 = y * y;
y3 = y2 * y;
y += (x - y3) / (3.0*y2);
}
return y;
}
/* Compute W_cubic(t) = C * (t - K) ^ 3 + W_max */
static double picoquic_cubic_W_cubic(
picoquic_cubic_state_t* cubic_state,
uint64_t current_time)
{
double delta_t_sec = ((double)(current_time - cubic_state->start_of_epoch) / 1000000.0) - cubic_state->K;
double W_cubic = (cubic_state->C * (delta_t_sec * delta_t_sec * delta_t_sec)) + cubic_state->W_max;
return W_cubic;
}
/* On entering congestion avoidance, need to compute the new coefficients
* of the cubit curve */
static void picoquic_cubic_enter_avoidance(
picoquic_cubic_state_t* cubic_state,
uint64_t current_time)
{
cubic_state->K = picoquic_cubic_root(cubic_state->W_max*(1.0 - cubic_state->beta) / cubic_state->C);
cubic_state->alg_state = picoquic_cubic_alg_congestion_avoidance;
cubic_state->start_of_epoch = current_time;
cubic_state->previous_start_of_epoch = cubic_state->start_of_epoch;
}
/* The recovery state last 1 RTT, during which parameters will be frozen
*/
static void picoquic_cubic_enter_recovery(picoquic_cnx_t * cnx,
picoquic_path_t* path_x,
picoquic_congestion_notification_t notification,
picoquic_cubic_state_t* cubic_state,
uint64_t current_time)
{
cubic_state->recovery_sequence = picoquic_cc_get_sequence_number(cnx);
/* Update similar to new reno, but different beta */
cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu;
/* Apply fast convergence */
if (cubic_state->W_max < cubic_state->W_last_max) {
cubic_state->W_last_max = cubic_state->W_max;
cubic_state->W_max = cubic_state->W_max *cubic_state->beta;
}
else {
cubic_state->W_last_max = cubic_state->W_max;
}
/* Compute the new ssthresh */
cubic_state->ssthresh = (uint64_t)(cubic_state->W_max * cubic_state->beta*(double)path_x->send_mtu);
if (cubic_state->ssthresh < PICOQUIC_CWIN_MINIMUM) {
/* If things are that bad, fall back to slow start */
cubic_state->ssthresh = PICOQUIC_CWIN_MINIMUM;
cubic_state->alg_state = picoquic_cubic_alg_slow_start;
cubic_state->ssthresh = UINT64_MAX;
cubic_state->previous_start_of_epoch = cubic_state->start_of_epoch;
cubic_state->start_of_epoch = current_time;
cubic_state->W_reno = PICOQUIC_CWIN_MINIMUM;
path_x->cwin = PICOQUIC_CWIN_MINIMUM;
}
else {
if (notification == picoquic_congestion_notification_timeout) {
path_x->cwin = PICOQUIC_CWIN_MINIMUM;
cubic_state->previous_start_of_epoch = cubic_state->start_of_epoch;
cubic_state->start_of_epoch = current_time;
cubic_state->alg_state = picoquic_cubic_alg_slow_start;
}
else {
/* Enter congestion avoidance immediately */
picoquic_cubic_enter_avoidance(cubic_state, current_time);
/* Compute the inital window for both Reno and Cubic */
double W_cubic = picoquic_cubic_W_cubic(cubic_state, current_time);
uint64_t win_cubic = (uint64_t)(W_cubic * (double)path_x->send_mtu);
cubic_state->W_reno = ((double)path_x->cwin) / 2.0;
/* Pick the largest */
if (win_cubic > cubic_state->W_reno) {
/* if cubic is larger than threshold, switch to cubic mode */
path_x->cwin = win_cubic;
}
else {
path_x->cwin = (uint64_t)cubic_state->W_reno;
}
}
}
}
/* On spurious repeat notification, restore the previous congestion control.
* Assume that K is still valid -- we only update it after exiting recovery.
* Set cwin to the value of W_max before the recovery event
* Set W_max to W_max_last, i.e. the value before the recovery event
* Set the epoch back to where it was, by computing the inverse of the
* W_cubic formula */
static void picoquic_cubic_correct_spurious(picoquic_path_t* path_x,
picoquic_cubic_state_t* cubic_state,
uint64_t current_time)
{
if (cubic_state->ssthresh != UINT64_MAX) {
cubic_state->W_max = cubic_state->W_last_max;
picoquic_cubic_enter_avoidance(cubic_state, cubic_state->previous_start_of_epoch);
double W_cubic = picoquic_cubic_W_cubic(cubic_state, current_time);
cubic_state->W_reno = W_cubic * (double)path_x->send_mtu;
cubic_state->ssthresh = (uint64_t)(cubic_state->W_max * cubic_state->beta * (double)path_x->send_mtu);
path_x->cwin = (uint64_t)cubic_state->W_reno;
}
}
/*
* Properly implementing Cubic requires managing a number of
* signals, such as packet losses or acknowledgements. We attempt
* to condensate all that in a single API, which could be shared
* by many different congestion control algorithms.
*/
static void picoquic_cubic_notify(
picoquic_cnx_t* cnx, picoquic_path_t* path_x,
picoquic_congestion_notification_t notification,
uint64_t rtt_measurement,
uint64_t one_way_delay,
uint64_t nb_bytes_acknowledged,
uint64_t lost_packet_number,
uint64_t current_time)
{
#ifdef _WINDOWS
UNREFERENCED_PARAMETER(rtt_measurement);
UNREFERENCED_PARAMETER(lost_packet_number);
#endif
picoquic_cubic_state_t* cubic_state = (picoquic_cubic_state_t*)path_x->congestion_alg_state;
if (cubic_state != NULL) {
switch (cubic_state->alg_state) {
case picoquic_cubic_alg_slow_start:
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) {
picoquic_hystart_increase(path_x, &cubic_state->rtt_filter, nb_bytes_acknowledged);
/* if cnx->cwin exceeds SSTHRESH, exit and go to CA */
if (path_x->cwin >= cubic_state->ssthresh) {
cubic_state->W_reno = ((double)path_x->cwin) / 2.0;
picoquic_cubic_enter_avoidance(cubic_state, current_time);
}
}
break;
case picoquic_congestion_notification_ecn_ec:
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
/* enter recovery */
if (current_time - cubic_state->start_of_epoch > path_x->smoothed_rtt ||
cubic_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx)) {
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
break;
case picoquic_congestion_notification_spurious_repeat:
/* Reset CWIN based on ssthresh, not based on current value. */
picoquic_cubic_correct_spurious(path_x, cubic_state, current_time);
break;
case picoquic_congestion_notification_rtt_measurement:
/* Using RTT increases as signal to get out of initial slow start */
if (cubic_state->ssthresh == UINT64_MAX &&
picoquic_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? one_way_delay : rtt_measurement,
cnx->path[0]->pacing_packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) {
/* RTT increased too much, get out of slow start! */
if (cubic_state->rtt_filter.rtt_filtered_min > PICOQUIC_TARGET_RENO_RTT){
double correction;
if (cubic_state->rtt_filter.rtt_filtered_min > PICOQUIC_TARGET_SATELLITE_RTT) {
correction = (double)PICOQUIC_TARGET_SATELLITE_RTT / (double)cubic_state->rtt_filter.rtt_filtered_min;
}
else {
correction = (double)PICOQUIC_TARGET_RENO_RTT / (double)cubic_state->rtt_filter.rtt_filtered_min;
}
uint64_t base_window = (uint64_t)(correction * (double)path_x->cwin);
uint64_t delta_window = path_x->cwin - base_window;
path_x->cwin -= (delta_window / 2);
}
cubic_state->ssthresh = path_x->cwin;
cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu;
cubic_state->W_last_max = cubic_state->W_max;
cubic_state->W_reno = ((double)path_x->cwin);
picoquic_cubic_enter_avoidance(cubic_state, current_time);
/* apply a correction to enter the test phase immediately */
uint64_t K_micro = (uint64_t)(cubic_state->K * 1000000.0);
if (K_micro > current_time) {
cubic_state->K = ((double)current_time) / 1000000.0;
cubic_state->start_of_epoch = 0;
}
else {
cubic_state->start_of_epoch = current_time - K_micro;
}
}
break;
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_bw_measurement: {
/* RTT measurements will happen after the bandwidth is estimated */
uint64_t max_win = path_x->max_bandwidth_estimate * path_x->smoothed_rtt / 1000000;
uint64_t min_win = max_win /= 2;
if (path_x->cwin < min_win) {
path_x->cwin = min_win;
}
break;
}
case picoquic_congestion_notification_reset:
picoquic_cubic_reset(cubic_state, path_x, current_time);
break;
default:
break;
}
break;
case picoquic_cubic_alg_recovery:
/* If the congestion notification is coming less than 1RTT after start,
* ignore it, unless it is a spurious retransmit detection */
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
/* exit recovery, move to CA or SS, depending on CWIN */
cubic_state->alg_state = picoquic_cubic_alg_slow_start;
path_x->cwin += nb_bytes_acknowledged;
/* if cnx->cwin exceeds SSTHRESH, exit and go to CA */
if (path_x->cwin >= cubic_state->ssthresh) {
cubic_state->alg_state = picoquic_cubic_alg_congestion_avoidance;
}
break;
case picoquic_congestion_notification_spurious_repeat:
picoquic_cubic_correct_spurious(path_x, cubic_state, current_time);
break;
case picoquic_congestion_notification_ecn_ec:
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
if (current_time - cubic_state->start_of_epoch > path_x->smoothed_rtt ||
cubic_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx)) {
/* re-enter recovery if this is a new loss */
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
break;
case picoquic_congestion_notification_rtt_measurement:
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_reset:
picoquic_cubic_reset(cubic_state, path_x, current_time);
break;
default:
/* ignore */
break;
}
break;
case picoquic_cubic_alg_congestion_avoidance:
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) {
double W_cubic;
uint64_t win_cubic;
/* Protection against limited senders. */
if (cubic_state->start_of_epoch < path_x->last_sender_limited_time) {
cubic_state->start_of_epoch = path_x->last_sender_limited_time;
}
/* Compute the cubic formula */
W_cubic = picoquic_cubic_W_cubic(cubic_state, current_time);
win_cubic = (uint64_t)(W_cubic * (double)path_x->send_mtu);
/* Also compute the Reno formula */
cubic_state->W_reno += ((double)nb_bytes_acknowledged) * ((double)path_x->send_mtu) / cubic_state->W_reno;
/* Pick the largest */
if (win_cubic > cubic_state->W_reno) {
/* if cubic is larger than threshold, switch to cubic mode */
path_x->cwin = win_cubic;
}
else {
path_x->cwin = (uint64_t)cubic_state->W_reno;
}
}
break;
case picoquic_congestion_notification_ecn_ec:
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
if (current_time - cubic_state->start_of_epoch > path_x->smoothed_rtt ||
cubic_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx)) {
/* re-enter recovery */
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
break;
case picoquic_congestion_notification_spurious_repeat:
picoquic_cubic_correct_spurious(path_x, cubic_state, current_time);
break;
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_rtt_measurement:
break;
case picoquic_congestion_notification_reset:
picoquic_cubic_reset(cubic_state, path_x, current_time);
break;
default:
/* ignore */
break;
}
break;
default:
break;
}
/* Compute pacing data */
picoquic_update_pacing_data(cnx, path_x, cubic_state->alg_state == picoquic_cubic_alg_slow_start &&
cubic_state->ssthresh == UINT64_MAX);
}
}
/* Exit slow start on either long delay of high loss
*/
static void dcubic_exit_slow_start(
picoquic_cnx_t* cnx, picoquic_path_t* path_x,
picoquic_congestion_notification_t notification,
picoquic_cubic_state_t* cubic_state,
uint64_t current_time)
{
if (cubic_state->ssthresh == UINT64_MAX) {
cubic_state->ssthresh = path_x->cwin;
cubic_state->W_max = (double)path_x->cwin / (double)path_x->send_mtu;
cubic_state->W_last_max = cubic_state->W_max;
cubic_state->W_reno = ((double)path_x->cwin);
picoquic_cubic_enter_avoidance(cubic_state, current_time);
/* apply a correction to enter the test phase immediately */
uint64_t K_micro = (uint64_t)(cubic_state->K * 1000000.0);
if (K_micro > current_time) {
cubic_state->K = ((double)current_time) / 1000000.0;
cubic_state->start_of_epoch = 0;
}
else {
cubic_state->start_of_epoch = current_time - K_micro;
}
}
else {
if (current_time - cubic_state->start_of_epoch > path_x->smoothed_rtt ||
cubic_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx)) {
/* re-enter recovery if this is a new event */
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
}
}
/*
* Define delay-based Cubic, dcubic, and alternative congestion control protocol similar to Cubic but
* using delay measurements instead of reacting to packet losses. This is a quic hack, intended for
* trials of a lossy satellite networks.
*/
static void picoquic_dcubic_notify(
picoquic_cnx_t* cnx, picoquic_path_t* path_x,
picoquic_congestion_notification_t notification,
uint64_t rtt_measurement,
uint64_t one_way_delay,
uint64_t nb_bytes_acknowledged,
uint64_t lost_packet_number,
uint64_t current_time)
{
#ifdef _WINDOWS
UNREFERENCED_PARAMETER(rtt_measurement);
UNREFERENCED_PARAMETER(lost_packet_number);
#endif
picoquic_cubic_state_t* cubic_state = (picoquic_cubic_state_t*)path_x->congestion_alg_state;
if (cubic_state != NULL) {
switch (cubic_state->alg_state) {
case picoquic_cubic_alg_slow_start:
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
/* Same as Cubic */
if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) {
picoquic_hystart_increase(path_x, &cubic_state->rtt_filter, nb_bytes_acknowledged);
/* if cnx->cwin exceeds SSTHRESH, exit and go to CA */
if (path_x->cwin >= cubic_state->ssthresh) {
cubic_state->W_reno = ((double)path_x->cwin) / 2.0;
picoquic_cubic_enter_avoidance(cubic_state, current_time);
}
}
break;
case picoquic_congestion_notification_ecn_ec:
/* In contrast to Cubic, do nothing here */
break;
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
/* In contrast to Cubic, only exit on high losses */
if (picoquic_hystart_loss_test(&cubic_state->rtt_filter, notification, lost_packet_number)) {
dcubic_exit_slow_start(cnx, path_x, notification, cubic_state, current_time);
}
break;
case picoquic_congestion_notification_spurious_repeat:
/* Unlike Cubic, losses have no effect so do nothing here */
break;
case picoquic_congestion_notification_rtt_measurement:
/* if in slow start, increase the window for long delay RTT */
if (path_x->rtt_min > PICOQUIC_TARGET_RENO_RTT && cubic_state->ssthresh == UINT64_MAX) {
uint64_t min_cwnd;
if (path_x->rtt_min > PICOQUIC_TARGET_SATELLITE_RTT) {
min_cwnd = (uint64_t)((double)PICOQUIC_CWIN_INITIAL * (double)PICOQUIC_TARGET_SATELLITE_RTT / (double)PICOQUIC_TARGET_RENO_RTT);
}
else {
min_cwnd = (uint64_t)((double)PICOQUIC_CWIN_INITIAL * (double)path_x->rtt_min / (double)PICOQUIC_TARGET_RENO_RTT);
}
if (min_cwnd > path_x->cwin) {
path_x->cwin = min_cwnd;
}
}
/* Using RTT increases as congestion signal. This is used
* for getting out of slow start, but also for ending a cycle
* during congestion avoidance */
if (picoquic_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? one_way_delay : rtt_measurement,
cnx->path[0]->pacing_packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) {
dcubic_exit_slow_start(cnx, path_x, notification, cubic_state, current_time);
}
break;
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_reset:
picoquic_cubic_reset(cubic_state, path_x, current_time);
break;
default:
/* ignore */
break;
}
break;
case picoquic_cubic_alg_recovery:
/* If the notification is coming less than 1RTT after start,
* ignore it, unless it is a spurious retransmit detection */
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
/* exit recovery, move to CA or SS, depending on CWIN */
cubic_state->alg_state = picoquic_cubic_alg_slow_start;
path_x->cwin += nb_bytes_acknowledged;
/* if cnx->cwin exceeds SSTHRESH, exit and go to CA */
if (path_x->cwin >= cubic_state->ssthresh) {
cubic_state->alg_state = picoquic_cubic_alg_congestion_avoidance;
}
break;
case picoquic_congestion_notification_spurious_repeat:
/* DO nothing */
break;
case picoquic_congestion_notification_ecn_ec:
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
/* do nothing */
case picoquic_congestion_notification_rtt_measurement:
/* if in slow start, increase the window for long delay RTT */
if (path_x->rtt_min > PICOQUIC_TARGET_RENO_RTT && cubic_state->ssthresh == UINT64_MAX) {
uint64_t min_cwnd;
if (path_x->rtt_min > PICOQUIC_TARGET_SATELLITE_RTT) {
min_cwnd = (uint64_t)((double)PICOQUIC_CWIN_INITIAL * (double)PICOQUIC_TARGET_SATELLITE_RTT / (double)PICOQUIC_TARGET_RENO_RTT);
}
else {
min_cwnd = (uint64_t)((double)PICOQUIC_CWIN_INITIAL * (double)path_x->rtt_min / (double)PICOQUIC_TARGET_RENO_RTT);
}
if (min_cwnd > path_x->cwin) {
path_x->cwin = min_cwnd;
}
}
if (picoquic_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? one_way_delay : rtt_measurement,
cnx->path[0]->pacing_packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) {
if (current_time - cubic_state->start_of_epoch > path_x->smoothed_rtt ||
cubic_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx)) {
/* re-enter recovery if this is a new event */
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
}
break;
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_reset:
picoquic_cubic_reset(cubic_state, path_x, current_time);
break;
default:
/* ignore */
break;
}
break;
case picoquic_cubic_alg_congestion_avoidance:
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) {
double W_cubic;
uint64_t win_cubic;
/* Protection against limited senders. */
if (cubic_state->start_of_epoch < path_x->last_sender_limited_time) {
cubic_state->start_of_epoch = path_x->last_sender_limited_time;
}
/* Compute the cubic formula */
W_cubic = picoquic_cubic_W_cubic(cubic_state, current_time);
win_cubic = (uint64_t)(W_cubic * (double)path_x->send_mtu);
/* Also compute the Reno formula */
cubic_state->W_reno += ((double)nb_bytes_acknowledged) * ((double)path_x->send_mtu) / cubic_state->W_reno;
/* Pick the largest */
if (win_cubic > cubic_state->W_reno) {
/* if cubic is larger than threshold, switch to cubic mode */
path_x->cwin = win_cubic;
}
else {
path_x->cwin = (uint64_t)cubic_state->W_reno;
}
}
break;
case picoquic_congestion_notification_ecn_ec:
/* Do nothing */
break;
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
/* In contrast to Cubic, only exit on high losses */
if (picoquic_hystart_loss_test(&cubic_state->rtt_filter, notification, lost_packet_number) &&
lost_packet_number > cubic_state->recovery_sequence) {
/* re-enter recovery */
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
break;
case picoquic_congestion_notification_spurious_repeat:
/* Do nothing */
break;
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_rtt_measurement:
if (picoquic_hystart_test(&cubic_state->rtt_filter, (cnx->is_time_stamp_enabled) ? one_way_delay : rtt_measurement,
cnx->path[0]->pacing_packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) {
if (current_time - cubic_state->start_of_epoch > path_x->smoothed_rtt ||
cubic_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx)) {
/* re-enter recovery */
picoquic_cubic_enter_recovery(cnx, path_x, notification, cubic_state, current_time);
}
}
break;
case picoquic_congestion_notification_reset:
picoquic_cubic_reset(cubic_state, path_x, current_time);
break;
default:
/* ignore */
break;
}
break;
default:
break;
}
/* Compute pacing data */
picoquic_update_pacing_data(cnx, path_x,
cubic_state->alg_state == picoquic_cubic_alg_slow_start && cubic_state->ssthresh == UINT64_MAX);
}
}
/* Release the state of the congestion control algorithm */
static void picoquic_cubic_delete(picoquic_path_t* path_x)
{
if (path_x->congestion_alg_state != NULL) {
free(path_x->congestion_alg_state);
path_x->congestion_alg_state = NULL;
}
}
/* Observe the state of congestion control */
void picoquic_cubic_observe(picoquic_path_t* path_x, uint64_t* cc_state, uint64_t* cc_param)
{
picoquic_cubic_state_t* cubic_state = (picoquic_cubic_state_t*)path_x->congestion_alg_state;
*cc_state = (uint64_t)cubic_state->alg_state;
*cc_param = (uint64_t)cubic_state->W_max;
}
/* Definition record for the Cubic algorithm */
#define picoquic_cubic_ID "cubic" /* CBIC */
#define picoquic_dcubic_ID "dcubic" /* DBIC */
picoquic_congestion_algorithm_t picoquic_cubic_algorithm_struct = {
picoquic_cubic_ID, 2,
picoquic_cubic_init,
picoquic_cubic_notify,
picoquic_cubic_delete,
picoquic_cubic_observe
};
picoquic_congestion_algorithm_t picoquic_dcubic_algorithm_struct = {
picoquic_dcubic_ID, 3,
picoquic_cubic_init,
picoquic_dcubic_notify,
picoquic_cubic_delete,
picoquic_cubic_observe
};
picoquic_congestion_algorithm_t* picoquic_cubic_algorithm = &picoquic_cubic_algorithm_struct;
picoquic_congestion_algorithm_t* picoquic_dcubic_algorithm = &picoquic_dcubic_algorithm_struct;