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 17 static OSSL_TIME fake_now(void *arg) 18 { 19 return cur_time; 20 } 21 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 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 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 45 static void confirm_frame(uint64_t frame_type, uint64_t stream_id, 46 QUIC_TXPIM_PKT *pkt, void *arg) 47 { 48 } 49 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 */ 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 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 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 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 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 375 int setup_tests(void) 376 { 377 ADD_ALL_TESTS(test_fifd, TEST_KIND_NUM); 378 return 1; 379 } 380