/src/openssl/ssl/quic/quic_fifd.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2022-2024 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 | 0 | { |
42 | 0 | if (cfq == NULL || ackm == NULL || txpim == NULL |
43 | 0 | || get_sstream_by_id == NULL || regen_frame == NULL) |
44 | 0 | return 0; |
45 | | |
46 | 0 | fifd->cfq = cfq; |
47 | 0 | fifd->ackm = ackm; |
48 | 0 | fifd->txpim = txpim; |
49 | 0 | fifd->get_sstream_by_id = get_sstream_by_id; |
50 | 0 | fifd->get_sstream_by_id_arg = get_sstream_by_id_arg; |
51 | 0 | fifd->regen_frame = regen_frame; |
52 | 0 | fifd->regen_frame_arg = regen_frame_arg; |
53 | 0 | fifd->confirm_frame = confirm_frame; |
54 | 0 | fifd->confirm_frame_arg = confirm_frame_arg; |
55 | 0 | fifd->sstream_updated = sstream_updated; |
56 | 0 | fifd->sstream_updated_arg = sstream_updated_arg; |
57 | 0 | fifd->get_qlog_cb = get_qlog_cb; |
58 | 0 | fifd->get_qlog_cb_arg = get_qlog_cb_arg; |
59 | 0 | return 1; |
60 | 0 | } |
61 | | |
62 | | void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd) |
63 | 0 | { |
64 | | /* No-op. */ |
65 | 0 | } |
66 | | |
67 | | static void on_acked(void *arg) |
68 | 0 | { |
69 | 0 | QUIC_TXPIM_PKT *pkt = arg; |
70 | 0 | QUIC_FIFD *fifd = pkt->fifd; |
71 | 0 | const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); |
72 | 0 | size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); |
73 | 0 | QUIC_SSTREAM *sstream; |
74 | 0 | QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; |
75 | | |
76 | | /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */ |
77 | 0 | for (i = 0; i < num_chunks; ++i) { |
78 | 0 | sstream = fifd->get_sstream_by_id(chunks[i].stream_id, |
79 | 0 | pkt->ackm_pkt.pkt_space, |
80 | 0 | fifd->get_sstream_by_id_arg); |
81 | 0 | if (sstream == NULL) |
82 | 0 | continue; |
83 | | |
84 | 0 | if (chunks[i].end >= chunks[i].start) |
85 | | /* coverity[check_return]: Best effort - we cannot fail here. */ |
86 | 0 | ossl_quic_sstream_mark_acked(sstream, |
87 | 0 | chunks[i].start, chunks[i].end); |
88 | |
|
89 | 0 | if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) |
90 | 0 | ossl_quic_sstream_mark_acked_fin(sstream); |
91 | |
|
92 | 0 | 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 | 0 | 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 | 0 | if (ossl_quic_sstream_is_totally_acked(sstream)) |
103 | 0 | fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg); |
104 | 0 | } |
105 | | |
106 | | /* GCR */ |
107 | 0 | for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { |
108 | 0 | cfq_item_next = cfq_item->pkt_next; |
109 | 0 | ossl_quic_cfq_release(fifd->cfq, cfq_item); |
110 | 0 | } |
111 | |
|
112 | 0 | ossl_quic_txpim_pkt_release(fifd->txpim, pkt); |
113 | 0 | } |
114 | | |
115 | | static QLOG *fifd_get_qlog(QUIC_FIFD *fifd) |
116 | 0 | { |
117 | 0 | if (fifd->get_qlog_cb == NULL) |
118 | 0 | return NULL; |
119 | | |
120 | 0 | return fifd->get_qlog_cb(fifd->get_qlog_cb_arg); |
121 | 0 | } |
122 | | |
123 | | static void on_lost(void *arg) |
124 | 0 | { |
125 | 0 | QUIC_TXPIM_PKT *pkt = arg; |
126 | 0 | QUIC_FIFD *fifd = pkt->fifd; |
127 | 0 | const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); |
128 | 0 | size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); |
129 | 0 | QUIC_SSTREAM *sstream; |
130 | 0 | QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; |
131 | 0 | int sstream_updated; |
132 | |
|
133 | 0 | ossl_qlog_event_recovery_packet_lost(fifd_get_qlog(fifd), pkt); |
134 | | |
135 | | /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */ |
136 | 0 | for (i = 0; i < num_chunks; ++i) { |
137 | 0 | sstream = fifd->get_sstream_by_id(chunks[i].stream_id, |
138 | 0 | pkt->ackm_pkt.pkt_space, |
139 | 0 | fifd->get_sstream_by_id_arg); |
140 | 0 | if (sstream == NULL) |
141 | 0 | continue; |
142 | | |
143 | 0 | sstream_updated = 0; |
144 | |
|
145 | 0 | 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 | 0 | ossl_quic_sstream_mark_lost(sstream, |
153 | 0 | chunks[i].start, chunks[i].end); |
154 | 0 | sstream_updated = 1; |
155 | 0 | } |
156 | |
|
157 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA, |
183 | 0 | chunks[i].stream_id, |
184 | 0 | pkt, |
185 | 0 | fifd->regen_frame_arg); |
186 | |
|
187 | 0 | if (sstream_updated && chunks[i].stream_id != UINT64_MAX) |
188 | 0 | fifd->sstream_updated(chunks[i].stream_id, |
189 | 0 | fifd->sstream_updated_arg); |
190 | 0 | } |
191 | | |
192 | | /* GCR */ |
193 | 0 | for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { |
194 | 0 | cfq_item_next = cfq_item->pkt_next; |
195 | 0 | ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX); |
196 | 0 | } |
197 | | |
198 | | /* Regenerate flag frames */ |
199 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN, |
226 | 0 | UINT64_MAX, pkt, |
227 | 0 | fifd->regen_frame_arg); |
228 | |
|
229 | 0 | ossl_quic_txpim_pkt_release(fifd->txpim, pkt); |
230 | 0 | } |
231 | | |
232 | | static void on_discarded(void *arg) |
233 | 0 | { |
234 | 0 | QUIC_TXPIM_PKT *pkt = arg; |
235 | 0 | QUIC_FIFD *fifd = pkt->fifd; |
236 | 0 | 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 | 0 | for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { |
245 | 0 | cfq_item_next = cfq_item->pkt_next; |
246 | 0 | ossl_quic_cfq_release(fifd->cfq, cfq_item); |
247 | 0 | } |
248 | |
|
249 | 0 | ossl_quic_txpim_pkt_release(fifd->txpim, pkt); |
250 | 0 | } |
251 | | |
252 | | int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt) |
253 | 0 | { |
254 | 0 | QUIC_CFQ_ITEM *cfq_item; |
255 | 0 | const QUIC_TXPIM_CHUNK *chunks; |
256 | 0 | size_t i, num_chunks; |
257 | 0 | QUIC_SSTREAM *sstream; |
258 | |
|
259 | 0 | pkt->fifd = fifd; |
260 | |
|
261 | 0 | pkt->ackm_pkt.on_lost = on_lost; |
262 | 0 | pkt->ackm_pkt.on_acked = on_acked; |
263 | 0 | pkt->ackm_pkt.on_discarded = on_discarded; |
264 | 0 | pkt->ackm_pkt.cb_arg = pkt; |
265 | |
|
266 | 0 | ossl_list_tx_history_init_elem(&pkt->ackm_pkt); |
267 | 0 | 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 | 0 | for (cfq_item = pkt->retx_head; |
274 | 0 | cfq_item != NULL; |
275 | 0 | cfq_item = cfq_item->pkt_next) |
276 | 0 | 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 | 0 | chunks = ossl_quic_txpim_pkt_get_chunks(pkt); |
283 | 0 | num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); |
284 | 0 | for (i = 0; i < num_chunks; ++i) { |
285 | 0 | sstream = fifd->get_sstream_by_id(chunks[i].stream_id, |
286 | 0 | pkt->ackm_pkt.pkt_space, |
287 | 0 | fifd->get_sstream_by_id_arg); |
288 | 0 | if (sstream == NULL) |
289 | 0 | continue; |
290 | | |
291 | 0 | if (chunks[i].end >= chunks[i].start |
292 | 0 | && !ossl_quic_sstream_mark_transmitted(sstream, |
293 | 0 | chunks[i].start, |
294 | 0 | chunks[i].end)) |
295 | 0 | return 0; |
296 | | |
297 | 0 | 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 | 0 | } |
302 | | |
303 | | /* Inform the ACKM. */ |
304 | 0 | return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt); |
305 | 0 | } |
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 | } |