Coverage Report

Created: 2026-07-12 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl36/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
49.5k
{
42
49.5k
    if (cfq == NULL || ackm == NULL || txpim == NULL
43
49.5k
        || get_sstream_by_id == NULL || regen_frame == NULL)
44
0
        return 0;
45
46
49.5k
    fifd->cfq = cfq;
47
49.5k
    fifd->ackm = ackm;
48
49.5k
    fifd->txpim = txpim;
49
49.5k
    fifd->get_sstream_by_id = get_sstream_by_id;
50
49.5k
    fifd->get_sstream_by_id_arg = get_sstream_by_id_arg;
51
49.5k
    fifd->regen_frame = regen_frame;
52
49.5k
    fifd->regen_frame_arg = regen_frame_arg;
53
49.5k
    fifd->confirm_frame = confirm_frame;
54
49.5k
    fifd->confirm_frame_arg = confirm_frame_arg;
55
49.5k
    fifd->sstream_updated = sstream_updated;
56
49.5k
    fifd->sstream_updated_arg = sstream_updated_arg;
57
49.5k
    fifd->get_qlog_cb = get_qlog_cb;
58
49.5k
    fifd->get_qlog_cb_arg = get_qlog_cb_arg;
59
49.5k
    return 1;
60
49.5k
}
61
62
void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd)
63
49.5k
{
64
    /* No-op. */
65
49.5k
}
66
67
static void on_acked(void *arg)
68
1.14M
{
69
1.14M
    QUIC_TXPIM_PKT *pkt = arg;
70
1.14M
    QUIC_FIFD *fifd = pkt->fifd;
71
1.14M
    const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
72
1.14M
    size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
73
1.14M
    QUIC_SSTREAM *sstream;
74
1.14M
    QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
75
76
    /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */
77
1.17M
    for (i = 0; i < num_chunks; ++i) {
78
34.0k
        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
79
34.0k
            pkt->ackm_pkt.pkt_space,
80
34.0k
            fifd->get_sstream_by_id_arg);
81
34.0k
        if (sstream == NULL)
82
966
            continue;
83
84
33.0k
        if (chunks[i].end >= chunks[i].start)
85
            /* coverity[check_return]: Best effort - we cannot fail here. */
86
33.0k
            ossl_quic_sstream_mark_acked(sstream,
87
33.0k
                chunks[i].start, chunks[i].end);
88
89
33.0k
        if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX)
90
0
            ossl_quic_sstream_mark_acked_fin(sstream);
91
92
33.0k
        if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
93
0
            fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
94
0
                chunks[i].stream_id, pkt,
95
0
                fifd->confirm_frame_arg);
96
97
33.0k
        if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
98
0
            fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
99
0
                chunks[i].stream_id, pkt,
100
0
                fifd->confirm_frame_arg);
101
102
33.0k
        if (ossl_quic_sstream_is_totally_acked(sstream))
103
15.5k
            fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg);
104
33.0k
    }
105
106
    /* GCR */
107
1.14M
    for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
108
4.35k
        cfq_item_next = cfq_item->pkt_next;
109
4.35k
        ossl_quic_cfq_release(fifd->cfq, cfq_item);
110
4.35k
    }
111
112
1.14M
    ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
113
1.14M
}
114
115
static QLOG *fifd_get_qlog(QUIC_FIFD *fifd)
116
126k
{
117
126k
    if (fifd->get_qlog_cb == NULL)
118
0
        return NULL;
119
120
126k
    return fifd->get_qlog_cb(fifd->get_qlog_cb_arg);
121
126k
}
122
123
static void on_lost(void *arg)
124
126k
{
125
126k
    QUIC_TXPIM_PKT *pkt = arg;
126
126k
    QUIC_FIFD *fifd = pkt->fifd;
127
126k
    const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
128
126k
    size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
129
126k
    QUIC_SSTREAM *sstream;
130
126k
    QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
131
126k
    int sstream_updated;
132
133
126k
    ossl_qlog_event_recovery_packet_lost(fifd_get_qlog(fifd), pkt);
134
135
    /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */
136
137k
    for (i = 0; i < num_chunks; ++i) {
137
10.7k
        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
138
10.7k
            pkt->ackm_pkt.pkt_space,
139
10.7k
            fifd->get_sstream_by_id_arg);
140
10.7k
        if (sstream == NULL)
141
1.44k
            continue;
142
143
9.32k
        sstream_updated = 0;
144
145
9.32k
        if (chunks[i].end >= chunks[i].start) {
146
            /*
147
             * Note: If the stream is being reset, we do not need to retransmit
148
             * old data as this is pointless. In this case this will be handled
149
             * by (sstream == NULL) above as the QSM will free the QUIC_SSTREAM
150
             * and our call to get_sstream_by_id above will return NULL.
151
             */
152
9.32k
            ossl_quic_sstream_mark_lost(sstream,
153
9.32k
                chunks[i].start, chunks[i].end);
154
9.32k
            sstream_updated = 1;
155
9.32k
        }
156
157
9.32k
        if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) {
158
0
            ossl_quic_sstream_mark_lost_fin(sstream);
159
0
            sstream_updated = 1;
160
0
        }
161
162
9.32k
        if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
163
0
            fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
164
0
                chunks[i].stream_id, pkt,
165
0
                fifd->regen_frame_arg);
166
167
9.32k
        if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
168
0
            fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
169
0
                chunks[i].stream_id, pkt,
170
0
                fifd->regen_frame_arg);
171
172
        /*
173
         * Inform caller that stream needs an FC frame.
174
         *
175
         * Note: We could track whether an FC frame was sent originally for the
176
         * stream to determine if it really needs to be regenerated or not.
177
         * However, if loss has occurred, it's probably better to ensure the
178
         * peer has up-to-date flow control data for the stream. Given that
179
         * these frames are extremely small, we may as well always send it when
180
         * handling loss.
181
         */
182
9.32k
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA,
183
9.32k
            chunks[i].stream_id,
184
9.32k
            pkt,
185
9.32k
            fifd->regen_frame_arg);
186
187
9.32k
        if (sstream_updated && chunks[i].stream_id != UINT64_MAX)
188
1.70k
            fifd->sstream_updated(chunks[i].stream_id,
189
1.70k
                fifd->sstream_updated_arg);
190
9.32k
    }
191
192
    /* GCR */
193
143k
    for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
194
17.6k
        cfq_item_next = cfq_item->pkt_next;
195
17.6k
        ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX);
196
17.6k
    }
197
198
    /* Regenerate flag frames */
199
126k
    if (pkt->had_handshake_done_frame)
200
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE,
201
0
            UINT64_MAX, pkt,
202
0
            fifd->regen_frame_arg);
203
204
126k
    if (pkt->had_max_data_frame)
205
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA,
206
0
            UINT64_MAX, pkt,
207
0
            fifd->regen_frame_arg);
208
209
126k
    if (pkt->had_max_streams_bidi_frame)
210
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI,
211
0
            UINT64_MAX, pkt,
212
0
            fifd->regen_frame_arg);
213
214
126k
    if (pkt->had_max_streams_uni_frame)
215
0
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI,
216
0
            UINT64_MAX, pkt,
217
0
            fifd->regen_frame_arg);
218
219
126k
    if (pkt->had_ack_frame)
220
        /*
221
         * We always use the ACK_WITH_ECN frame type to represent the ACK frame
222
         * type in our callback; we assume it is the caller's job to decide
223
         * whether it wants to send ECN data or not.
224
         */
225
13.2k
        fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN,
226
13.2k
            UINT64_MAX, pkt,
227
13.2k
            fifd->regen_frame_arg);
228
229
126k
    ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
230
126k
}
231
232
static void on_discarded(void *arg)
233
899k
{
234
899k
    QUIC_TXPIM_PKT *pkt = arg;
235
899k
    QUIC_FIFD *fifd = pkt->fifd;
236
899k
    QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
237
238
    /*
239
     * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as
240
     * we assume caller will clean them up.
241
     */
242
243
    /* GCR */
244
951k
    for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
245
52.1k
        cfq_item_next = cfq_item->pkt_next;
246
52.1k
        ossl_quic_cfq_release(fifd->cfq, cfq_item);
247
52.1k
    }
248
249
899k
    ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
250
899k
}
251
252
int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
253
2.16M
{
254
2.16M
    QUIC_CFQ_ITEM *cfq_item;
255
2.16M
    const QUIC_TXPIM_CHUNK *chunks;
256
2.16M
    size_t i, num_chunks;
257
2.16M
    QUIC_SSTREAM *sstream;
258
259
2.16M
    pkt->fifd = fifd;
260
261
2.16M
    pkt->ackm_pkt.on_lost = on_lost;
262
2.16M
    pkt->ackm_pkt.on_acked = on_acked;
263
2.16M
    pkt->ackm_pkt.on_discarded = on_discarded;
264
2.16M
    pkt->ackm_pkt.cb_arg = pkt;
265
266
2.16M
    ossl_list_tx_history_init_elem(&pkt->ackm_pkt);
267
2.16M
    pkt->ackm_pkt.anext = pkt->ackm_pkt.lnext = NULL;
268
269
    /*
270
     * Mark the CFQ items which have been added to this packet as having been
271
     * transmitted.
272
     */
273
2.16M
    for (cfq_item = pkt->retx_head;
274
2.24M
        cfq_item != NULL;
275
2.16M
        cfq_item = cfq_item->pkt_next)
276
82.2k
        ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item);
277
278
    /*
279
     * Mark the send stream chunks which have been added to the packet as having
280
     * been transmitted.
281
     */
282
2.16M
    chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
283
2.16M
    num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
284
2.27M
    for (i = 0; i < num_chunks; ++i) {
285
108k
        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
286
108k
            pkt->ackm_pkt.pkt_space,
287
108k
            fifd->get_sstream_by_id_arg);
288
108k
        if (sstream == NULL)
289
5.80k
            continue;
290
291
102k
        if (chunks[i].end >= chunks[i].start
292
102k
            && !ossl_quic_sstream_mark_transmitted(sstream,
293
102k
                chunks[i].start,
294
102k
                chunks[i].end))
295
0
            return 0;
296
297
102k
        if (chunks[i].has_fin
298
0
            && !ossl_quic_sstream_mark_transmitted_fin(sstream,
299
0
                chunks[i].end + 1))
300
0
            return 0;
301
102k
    }
302
303
    /* Inform the ACKM. */
304
2.16M
    return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt);
305
2.16M
}
306
307
void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg),
308
    void *get_qlog_cb_arg)
309
0
{
310
0
    fifd->get_qlog_cb = get_qlog_cb;
311
0
    fifd->get_qlog_cb_arg = get_qlog_cb_arg;
312
0
}
313
314
static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item)
315
8.03k
{
316
8.03k
    QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev;
317
318
8.03k
    if (prev != NULL) {
319
0
        prev->pkt_next = cfq_item->pkt_next;
320
8.03k
    } else {
321
8.03k
        pkt->retx_head = cfq_item->pkt_next;
322
8.03k
    }
323
324
8.03k
    if (cfq_item->pkt_next != NULL)
325
4.83k
        cfq_item->pkt_next->pkt_prev = prev;
326
327
8.03k
    cfq_item->pkt_prev = NULL;
328
8.03k
    cfq_item->pkt_next = NULL;
329
8.03k
}
330
331
void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
332
1.39M
{
333
1.39M
    QUIC_CFQ_ITEM *cfq_item, *cfq_next;
334
335
    /*
336
     * The packet has been written to network. We can discard frames we don't
337
     * retransmit when loss is detected.
338
     */
339
1.39M
    cfq_item = pkt->retx_head;
340
1.41M
    while (cfq_item != NULL) {
341
        /*
342
         * Discarded items are moved to free list. If item
343
         * got moved to free list we must also remove it from
344
         * cfq list kept in pkt, so ACKM does not find it when
345
         * receives an ACK for pkt.
346
         */
347
17.5k
        if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) {
348
8.03k
            cfq_next = cfq_item->pkt_next;
349
8.03k
            txpim_pkt_remove_cfq_item(pkt, cfq_item);
350
8.03k
            cfq_item = cfq_next;
351
9.49k
        } else {
352
9.49k
            cfq_item = cfq_item->pkt_next;
353
9.49k
        }
354
17.5k
    }
355
1.39M
}