1 1.1 christos /* 2 1.1 christos * Copyright 2022-2024 The OpenSSL Project Authors. All Rights Reserved. 3 1.1 christos * 4 1.1 christos * Licensed under the Apache License 2.0 (the "License"). You may not use 5 1.1 christos * this file except in compliance with the License. You can obtain a copy 6 1.1 christos * in the file LICENSE in the source distribution or at 7 1.1 christos * https://www.openssl.org/source/license.html 8 1.1 christos */ 9 1.1 christos 10 1.1 christos #include "internal/quic_fc.h" 11 1.1 christos #include "internal/quic_error.h" 12 1.1 christos #include "internal/common.h" 13 1.1 christos #include "internal/safe_math.h" 14 1.1 christos #include <assert.h> 15 1.1 christos 16 1.1 christos OSSL_SAFE_MATH_UNSIGNED(uint64_t, uint64_t) 17 1.1 christos 18 1.1 christos /* 19 1.1 christos * TX Flow Controller (TXFC) 20 1.1 christos * ========================= 21 1.1 christos */ 22 1.1 christos 23 1.1 christos int ossl_quic_txfc_init(QUIC_TXFC *txfc, QUIC_TXFC *conn_txfc) 24 1.1 christos { 25 1.1 christos if (conn_txfc != NULL && conn_txfc->parent != NULL) 26 1.1 christos return 0; 27 1.1 christos 28 1.1.1.2 christos txfc->swm = 0; 29 1.1.1.2 christos txfc->cwm = 0; 30 1.1.1.2 christos txfc->parent = conn_txfc; 31 1.1.1.2 christos txfc->has_become_blocked = 0; 32 1.1 christos return 1; 33 1.1 christos } 34 1.1 christos 35 1.1 christos QUIC_TXFC *ossl_quic_txfc_get_parent(QUIC_TXFC *txfc) 36 1.1 christos { 37 1.1 christos return txfc->parent; 38 1.1 christos } 39 1.1 christos 40 1.1 christos int ossl_quic_txfc_bump_cwm(QUIC_TXFC *txfc, uint64_t cwm) 41 1.1 christos { 42 1.1 christos if (cwm <= txfc->cwm) 43 1.1 christos return 0; 44 1.1 christos 45 1.1 christos txfc->cwm = cwm; 46 1.1 christos return 1; 47 1.1 christos } 48 1.1 christos 49 1.1 christos uint64_t ossl_quic_txfc_get_credit_local(QUIC_TXFC *txfc, uint64_t consumed) 50 1.1 christos { 51 1.1 christos assert((txfc->swm + consumed) <= txfc->cwm); 52 1.1 christos return txfc->cwm - (consumed + txfc->swm); 53 1.1 christos } 54 1.1 christos 55 1.1 christos uint64_t ossl_quic_txfc_get_credit(QUIC_TXFC *txfc, uint64_t consumed) 56 1.1 christos { 57 1.1 christos uint64_t r, conn_r; 58 1.1 christos 59 1.1 christos r = ossl_quic_txfc_get_credit_local(txfc, 0); 60 1.1 christos 61 1.1 christos if (txfc->parent != NULL) { 62 1.1 christos assert(txfc->parent->parent == NULL); 63 1.1 christos conn_r = ossl_quic_txfc_get_credit_local(txfc->parent, consumed); 64 1.1 christos if (conn_r < r) 65 1.1 christos r = conn_r; 66 1.1 christos } 67 1.1 christos 68 1.1 christos return r; 69 1.1 christos } 70 1.1 christos 71 1.1 christos int ossl_quic_txfc_consume_credit_local(QUIC_TXFC *txfc, uint64_t num_bytes) 72 1.1 christos { 73 1.1 christos int ok = 1; 74 1.1 christos uint64_t credit = ossl_quic_txfc_get_credit_local(txfc, 0); 75 1.1 christos 76 1.1 christos if (num_bytes > credit) { 77 1.1 christos ok = 0; 78 1.1 christos num_bytes = credit; 79 1.1 christos } 80 1.1 christos 81 1.1 christos if (num_bytes > 0 && num_bytes == credit) 82 1.1 christos txfc->has_become_blocked = 1; 83 1.1 christos 84 1.1 christos txfc->swm += num_bytes; 85 1.1 christos return ok; 86 1.1 christos } 87 1.1 christos 88 1.1 christos int ossl_quic_txfc_consume_credit(QUIC_TXFC *txfc, uint64_t num_bytes) 89 1.1 christos { 90 1.1 christos int ok = ossl_quic_txfc_consume_credit_local(txfc, num_bytes); 91 1.1 christos 92 1.1 christos if (txfc->parent != NULL) { 93 1.1 christos assert(txfc->parent->parent == NULL); 94 1.1 christos if (!ossl_quic_txfc_consume_credit_local(txfc->parent, num_bytes)) 95 1.1 christos return 0; 96 1.1 christos } 97 1.1 christos 98 1.1 christos return ok; 99 1.1 christos } 100 1.1 christos 101 1.1 christos int ossl_quic_txfc_has_become_blocked(QUIC_TXFC *txfc, int clear) 102 1.1 christos { 103 1.1 christos int r = txfc->has_become_blocked; 104 1.1 christos 105 1.1 christos if (clear) 106 1.1 christos txfc->has_become_blocked = 0; 107 1.1 christos 108 1.1 christos return r; 109 1.1 christos } 110 1.1 christos 111 1.1 christos uint64_t ossl_quic_txfc_get_cwm(QUIC_TXFC *txfc) 112 1.1 christos { 113 1.1 christos return txfc->cwm; 114 1.1 christos } 115 1.1 christos 116 1.1 christos uint64_t ossl_quic_txfc_get_swm(QUIC_TXFC *txfc) 117 1.1 christos { 118 1.1 christos return txfc->swm; 119 1.1 christos } 120 1.1 christos 121 1.1 christos /* 122 1.1 christos * RX Flow Controller (RXFC) 123 1.1 christos * ========================= 124 1.1 christos */ 125 1.1 christos 126 1.1 christos int ossl_quic_rxfc_init(QUIC_RXFC *rxfc, QUIC_RXFC *conn_rxfc, 127 1.1.1.2 christos uint64_t initial_window_size, 128 1.1.1.2 christos uint64_t max_window_size, 129 1.1.1.2 christos OSSL_TIME (*now)(void *now_arg), 130 1.1.1.2 christos void *now_arg) 131 1.1 christos { 132 1.1 christos if (conn_rxfc != NULL && conn_rxfc->parent != NULL) 133 1.1 christos return 0; 134 1.1 christos 135 1.1.1.2 christos rxfc->swm = 0; 136 1.1.1.2 christos rxfc->cwm = initial_window_size; 137 1.1.1.2 christos rxfc->rwm = 0; 138 1.1.1.2 christos rxfc->esrwm = 0; 139 1.1.1.2 christos rxfc->hwm = 0; 140 1.1.1.2 christos rxfc->cur_window_size = initial_window_size; 141 1.1.1.2 christos rxfc->max_window_size = max_window_size; 142 1.1.1.2 christos rxfc->parent = conn_rxfc; 143 1.1.1.2 christos rxfc->error_code = 0; 144 1.1.1.2 christos rxfc->has_cwm_changed = 0; 145 1.1.1.2 christos rxfc->epoch_start = ossl_time_zero(); 146 1.1.1.2 christos rxfc->now = now; 147 1.1.1.2 christos rxfc->now_arg = now_arg; 148 1.1.1.2 christos rxfc->is_fin = 0; 149 1.1.1.2 christos rxfc->standalone = 0; 150 1.1 christos return 1; 151 1.1 christos } 152 1.1 christos 153 1.1 christos int ossl_quic_rxfc_init_standalone(QUIC_RXFC *rxfc, 154 1.1.1.2 christos uint64_t initial_window_size, 155 1.1.1.2 christos OSSL_TIME (*now)(void *arg), 156 1.1.1.2 christos void *now_arg) 157 1.1 christos { 158 1.1 christos if (!ossl_quic_rxfc_init(rxfc, NULL, 159 1.1.1.2 christos initial_window_size, initial_window_size, 160 1.1.1.2 christos now, now_arg)) 161 1.1 christos return 0; 162 1.1 christos 163 1.1 christos rxfc->standalone = 1; 164 1.1 christos return 1; 165 1.1 christos } 166 1.1 christos 167 1.1 christos QUIC_RXFC *ossl_quic_rxfc_get_parent(QUIC_RXFC *rxfc) 168 1.1 christos { 169 1.1 christos return rxfc->parent; 170 1.1 christos } 171 1.1 christos 172 1.1 christos void ossl_quic_rxfc_set_max_window_size(QUIC_RXFC *rxfc, 173 1.1.1.2 christos size_t max_window_size) 174 1.1 christos { 175 1.1 christos rxfc->max_window_size = max_window_size; 176 1.1 christos } 177 1.1 christos 178 1.1 christos static void rxfc_start_epoch(QUIC_RXFC *rxfc) 179 1.1 christos { 180 1.1.1.2 christos rxfc->epoch_start = rxfc->now(rxfc->now_arg); 181 1.1.1.2 christos rxfc->esrwm = rxfc->rwm; 182 1.1 christos } 183 1.1 christos 184 1.1 christos static int on_rx_controlled_bytes(QUIC_RXFC *rxfc, uint64_t num_bytes) 185 1.1 christos { 186 1.1 christos int ok = 1; 187 1.1 christos uint64_t credit = rxfc->cwm - rxfc->swm; 188 1.1 christos 189 1.1 christos if (num_bytes > credit) { 190 1.1 christos ok = 0; 191 1.1 christos num_bytes = credit; 192 1.1 christos rxfc->error_code = OSSL_QUIC_ERR_FLOW_CONTROL_ERROR; 193 1.1 christos } 194 1.1 christos 195 1.1 christos rxfc->swm += num_bytes; 196 1.1 christos return ok; 197 1.1 christos } 198 1.1 christos 199 1.1 christos int ossl_quic_rxfc_on_rx_stream_frame(QUIC_RXFC *rxfc, uint64_t end, int is_fin) 200 1.1 christos { 201 1.1 christos uint64_t delta; 202 1.1 christos 203 1.1 christos if (!rxfc->standalone && rxfc->parent == NULL) 204 1.1 christos return 0; 205 1.1 christos 206 1.1 christos if (rxfc->is_fin && ((is_fin && rxfc->hwm != end) || end > rxfc->hwm)) { 207 1.1 christos /* Stream size cannot change after the stream is finished */ 208 1.1 christos rxfc->error_code = OSSL_QUIC_ERR_FINAL_SIZE_ERROR; 209 1.1 christos return 1; /* not a caller error */ 210 1.1 christos } 211 1.1 christos 212 1.1 christos if (is_fin) 213 1.1 christos rxfc->is_fin = 1; 214 1.1 christos 215 1.1 christos if (end > rxfc->hwm) { 216 1.1 christos delta = end - rxfc->hwm; 217 1.1 christos rxfc->hwm = end; 218 1.1 christos 219 1.1.1.2 christos on_rx_controlled_bytes(rxfc, delta); /* result ignored */ 220 1.1 christos if (rxfc->parent != NULL) 221 1.1 christos on_rx_controlled_bytes(rxfc->parent, delta); /* result ignored */ 222 1.1 christos } else if (end < rxfc->hwm && is_fin) { 223 1.1 christos rxfc->error_code = OSSL_QUIC_ERR_FINAL_SIZE_ERROR; 224 1.1 christos return 1; /* not a caller error */ 225 1.1 christos } 226 1.1 christos 227 1.1 christos return 1; 228 1.1 christos } 229 1.1 christos 230 1.1 christos /* threshold = 3/4 */ 231 1.1 christos #define WINDOW_THRESHOLD_NUM 3 232 1.1 christos #define WINDOW_THRESHOLD_DEN 4 233 1.1 christos 234 1.1 christos static int rxfc_cwm_bump_desired(QUIC_RXFC *rxfc) 235 1.1 christos { 236 1.1 christos int err = 0; 237 1.1 christos uint64_t window_rem = rxfc->cwm - rxfc->rwm; 238 1.1 christos uint64_t threshold 239 1.1 christos = safe_muldiv_uint64_t(rxfc->cur_window_size, 240 1.1.1.2 christos WINDOW_THRESHOLD_NUM, WINDOW_THRESHOLD_DEN, &err); 241 1.1 christos 242 1.1 christos if (err) 243 1.1 christos /* 244 1.1 christos * Extremely large window should never occur, but if it does, just use 245 1.1 christos * 1/2 as the threshold. 246 1.1 christos */ 247 1.1 christos threshold = rxfc->cur_window_size / 2; 248 1.1 christos 249 1.1 christos /* 250 1.1 christos * No point emitting a new MAX_STREAM_DATA frame if the stream has a final 251 1.1 christos * size. 252 1.1 christos */ 253 1.1 christos return !rxfc->is_fin && window_rem <= threshold; 254 1.1 christos } 255 1.1 christos 256 1.1 christos static int rxfc_should_bump_window_size(QUIC_RXFC *rxfc, OSSL_TIME rtt) 257 1.1 christos { 258 1.1 christos /* 259 1.1 christos * dt: time since start of epoch 260 1.1 christos * b: bytes of window consumed since start of epoch 261 1.1 christos * dw: proportion of window consumed since start of epoch 262 1.1 christos * T_window: time it will take to use up the entire window, based on dt, dw 263 1.1 christos * RTT: The current estimated RTT. 264 1.1 christos * 265 1.1 christos * b = rwm - esrwm 266 1.1 christos * dw = b / window_size 267 1.1 christos * T_window = dt / dw 268 1.1 christos * T_window = dt / (b / window_size) 269 1.1 christos * T_window = (dt * window_size) / b 270 1.1 christos * 271 1.1 christos * We bump the window size if T_window < 4 * RTT. 272 1.1 christos * 273 1.1 christos * We leave the division by b on the LHS to reduce the risk of overflowing 274 1.1 christos * our 64-bit nanosecond representation, which will afford plenty of 275 1.1 christos * precision left over after the division anyway. 276 1.1 christos */ 277 1.1.1.2 christos uint64_t b = rxfc->rwm - rxfc->esrwm; 278 1.1 christos OSSL_TIME now, dt, t_window; 279 1.1 christos 280 1.1 christos if (b == 0) 281 1.1 christos return 0; 282 1.1 christos 283 1.1.1.2 christos now = rxfc->now(rxfc->now_arg); 284 1.1.1.2 christos dt = ossl_time_subtract(now, rxfc->epoch_start); 285 1.1 christos t_window = ossl_time_muldiv(dt, rxfc->cur_window_size, b); 286 1.1 christos 287 1.1 christos return ossl_time_compare(t_window, ossl_time_multiply(rtt, 4)) < 0; 288 1.1 christos } 289 1.1 christos 290 1.1 christos static void rxfc_adjust_window_size(QUIC_RXFC *rxfc, uint64_t min_window_size, 291 1.1.1.2 christos OSSL_TIME rtt) 292 1.1 christos { 293 1.1 christos /* Are we sending updates too often? */ 294 1.1 christos uint64_t new_window_size; 295 1.1 christos 296 1.1 christos new_window_size = rxfc->cur_window_size; 297 1.1 christos 298 1.1 christos if (rxfc_should_bump_window_size(rxfc, rtt)) 299 1.1 christos new_window_size *= 2; 300 1.1 christos 301 1.1 christos if (new_window_size < min_window_size) 302 1.1 christos new_window_size = min_window_size; 303 1.1 christos if (new_window_size > rxfc->max_window_size) /* takes precedence over min size */ 304 1.1 christos new_window_size = rxfc->max_window_size; 305 1.1 christos 306 1.1 christos rxfc->cur_window_size = new_window_size; 307 1.1 christos rxfc_start_epoch(rxfc); 308 1.1 christos } 309 1.1 christos 310 1.1 christos static void rxfc_update_cwm(QUIC_RXFC *rxfc, uint64_t min_window_size, 311 1.1.1.2 christos OSSL_TIME rtt) 312 1.1 christos { 313 1.1 christos uint64_t new_cwm; 314 1.1 christos 315 1.1 christos if (!rxfc_cwm_bump_desired(rxfc)) 316 1.1 christos return; 317 1.1 christos 318 1.1 christos rxfc_adjust_window_size(rxfc, min_window_size, rtt); 319 1.1 christos 320 1.1 christos new_cwm = rxfc->rwm + rxfc->cur_window_size; 321 1.1 christos if (new_cwm > rxfc->cwm) { 322 1.1 christos rxfc->cwm = new_cwm; 323 1.1 christos rxfc->has_cwm_changed = 1; 324 1.1 christos } 325 1.1 christos } 326 1.1 christos 327 1.1 christos static int rxfc_on_retire(QUIC_RXFC *rxfc, uint64_t num_bytes, 328 1.1.1.2 christos uint64_t min_window_size, 329 1.1.1.2 christos OSSL_TIME rtt) 330 1.1 christos { 331 1.1 christos if (ossl_time_is_zero(rxfc->epoch_start)) 332 1.1 christos /* This happens when we retire our first ever bytes. */ 333 1.1 christos rxfc_start_epoch(rxfc); 334 1.1 christos 335 1.1 christos rxfc->rwm += num_bytes; 336 1.1 christos rxfc_update_cwm(rxfc, min_window_size, rtt); 337 1.1 christos return 1; 338 1.1 christos } 339 1.1 christos 340 1.1 christos int ossl_quic_rxfc_on_retire(QUIC_RXFC *rxfc, 341 1.1.1.2 christos uint64_t num_bytes, 342 1.1.1.2 christos OSSL_TIME rtt) 343 1.1 christos { 344 1.1 christos if (rxfc->parent == NULL && !rxfc->standalone) 345 1.1 christos return 0; 346 1.1 christos 347 1.1 christos if (num_bytes == 0) 348 1.1 christos return 1; 349 1.1 christos 350 1.1 christos if (rxfc->rwm + num_bytes > rxfc->swm) 351 1.1 christos /* Impossible for us to retire more bytes than we have received. */ 352 1.1 christos return 0; 353 1.1 christos 354 1.1 christos rxfc_on_retire(rxfc, num_bytes, 0, rtt); 355 1.1 christos 356 1.1 christos if (!rxfc->standalone) 357 1.1 christos rxfc_on_retire(rxfc->parent, num_bytes, rxfc->cur_window_size, rtt); 358 1.1 christos 359 1.1 christos return 1; 360 1.1 christos } 361 1.1 christos 362 1.1 christos uint64_t ossl_quic_rxfc_get_cwm(const QUIC_RXFC *rxfc) 363 1.1 christos { 364 1.1 christos return rxfc->cwm; 365 1.1 christos } 366 1.1 christos 367 1.1 christos uint64_t ossl_quic_rxfc_get_swm(const QUIC_RXFC *rxfc) 368 1.1 christos { 369 1.1 christos return rxfc->swm; 370 1.1 christos } 371 1.1 christos 372 1.1 christos uint64_t ossl_quic_rxfc_get_rwm(const QUIC_RXFC *rxfc) 373 1.1 christos { 374 1.1 christos return rxfc->rwm; 375 1.1 christos } 376 1.1 christos 377 1.1 christos uint64_t ossl_quic_rxfc_get_credit(const QUIC_RXFC *rxfc) 378 1.1 christos { 379 1.1 christos return ossl_quic_rxfc_get_cwm(rxfc) - ossl_quic_rxfc_get_swm(rxfc); 380 1.1 christos } 381 1.1 christos 382 1.1 christos int ossl_quic_rxfc_has_cwm_changed(QUIC_RXFC *rxfc, int clear) 383 1.1 christos { 384 1.1 christos int r = rxfc->has_cwm_changed; 385 1.1 christos 386 1.1 christos if (clear) 387 1.1 christos rxfc->has_cwm_changed = 0; 388 1.1 christos 389 1.1 christos return r; 390 1.1 christos } 391 1.1 christos 392 1.1 christos int ossl_quic_rxfc_get_error(QUIC_RXFC *rxfc, int clear) 393 1.1 christos { 394 1.1 christos int r = rxfc->error_code; 395 1.1 christos 396 1.1 christos if (clear) 397 1.1 christos rxfc->error_code = 0; 398 1.1 christos 399 1.1 christos return r; 400 1.1 christos } 401 1.1 christos 402 1.1 christos int ossl_quic_rxfc_get_final_size(const QUIC_RXFC *rxfc, uint64_t *final_size) 403 1.1 christos { 404 1.1 christos if (!rxfc->is_fin) 405 1.1 christos return 0; 406 1.1 christos 407 1.1 christos if (final_size != NULL) 408 1.1 christos *final_size = rxfc->hwm; 409 1.1 christos 410 1.1 christos return 1; 411 1.1 christos } 412