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