1 /*
2 * Copyright 2022-2025 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/packet.h"
11 #include "internal/quic_txpim.h"
12 #include "internal/quic_fifd.h"
13 #include "testutil.h"
14
15 static OSSL_TIME cur_time;
16
fake_now(void * arg)17 static OSSL_TIME fake_now(void *arg)
18 {
19 return cur_time;
20 }
21
step_time(uint64_t ms)22 static void step_time(uint64_t ms)
23 {
24 cur_time = ossl_time_add(cur_time, ossl_ms2time(ms));
25 }
26
27 static QUIC_SSTREAM *(*get_sstream_by_id_p)(uint64_t stream_id, uint32_t pn_space,
28 void *arg);
29
get_sstream_by_id(uint64_t stream_id,uint32_t pn_space,void * arg)30 static QUIC_SSTREAM *get_sstream_by_id(uint64_t stream_id, uint32_t pn_space,
31 void *arg)
32 {
33 return get_sstream_by_id_p(stream_id, pn_space, arg);
34 }
35
36 static void (*regen_frame_p)(uint64_t frame_type, uint64_t stream_id,
37 QUIC_TXPIM_PKT *pkt, void *arg);
38
regen_frame(uint64_t frame_type,uint64_t stream_id,QUIC_TXPIM_PKT * pkt,void * arg)39 static void regen_frame(uint64_t frame_type, uint64_t stream_id,
40 QUIC_TXPIM_PKT *pkt, void *arg)
41 {
42 regen_frame_p(frame_type, stream_id, pkt, arg);
43 }
44
confirm_frame(uint64_t frame_type,uint64_t stream_id,QUIC_TXPIM_PKT * pkt,void * arg)45 static void confirm_frame(uint64_t frame_type, uint64_t stream_id,
46 QUIC_TXPIM_PKT *pkt, void *arg)
47 {
48 }
49
sstream_updated(uint64_t stream_id,void * arg)50 static void sstream_updated(uint64_t stream_id, void *arg)
51 {
52 }
53
54 typedef struct info_st {
55 QUIC_FIFD fifd;
56 OSSL_ACKM *ackm;
57 QUIC_CFQ *cfq;
58 QUIC_TXPIM *txpim;
59 OSSL_STATM statm;
60 OSSL_CC_DATA *ccdata;
61 QUIC_SSTREAM *sstream[4];
62 } INFO;
63
64 static INFO *cur_info;
65 static int cb_fail;
66 static int cfq_freed;
67
68 /* ----------------------------------------------------------------------
69 * 1. Test that a submitted packet, on ack, acks all streams inside of it
70 * Test that a submitted packet, on ack, calls the get by ID function
71 * correctly
72 * Test that a submitted packet, on ack, acks all fins inside it
73 * Test that a submitted packet, on ack, releases the TXPIM packet
74 */
sstream_expect(uint64_t stream_id,uint32_t pn_space,void * arg)75 static QUIC_SSTREAM *sstream_expect(uint64_t stream_id, uint32_t pn_space,
76 void *arg)
77 {
78 if (stream_id == 42 || stream_id == 43)
79 return cur_info->sstream[stream_id - 42];
80
81 cb_fail = 1;
82 return NULL;
83 }
84
85 static uint64_t regen_frame_type[16];
86 static uint64_t regen_stream_id[16];
87 static size_t regen_count;
88
regen_expect(uint64_t frame_type,uint64_t stream_id,QUIC_TXPIM_PKT * pkt,void * arg)89 static void regen_expect(uint64_t frame_type, uint64_t stream_id,
90 QUIC_TXPIM_PKT *pkt, void *arg)
91 {
92 regen_frame_type[regen_count] = frame_type;
93 regen_stream_id[regen_count] = stream_id;
94 ++regen_count;
95 }
96
97 static const unsigned char placeholder_data[] = "placeholder";
98
cfq_free_cb_(unsigned char * buf,size_t buf_len,void * arg)99 static void cfq_free_cb_(unsigned char *buf, size_t buf_len, void *arg)
100 {
101 if (buf == placeholder_data && buf_len == sizeof(placeholder_data))
102 cfq_freed = 1;
103 }
104
105 #define TEST_KIND_ACK 0
106 #define TEST_KIND_LOSS 1
107 #define TEST_KIND_DISCARD 2
108 #define TEST_KIND_NUM 3
109
test_generic(INFO * info,int kind)110 static int test_generic(INFO *info, int kind)
111 {
112 int testresult = 0;
113 size_t i, consumed = 0;
114 QUIC_TXPIM_PKT *pkt = NULL, *pkt2 = NULL;
115 OSSL_QUIC_FRAME_STREAM hdr = { 0 };
116 OSSL_QTX_IOVEC iov[2];
117 size_t num_iov;
118 QUIC_TXPIM_CHUNK chunk = { 42, 0, 11, 0 };
119 OSSL_QUIC_FRAME_ACK ack = { 0 };
120 OSSL_QUIC_ACK_RANGE ack_ranges[1] = { 0 };
121 QUIC_CFQ_ITEM *cfq_item = NULL;
122 uint32_t pn_space = (kind == TEST_KIND_DISCARD)
123 ? QUIC_PN_SPACE_HANDSHAKE
124 : QUIC_PN_SPACE_APP;
125
126 cur_time = ossl_seconds2time(1000);
127 regen_count = 0;
128
129 get_sstream_by_id_p = sstream_expect;
130 regen_frame_p = regen_expect;
131
132 if (!TEST_ptr(pkt = ossl_quic_txpim_pkt_alloc(info->txpim)))
133 goto err;
134
135 for (i = 0; i < 2; ++i) {
136 num_iov = OSSL_NELEM(iov);
137 if (!TEST_true(ossl_quic_sstream_append(info->sstream[i],
138 (unsigned char *)"Test message",
139 12, &consumed))
140 || !TEST_size_t_eq(consumed, 12))
141 goto err;
142
143 if (i == 1)
144 ossl_quic_sstream_fin(info->sstream[i]);
145
146 if (!TEST_true(ossl_quic_sstream_get_stream_frame(info->sstream[i], 0,
147 &hdr, iov, &num_iov))
148 || !TEST_int_eq(hdr.is_fin, i == 1)
149 || !TEST_uint64_t_eq(hdr.offset, 0)
150 || !TEST_uint64_t_eq(hdr.len, 12)
151 || !TEST_size_t_eq(ossl_quic_sstream_get_buffer_used(info->sstream[i]), 12)
152 || !TEST_true(ossl_quic_sstream_mark_transmitted(info->sstream[i],
153 hdr.offset,
154 hdr.offset + hdr.len - 1)))
155 goto err;
156
157 if (i == 1 && !TEST_true(ossl_quic_sstream_mark_transmitted_fin(info->sstream[i], hdr.offset + hdr.len)))
158 goto err;
159
160 chunk.has_fin = hdr.is_fin;
161 chunk.stream_id = 42 + i;
162 if (!TEST_true(ossl_quic_txpim_pkt_append_chunk(pkt, &chunk)))
163 goto err;
164 }
165
166 cfq_freed = 0;
167 if (!TEST_ptr(cfq_item = ossl_quic_cfq_add_frame(info->cfq, 10,
168 pn_space,
169 OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID, 0,
170 placeholder_data,
171 sizeof(placeholder_data),
172 cfq_free_cb_, NULL))
173 || !TEST_ptr_eq(cfq_item, ossl_quic_cfq_get_priority_head(info->cfq, pn_space)))
174 goto err;
175
176 ossl_quic_txpim_pkt_add_cfq_item(pkt, cfq_item);
177
178 pkt->ackm_pkt.pkt_num = 0;
179 pkt->ackm_pkt.pkt_space = pn_space;
180 pkt->ackm_pkt.largest_acked = QUIC_PN_INVALID;
181 pkt->ackm_pkt.num_bytes = 50;
182 pkt->ackm_pkt.time = cur_time;
183 pkt->ackm_pkt.is_inflight = 1;
184 pkt->ackm_pkt.is_ack_eliciting = 1;
185 if (kind == TEST_KIND_LOSS) {
186 pkt->had_handshake_done_frame = 1;
187 pkt->had_max_data_frame = 1;
188 pkt->had_max_streams_bidi_frame = 1;
189 pkt->had_max_streams_uni_frame = 1;
190 pkt->had_ack_frame = 1;
191 }
192
193 ack_ranges[0].start = 0;
194 ack_ranges[0].end = 0;
195 ack.ack_ranges = ack_ranges;
196 ack.num_ack_ranges = 1;
197
198 if (!TEST_true(ossl_quic_fifd_pkt_commit(&info->fifd, pkt)))
199 goto err;
200
201 /* CFQ item should have been marked as transmitted */
202 if (!TEST_ptr_null(ossl_quic_cfq_get_priority_head(info->cfq, pn_space)))
203 goto err;
204
205 switch (kind) {
206 case TEST_KIND_ACK:
207 if (!TEST_true(ossl_ackm_on_rx_ack_frame(info->ackm, &ack,
208 pn_space,
209 cur_time)))
210 goto err;
211
212 for (i = 0; i < 2; ++i)
213 if (!TEST_size_t_eq(ossl_quic_sstream_get_buffer_used(info->sstream[i]), 0))
214 goto err;
215
216 /* This should fail, which proves the FIN was acked */
217 if (!TEST_false(ossl_quic_sstream_mark_lost_fin(info->sstream[1])))
218 goto err;
219
220 /* CFQ item must have been released */
221 if (!TEST_true(cfq_freed))
222 goto err;
223
224 /* No regen calls should have been made */
225 if (!TEST_size_t_eq(regen_count, 0))
226 goto err;
227
228 break;
229
230 case TEST_KIND_LOSS:
231 /* Trigger loss detection via packet threshold. */
232 if (!TEST_ptr(pkt2 = ossl_quic_txpim_pkt_alloc(info->txpim)))
233 goto err;
234
235 step_time(10000);
236 pkt2->ackm_pkt.pkt_num = 50;
237 pkt2->ackm_pkt.pkt_space = pn_space;
238 pkt2->ackm_pkt.largest_acked = QUIC_PN_INVALID;
239 pkt2->ackm_pkt.num_bytes = 50;
240 pkt2->ackm_pkt.time = cur_time;
241 pkt2->ackm_pkt.is_inflight = 1;
242 pkt2->ackm_pkt.is_ack_eliciting = 1;
243
244 ack_ranges[0].start = 50;
245 ack_ranges[0].end = 50;
246 ack.ack_ranges = ack_ranges;
247 ack.num_ack_ranges = 1;
248
249 if (!TEST_true(ossl_quic_fifd_pkt_commit(&info->fifd, pkt2))
250 || !TEST_true(ossl_ackm_on_rx_ack_frame(info->ackm, &ack,
251 pn_space, cur_time)))
252 goto err;
253
254 for (i = 0; i < 2; ++i) {
255 num_iov = OSSL_NELEM(iov);
256 /*
257 * Stream data we sent must have been marked as lost; check by
258 * ensuring it is returned again
259 */
260 if (!TEST_true(ossl_quic_sstream_get_stream_frame(info->sstream[i], 0,
261 &hdr, iov, &num_iov))
262 || !TEST_uint64_t_eq(hdr.offset, 0)
263 || !TEST_uint64_t_eq(hdr.len, 12))
264 goto err;
265 }
266
267 /* FC frame should have regenerated for each stream */
268 if (!TEST_size_t_eq(regen_count, 7)
269 || !TEST_uint64_t_eq(regen_stream_id[0], 42)
270 || !TEST_uint64_t_eq(regen_frame_type[0], OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA)
271 || !TEST_uint64_t_eq(regen_stream_id[1], 43)
272 || !TEST_uint64_t_eq(regen_frame_type[1], OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA)
273 || !TEST_uint64_t_eq(regen_frame_type[2], OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE)
274 || !TEST_uint64_t_eq(regen_stream_id[2], UINT64_MAX)
275 || !TEST_uint64_t_eq(regen_frame_type[3], OSSL_QUIC_FRAME_TYPE_MAX_DATA)
276 || !TEST_uint64_t_eq(regen_stream_id[3], UINT64_MAX)
277 || !TEST_uint64_t_eq(regen_frame_type[4], OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI)
278 || !TEST_uint64_t_eq(regen_stream_id[4], UINT64_MAX)
279 || !TEST_uint64_t_eq(regen_frame_type[5], OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI)
280 || !TEST_uint64_t_eq(regen_stream_id[5], UINT64_MAX)
281 || !TEST_uint64_t_eq(regen_frame_type[6], OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN)
282 || !TEST_uint64_t_eq(regen_stream_id[6], UINT64_MAX))
283 goto err;
284
285 /* CFQ item should have been marked as lost */
286 if (!TEST_ptr_eq(cfq_item, ossl_quic_cfq_get_priority_head(info->cfq, pn_space)))
287 goto err;
288
289 /* FIN should have been marked as lost */
290 num_iov = OSSL_NELEM(iov);
291 if (!TEST_true(ossl_quic_sstream_get_stream_frame(info->sstream[1], 1,
292 &hdr, iov, &num_iov))
293 || !TEST_true(hdr.is_fin)
294 || !TEST_uint64_t_eq(hdr.len, 0))
295 goto err;
296
297 break;
298
299 case TEST_KIND_DISCARD:
300 if (!TEST_true(ossl_ackm_on_pkt_space_discarded(info->ackm, pn_space)))
301 goto err;
302
303 /* CFQ item must have been released */
304 if (!TEST_true(cfq_freed))
305 goto err;
306
307 break;
308
309 default:
310 goto err;
311 }
312
313 /* TXPIM must have been released */
314 if (!TEST_size_t_eq(ossl_quic_txpim_get_in_use(info->txpim), 0))
315 goto err;
316
317 testresult = 1;
318 err:
319 return testresult;
320 }
321
test_fifd(int idx)322 static int test_fifd(int idx)
323 {
324 int testresult = 0;
325 INFO info = { 0 };
326 size_t i;
327
328 cur_info = &info;
329 cb_fail = 0;
330
331 if (!TEST_true(ossl_statm_init(&info.statm))
332 || !TEST_ptr(info.ccdata = ossl_cc_dummy_method.new(fake_now, NULL))
333 || !TEST_ptr(info.ackm = ossl_ackm_new(fake_now, NULL,
334 &info.statm,
335 &ossl_cc_dummy_method,
336 info.ccdata,
337 /* is_server */ 0))
338 || !TEST_true(ossl_ackm_on_handshake_confirmed(info.ackm))
339 || !TEST_ptr(info.cfq = ossl_quic_cfq_new())
340 || !TEST_ptr(info.txpim = ossl_quic_txpim_new())
341 || !TEST_true(ossl_quic_fifd_init(&info.fifd, info.cfq, info.ackm,
342 info.txpim,
343 get_sstream_by_id, NULL,
344 regen_frame, NULL,
345 confirm_frame, NULL,
346 sstream_updated, NULL,
347 NULL, NULL)))
348 goto err;
349
350 for (i = 0; i < OSSL_NELEM(info.sstream); ++i)
351 if (!TEST_ptr(info.sstream[i] = ossl_quic_sstream_new(1024)))
352 goto err;
353
354 ossl_statm_update_rtt(&info.statm, ossl_time_zero(), ossl_ms2time(1));
355
356 if (!TEST_true(test_generic(&info, idx))
357 || !TEST_false(cb_fail))
358 goto err;
359
360 testresult = 1;
361 err:
362 ossl_quic_fifd_cleanup(&info.fifd);
363 ossl_quic_cfq_free(info.cfq);
364 ossl_quic_txpim_free(info.txpim);
365 ossl_ackm_free(info.ackm);
366 ossl_statm_destroy(&info.statm);
367 if (info.ccdata != NULL)
368 ossl_cc_dummy_method.free(info.ccdata);
369 for (i = 0; i < OSSL_NELEM(info.sstream); ++i)
370 ossl_quic_sstream_free(info.sstream[i]);
371 cur_info = NULL;
372 return testresult;
373 }
374
setup_tests(void)375 int setup_tests(void)
376 {
377 ADD_ALL_TESTS(test_fifd, TEST_KIND_NUM);
378 return 1;
379 }
380