openssl/test/quic_ackm_test.c
Hugo Landau fa4e92a70a QUIC ACK Manager, Statistics Manager and Congestion Control API
This is the initial implementation of the ACK Manager for OpenSSL's QUIC
support, with supporting design documentation and tests.

Because the ACK Manager also depends on the Statistics Manager, it is
also implemented here. The Statistics Manager is quite simple, so this
does not amount to a large amount of extra code.

Because the ACK Manager depends on a congestion controller, it adds a
no-op congestion controller, which uses the previously workshopped
congestion control API.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18676)
2022-08-24 14:05:46 +01:00

1040 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 (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_true(deadline == loss_detection_deadline))
goto err;
/* We should have a PTO deadline. */
if (!TEST_true(deadline > fake_time))
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 = deadline + 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;
OSSL_TIME 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 += 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 += 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;
OSSL_TIME 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] = {
OSSL_TIME_INFINITY, OSSL_TIME_INFINITY, OSSL_TIME_INFINITY
};
/* 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 += 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_infinity(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_ackm_get_ack_deadline(h.ackm, i)
== OSSL_TIME_INFINITY))
goto err;
if (!TEST_true(ossl_ackm_get_ack_deadline(h.ackm, i)
== ack_deadline[i]))
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;
}