xref: /freebsd/crypto/openssl/test/quic_fifd_test.c (revision f25b8c9fb4f58cf61adb47d7570abe7caa6d385d)
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