openssl/ssl/quic/quic_fc.c
Hugo Landau c4abf9ebb0 QUIC RXFC: Don't emit a MAX_STREAM_DATA frame if we have a final size
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/19703)
2023-01-13 13:20:11 +00:00

377 lines
9.3 KiB
C

/*
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "internal/quic_fc.h"
#include "internal/quic_error.h"
#include "internal/common.h"
#include "internal/safe_math.h"
#include <assert.h>
OSSL_SAFE_MATH_UNSIGNED(uint64_t, uint64_t)
/*
* TX Flow Controller (TXFC)
* =========================
*/
int ossl_quic_txfc_init(QUIC_TXFC *txfc, QUIC_TXFC *conn_txfc)
{
if (conn_txfc != NULL && conn_txfc->parent != NULL)
return 0;
txfc->swm = 0;
txfc->cwm = 0;
txfc->parent = conn_txfc;
txfc->has_become_blocked = 0;
return 1;
}
QUIC_TXFC *ossl_quic_txfc_get_parent(QUIC_TXFC *txfc)
{
return txfc->parent;
}
int ossl_quic_txfc_bump_cwm(QUIC_TXFC *txfc, uint64_t cwm)
{
if (cwm <= txfc->cwm)
return 0;
txfc->cwm = cwm;
return 1;
}
uint64_t ossl_quic_txfc_get_credit_local(QUIC_TXFC *txfc)
{
assert(txfc->swm <= txfc->cwm);
return txfc->cwm - txfc->swm;
}
uint64_t ossl_quic_txfc_get_credit(QUIC_TXFC *txfc)
{
uint64_t r, conn_r;
r = ossl_quic_txfc_get_credit_local(txfc);
if (txfc->parent != NULL) {
assert(txfc->parent->parent == NULL);
conn_r = ossl_quic_txfc_get_credit_local(txfc->parent);
if (conn_r < r)
r = conn_r;
}
return r;
}
int ossl_quic_txfc_consume_credit_local(QUIC_TXFC *txfc, uint64_t num_bytes)
{
int ok = 1;
uint64_t credit = ossl_quic_txfc_get_credit_local(txfc);
if (num_bytes > credit) {
ok = 0;
num_bytes = credit;
}
if (num_bytes > 0 && num_bytes == credit)
txfc->has_become_blocked = 1;
txfc->swm += num_bytes;
return ok;
}
int ossl_quic_txfc_consume_credit(QUIC_TXFC *txfc, uint64_t num_bytes)
{
int ok = ossl_quic_txfc_consume_credit_local(txfc, num_bytes);
if (txfc->parent != NULL) {
assert(txfc->parent->parent == NULL);
if (!ossl_quic_txfc_consume_credit_local(txfc->parent, num_bytes))
return 0;
}
return ok;
}
int ossl_quic_txfc_has_become_blocked(QUIC_TXFC *txfc, int clear)
{
int r = txfc->has_become_blocked;
if (clear)
txfc->has_become_blocked = 0;
return r;
}
uint64_t ossl_quic_txfc_get_cwm(QUIC_TXFC *txfc)
{
return txfc->cwm;
}
uint64_t ossl_quic_txfc_get_swm(QUIC_TXFC *txfc)
{
return txfc->swm;
}
/*
* RX Flow Controller (RXFC)
* =========================
*/
int ossl_quic_rxfc_init(QUIC_RXFC *rxfc, QUIC_RXFC *conn_rxfc,
uint64_t initial_window_size,
uint64_t max_window_size,
OSSL_TIME (*now)(void *now_arg),
void *now_arg)
{
if (conn_rxfc != NULL && conn_rxfc->parent != NULL)
return 0;
rxfc->swm = 0;
rxfc->cwm = initial_window_size;
rxfc->rwm = 0;
rxfc->esrwm = 0;
rxfc->hwm = 0;
rxfc->cur_window_size = initial_window_size;
rxfc->max_window_size = max_window_size;
rxfc->parent = conn_rxfc;
rxfc->error_code = 0;
rxfc->has_cwm_changed = 0;
rxfc->epoch_start = ossl_time_zero();
rxfc->now = now;
rxfc->now_arg = now_arg;
rxfc->is_fin = 0;
return 1;
}
QUIC_RXFC *ossl_quic_rxfc_get_parent(QUIC_RXFC *rxfc)
{
return rxfc->parent;
}
void ossl_quic_rxfc_set_max_window_size(QUIC_RXFC *rxfc,
size_t max_window_size)
{
rxfc->max_window_size = max_window_size;
}
static void rxfc_start_epoch(QUIC_RXFC *rxfc)
{
rxfc->epoch_start = rxfc->now(rxfc->now_arg);
rxfc->esrwm = rxfc->rwm;
}
static int on_rx_controlled_bytes(QUIC_RXFC *rxfc, uint64_t num_bytes)
{
int ok = 1;
uint64_t credit = rxfc->cwm - rxfc->swm;
if (num_bytes > credit) {
ok = 0;
num_bytes = credit;
rxfc->error_code = QUIC_ERR_FLOW_CONTROL_ERROR;
}
rxfc->swm += num_bytes;
return ok;
}
int ossl_quic_rxfc_on_rx_stream_frame(QUIC_RXFC *rxfc, uint64_t end, int is_fin)
{
uint64_t delta;
if (rxfc->parent == NULL)
return 0;
if (rxfc->is_fin && ((is_fin && rxfc->hwm != end) || end > rxfc->hwm)) {
/* Stream size cannot change after the stream is finished */
rxfc->error_code = QUIC_ERR_FINAL_SIZE_ERROR;
return 1; /* not a caller error */
}
if (is_fin)
rxfc->is_fin = 1;
if (end > rxfc->hwm) {
delta = end - rxfc->hwm;
rxfc->hwm = end;
on_rx_controlled_bytes(rxfc, delta); /* result ignored */
on_rx_controlled_bytes(rxfc->parent, delta); /* result ignored */
} else if (end < rxfc->hwm && is_fin) {
rxfc->error_code = QUIC_ERR_FINAL_SIZE_ERROR;
return 1; /* not a caller error */
}
return 1;
}
/* threshold = 3/4 */
#define WINDOW_THRESHOLD_NUM 3
#define WINDOW_THRESHOLD_DEN 4
static int rxfc_cwm_bump_desired(QUIC_RXFC *rxfc)
{
int err = 0;
uint64_t window_rem = rxfc->cwm - rxfc->rwm;
uint64_t threshold
= safe_mul_uint64_t(rxfc->cur_window_size,
WINDOW_THRESHOLD_NUM, &err) / WINDOW_THRESHOLD_DEN;
if (err)
/*
* Extremely large window should never occur, but if it does, just use
* 1/2 as the threshold.
*/
threshold = rxfc->cur_window_size / 2;
/*
* No point emitting a new MAX_STREAM_DATA frame if the stream has a final
* size.
*/
return !rxfc->is_fin && window_rem <= threshold;
}
static int rxfc_should_bump_window_size(QUIC_RXFC *rxfc, OSSL_TIME rtt)
{
/*
* dt: time since start of epoch
* b: bytes of window consumed since start of epoch
* dw: proportion of window consumed since start of epoch
* T_window: time it will take to use up the entire window, based on dt, dw
* RTT: The current estimated RTT.
*
* b = rwm - esrwm
* dw = b / window_size
* T_window = dt / dw
* T_window = dt / (b / window_size)
* T_window = (dt * window_size) / b
*
* We bump the window size if T_window < 4 * RTT.
*
* We leave the division by b on the LHS to reduce the risk of overflowing
* our 64-bit nanosecond representation, which will afford plenty of
* precision left over after the division anyway.
*/
uint64_t b = rxfc->rwm - rxfc->esrwm;
OSSL_TIME now, dt, t_window;
if (b == 0)
return 0;
now = rxfc->now(rxfc->now_arg);
dt = ossl_time_subtract(now, rxfc->epoch_start);
t_window = ossl_time_muldiv(dt, rxfc->cur_window_size, b);
return ossl_time_compare(t_window, ossl_time_multiply(rtt, 4)) < 0;
}
static void rxfc_adjust_window_size(QUIC_RXFC *rxfc, uint64_t min_window_size,
OSSL_TIME rtt)
{
/* Are we sending updates too often? */
uint64_t new_window_size;
new_window_size = rxfc->cur_window_size;
if (rxfc_should_bump_window_size(rxfc, rtt))
new_window_size *= 2;
if (new_window_size < min_window_size)
new_window_size = min_window_size;
if (new_window_size > rxfc->max_window_size) /* takes precedence over min size */
new_window_size = rxfc->max_window_size;
rxfc->cur_window_size = new_window_size;
rxfc_start_epoch(rxfc);
}
static void rxfc_update_cwm(QUIC_RXFC *rxfc, uint64_t min_window_size,
OSSL_TIME rtt)
{
uint64_t new_cwm;
if (!rxfc_cwm_bump_desired(rxfc))
return;
rxfc_adjust_window_size(rxfc, min_window_size, rtt);
new_cwm = rxfc->rwm + rxfc->cur_window_size;
if (new_cwm > rxfc->cwm) {
rxfc->cwm = new_cwm;
rxfc->has_cwm_changed = 1;
}
}
static int rxfc_on_retire(QUIC_RXFC *rxfc, uint64_t num_bytes,
uint64_t min_window_size,
OSSL_TIME rtt)
{
if (ossl_time_is_zero(rxfc->epoch_start))
/* This happens when we retire our first ever bytes. */
rxfc_start_epoch(rxfc);
rxfc->rwm += num_bytes;
rxfc_update_cwm(rxfc, min_window_size, rtt);
return 1;
}
int ossl_quic_rxfc_on_retire(QUIC_RXFC *rxfc,
uint64_t num_bytes,
OSSL_TIME rtt)
{
if (rxfc->parent == NULL)
return 0;
if (num_bytes == 0)
return 1;
if (rxfc->rwm + num_bytes > rxfc->swm)
/* Impossible for us to retire more bytes than we have received. */
return 0;
rxfc_on_retire(rxfc, num_bytes, 0, rtt);
rxfc_on_retire(rxfc->parent, num_bytes, rxfc->cur_window_size, rtt);
return 1;
}
uint64_t ossl_quic_rxfc_get_cwm(QUIC_RXFC *rxfc)
{
return rxfc->cwm;
}
uint64_t ossl_quic_rxfc_get_swm(QUIC_RXFC *rxfc)
{
return rxfc->swm;
}
uint64_t ossl_quic_rxfc_get_rwm(QUIC_RXFC *rxfc)
{
return rxfc->rwm;
}
int ossl_quic_rxfc_has_cwm_changed(QUIC_RXFC *rxfc, int clear)
{
int r = rxfc->has_cwm_changed;
if (clear)
rxfc->has_cwm_changed = 0;
return r;
}
int ossl_quic_rxfc_get_error(QUIC_RXFC *rxfc, int clear)
{
int r = rxfc->error_code;
if (clear)
rxfc->error_code = 0;
return r;
}