Coverage Report

Created: 2025-06-13 06:58

/src/openssl32/ssl/quic/cc_newreno.c
Line
Count
Source (jump to first uncovered line)
1
#include "internal/quic_cc.h"
2
#include "internal/quic_types.h"
3
#include "internal/safe_math.h"
4
5
OSSL_SAFE_MATH_UNSIGNED(u64, uint64_t)
6
7
typedef struct ossl_cc_newreno_st {
8
    /* Dependencies. */
9
    OSSL_TIME   (*now_cb)(void *arg);
10
    void        *now_cb_arg;
11
12
    /* 'Constants' (which we allow to be configurable). */
13
    uint64_t    k_init_wnd, k_min_wnd;
14
    uint32_t    k_loss_reduction_factor_num, k_loss_reduction_factor_den;
15
    uint32_t    persistent_cong_thresh;
16
17
    /* State. */
18
    size_t      max_dgram_size;
19
    uint64_t    bytes_in_flight, cong_wnd, slow_start_thresh, bytes_acked;
20
    OSSL_TIME   cong_recovery_start_time;
21
22
    /* Unflushed state during multiple on-loss calls. */
23
    int         processing_loss; /* 1 if not flushed */
24
    OSSL_TIME   tx_time_of_last_loss;
25
26
    /* Diagnostic state. */
27
    int         in_congestion_recovery;
28
29
    /* Diagnostic output locations. */
30
    size_t      *p_diag_max_dgram_payload_len;
31
    uint64_t    *p_diag_cur_cwnd_size;
32
    uint64_t    *p_diag_min_cwnd_size;
33
    uint64_t    *p_diag_cur_bytes_in_flight;
34
    uint32_t    *p_diag_cur_state;
35
} OSSL_CC_NEWRENO;
36
37
44.1k
#define MIN_MAX_INIT_WND_SIZE    14720  /* RFC 9002 s. 7.2 */
38
39
/* TODO(QUIC FUTURE): Pacing support. */
40
41
static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
42
                                       size_t max_dgram_size);
43
static void newreno_update_diag(OSSL_CC_NEWRENO *nr);
44
45
static void newreno_reset(OSSL_CC_DATA *cc);
46
47
static OSSL_CC_DATA *newreno_new(OSSL_TIME (*now_cb)(void *arg),
48
                                 void *now_cb_arg)
49
22.0k
{
50
22.0k
    OSSL_CC_NEWRENO *nr;
51
52
22.0k
    if ((nr = OPENSSL_zalloc(sizeof(*nr))) == NULL)
53
0
        return NULL;
54
55
22.0k
    nr->now_cb          = now_cb;
56
22.0k
    nr->now_cb_arg      = now_cb_arg;
57
58
22.0k
    newreno_set_max_dgram_size(nr, QUIC_MIN_INITIAL_DGRAM_LEN);
59
22.0k
    newreno_reset((OSSL_CC_DATA *)nr);
60
61
22.0k
    return (OSSL_CC_DATA *)nr;
62
22.0k
}
63
64
static void newreno_free(OSSL_CC_DATA *cc)
65
22.0k
{
66
22.0k
    OPENSSL_free(cc);
67
22.0k
}
68
69
static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
70
                                       size_t max_dgram_size)
71
22.0k
{
72
22.0k
    size_t max_init_wnd;
73
22.0k
    int is_reduced = (max_dgram_size < nr->max_dgram_size);
74
75
22.0k
    nr->max_dgram_size = max_dgram_size;
76
77
22.0k
    max_init_wnd = 2 * max_dgram_size;
78
22.0k
    if (max_init_wnd < MIN_MAX_INIT_WND_SIZE)
79
22.0k
        max_init_wnd = MIN_MAX_INIT_WND_SIZE;
80
81
22.0k
    nr->k_init_wnd = 10 * max_dgram_size;
82
22.0k
    if (nr->k_init_wnd > max_init_wnd)
83
0
        nr->k_init_wnd = max_init_wnd;
84
85
22.0k
    nr->k_min_wnd = 2 * max_dgram_size;
86
87
22.0k
    if (is_reduced)
88
0
        nr->cong_wnd = nr->k_init_wnd;
89
90
22.0k
    newreno_update_diag(nr);
91
22.0k
}
92
93
static void newreno_reset(OSSL_CC_DATA *cc)
94
22.0k
{
95
22.0k
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
96
97
22.0k
    nr->k_loss_reduction_factor_num     = 1;
98
22.0k
    nr->k_loss_reduction_factor_den     = 2;
99
22.0k
    nr->persistent_cong_thresh          = 3;
100
101
22.0k
    nr->cong_wnd                    = nr->k_init_wnd;
102
22.0k
    nr->bytes_in_flight             = 0;
103
22.0k
    nr->bytes_acked                 = 0;
104
22.0k
    nr->slow_start_thresh           = UINT64_MAX;
105
22.0k
    nr->cong_recovery_start_time    = ossl_time_zero();
106
107
22.0k
    nr->processing_loss         = 0;
108
22.0k
    nr->tx_time_of_last_loss    = ossl_time_zero();
109
22.0k
    nr->in_congestion_recovery  = 0;
110
22.0k
}
111
112
static int newreno_set_input_params(OSSL_CC_DATA *cc, const OSSL_PARAM *params)
113
0
{
114
0
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
115
0
    const OSSL_PARAM *p;
116
0
    size_t value;
117
118
0
    p = OSSL_PARAM_locate_const(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN);
119
0
    if (p != NULL) {
120
0
        if (!OSSL_PARAM_get_size_t(p, &value))
121
0
            return 0;
122
0
        if (value < QUIC_MIN_INITIAL_DGRAM_LEN)
123
0
            return 0;
124
125
0
        newreno_set_max_dgram_size(nr, value);
126
0
    }
127
128
0
    return 1;
129
0
}
130
131
static int bind_diag(OSSL_PARAM *params, const char *param_name, size_t len,
132
                     void **pp)
133
0
{
134
0
    const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
135
136
0
    *pp = NULL;
137
138
0
    if (p == NULL)
139
0
        return 1;
140
141
0
    if (p->data_type != OSSL_PARAM_UNSIGNED_INTEGER
142
0
        || p->data_size != len)
143
0
        return 0;
144
145
0
    *pp = p->data;
146
0
    return 1;
147
0
}
148
149
static int newreno_bind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
150
0
{
151
0
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
152
0
    size_t *new_p_max_dgram_payload_len;
153
0
    uint64_t *new_p_cur_cwnd_size;
154
0
    uint64_t *new_p_min_cwnd_size;
155
0
    uint64_t *new_p_cur_bytes_in_flight;
156
0
    uint32_t *new_p_cur_state;
157
158
0
    if (!bind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
159
0
                   sizeof(size_t), (void **)&new_p_max_dgram_payload_len)
160
0
        || !bind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
161
0
                      sizeof(uint64_t), (void **)&new_p_cur_cwnd_size)
162
0
        || !bind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
163
0
                      sizeof(uint64_t), (void **)&new_p_min_cwnd_size)
164
0
        || !bind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
165
0
                      sizeof(uint64_t), (void **)&new_p_cur_bytes_in_flight)
166
0
        || !bind_diag(params, OSSL_CC_OPTION_CUR_STATE,
167
0
                      sizeof(uint32_t), (void **)&new_p_cur_state))
168
0
        return 0;
169
170
0
    if (new_p_max_dgram_payload_len != NULL)
171
0
        nr->p_diag_max_dgram_payload_len = new_p_max_dgram_payload_len;
172
173
0
    if (new_p_cur_cwnd_size != NULL)
174
0
        nr->p_diag_cur_cwnd_size = new_p_cur_cwnd_size;
175
176
0
    if (new_p_min_cwnd_size != NULL)
177
0
        nr->p_diag_min_cwnd_size = new_p_min_cwnd_size;
178
179
0
    if (new_p_cur_bytes_in_flight != NULL)
180
0
        nr->p_diag_cur_bytes_in_flight = new_p_cur_bytes_in_flight;
181
182
0
    if (new_p_cur_state != NULL)
183
0
        nr->p_diag_cur_state = new_p_cur_state;
184
185
0
    newreno_update_diag(nr);
186
0
    return 1;
187
0
}
188
189
static void unbind_diag(OSSL_PARAM *params, const char *param_name,
190
                        void **pp)
191
0
{
192
0
    const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
193
194
0
    if (p != NULL)
195
0
        *pp = NULL;
196
0
}
197
198
static int newreno_unbind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
199
0
{
200
0
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
201
202
0
    unbind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
203
0
                (void **)&nr->p_diag_max_dgram_payload_len);
204
0
    unbind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
205
0
                (void **)&nr->p_diag_cur_cwnd_size);
206
0
    unbind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
207
0
                (void **)&nr->p_diag_min_cwnd_size);
208
0
    unbind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
209
0
                (void **)&nr->p_diag_cur_bytes_in_flight);
210
0
    unbind_diag(params, OSSL_CC_OPTION_CUR_STATE,
211
0
                (void **)&nr->p_diag_cur_state);
212
0
    return 1;
213
0
}
214
215
static void newreno_update_diag(OSSL_CC_NEWRENO *nr)
216
1.30M
{
217
1.30M
    if (nr->p_diag_max_dgram_payload_len != NULL)
218
0
        *nr->p_diag_max_dgram_payload_len = nr->max_dgram_size;
219
220
1.30M
    if (nr->p_diag_cur_cwnd_size != NULL)
221
0
        *nr->p_diag_cur_cwnd_size = nr->cong_wnd;
222
223
1.30M
    if (nr->p_diag_min_cwnd_size != NULL)
224
0
        *nr->p_diag_min_cwnd_size = nr->k_min_wnd;
225
226
1.30M
    if (nr->p_diag_cur_bytes_in_flight != NULL)
227
0
        *nr->p_diag_cur_bytes_in_flight = nr->bytes_in_flight;
228
229
1.30M
    if (nr->p_diag_cur_state != NULL) {
230
0
        if (nr->in_congestion_recovery)
231
0
            *nr->p_diag_cur_state = 'R';
232
0
        else if (nr->cong_wnd < nr->slow_start_thresh)
233
0
            *nr->p_diag_cur_state = 'S';
234
0
        else
235
0
            *nr->p_diag_cur_state = 'A';
236
0
    }
237
1.30M
}
238
239
static int newreno_in_cong_recovery(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
240
77.6k
{
241
77.6k
    return ossl_time_compare(tx_time, nr->cong_recovery_start_time) <= 0;
242
77.6k
}
243
244
static void newreno_cong(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
245
6.16k
{
246
6.16k
    int err = 0;
247
248
    /* No reaction if already in a recovery period. */
249
6.16k
    if (newreno_in_cong_recovery(nr, tx_time))
250
1.32k
        return;
251
252
    /* Start a new recovery period. */
253
4.83k
    nr->in_congestion_recovery = 1;
254
4.83k
    nr->cong_recovery_start_time = nr->now_cb(nr->now_cb_arg);
255
256
    /* slow_start_thresh = cong_wnd * loss_reduction_factor */
257
4.83k
    nr->slow_start_thresh
258
4.83k
        = safe_muldiv_u64(nr->cong_wnd,
259
4.83k
                          nr->k_loss_reduction_factor_num,
260
4.83k
                          nr->k_loss_reduction_factor_den,
261
4.83k
                          &err);
262
263
4.83k
    if (err)
264
0
        nr->slow_start_thresh = UINT64_MAX;
265
266
4.83k
    nr->cong_wnd = nr->slow_start_thresh;
267
4.83k
    if (nr->cong_wnd < nr->k_min_wnd)
268
134
        nr->cong_wnd = nr->k_min_wnd;
269
4.83k
}
270
271
static void newreno_flush(OSSL_CC_NEWRENO *nr, uint32_t flags)
272
7.12k
{
273
7.12k
    if (!nr->processing_loss)
274
967
        return;
275
276
6.16k
    newreno_cong(nr, nr->tx_time_of_last_loss);
277
278
6.16k
    if ((flags & OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION) != 0) {
279
0
        nr->cong_wnd                    = nr->k_min_wnd;
280
0
        nr->cong_recovery_start_time    = ossl_time_zero();
281
0
    }
282
283
6.16k
    nr->processing_loss = 0;
284
6.16k
    newreno_update_diag(nr);
285
6.16k
}
286
287
static uint64_t newreno_get_tx_allowance(OSSL_CC_DATA *cc)
288
82.1M
{
289
82.1M
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
290
291
82.1M
    if (nr->bytes_in_flight >= nr->cong_wnd)
292
69.5M
        return 0;
293
294
12.6M
    return nr->cong_wnd - nr->bytes_in_flight;
295
82.1M
}
296
297
static OSSL_TIME newreno_get_wakeup_deadline(OSSL_CC_DATA *cc)
298
23.1M
{
299
23.1M
    if (newreno_get_tx_allowance(cc) > 0) {
300
        /* We have TX allowance now so wakeup immediately */
301
0
        return ossl_time_zero();
302
23.1M
    } else {
303
        /*
304
         * The NewReno congestion controller does not vary its state in time,
305
         * only in response to stimulus.
306
         */
307
23.1M
        return ossl_time_infinite();
308
23.1M
    }
309
23.1M
}
310
311
static int newreno_on_data_sent(OSSL_CC_DATA *cc, uint64_t num_bytes)
312
789k
{
313
789k
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
314
315
789k
    nr->bytes_in_flight += num_bytes;
316
789k
    newreno_update_diag(nr);
317
789k
    return 1;
318
789k
}
319
320
static int newreno_is_cong_limited(OSSL_CC_NEWRENO *nr)
321
414k
{
322
414k
    uint64_t wnd_rem;
323
324
    /* We are congestion-limited if we are already at the congestion window. */
325
414k
    if (nr->bytes_in_flight >= nr->cong_wnd)
326
27.9k
        return 1;
327
328
386k
    wnd_rem = nr->cong_wnd - nr->bytes_in_flight;
329
330
    /*
331
     * Consider ourselves congestion-limited if less than three datagrams' worth
332
     * of congestion window remains to be spent, or if we are in slow start and
333
     * have consumed half of our window.
334
     */
335
386k
    return (nr->cong_wnd < nr->slow_start_thresh && wnd_rem <= nr->cong_wnd / 2)
336
386k
           || wnd_rem <= 3 * nr->max_dgram_size;
337
414k
}
338
339
static int newreno_on_data_acked(OSSL_CC_DATA *cc,
340
                                 const OSSL_CC_ACK_INFO *info)
341
414k
{
342
414k
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
343
344
    /*
345
     * Packet has been acked. Firstly, remove it from the aggregate count of
346
     * bytes in flight.
347
     */
348
414k
    nr->bytes_in_flight -= info->tx_size;
349
350
    /*
351
     * We use acknowledgement of data as a signal that we are not at channel
352
     * capacity and that it may be reasonable to increase the congestion window.
353
     * However, acknowledgement is not a useful signal that there is further
354
     * capacity if we are not actually saturating the congestion window that we
355
     * already have (for example, if the application is not generating much data
356
     * or we are limited by flow control). Therefore, we only expand the
357
     * congestion window if we are consuming a significant fraction of the
358
     * congestion window.
359
     */
360
414k
    if (!newreno_is_cong_limited(nr))
361
343k
        goto out;
362
363
    /*
364
     * We can handle acknowledgement of a packet in one of three ways
365
     * depending on our current state:
366
     *
367
     *   - Congestion Recovery: Do nothing. We don't start increasing
368
     *     the congestion window in response to acknowledgements until
369
     *     we are no longer in the Congestion Recovery state.
370
     *
371
     *   - Slow Start: Increase the congestion window using the slow
372
     *     start scale.
373
     *
374
     *   - Congestion Avoidance: Increase the congestion window using
375
     *     the congestion avoidance scale.
376
     */
377
71.5k
    if (newreno_in_cong_recovery(nr, info->tx_time)) {
378
        /* Congestion recovery, do nothing. */
379
55.0k
    } else if (nr->cong_wnd < nr->slow_start_thresh) {
380
        /* When this condition is true we are in the Slow Start state. */
381
49.8k
        nr->cong_wnd += info->tx_size;
382
49.8k
        nr->in_congestion_recovery = 0;
383
49.8k
    } else {
384
        /* Otherwise, we are in the Congestion Avoidance state. */
385
5.22k
        nr->bytes_acked += info->tx_size;
386
387
        /*
388
         * Avoid integer division as per RFC 9002 s. B.5. / RFC3465 s. 2.1.
389
         */
390
5.22k
        if (nr->bytes_acked >= nr->cong_wnd) {
391
483
            nr->bytes_acked -= nr->cong_wnd;
392
483
            nr->cong_wnd    += nr->max_dgram_size;
393
483
        }
394
395
5.22k
        nr->in_congestion_recovery = 0;
396
5.22k
    }
397
398
414k
out:
399
414k
    newreno_update_diag(nr);
400
414k
    return 1;
401
71.5k
}
402
403
static int newreno_on_data_lost(OSSL_CC_DATA *cc,
404
                                const OSSL_CC_LOSS_INFO *info)
405
36.1k
{
406
36.1k
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
407
408
36.1k
    if (info->tx_size > nr->bytes_in_flight)
409
0
        return 0;
410
411
36.1k
    nr->bytes_in_flight -= info->tx_size;
412
413
36.1k
    if (!nr->processing_loss) {
414
415
6.51k
        if (ossl_time_compare(info->tx_time, nr->tx_time_of_last_loss) <= 0)
416
            /*
417
             * After triggering congestion due to a lost packet at time t, don't
418
             * trigger congestion again due to any subsequently detected lost
419
             * packet at a time s < t, as we've effectively already signalled
420
             * congestion on loss of that and subsequent packets.
421
             */
422
356
            goto out;
423
424
6.16k
        nr->processing_loss = 1;
425
426
        /*
427
         * Cancel any pending window increase in the Congestion Avoidance state.
428
         */
429
6.16k
        nr->bytes_acked = 0;
430
6.16k
    }
431
432
35.7k
    nr->tx_time_of_last_loss
433
35.7k
        = ossl_time_max(nr->tx_time_of_last_loss, info->tx_time);
434
435
36.1k
out:
436
36.1k
    newreno_update_diag(nr);
437
36.1k
    return 1;
438
35.7k
}
439
440
static int newreno_on_data_lost_finished(OSSL_CC_DATA *cc, uint32_t flags)
441
7.12k
{
442
7.12k
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
443
444
7.12k
    newreno_flush(nr, flags);
445
7.12k
    return 1;
446
7.12k
}
447
448
static int newreno_on_data_invalidated(OSSL_CC_DATA *cc,
449
                                       uint64_t num_bytes)
450
33.0k
{
451
33.0k
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
452
453
33.0k
    nr->bytes_in_flight -= num_bytes;
454
33.0k
    newreno_update_diag(nr);
455
33.0k
    return 1;
456
33.0k
}
457
458
static int newreno_on_ecn(OSSL_CC_DATA *cc,
459
                          const OSSL_CC_ECN_INFO *info)
460
0
{
461
0
    OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
462
463
0
    nr->processing_loss         = 1;
464
0
    nr->bytes_acked             = 0;
465
0
    nr->tx_time_of_last_loss    = info->largest_acked_time;
466
0
    newreno_flush(nr, 0);
467
0
    return 1;
468
0
}
469
470
const OSSL_CC_METHOD ossl_cc_newreno_method = {
471
    newreno_new,
472
    newreno_free,
473
    newreno_reset,
474
    newreno_set_input_params,
475
    newreno_bind_diagnostic,
476
    newreno_unbind_diagnostic,
477
    newreno_get_tx_allowance,
478
    newreno_get_wakeup_deadline,
479
    newreno_on_data_sent,
480
    newreno_on_data_acked,
481
    newreno_on_data_lost,
482
    newreno_on_data_lost_finished,
483
    newreno_on_data_invalidated,
484
    newreno_on_ecn,
485
};