Coverage Report

Created: 2026-10-28 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl41/ssl/quic/quic_fifd.c
Line
Count
Source
1
/*
2
 * Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License 2.0 (the "License").  You may not use
5
 * this file except in compliance with the License.  You can obtain a copy
6
 * in the file LICENSE in the source distribution or at
7
 * https://www.openssl.org/source/license.html
8
 */
9
10
#include "internal/quic_fifd.h"
11
#include "internal/quic_wire.h"
12
#include "internal/qlog_event_helpers.h"
13
14
DEFINE_LIST_OF(tx_history, OSSL_ACKM_TX_PKT);
15
16
int ossl_quic_fifd_init(QUIC_FIFD *fifd,
17
    QUIC_CFQ *cfq,
18
    OSSL_ACKM *ackm,
19
    QUIC_TXPIM *txpim,
20
    /* stream_id is UINT64_MAX for the crypto stream */
21
    QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id,
22
        uint32_t pn_space,
23
        void *arg),
24
    void *get_sstream_by_id_arg,
25
    /* stream_id is UINT64_MAX if not applicable */
26
    void (*regen_frame)(uint64_t frame_type,
27
        uint64_t stream_id,
28
        QUIC_TXPIM_PKT *pkt,
29
        void *arg),
30
    void *regen_frame_arg,
31
    void (*confirm_frame)(uint64_t frame_type,
32
        uint64_t stream_id,
33
        QUIC_TXPIM_PKT *pkt,
34
        void *arg),
35
    void *confirm_frame_arg,
36
    void (*sstream_updated)(uint64_t stream_id,
37
        void *arg),
38
    void *sstream_updated_arg,
39
    QLOG *(*get_qlog_cb)(void *arg),
40
    void *get_qlog_cb_arg)
41
53.0k
{
42
53.0k
    if (cfq == NULL || ackm == NULL || txpim == NULL
43
53.0k
        || get_sstream_by_id == NULL || regen_frame == NULL)
44
0
        return 0;
45
46
53.0k
    fifd->cfq = cfq;
47
53.0k
    fifd->ackm = ackm;
48
53.0k
    fifd->txpim = txpim;
49
53.0k
    fifd->get_sstream_by_id = get_sstream_by_id;
50
53.0k
    fifd->get_sstream_by_id_arg = get_sstream_by_id_arg;
51
53.0k
    fifd->regen_frame = regen_frame;
52
53.0k
    fifd->regen_frame_arg = regen_frame_arg;
53
53.0k
    fifd->confirm_frame = confirm_frame;
54
53.0k
    fifd->confirm_frame_arg = confirm_frame_arg;
55
53.0k
    fifd->sstream_updated = sstream_updated;
56
53.0k
    fifd->sstream_updated_arg = sstream_updated_arg;
57
53.0k
    fifd->get_qlog_cb = get_qlog_cb;
58
53.0k
    fifd->get_qlog_cb_arg = get_qlog_cb_arg;
59
53.0k
    return 1;
60
53.0k
}
61
62
void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd)
63
53.0k
{
64
    /* No-op. */
65
53.0k
}
66
67
static void on_acked(void *arg)
68
71.1k
{
69
71.1k
    QUIC_TXPIM_PKT *pkt = arg;
70
71.1k
    QUIC_FIFD *fifd = pkt->fifd;
71
71.1k
    const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
72
71.1k
    size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
73
71.1k
    QUIC_SSTREAM *sstream;
74
71.1k
    QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
75
76
    /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */
77
106k
    for (i = 0; i < num_chunks; ++i) {
78
35.3k
        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
79
35.3k
            pkt->ackm_pkt.pkt_space,
80
35.3k
            fifd->get_sstream_by_id_arg);
81
82
35.3k
        if (sstream != NULL) {
83
34.3k
            if (chunks[i].end >= chunks[i].start)
84
                /* coverity[check_return]: Best effort - we cannot fail here. */
85
34.3k
                ossl_quic_sstream_mark_acked(sstream,
86
34.3k
                    chunks[i].start, chunks[i].end);
87
88
34.3k
            if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX)
89
0
                ossl_quic_sstream_mark_acked_fin(sstream);
90
34.3k
        }
91
92
        /*
93
         * Resetting the send part frees the send stream, so these must be
94
         * confirmed even when it is already gone.
95
         */
96
35.3k
        if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
97
0
            fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
98
0
                chunks[i].stream_id, pkt,
99
0
                fifd->confirm_frame_arg);
100
101
35.3k
        if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
102
965
            fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
103
965
                chunks[i].stream_id, pkt,
104
965
                fifd->confirm_frame_arg);
105
106
35.3k
        if (sstream != NULL && ossl_quic_sstream_is_totally_acked(sstream))
107
8.57k
            fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg);
108
35.3k
    }
109
110
    /* GCR */
111
73.5k
    for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
112
2.37k
        cfq_item_next = cfq_item->pkt_next;
113
2.37k
        ossl_quic_cfq_release(fifd->cfq, cfq_item);
114
2.37k
    }
115
116
71.1k
    ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
117
71.1k
}
118
119
static QLOG *fifd_get_qlog(QUIC_FIFD *fifd)
120
38.2k
{
121
38.2k
    if (fifd->get_qlog_cb == NULL)
122
0
        return NULL;
123
124
38.2k
    return fifd->get_qlog_cb(fifd->get_qlog_cb_arg);
125
38.2k
}
126
127
static void on_lost(void *arg)
128
38.2k
{
129
38.2k
    QUIC_TXPIM_PKT *pkt = arg;
130
38.2k
    QUIC_FIFD *fifd = pkt->fifd;
131
38.2k
    const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
132
38.2k
    size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
133
38.2k
    QUIC_SSTREAM *sstream;
134
38.2k
    QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
135
38.2k
    int sstream_updated;
136
137
38.2k
    ossl_qlog_event_recovery_packet_lost(fifd_get_qlog(fifd), pkt);
138
139
    /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */
140
47.3k
    for (i = 0; i < num_chunks; ++i) {
141
9.08k
        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
142
9.08k
            pkt->ackm_pkt.pkt_space,
143
9.08k
            fifd->get_sstream_by_id_arg);
144
9.08k
        if (sstream == NULL)
145
284
            continue;
146
147
8.80k
        sstream_updated = 0;
148
149
8.80k
        if (chunks[i].end >= chunks[i].start) {
150
            /*
151
             * Note: If the stream is being reset, we do not need to retransmit
152
             * old data as this is pointless. In this case this will be handled
153
             * by (sstream == NULL) above as the QSM will free the QUIC_SSTREAM
154
             * and our call to get_sstream_by_id above will return NULL.
155
             */
156
8.80k
            ossl_quic_sstream_mark_lost(sstream,
157
8.80k
                chunks[i].start, chunks[i].end);
158
8.80k
            sstream_updated = 1;
159
8.80k
        }
160
161
8.80k
        if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) {
162
0
            ossl_quic_sstream_mark_lost_fin(sstream);
163
0
            sstream_updated = 1;
164
0
        }
165
166
8.80k
        if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
167
0
            fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
168
0
                chunks[i].stream_id, pkt,
169
0
                fifd->regen_frame_arg);
170
171
8.80k
        if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
172
0
            fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
173
0
                chunks[i].stream_id, pkt,
174
0
                fifd->regen_frame_arg);
175
176
        /*
177
         * Inform caller that stream needs an FC frame.
178
         *
179
         * Note: We could track whether an FC frame was sent originally for the
180
         * stream to determine if it really needs to be regenerated or not.
181
         * However, if loss has occurred, it's probably better to ensure the
182
         * peer has up-to-date flow control data for the stream. Given that
183
         * these frames are extremely small, we may as well always send it when
184
         * handling loss.
185
         */
186
8.80k
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA,
187
8.80k
            chunks[i].stream_id,
188
8.80k
            pkt,
189
8.80k
            fifd->regen_frame_arg);
190
191
8.80k
        if (sstream_updated && chunks[i].stream_id != UINT64_MAX)
192
409
            fifd->sstream_updated(chunks[i].stream_id,
193
409
                fifd->sstream_updated_arg);
194
8.80k
    }
195
196
    /* GCR */
197
38.8k
    for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
198
601
        cfq_item_next = cfq_item->pkt_next;
199
601
        ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX);
200
601
    }
201
202
    /* Regenerate flag frames */
203
38.2k
    if (pkt->had_handshake_done_frame)
204
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE,
205
0
            UINT64_MAX, pkt,
206
0
            fifd->regen_frame_arg);
207
208
38.2k
    if (pkt->had_max_data_frame)
209
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA,
210
0
            UINT64_MAX, pkt,
211
0
            fifd->regen_frame_arg);
212
213
38.2k
    if (pkt->had_max_streams_bidi_frame)
214
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI,
215
0
            UINT64_MAX, pkt,
216
0
            fifd->regen_frame_arg);
217
218
38.2k
    if (pkt->had_max_streams_uni_frame)
219
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI,
220
0
            UINT64_MAX, pkt,
221
0
            fifd->regen_frame_arg);
222
223
38.2k
    if (pkt->had_ack_frame)
224
        /*
225
         * We always use the ACK_WITH_ECN frame type to represent the ACK frame
226
         * type in our callback; we assume it is the caller's job to decide
227
         * whether it wants to send ECN data or not.
228
         */
229
3.70k
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN,
230
3.70k
            UINT64_MAX, pkt,
231
3.70k
            fifd->regen_frame_arg);
232
233
38.2k
    ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
234
38.2k
}
235
236
static void on_discarded(void *arg)
237
976k
{
238
976k
    QUIC_TXPIM_PKT *pkt = arg;
239
976k
    QUIC_FIFD *fifd = pkt->fifd;
240
976k
    QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
241
242
    /*
243
     * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as
244
     * we assume caller will clean them up.
245
     */
246
247
    /* GCR */
248
985k
    for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
249
9.19k
        cfq_item_next = cfq_item->pkt_next;
250
9.19k
        ossl_quic_cfq_release(fifd->cfq, cfq_item);
251
9.19k
    }
252
253
976k
    ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
254
976k
}
255
256
int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
257
1.08M
{
258
1.08M
    QUIC_CFQ_ITEM *cfq_item;
259
1.08M
    const QUIC_TXPIM_CHUNK *chunks;
260
1.08M
    size_t i, num_chunks;
261
1.08M
    QUIC_SSTREAM *sstream;
262
263
1.08M
    pkt->fifd = fifd;
264
265
1.08M
    pkt->ackm_pkt.on_lost = on_lost;
266
1.08M
    pkt->ackm_pkt.on_acked = on_acked;
267
1.08M
    pkt->ackm_pkt.on_discarded = on_discarded;
268
1.08M
    pkt->ackm_pkt.cb_arg = pkt;
269
270
1.08M
    ossl_list_tx_history_init_elem(&pkt->ackm_pkt);
271
1.08M
    pkt->ackm_pkt.anext = pkt->ackm_pkt.lnext = NULL;
272
273
    /*
274
     * Mark the CFQ items which have been added to this packet as having been
275
     * transmitted.
276
     */
277
1.08M
    for (cfq_item = pkt->retx_head;
278
1.10M
        cfq_item != NULL;
279
1.08M
        cfq_item = cfq_item->pkt_next)
280
22.5k
        ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item);
281
282
    /*
283
     * Mark the send stream chunks which have been added to the packet as having
284
     * been transmitted.
285
     */
286
1.08M
    chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
287
1.08M
    num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
288
1.21M
    for (i = 0; i < num_chunks; ++i) {
289
124k
        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
290
124k
            pkt->ackm_pkt.pkt_space,
291
124k
            fifd->get_sstream_by_id_arg);
292
124k
        if (sstream == NULL)
293
5.10k
            continue;
294
295
119k
        if (chunks[i].end >= chunks[i].start
296
119k
            && !ossl_quic_sstream_mark_transmitted(sstream,
297
119k
                chunks[i].start,
298
119k
                chunks[i].end))
299
0
            return 0;
300
301
119k
        if (chunks[i].has_fin
302
0
            && !ossl_quic_sstream_mark_transmitted_fin(sstream,
303
0
                chunks[i].end + 1))
304
0
            return 0;
305
119k
    }
306
307
    /* Inform the ACKM. */
308
1.08M
    return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt);
309
1.08M
}
310
311
void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg),
312
    void *get_qlog_cb_arg)
313
0
{
314
0
    fifd->get_qlog_cb = get_qlog_cb;
315
0
    fifd->get_qlog_cb_arg = get_qlog_cb_arg;
316
0
}
317
318
static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item)
319
10.3k
{
320
10.3k
    QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev;
321
322
10.3k
    if (prev != NULL) {
323
0
        prev->pkt_next = cfq_item->pkt_next;
324
10.3k
    } else {
325
10.3k
        pkt->retx_head = cfq_item->pkt_next;
326
10.3k
    }
327
328
10.3k
    if (cfq_item->pkt_next != NULL)
329
8.00k
        cfq_item->pkt_next->pkt_prev = prev;
330
331
10.3k
    cfq_item->pkt_prev = NULL;
332
10.3k
    cfq_item->pkt_next = NULL;
333
10.3k
}
334
335
void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
336
1.15M
{
337
1.15M
    QUIC_CFQ_ITEM *cfq_item, *cfq_next;
338
339
    /*
340
     * The packet has been written to network. We can discard frames we don't
341
     * retransmit when loss is detected.
342
     */
343
1.15M
    cfq_item = pkt->retx_head;
344
1.17M
    while (cfq_item != NULL) {
345
        /*
346
         * Discarded items are moved to free list. If item
347
         * got moved to free list we must also remove it from
348
         * cfq list kept in pkt, so ACKM does not find it when
349
         * receives an ACK for pkt.
350
         */
351
22.5k
        if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) {
352
10.3k
            cfq_next = cfq_item->pkt_next;
353
10.3k
            txpim_pkt_remove_cfq_item(pkt, cfq_item);
354
10.3k
            cfq_item = cfq_next;
355
12.1k
        } else {
356
12.1k
            cfq_item = cfq_item->pkt_next;
357
12.1k
        }
358
22.5k
    }
359
1.15M
}