Skip to main content
  • Home
  • login
  • Browse the archive

    swh mirror partner logo
swh logo
SoftwareHeritage
Software
Heritage
Mirror
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

Revision 098f27f9ef8be2a418f76896ee3c824e8709fcf7 authored by Matt Caswell on 17 October 2023, 13:55:48 UTC, committed by Tomas Mraz on 19 October 2023, 09:54:44 UTC
Ignore ping deadline when calculating tick deadline if we can't send
If the CC TX allowance is zero then we cannot send a PING frame at the
moment, so do not take into account the ping deadline when calculating the
tick deadline in that case.

This avoids the hang found by the fuzzer mentioned in
https://github.com/openssl/openssl/pull/22368#issuecomment-1765131727

Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22410)
1 parent 56e3032
  • Files
  • Changes
  • 55eafe5
  • /
  • test
  • /
  • quic_ackm_test.c
Raw File
Cook and download a directory from the Software Heritage Vault

You have requested the cooking of the directory with identifier None into a standard tar.gz archive.

Are you sure you want to continue ?

Download a directory from the Software Heritage Vault

You have requested the download of the directory with identifier None as a standard tar.gz archive.

Are you sure you want to continue ?

Cook and download a revision from the Software Heritage Vault

You have requested the cooking of the history heading to revision with identifier swh:1:rev:098f27f9ef8be2a418f76896ee3c824e8709fcf7 into a bare git archive.

Are you sure you want to continue ?

Download a revision from the Software Heritage Vault

You have requested the download of the history heading to revision with identifier swh:1:rev:098f27f9ef8be2a418f76896ee3c824e8709fcf7 as a bare git archive.

Are you sure you want to continue ?

Invalid Email !

The provided email is not well-formed.

Download link has expired

The requested archive is no longer available for download from the Software Heritage Vault.

Do you want to cook it again ?

Permalinks

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • revision
  • content
revision badge
swh:1:rev:098f27f9ef8be2a418f76896ee3c824e8709fcf7
content badge Iframe embedding
swh:1:cnt:0f26e9d38a0e8a587dff689f8a9672ff7298db24
quic_ackm_test.c
/*
 * Copyright 2022-2023 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(fake_now, 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 anti_deadlock_handshake,
                             uint32_t anti_deadlock_initial,
                             uint32_t pto_initial,
                             uint32_t pto_handshake,
                             uint32_t pto_app)
{
    if (!TEST_uint_eq(p->anti_deadlock_handshake, anti_deadlock_handshake))
        return 0;
    if (!TEST_uint_eq(p->anti_deadlock_initial, anti_deadlock_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;

        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. */
        probe = *ossl_ackm_get0_probe_request(h.ackm);
        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) {
            probe = *ossl_ackm_get0_probe_request(h.ackm);
            if (i > 0)
                memset(ossl_ackm_get0_probe_request(h.ackm), 0, sizeof(probe));

            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 */
    RX_OPK_SKIP_IF_PN_SPACE  /* skip for a given PN space */
};

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_SKIP_IF_PN_SPACE(pn_space)                            \
    {                                                               \
      RX_OPK_SKIP_IF_PN_SPACE, 0, (pn_space), 0,                    \
      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) (1-RTT) */
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[] = {
    /*
     * We skip this for INITIAL/HANDSHAKE and use a separate version
     * (rx_script_4) for those spaces as those spaces should not delay ACK
     * generation, so a different RX_OP_CHECK_STATE test is needed.
     */
    RX_OP_SKIP_IF_PN_SPACE(QUIC_PN_SPACE_INITIAL)
    RX_OP_SKIP_IF_PN_SPACE(QUIC_PN_SPACE_HANDSHAKE)

    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
};

/*
 * RX 4. Simple Test with ACK Not Yet Desired (Packet Threshold)
 * (Initial/Handshake)
 */
static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_4a[] = {
    { 0, 1 }
};

static const struct rx_test_op rx_script_4[] = {
    /* The application PN space is tested in rx_script_2. */
    RX_OP_SKIP_IF_PN_SPACE(QUIC_PN_SPACE_APP)

    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 cause ACK-desired state because we are
     * INITIAL/HANDSHAKE (RFC 9000 s. 13.2.1)
     */
    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, 1, 1)
    RX_OP_CHECK_ACKS    (0, rx_ack_ranges_4a)
    RX_OP_CHECK_STATE   (0, 0, 0)   /* desired state should have been cleared */

    /* At this point we would generate e.g. a packet with an ACK. */
    RX_OP_TX            (0, 0, 1)   /* ACKs all */
    RX_OP_CHECK_ACKS    (0, rx_ack_ranges_4a) /* 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
};

static const struct rx_test_op *const rx_test_scripts[] = {
    rx_script_1,
    rx_script_2,
    rx_script_3,
    rx_script_4
};

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];
    size_t opn = 0;

    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, ++opn) {
        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;

        case RX_OPK_SKIP_IF_PN_SPACE:
            if (space == (int)s->pn) {
                testresult = 1;
                goto err;
            }

            break;

        default:
            goto err;
        }
    }

    testresult = 1;
err:
    if (!testresult)
        TEST_error("error in ACKM RX script %d, op %zu", tidx + 1, opn + 1);

    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;
}
The diff you're trying to view is too large. Only the first 1000 changed files have been loaded.
Showing with 0 additions and 0 deletions (0 / 0 diffs computed)
swh spinner

Computing file changes ...

ENEA — Copyright (C), ENEA. License: GNU AGPLv3+.
Legal notes  ::  JavaScript license information ::  Web API

back to top