mirror of
https://github.com/openssl/openssl.git
synced 2024-12-27 06:21:43 +08:00
4d32f5332f
Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Richard Levitte <levitte@openssl.org> (Merged from https://github.com/openssl/openssl/pull/18676)
1044 lines
33 KiB
C
1044 lines
33 KiB
C
/*
|
|
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
* this file except in compliance with the License. You can obtain a copy
|
|
* in the file LICENSE in the source distribution or at
|
|
* https://www.openssl.org/source/license.html
|
|
*/
|
|
|
|
#include "testutil.h"
|
|
#include <openssl/ssl.h>
|
|
#include "internal/quic_ackm.h"
|
|
#include "internal/quic_cc.h"
|
|
|
|
static OSSL_TIME fake_time = {0};
|
|
|
|
#define TIME_BASE (ossl_ticks2time(123 * OSSL_TIME_SECOND))
|
|
|
|
static OSSL_TIME fake_now(void *arg)
|
|
{
|
|
return fake_time;
|
|
}
|
|
|
|
struct pkt_info {
|
|
OSSL_ACKM_TX_PKT *pkt;
|
|
int lost, acked, discarded;
|
|
};
|
|
|
|
static void on_lost(void *arg)
|
|
{
|
|
struct pkt_info *info = arg;
|
|
++info->lost;
|
|
}
|
|
|
|
static void on_acked(void *arg)
|
|
{
|
|
struct pkt_info *info = arg;
|
|
++info->acked;
|
|
}
|
|
|
|
static void on_discarded(void *arg)
|
|
{
|
|
struct pkt_info *info = arg;
|
|
++info->discarded;
|
|
}
|
|
|
|
struct helper {
|
|
OSSL_ACKM *ackm;
|
|
struct pkt_info *pkts;
|
|
size_t num_pkts;
|
|
OSSL_CC_DATA *ccdata;
|
|
OSSL_STATM statm;
|
|
int have_statm;
|
|
};
|
|
|
|
static void helper_destroy(struct helper *h)
|
|
{
|
|
size_t i;
|
|
|
|
if (h->ackm != NULL) {
|
|
ossl_ackm_free(h->ackm);
|
|
h->ackm = NULL;
|
|
}
|
|
|
|
if (h->ccdata != NULL) {
|
|
ossl_cc_dummy_method.free(h->ccdata);
|
|
h->ccdata = NULL;
|
|
}
|
|
|
|
if (h->have_statm) {
|
|
ossl_statm_destroy(&h->statm);
|
|
h->have_statm = 0;
|
|
}
|
|
|
|
if (h->pkts != NULL) {
|
|
for (i = 0; i < h->num_pkts; ++i) {
|
|
OPENSSL_free(h->pkts[i].pkt);
|
|
h->pkts[i].pkt = NULL;
|
|
}
|
|
|
|
OPENSSL_free(h->pkts);
|
|
h->pkts = NULL;
|
|
}
|
|
}
|
|
|
|
static int helper_init(struct helper *h, size_t num_pkts)
|
|
{
|
|
int rc = 0;
|
|
|
|
memset(h, 0, sizeof(*h));
|
|
|
|
fake_time = TIME_BASE;
|
|
|
|
/* Initialise statistics tracker. */
|
|
if (!TEST_int_eq(ossl_statm_init(&h->statm), 1))
|
|
goto err;
|
|
|
|
h->have_statm = 1;
|
|
|
|
/* Initialise congestion controller. */
|
|
h->ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL);
|
|
if (!TEST_ptr(h->ccdata))
|
|
goto err;
|
|
|
|
/* Initialise ACK manager. */
|
|
h->ackm = ossl_ackm_new(fake_now, NULL, &h->statm,
|
|
&ossl_cc_dummy_method, h->ccdata);
|
|
if (!TEST_ptr(h->ackm))
|
|
goto err;
|
|
|
|
/* Allocate our array of packet information. */
|
|
h->num_pkts = num_pkts;
|
|
if (num_pkts > 0) {
|
|
h->pkts = OPENSSL_zalloc(sizeof(struct pkt_info) * num_pkts);
|
|
if (!TEST_ptr(h->pkts))
|
|
goto err;
|
|
} else {
|
|
h->pkts = NULL;
|
|
}
|
|
|
|
rc = 1;
|
|
err:
|
|
if (rc == 0)
|
|
helper_destroy(h);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static const QUIC_PN linear_20[] = {
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
|
};
|
|
|
|
static const QUIC_PN high_linear_20[] = {
|
|
1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008,
|
|
1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017,
|
|
1018, 1019
|
|
};
|
|
|
|
/*
|
|
* TX ACK (Packet Threshold) Test Cases
|
|
* ******************************************************************
|
|
*/
|
|
struct tx_ack_test_case {
|
|
const QUIC_PN *pn_table;
|
|
size_t pn_table_len;
|
|
const OSSL_QUIC_ACK_RANGE *ack_ranges;
|
|
size_t num_ack_ranges;
|
|
const char *expect_ack; /* 1=ack, 2=lost, 4=discarded */
|
|
};
|
|
|
|
#define DEFINE_TX_ACK_CASE(n, pntable) \
|
|
static const struct tx_ack_test_case tx_ack_case_##n = { \
|
|
(pntable), OSSL_NELEM(pntable), \
|
|
tx_ack_range_##n, OSSL_NELEM(tx_ack_range_##n), \
|
|
tx_ack_expect_##n \
|
|
}
|
|
|
|
/* One range, partial coverage of space */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_1[] = {
|
|
{ 0, 10 },
|
|
};
|
|
static const char tx_ack_expect_1[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(1, linear_20);
|
|
|
|
/* Two ranges, partial coverage of space, overlapping by 1 */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_2[] = {
|
|
{ 5, 10 }, { 0, 5 }
|
|
};
|
|
static const char tx_ack_expect_2[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(2, linear_20);
|
|
|
|
/* Two ranges, partial coverage of space, together contiguous */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_3[] = {
|
|
{ 6, 10 }, { 0, 5 }
|
|
};
|
|
static const char tx_ack_expect_3[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(3, linear_20);
|
|
|
|
/*
|
|
* Two ranges, partial coverage of space, non-contiguous by 1
|
|
* Causes inferred loss due to packet threshold being exceeded.
|
|
*/
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_4[] = {
|
|
{ 7, 10 }, { 0, 5 }
|
|
};
|
|
static const char tx_ack_expect_4[] = {
|
|
1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(4, linear_20);
|
|
|
|
/*
|
|
* Two ranges, partial coverage of space, non-contiguous by 2
|
|
* Causes inferred loss due to packet threshold being exceeded.
|
|
*/
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_5[] = {
|
|
{ 7, 10 }, { 0, 4 }
|
|
};
|
|
static const char tx_ack_expect_5[] = {
|
|
1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(5, linear_20);
|
|
|
|
/* One range, covering entire space */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_6[] = {
|
|
{ 0, 20 },
|
|
};
|
|
static const char tx_ack_expect_6[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
|
};
|
|
DEFINE_TX_ACK_CASE(6, linear_20);
|
|
|
|
/* One range, covering more space than exists */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_7[] = {
|
|
{ 0, 30 },
|
|
};
|
|
static const char tx_ack_expect_7[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
|
};
|
|
DEFINE_TX_ACK_CASE(7, linear_20);
|
|
|
|
/* One range, covering nothing (too high) */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_8[] = {
|
|
{ 21, 30 },
|
|
};
|
|
static const char tx_ack_expect_8[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(8, linear_20);
|
|
|
|
/* One range, covering nothing (too low) */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_9[] = {
|
|
{ 0, 999 },
|
|
};
|
|
static const char tx_ack_expect_9[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(9, high_linear_20);
|
|
|
|
/* One single packet at start of PN set */
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_10[] = {
|
|
{ 0, 0 },
|
|
};
|
|
static const char tx_ack_expect_10[] = {
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(10, linear_20);
|
|
|
|
/*
|
|
* One single packet in middle of PN set
|
|
* Causes inferred loss of one packet due to packet threshold being exceeded,
|
|
* but several other previous packets survive as they are under the threshold.
|
|
*/
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_11[] = {
|
|
{ 3, 3 },
|
|
};
|
|
static const char tx_ack_expect_11[] = {
|
|
2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(11, linear_20);
|
|
|
|
/*
|
|
* One single packet at end of PN set
|
|
* Causes inferred loss due to packet threshold being exceeded.
|
|
*/
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_12[] = {
|
|
{ 19, 19 },
|
|
};
|
|
static const char tx_ack_expect_12[] = {
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 1
|
|
};
|
|
DEFINE_TX_ACK_CASE(12, linear_20);
|
|
|
|
/*
|
|
* Mixed straddling
|
|
* Causes inferred loss due to packet threshold being exceeded.
|
|
*/
|
|
static const OSSL_QUIC_ACK_RANGE tx_ack_range_13[] = {
|
|
{ 1008, 1008 }, { 1004, 1005 }, { 1001, 1002 }
|
|
};
|
|
static const char tx_ack_expect_13[] = {
|
|
2, 1, 1, 2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
DEFINE_TX_ACK_CASE(13, high_linear_20);
|
|
|
|
static const struct tx_ack_test_case *const tx_ack_cases[] = {
|
|
&tx_ack_case_1,
|
|
&tx_ack_case_2,
|
|
&tx_ack_case_3,
|
|
&tx_ack_case_4,
|
|
&tx_ack_case_5,
|
|
&tx_ack_case_6,
|
|
&tx_ack_case_7,
|
|
&tx_ack_case_8,
|
|
&tx_ack_case_9,
|
|
&tx_ack_case_10,
|
|
&tx_ack_case_11,
|
|
&tx_ack_case_12,
|
|
&tx_ack_case_13,
|
|
};
|
|
|
|
enum {
|
|
MODE_ACK, MODE_DISCARD, MODE_PTO, MODE_NUM
|
|
};
|
|
|
|
static int test_probe_counts(const OSSL_ACKM_PROBE_INFO *p,
|
|
uint32_t handshake,
|
|
uint32_t padded_initial,
|
|
uint32_t pto_initial,
|
|
uint32_t pto_handshake,
|
|
uint32_t pto_app)
|
|
{
|
|
if (!TEST_uint_eq(p->handshake, handshake))
|
|
return 0;
|
|
if (!TEST_uint_eq(p->padded_initial, padded_initial))
|
|
return 0;
|
|
if (!TEST_uint_eq(p->pto[QUIC_PN_SPACE_INITIAL], pto_initial))
|
|
return 0;
|
|
if (!TEST_uint_eq(p->pto[QUIC_PN_SPACE_HANDSHAKE], pto_handshake))
|
|
return 0;
|
|
if (!TEST_uint_eq(p->pto[QUIC_PN_SPACE_APP], pto_app))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void on_loss_detection_deadline_callback(OSSL_TIME deadline, void *arg)
|
|
{
|
|
*(OSSL_TIME *)arg = deadline;
|
|
}
|
|
|
|
static int test_tx_ack_case_actual(int tidx, int space, int mode)
|
|
{
|
|
int testresult = 0;
|
|
struct helper h;
|
|
size_t i;
|
|
OSSL_ACKM_TX_PKT *tx;
|
|
const struct tx_ack_test_case *c = tx_ack_cases[tidx];
|
|
OSSL_QUIC_FRAME_ACK ack = {0};
|
|
OSSL_TIME loss_detection_deadline = ossl_time_zero();
|
|
|
|
/* Cannot discard app space, so skip this */
|
|
if (mode == MODE_DISCARD && space == QUIC_PN_SPACE_APP) {
|
|
TEST_skip("skipping test for app space");
|
|
return 1;
|
|
}
|
|
|
|
if (!TEST_int_eq(helper_init(&h, c->pn_table_len), 1))
|
|
goto err;
|
|
|
|
/* Arm callback. */
|
|
ossl_ackm_set_loss_detection_deadline_callback(h.ackm,
|
|
on_loss_detection_deadline_callback,
|
|
&loss_detection_deadline);
|
|
|
|
/* Allocate TX packet structures. */
|
|
for (i = 0; i < c->pn_table_len; ++i) {
|
|
h.pkts[i].pkt = tx = OPENSSL_zalloc(sizeof(*tx));
|
|
if (!TEST_ptr(tx))
|
|
goto err;
|
|
|
|
tx->pkt_num = c->pn_table[i];
|
|
tx->pkt_space = space;
|
|
tx->is_inflight = 1;
|
|
tx->is_ack_eliciting = 1;
|
|
tx->num_bytes = 123;
|
|
tx->largest_acked = QUIC_PN_INVALID;
|
|
tx->on_lost = on_lost;
|
|
tx->on_acked = on_acked;
|
|
tx->on_discarded = on_discarded;
|
|
tx->cb_arg = &h.pkts[i];
|
|
|
|
tx->time = fake_time;
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_tx_packet(h.ackm, tx), 1))
|
|
goto err;
|
|
}
|
|
|
|
if (mode == MODE_DISCARD) {
|
|
/* Try discarding. */
|
|
if (!TEST_int_eq(ossl_ackm_on_pkt_space_discarded(h.ackm, space), 1))
|
|
goto err;
|
|
|
|
/* Check all discard callbacks were called. */
|
|
for (i = 0; i < c->pn_table_len; ++i) {
|
|
if (!TEST_int_eq(h.pkts[i].acked, 0))
|
|
goto err;
|
|
if (!TEST_int_eq(h.pkts[i].lost, 0))
|
|
goto err;
|
|
if (!TEST_int_eq(h.pkts[i].discarded, 1))
|
|
goto err;
|
|
}
|
|
} else if (mode == MODE_ACK) {
|
|
/* Try acknowledging. */
|
|
ack.ack_ranges = (OSSL_QUIC_ACK_RANGE *)c->ack_ranges;
|
|
ack.num_ack_ranges = c->num_ack_ranges;
|
|
if (!TEST_int_eq(ossl_ackm_on_rx_ack_frame(h.ackm, &ack, space, fake_time), 1))
|
|
goto err;
|
|
|
|
/* Check correct ranges were acknowledged. */
|
|
for (i = 0; i < c->pn_table_len; ++i) {
|
|
if (!TEST_int_eq(h.pkts[i].acked,
|
|
(c->expect_ack[i] & 1) != 0 ? 1 : 0))
|
|
goto err;
|
|
if (!TEST_int_eq(h.pkts[i].lost,
|
|
(c->expect_ack[i] & 2) != 0 ? 1 : 0))
|
|
goto err;
|
|
if (!TEST_int_eq(h.pkts[i].discarded,
|
|
(c->expect_ack[i] & 4) != 0 ? 1 : 0))
|
|
goto err;
|
|
}
|
|
} else if (mode == MODE_PTO) {
|
|
OSSL_TIME deadline = ossl_ackm_get_loss_detection_deadline(h.ackm);
|
|
OSSL_ACKM_PROBE_INFO probe = {0};
|
|
|
|
if (!TEST_int_eq(ossl_time_compare(deadline, loss_detection_deadline), 0))
|
|
goto err;
|
|
|
|
/* We should have a PTO deadline. */
|
|
if (!TEST_int_gt(ossl_time_compare(deadline, fake_time), 0))
|
|
goto err;
|
|
|
|
/* Should not have any probe requests yet. */
|
|
if (!TEST_int_eq(ossl_ackm_get_probe_request(h.ackm, 0, &probe), 1))
|
|
goto err;
|
|
|
|
if (!TEST_int_eq(test_probe_counts(&probe, 0, 0, 0, 0, 0), 1))
|
|
goto err;
|
|
|
|
/*
|
|
* If in app space, confirm handshake, as this is necessary to enable
|
|
* app space PTO probe requests.
|
|
*/
|
|
if (space == QUIC_PN_SPACE_APP)
|
|
if (!TEST_int_eq(ossl_ackm_on_handshake_confirmed(h.ackm), 1))
|
|
goto err;
|
|
|
|
/* Advance to the PTO deadline. */
|
|
fake_time = ossl_time_add(deadline, ossl_ticks2time(1));
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_timeout(h.ackm), 1))
|
|
goto err;
|
|
|
|
/* Should have a probe request. Not cleared by first call. */
|
|
for (i = 0; i < 3; ++i) {
|
|
if (!TEST_int_eq(ossl_ackm_get_probe_request(h.ackm, i > 0, &probe), 1))
|
|
goto err;
|
|
|
|
if (i == 2) {
|
|
if (!TEST_int_eq(test_probe_counts(&probe, 0, 0, 0, 0, 0), 1))
|
|
goto err;
|
|
} else {
|
|
if (!TEST_int_eq(test_probe_counts(&probe, 0, 0,
|
|
space == QUIC_PN_SPACE_INITIAL,
|
|
space == QUIC_PN_SPACE_HANDSHAKE,
|
|
space == QUIC_PN_SPACE_APP), 1))
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
} else
|
|
goto err;
|
|
|
|
testresult = 1;
|
|
err:
|
|
helper_destroy(&h);
|
|
return testresult;
|
|
}
|
|
|
|
/*
|
|
* TX ACK (Time Threshold) Test
|
|
* ******************************************************************
|
|
*/
|
|
enum {
|
|
TX_ACK_TIME_OP_END,
|
|
TX_ACK_TIME_OP_PKT, /* TX packets */
|
|
TX_ACK_TIME_OP_ACK, /* Synthesise incoming ACK of single PN range */
|
|
TX_ACK_TIME_OP_EXPECT /* Ack/loss assertion */
|
|
};
|
|
|
|
struct tx_ack_time_op {
|
|
int kind;
|
|
uint64_t time_advance; /* all ops */
|
|
QUIC_PN pn; /* PKT, ACK */
|
|
size_t num_pn; /* PKT, ACK */
|
|
const char *expect; /* 1=ack, 2=lost, 4=discarded */
|
|
};
|
|
|
|
#define TX_OP_PKT(advance, pn, num_pn) \
|
|
{ TX_ACK_TIME_OP_PKT, (advance) * OSSL_TIME_MS, (pn), (num_pn), NULL },
|
|
#define TX_OP_ACK(advance, pn, num_pn) \
|
|
{ TX_ACK_TIME_OP_ACK, (advance) * OSSL_TIME_MS, (pn), (num_pn), NULL },
|
|
#define TX_OP_EXPECT(expect) \
|
|
{ TX_ACK_TIME_OP_EXPECT, 0, 0, 0, (expect) },
|
|
#define TX_OP_END { TX_ACK_TIME_OP_END }
|
|
|
|
static const char tx_ack_time_script_1_expect[] = {
|
|
2, 1
|
|
};
|
|
|
|
static const struct tx_ack_time_op tx_ack_time_script_1[] = {
|
|
TX_OP_PKT ( 0, 0, 1)
|
|
TX_OP_PKT (3600000, 1, 1)
|
|
TX_OP_ACK ( 1000, 1, 1)
|
|
TX_OP_EXPECT(tx_ack_time_script_1_expect)
|
|
TX_OP_END
|
|
};
|
|
|
|
static const struct tx_ack_time_op *const tx_ack_time_scripts[] = {
|
|
tx_ack_time_script_1,
|
|
};
|
|
|
|
static int test_tx_ack_time_script(int tidx)
|
|
{
|
|
int testresult = 0;
|
|
struct helper h;
|
|
OSSL_ACKM_TX_PKT *tx = NULL;
|
|
OSSL_QUIC_FRAME_ACK ack = {0};
|
|
OSSL_QUIC_ACK_RANGE ack_range = {0};
|
|
size_t i, num_pkts = 0, pkt_idx = 0;
|
|
const struct tx_ack_time_op *script = tx_ack_time_scripts[tidx], *s;
|
|
|
|
/* Calculate number of packets. */
|
|
for (s = script; s->kind != TX_ACK_TIME_OP_END; ++s)
|
|
if (s->kind == TX_ACK_TIME_OP_PKT)
|
|
num_pkts += s->num_pn;
|
|
|
|
/* Initialise ACK manager and packet structures. */
|
|
if (!TEST_int_eq(helper_init(&h, num_pkts), 1))
|
|
goto err;
|
|
|
|
for (i = 0; i < num_pkts; ++i) {
|
|
h.pkts[i].pkt = tx = OPENSSL_zalloc(sizeof(*tx));
|
|
if (!TEST_ptr(tx))
|
|
goto err;
|
|
}
|
|
|
|
/* Run script. */
|
|
for (s = script; s->kind != TX_ACK_TIME_OP_END; ++s)
|
|
switch (s->kind) {
|
|
case TX_ACK_TIME_OP_PKT:
|
|
for (i = 0; i < s->num_pn; ++i) {
|
|
tx = h.pkts[pkt_idx + i].pkt;
|
|
|
|
tx->pkt_num = s->pn + i;
|
|
tx->pkt_space = QUIC_PN_SPACE_INITIAL;
|
|
tx->num_bytes = 123;
|
|
tx->largest_acked = QUIC_PN_INVALID;
|
|
tx->is_inflight = 1;
|
|
tx->is_ack_eliciting = 1;
|
|
tx->on_lost = on_lost;
|
|
tx->on_acked = on_acked;
|
|
tx->on_discarded = on_discarded;
|
|
tx->cb_arg = &h.pkts[pkt_idx + i];
|
|
|
|
fake_time = ossl_time_add(fake_time,
|
|
ossl_ticks2time(s->time_advance));
|
|
tx->time = fake_time;
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_tx_packet(h.ackm, tx), 1))
|
|
goto err;
|
|
}
|
|
|
|
pkt_idx += s->num_pn;
|
|
break;
|
|
|
|
case TX_ACK_TIME_OP_ACK:
|
|
ack.ack_ranges = &ack_range;
|
|
ack.num_ack_ranges = 1;
|
|
|
|
ack_range.start = s->pn;
|
|
ack_range.end = s->pn + s->num_pn;
|
|
|
|
fake_time = ossl_time_add(fake_time,
|
|
ossl_ticks2time(s->time_advance));
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_rx_ack_frame(h.ackm, &ack,
|
|
QUIC_PN_SPACE_INITIAL,
|
|
fake_time), 1))
|
|
goto err;
|
|
|
|
break;
|
|
|
|
case TX_ACK_TIME_OP_EXPECT:
|
|
for (i = 0; i < num_pkts; ++i) {
|
|
if (!TEST_int_eq(h.pkts[i].acked,
|
|
(s->expect[i] & 1) != 0 ? 1 : 0))
|
|
goto err;
|
|
if (!TEST_int_eq(h.pkts[i].lost,
|
|
(s->expect[i] & 2) != 0 ? 1 : 0))
|
|
goto err;
|
|
if (!TEST_int_eq(h.pkts[i].discarded,
|
|
(s->expect[i] & 4) != 0 ? 1 : 0))
|
|
goto err;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
testresult = 1;
|
|
err:
|
|
helper_destroy(&h);
|
|
return testresult;
|
|
}
|
|
|
|
/*
|
|
* RX ACK Test
|
|
* ******************************************************************
|
|
*/
|
|
enum {
|
|
RX_OPK_END,
|
|
RX_OPK_PKT, /* RX packet */
|
|
RX_OPK_CHECK_UNPROC, /* check PNs unprocessable */
|
|
RX_OPK_CHECK_PROC, /* check PNs processable */
|
|
RX_OPK_CHECK_STATE, /* check is_desired/deadline */
|
|
RX_OPK_CHECK_ACKS, /* check ACK ranges */
|
|
RX_OPK_TX, /* TX packet */
|
|
RX_OPK_RX_ACK /* RX ACK frame */
|
|
};
|
|
|
|
struct rx_test_op {
|
|
int kind;
|
|
uint64_t time_advance;
|
|
|
|
QUIC_PN pn; /* PKT, CHECK_(UN)PROC, TX, RX_ACK */
|
|
size_t num_pn; /* PKT, CHECK_(UN)PROC, TX, RX_ACK */
|
|
|
|
char expect_desired; /* CHECK_STATE */
|
|
char expect_deadline; /* CHECK_STATE */
|
|
|
|
const OSSL_QUIC_ACK_RANGE *ack_ranges; /* CHECK_ACKS */
|
|
size_t num_ack_ranges; /* CHECK_ACKS */
|
|
|
|
QUIC_PN largest_acked; /* TX */
|
|
};
|
|
|
|
#define RX_OP_PKT(advance, pn, num_pn) \
|
|
{ \
|
|
RX_OPK_PKT, (advance) * OSSL_TIME_MS, (pn), (num_pn), \
|
|
0, 0, NULL, 0, 0 \
|
|
},
|
|
|
|
#define RX_OP_CHECK_UNPROC(advance, pn, num_pn) \
|
|
{ \
|
|
RX_OPK_CHECK_UNPROC, (advance) * OSSL_TIME_MS, (pn), (num_pn),\
|
|
0, 0, NULL, 0, 0 \
|
|
},
|
|
|
|
#define RX_OP_CHECK_PROC(advance, pn, num_pn) \
|
|
{ \
|
|
RX_OPK_CHECK_PROC, (advance) * OSSL_TIME_MS, (pn), (num_pn), \
|
|
0, 0, NULL, 0, 0 \
|
|
},
|
|
|
|
#define RX_OP_CHECK_STATE(advance, expect_desired, expect_deadline) \
|
|
{ \
|
|
RX_OPK_CHECK_STATE, (advance) * OSSL_TIME_MS, 0, 0, \
|
|
(expect_desired), (expect_deadline), NULL, 0, 0 \
|
|
},
|
|
|
|
#define RX_OP_CHECK_ACKS(advance, ack_ranges) \
|
|
{ \
|
|
RX_OPK_CHECK_ACKS, (advance) * OSSL_TIME_MS, 0, 0, \
|
|
0, 0, (ack_ranges), OSSL_NELEM(ack_ranges), 0 \
|
|
},
|
|
|
|
#define RX_OP_CHECK_NO_ACKS(advance) \
|
|
{ \
|
|
RX_OPK_CHECK_ACKS, (advance) * OSSL_TIME_MS, 0, 0, \
|
|
0, 0, NULL, 0, 0 \
|
|
},
|
|
|
|
#define RX_OP_TX(advance, pn, largest_acked) \
|
|
{ \
|
|
RX_OPK_TX, (advance) * OSSL_TIME_MS, (pn), 1, \
|
|
0, 0, NULL, 0, (largest_acked) \
|
|
},
|
|
|
|
#define RX_OP_RX_ACK(advance, pn, num_pn) \
|
|
{ \
|
|
RX_OPK_RX_ACK, (advance) * OSSL_TIME_MS, (pn), (num_pn), \
|
|
0, 0, NULL, 0, 0 \
|
|
},
|
|
|
|
#define RX_OP_END \
|
|
{ RX_OPK_END }
|
|
|
|
/* RX 1. Simple Test with ACK Desired (Packet Threshold, Exactly) */
|
|
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_1a[] = {
|
|
{ 0, 1 }
|
|
};
|
|
|
|
static const struct rx_test_op rx_script_1[] = {
|
|
RX_OP_CHECK_STATE (0, 0, 0) /* no threshold yet */
|
|
RX_OP_CHECK_PROC (0, 0, 3)
|
|
|
|
RX_OP_PKT (0, 0, 2) /* two packets, threshold */
|
|
RX_OP_CHECK_UNPROC (0, 0, 2)
|
|
RX_OP_CHECK_PROC (0, 2, 1)
|
|
RX_OP_CHECK_STATE (0, 1, 0) /* threshold met, immediate */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_1a)
|
|
|
|
/* At this point we would generate e.g. a packet with an ACK. */
|
|
RX_OP_TX (0, 0, 1) /* ACKs both */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_1a) /* not provably ACKed yet */
|
|
RX_OP_RX_ACK (0, 0, 1) /* TX'd packet is ACK'd */
|
|
|
|
RX_OP_CHECK_NO_ACKS (0) /* nothing more to ACK */
|
|
RX_OP_CHECK_UNPROC (0, 0, 2) /* still unprocessable */
|
|
RX_OP_CHECK_PROC (0, 2, 1) /* still processable */
|
|
|
|
RX_OP_END
|
|
};
|
|
|
|
/* RX 2. Simple Test with ACK Not Yet Desired (Packet Threshold) */
|
|
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_2a[] = {
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_2b[] = {
|
|
{ 0, 2 }
|
|
};
|
|
|
|
static const struct rx_test_op rx_script_2[] = {
|
|
RX_OP_CHECK_STATE (0, 0, 0) /* no threshold yet */
|
|
RX_OP_CHECK_PROC (0, 0, 3)
|
|
|
|
/* First packet always generates an ACK so get it out of the way. */
|
|
RX_OP_PKT (0, 0, 1)
|
|
RX_OP_CHECK_UNPROC (0, 0, 1)
|
|
RX_OP_CHECK_PROC (0, 1, 1)
|
|
RX_OP_CHECK_STATE (0, 1, 0) /* first packet always causes ACK */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_2a) /* clears packet counter */
|
|
RX_OP_CHECK_STATE (0, 0, 0) /* desired state should have been cleared */
|
|
|
|
/* Second packet should not cause ACK-desired state */
|
|
RX_OP_PKT (0, 1, 1) /* just one packet, threshold is 2 */
|
|
RX_OP_CHECK_UNPROC (0, 0, 2)
|
|
RX_OP_CHECK_PROC (0, 2, 1)
|
|
RX_OP_CHECK_STATE (0, 0, 1) /* threshold not yet met, so deadline */
|
|
/* Don't check ACKs here, as it would reset our threshold counter. */
|
|
|
|
/* Now receive a second packet, triggering the threshold */
|
|
RX_OP_PKT (0, 2, 1) /* second packet meets threshold */
|
|
RX_OP_CHECK_UNPROC (0, 0, 3)
|
|
RX_OP_CHECK_PROC (0, 3, 1)
|
|
RX_OP_CHECK_STATE (0, 1, 0) /* desired immediately */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_2b)
|
|
|
|
/* At this point we would generate e.g. a packet with an ACK. */
|
|
RX_OP_TX (0, 0, 2) /* ACKs all */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_2b) /* not provably ACKed yet */
|
|
RX_OP_RX_ACK (0, 0, 1) /* TX'd packet is ACK'd */
|
|
|
|
RX_OP_CHECK_NO_ACKS (0) /* nothing more to ACK */
|
|
RX_OP_CHECK_UNPROC (0, 0, 3) /* still unprocessable */
|
|
RX_OP_CHECK_PROC (0, 3, 1) /* still processable */
|
|
|
|
RX_OP_END
|
|
};
|
|
|
|
/* RX 3. Simple Test with ACK Desired (Packet Threshold, Multiple Watermarks) */
|
|
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_3a[] = {
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_3b[] = {
|
|
{ 0, 10 }
|
|
};
|
|
|
|
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_3c[] = {
|
|
{ 6, 10 }
|
|
};
|
|
|
|
static const struct rx_test_op rx_script_3[] = {
|
|
RX_OP_CHECK_STATE (0, 0, 0) /* no threshold yet */
|
|
RX_OP_CHECK_PROC (0, 0, 11)
|
|
|
|
/* First packet always generates an ACK so get it out of the way. */
|
|
RX_OP_PKT (0, 0, 1)
|
|
RX_OP_CHECK_UNPROC (0, 0, 1)
|
|
RX_OP_CHECK_PROC (0, 1, 1)
|
|
RX_OP_CHECK_STATE (0, 1, 0) /* first packet always causes ACK */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_3a) /* clears packet counter */
|
|
RX_OP_CHECK_STATE (0, 0, 0) /* desired state should have been cleared */
|
|
|
|
/* Generate ten packets, exceeding the threshold. */
|
|
RX_OP_PKT (0, 1, 10) /* ten packets, threshold is 2 */
|
|
RX_OP_CHECK_UNPROC (0, 0, 11)
|
|
RX_OP_CHECK_PROC (0, 11, 1)
|
|
RX_OP_CHECK_STATE (0, 1, 0) /* threshold met, immediate */
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_3b)
|
|
|
|
/*
|
|
* Test TX'ing a packet which doesn't ACK anything.
|
|
*/
|
|
RX_OP_TX (0, 0, QUIC_PN_INVALID)
|
|
RX_OP_RX_ACK (0, 0, 1)
|
|
|
|
/*
|
|
* At this point we would generate a packet with an ACK immediately.
|
|
* TX a packet which when ACKed makes [0,5] provably ACKed.
|
|
*/
|
|
RX_OP_TX (0, 1, 5)
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_3b) /* not provably ACKed yet */
|
|
RX_OP_RX_ACK (0, 1, 1)
|
|
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_3c) /* provably ACKed now gone */
|
|
RX_OP_CHECK_UNPROC (0, 0, 11) /* still unprocessable */
|
|
RX_OP_CHECK_PROC (0, 11, 1) /* still processable */
|
|
|
|
/*
|
|
* Now TX another packet which provably ACKs the rest when ACKed.
|
|
*/
|
|
RX_OP_TX (0, 2, 10)
|
|
RX_OP_CHECK_ACKS (0, rx_ack_ranges_3c) /* not provably ACKed yet */
|
|
RX_OP_RX_ACK (0, 2, 1)
|
|
|
|
RX_OP_CHECK_NO_ACKS (0) /* provably ACKed now gone */
|
|
RX_OP_CHECK_UNPROC (0, 0, 11) /* still unprocessable */
|
|
RX_OP_CHECK_PROC (0, 11, 1) /* still processable */
|
|
|
|
RX_OP_END
|
|
};
|
|
|
|
static const struct rx_test_op *const rx_test_scripts[] = {
|
|
rx_script_1,
|
|
rx_script_2,
|
|
rx_script_3
|
|
};
|
|
|
|
static void on_ack_deadline_callback(OSSL_TIME deadline,
|
|
int pkt_space, void *arg)
|
|
{
|
|
((OSSL_TIME *)arg)[pkt_space] = deadline;
|
|
}
|
|
|
|
static int test_rx_ack_actual(int tidx, int space)
|
|
{
|
|
int testresult = 0;
|
|
struct helper h;
|
|
const struct rx_test_op *script = rx_test_scripts[tidx], *s;
|
|
size_t i, num_tx = 0, txi = 0;
|
|
const OSSL_QUIC_FRAME_ACK *ack;
|
|
OSSL_QUIC_FRAME_ACK rx_ack = {0};
|
|
OSSL_QUIC_ACK_RANGE rx_ack_range = {0};
|
|
struct pkt_info *pkts = NULL;
|
|
OSSL_ACKM_TX_PKT *txs = NULL, *tx;
|
|
OSSL_TIME ack_deadline[QUIC_PN_SPACE_NUM];
|
|
|
|
for (i = 0; i < QUIC_PN_SPACE_NUM; ++i)
|
|
ack_deadline[i] = ossl_time_infinite();
|
|
|
|
/* Initialise ACK manager. */
|
|
if (!TEST_int_eq(helper_init(&h, 0), 1))
|
|
goto err;
|
|
|
|
/* Arm callback for testing. */
|
|
ossl_ackm_set_ack_deadline_callback(h.ackm, on_ack_deadline_callback,
|
|
ack_deadline);
|
|
|
|
/*
|
|
* Determine how many packets we are TXing, and therefore how many packet
|
|
* structures we need.
|
|
*/
|
|
for (s = script; s->kind != RX_OPK_END; ++s)
|
|
if (s->kind == RX_OPK_TX)
|
|
num_tx += s->num_pn;
|
|
|
|
/* Allocate packet information structures. */
|
|
txs = OPENSSL_zalloc(sizeof(*txs) * num_tx);
|
|
if (!TEST_ptr(txs))
|
|
goto err;
|
|
|
|
pkts = OPENSSL_zalloc(sizeof(*pkts) * num_tx);
|
|
if (!TEST_ptr(pkts))
|
|
goto err;
|
|
|
|
/* Run script. */
|
|
for (s = script; s->kind != RX_OPK_END; ++s) {
|
|
fake_time = ossl_time_add(fake_time,
|
|
ossl_ticks2time(s->time_advance));
|
|
switch (s->kind) {
|
|
case RX_OPK_PKT:
|
|
for (i = 0; i < s->num_pn; ++i) {
|
|
OSSL_ACKM_RX_PKT pkt = {0};
|
|
|
|
pkt.pkt_num = s->pn + i;
|
|
pkt.time = fake_time;
|
|
pkt.pkt_space = space;
|
|
pkt.is_ack_eliciting = 1;
|
|
|
|
/* The packet should be processable before we feed it. */
|
|
if (!TEST_int_eq(ossl_ackm_is_rx_pn_processable(h.ackm,
|
|
pkt.pkt_num,
|
|
pkt.pkt_space), 1))
|
|
goto err;
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_rx_packet(h.ackm, &pkt), 1))
|
|
goto err;
|
|
}
|
|
|
|
break;
|
|
|
|
case RX_OPK_CHECK_UNPROC:
|
|
case RX_OPK_CHECK_PROC:
|
|
for (i = 0; i < s->num_pn; ++i)
|
|
if (!TEST_int_eq(ossl_ackm_is_rx_pn_processable(h.ackm,
|
|
s->pn + i, space),
|
|
(s->kind == RX_OPK_CHECK_PROC)))
|
|
goto err;
|
|
|
|
break;
|
|
|
|
case RX_OPK_CHECK_STATE:
|
|
if (!TEST_int_eq(ossl_ackm_is_ack_desired(h.ackm, space),
|
|
s->expect_desired))
|
|
goto err;
|
|
|
|
if (!TEST_int_eq(!ossl_time_is_infinite(ossl_ackm_get_ack_deadline(h.ackm, space))
|
|
&& !ossl_time_is_zero(ossl_ackm_get_ack_deadline(h.ackm, space)),
|
|
s->expect_deadline))
|
|
goto err;
|
|
|
|
for (i = 0; i < QUIC_PN_SPACE_NUM; ++i) {
|
|
if (i != (size_t)space
|
|
&& !TEST_true(ossl_time_is_infinite(ossl_ackm_get_ack_deadline(h.ackm, i))))
|
|
goto err;
|
|
|
|
if (!TEST_int_eq(ossl_time_compare(ossl_ackm_get_ack_deadline(h.ackm, i),
|
|
ack_deadline[i]), 0))
|
|
goto err;
|
|
}
|
|
|
|
break;
|
|
|
|
case RX_OPK_CHECK_ACKS:
|
|
ack = ossl_ackm_get_ack_frame(h.ackm, space);
|
|
|
|
/* Should always be able to get an ACK frame. */
|
|
if (!TEST_ptr(ack))
|
|
goto err;
|
|
|
|
if (!TEST_size_t_eq(ack->num_ack_ranges, s->num_ack_ranges))
|
|
goto err;
|
|
|
|
for (i = 0; i < ack->num_ack_ranges; ++i) {
|
|
if (!TEST_uint64_t_eq(ack->ack_ranges[i].start,
|
|
s->ack_ranges[i].start))
|
|
goto err;
|
|
if (!TEST_uint64_t_eq(ack->ack_ranges[i].end,
|
|
s->ack_ranges[i].end))
|
|
goto err;
|
|
}
|
|
|
|
break;
|
|
|
|
case RX_OPK_TX:
|
|
pkts[txi].pkt = tx = &txs[txi];
|
|
|
|
tx->pkt_num = s->pn;
|
|
tx->pkt_space = space;
|
|
tx->num_bytes = 123;
|
|
tx->largest_acked = s->largest_acked;
|
|
tx->is_inflight = 1;
|
|
tx->is_ack_eliciting = 1;
|
|
tx->on_lost = on_lost;
|
|
tx->on_acked = on_acked;
|
|
tx->on_discarded = on_discarded;
|
|
tx->cb_arg = &pkts[txi];
|
|
tx->time = fake_time;
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_tx_packet(h.ackm, tx), 1))
|
|
goto err;
|
|
|
|
++txi;
|
|
break;
|
|
|
|
case RX_OPK_RX_ACK:
|
|
rx_ack.ack_ranges = &rx_ack_range;
|
|
rx_ack.num_ack_ranges = 1;
|
|
|
|
rx_ack_range.start = s->pn;
|
|
rx_ack_range.end = s->pn + s->num_pn - 1;
|
|
|
|
if (!TEST_int_eq(ossl_ackm_on_rx_ack_frame(h.ackm, &rx_ack,
|
|
space, fake_time), 1))
|
|
goto err;
|
|
|
|
break;
|
|
|
|
default:
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
testresult = 1;
|
|
err:
|
|
helper_destroy(&h);
|
|
OPENSSL_free(pkts);
|
|
OPENSSL_free(txs);
|
|
return testresult;
|
|
}
|
|
|
|
/*
|
|
* Driver
|
|
* ******************************************************************
|
|
*/
|
|
static int test_tx_ack_case(int idx)
|
|
{
|
|
int tidx, space;
|
|
|
|
tidx = idx % OSSL_NELEM(tx_ack_cases);
|
|
idx /= OSSL_NELEM(tx_ack_cases);
|
|
|
|
space = idx % QUIC_PN_SPACE_NUM;
|
|
idx /= QUIC_PN_SPACE_NUM;
|
|
|
|
return test_tx_ack_case_actual(tidx, space, idx);
|
|
}
|
|
|
|
static int test_rx_ack(int idx)
|
|
{
|
|
int tidx;
|
|
|
|
tidx = idx % OSSL_NELEM(rx_test_scripts);
|
|
idx /= OSSL_NELEM(rx_test_scripts);
|
|
|
|
return test_rx_ack_actual(tidx, idx);
|
|
}
|
|
|
|
int setup_tests(void)
|
|
{
|
|
ADD_ALL_TESTS(test_tx_ack_case,
|
|
OSSL_NELEM(tx_ack_cases) * MODE_NUM * QUIC_PN_SPACE_NUM);
|
|
ADD_ALL_TESTS(test_tx_ack_time_script, OSSL_NELEM(tx_ack_time_scripts));
|
|
ADD_ALL_TESTS(test_rx_ack, OSSL_NELEM(rx_test_scripts) * QUIC_PN_SPACE_NUM);
|
|
return 1;
|
|
}
|