2 * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
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
10 #include "internal/quic_fifd.h"
11 #include "internal/quic_wire.h"
13 DEFINE_LIST_OF(tx_history
, OSSL_ACKM_TX_PKT
);
15 int ossl_quic_fifd_init(QUIC_FIFD
*fifd
,
19 /* stream_id is UINT64_MAX for the crypto stream */
20 QUIC_SSTREAM
*(*get_sstream_by_id
)(uint64_t stream_id
,
23 void *get_sstream_by_id_arg
,
24 /* stream_id is UINT64_MAX if not applicable */
25 void (*regen_frame
)(uint64_t frame_type
,
29 void *regen_frame_arg
,
30 void (*confirm_frame
)(uint64_t frame_type
,
34 void *confirm_frame_arg
,
35 void (*sstream_updated
)(uint64_t stream_id
,
37 void *sstream_updated_arg
)
39 if (cfq
== NULL
|| ackm
== NULL
|| txpim
== NULL
40 || get_sstream_by_id
== NULL
|| regen_frame
== NULL
)
46 fifd
->get_sstream_by_id
= get_sstream_by_id
;
47 fifd
->get_sstream_by_id_arg
= get_sstream_by_id_arg
;
48 fifd
->regen_frame
= regen_frame
;
49 fifd
->regen_frame_arg
= regen_frame_arg
;
50 fifd
->confirm_frame
= confirm_frame
;
51 fifd
->confirm_frame_arg
= confirm_frame_arg
;
52 fifd
->sstream_updated
= sstream_updated
;
53 fifd
->sstream_updated_arg
= sstream_updated_arg
;
57 void ossl_quic_fifd_cleanup(QUIC_FIFD
*fifd
)
62 static void on_acked(void *arg
)
64 QUIC_TXPIM_PKT
*pkt
= arg
;
65 QUIC_FIFD
*fifd
= pkt
->fifd
;
66 const QUIC_TXPIM_CHUNK
*chunks
= ossl_quic_txpim_pkt_get_chunks(pkt
);
67 size_t i
, num_chunks
= ossl_quic_txpim_pkt_get_num_chunks(pkt
);
68 QUIC_SSTREAM
*sstream
;
69 QUIC_CFQ_ITEM
*cfq_item
, *cfq_item_next
;
71 /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */
72 for (i
= 0; i
< num_chunks
; ++i
) {
73 sstream
= fifd
->get_sstream_by_id(chunks
[i
].stream_id
,
74 pkt
->ackm_pkt
.pkt_space
,
75 fifd
->get_sstream_by_id_arg
);
79 if (chunks
[i
].end
>= chunks
[i
].start
)
80 /* coverity[check_return]: Best effort - we cannot fail here. */
81 ossl_quic_sstream_mark_acked(sstream
,
82 chunks
[i
].start
, chunks
[i
].end
);
84 if (chunks
[i
].has_fin
&& chunks
[i
].stream_id
!= UINT64_MAX
)
85 ossl_quic_sstream_mark_acked_fin(sstream
);
87 if (chunks
[i
].has_stop_sending
&& chunks
[i
].stream_id
!= UINT64_MAX
)
88 fifd
->confirm_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING
,
89 chunks
[i
].stream_id
, pkt
,
90 fifd
->confirm_frame_arg
);
92 if (chunks
[i
].has_reset_stream
&& chunks
[i
].stream_id
!= UINT64_MAX
)
93 fifd
->confirm_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM
,
94 chunks
[i
].stream_id
, pkt
,
95 fifd
->confirm_frame_arg
);
97 if (ossl_quic_sstream_is_totally_acked(sstream
))
98 fifd
->sstream_updated(chunks
[i
].stream_id
, fifd
->sstream_updated_arg
);
102 for (cfq_item
= pkt
->retx_head
; cfq_item
!= NULL
; cfq_item
= cfq_item_next
) {
103 cfq_item_next
= cfq_item
->pkt_next
;
104 ossl_quic_cfq_release(fifd
->cfq
, cfq_item
);
107 ossl_quic_txpim_pkt_release(fifd
->txpim
, pkt
);
110 static void on_lost(void *arg
)
112 QUIC_TXPIM_PKT
*pkt
= arg
;
113 QUIC_FIFD
*fifd
= pkt
->fifd
;
114 const QUIC_TXPIM_CHUNK
*chunks
= ossl_quic_txpim_pkt_get_chunks(pkt
);
115 size_t i
, num_chunks
= ossl_quic_txpim_pkt_get_num_chunks(pkt
);
116 QUIC_SSTREAM
*sstream
;
117 QUIC_CFQ_ITEM
*cfq_item
, *cfq_item_next
;
120 /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */
121 for (i
= 0; i
< num_chunks
; ++i
) {
122 sstream
= fifd
->get_sstream_by_id(chunks
[i
].stream_id
,
123 pkt
->ackm_pkt
.pkt_space
,
124 fifd
->get_sstream_by_id_arg
);
130 if (chunks
[i
].end
>= chunks
[i
].start
) {
132 * Note: If the stream is being reset, we do not need to retransmit
133 * old data as this is pointless. In this case this will be handled
134 * by (sstream == NULL) above as the QSM will free the QUIC_SSTREAM
135 * and our call to get_sstream_by_id above will return NULL.
137 ossl_quic_sstream_mark_lost(sstream
,
138 chunks
[i
].start
, chunks
[i
].end
);
142 if (chunks
[i
].has_fin
&& chunks
[i
].stream_id
!= UINT64_MAX
) {
143 ossl_quic_sstream_mark_lost_fin(sstream
);
147 if (chunks
[i
].has_stop_sending
&& chunks
[i
].stream_id
!= UINT64_MAX
)
148 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING
,
149 chunks
[i
].stream_id
, pkt
,
150 fifd
->regen_frame_arg
);
152 if (chunks
[i
].has_reset_stream
&& chunks
[i
].stream_id
!= UINT64_MAX
)
153 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM
,
154 chunks
[i
].stream_id
, pkt
,
155 fifd
->regen_frame_arg
);
158 * Inform caller that stream needs an FC frame.
160 * Note: We could track whether an FC frame was sent originally for the
161 * stream to determine if it really needs to be regenerated or not.
162 * However, if loss has occurred, it's probably better to ensure the
163 * peer has up-to-date flow control data for the stream. Given that
164 * these frames are extremely small, we may as well always send it when
167 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA
,
170 fifd
->regen_frame_arg
);
172 if (sstream_updated
&& chunks
[i
].stream_id
!= UINT64_MAX
)
173 fifd
->sstream_updated(chunks
[i
].stream_id
,
174 fifd
->sstream_updated_arg
);
178 for (cfq_item
= pkt
->retx_head
; cfq_item
!= NULL
; cfq_item
= cfq_item_next
) {
179 cfq_item_next
= cfq_item
->pkt_next
;
180 ossl_quic_cfq_mark_lost(fifd
->cfq
, cfq_item
, UINT32_MAX
);
183 /* Regenerate flag frames */
184 if (pkt
->had_handshake_done_frame
)
185 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE
,
187 fifd
->regen_frame_arg
);
189 if (pkt
->had_max_data_frame
)
190 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA
,
192 fifd
->regen_frame_arg
);
194 if (pkt
->had_max_streams_bidi_frame
)
195 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI
,
197 fifd
->regen_frame_arg
);
199 if (pkt
->had_max_streams_uni_frame
)
200 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI
,
202 fifd
->regen_frame_arg
);
204 if (pkt
->had_ack_frame
)
206 * We always use the ACK_WITH_ECN frame type to represent the ACK frame
207 * type in our callback; we assume it is the caller's job to decide
208 * whether it wants to send ECN data or not.
210 fifd
->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN
,
212 fifd
->regen_frame_arg
);
214 ossl_quic_txpim_pkt_release(fifd
->txpim
, pkt
);
217 static void on_discarded(void *arg
)
219 QUIC_TXPIM_PKT
*pkt
= arg
;
220 QUIC_FIFD
*fifd
= pkt
->fifd
;
221 QUIC_CFQ_ITEM
*cfq_item
, *cfq_item_next
;
224 * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as
225 * we assume caller will clean them up.
229 for (cfq_item
= pkt
->retx_head
; cfq_item
!= NULL
; cfq_item
= cfq_item_next
) {
230 cfq_item_next
= cfq_item
->pkt_next
;
231 ossl_quic_cfq_release(fifd
->cfq
, cfq_item
);
234 ossl_quic_txpim_pkt_release(fifd
->txpim
, pkt
);
237 int ossl_quic_fifd_pkt_commit(QUIC_FIFD
*fifd
, QUIC_TXPIM_PKT
*pkt
)
239 QUIC_CFQ_ITEM
*cfq_item
;
240 const QUIC_TXPIM_CHUNK
*chunks
;
241 size_t i
, num_chunks
;
242 QUIC_SSTREAM
*sstream
;
246 pkt
->ackm_pkt
.on_lost
= on_lost
;
247 pkt
->ackm_pkt
.on_acked
= on_acked
;
248 pkt
->ackm_pkt
.on_discarded
= on_discarded
;
249 pkt
->ackm_pkt
.cb_arg
= pkt
;
251 ossl_list_tx_history_init_elem(&pkt
->ackm_pkt
);
252 pkt
->ackm_pkt
.anext
= pkt
->ackm_pkt
.lnext
= NULL
;
255 * Mark the CFQ items which have been added to this packet as having been
258 for (cfq_item
= pkt
->retx_head
;
260 cfq_item
= cfq_item
->pkt_next
)
261 ossl_quic_cfq_mark_tx(fifd
->cfq
, cfq_item
);
264 * Mark the send stream chunks which have been added to the packet as having
267 chunks
= ossl_quic_txpim_pkt_get_chunks(pkt
);
268 num_chunks
= ossl_quic_txpim_pkt_get_num_chunks(pkt
);
269 for (i
= 0; i
< num_chunks
; ++i
) {
270 sstream
= fifd
->get_sstream_by_id(chunks
[i
].stream_id
,
271 pkt
->ackm_pkt
.pkt_space
,
272 fifd
->get_sstream_by_id_arg
);
276 if (chunks
[i
].end
>= chunks
[i
].start
277 && !ossl_quic_sstream_mark_transmitted(sstream
,
282 if (chunks
[i
].has_fin
283 && !ossl_quic_sstream_mark_transmitted_fin(sstream
,
288 /* Inform the ACKM. */
289 return ossl_ackm_on_tx_packet(fifd
->ackm
, &pkt
->ackm_pkt
);