/* Copyright (c) 2017 - 2026 LiteSpeed Technologies Inc.  See LICENSE. */
/* Test both generation and parsing of IETF ACK frames */

#include <assert.h>
#include <string.h>

#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_sizes.h"
#include "lsquic_parse.h"
#include "lsquic_trans_params.h"

struct test
{
    int                 lineno;
    int                 skip_gen;

    struct ack_info     acki;

    size_t              encoded_sz;
    unsigned char       encoded[0x1000];
};

static const lsquic_time_t now = 0x12345676890;

static const struct test tests[] =
{
    {
        .lineno         = __LINE__,
        .acki           =
        {
            .n_ranges   = 2,
            .lack_delta = 0xFEDCB0,
            .ranges     =
            {
                [0] = { 7, 7, },
                [1] = { 0, 0, },
            },
        },
        .encoded    =
        {
        /* Type */              0x02,
        /* Largest acked */     0x07,
        /* ACK delay */         0x80, 0x1F, 0xDB, 0x96,
        /* Addl block count */  0x01,
        /* First ACK block */   0x00,
        /* Gap */               0x05,
        /* ACK block */         0x00,
        },
        .encoded_sz = 10,
    },

    {
        .lineno         = __LINE__,
        .acki           =
        {
            .n_ranges   = 3,
            .lack_delta = 0xFEDCB0,
            .ranges     =
            {
                [0] = { 10, 10, },
                [1] = { 7, 7, },
                [2] = { 0, 0, },
            },
        },
        .encoded    =
        {
        /* Type */              0x02,
        /* Largest acked */     10,
        /* ACK delay */         0x80, 0x1F, 0xDB, 0x96,
        /* Addl block count */  2,
        /* First ACK block */   0x00,
        /* Gap */               0x01,
        /* ACK block */         0x00,
        /* Gap */               0x05,
        /* ACK block */         0x00,
        },
        .encoded_sz = 12,
    },

    {
        .lineno         = __LINE__,
        .acki           =
        {
            .flags      = AI_ECN,
            .n_ranges   = 3,
            .lack_delta = 0xFEDCB0,
            .ecn_counts = { 0, 0x010203, 1, 0x49, },
            .ranges     =
            {
                [0] = { 10, 10, },
                [1] = { 7, 7, },
                [2] = { 0, 0, },
            },
        },
        .encoded    =
        {
        /* Type */              0x03,
        /* Largest acked */     10,
        /* ACK delay */         0x80, 0x1F, 0xDB, 0x96,
        /* Addl block count */  2,
        /* First ACK block */   0x00,
        /* Gap */               0x01,
        /* ACK block */         0x00,
        /* Gap */               0x05,
        /* ACK block */         0x00,
        /* ECT(1) count */      0x01,
        /* ECT(0) count */      0x80, 0x01, 0x02, 0x03,
        /* ECN-CE count */      0x40, 0x49,
        },
        .encoded_sz = 19,
    },

};

struct rechist
{
    const struct ack_info   *acki;
    unsigned                 next_range;
};


static const struct lsquic_packno_range *
rechist_next (void *ctx)
{
    struct rechist *rechist = ctx;
    if (rechist->next_range < rechist->acki->n_ranges)
        return rechist->acki->ranges + rechist->next_range++;
    else
        return NULL;
}

static const struct lsquic_packno_range *
rechist_first (void *ctx)
{
    struct rechist *rechist = ctx;
    rechist->next_range = 0;
    return rechist_next(rechist);
}

static lsquic_time_t
rechist_largest_recv (void *ctx)
{
    struct rechist *rechist = ctx;
    return now - rechist->acki->lack_delta;
}


static void
compare_ackis (const struct ack_info *exp, const struct ack_info *got)
{
    unsigned i;

    assert(exp->flags == got->flags);
    assert(exp->n_ranges == got->n_ranges);
    assert(exp->lack_delta == got->lack_delta);

    for (i = 0; i < exp->n_ranges; ++i)
    {
        assert(exp->ranges[i].high == got->ranges[i].high);
        assert(exp->ranges[i].low == got->ranges[i].low);
    }

    if (exp->flags & AI_ECN)
        for (i = 1; i <= 3; ++i)
            assert(exp->ecn_counts[i] == got->ecn_counts[i]);
}


static void
run_test (const struct test *test)
{
    int len, has_missing;
    lsquic_packno_t largest_received;
    const struct parse_funcs *pf;
    struct rechist rechist;
    struct ack_info acki;
    size_t sz;
    unsigned char buf[0x1000];

    pf = select_pf_by_ver(LSQVER_I001);
    if (!test->skip_gen)
    {
        rechist.acki = &test->acki;
        len = pf->pf_gen_ack_frame(buf, sizeof(buf), rechist_first, rechist_next,
            rechist_largest_recv, &rechist, now, &has_missing, &largest_received,
            test->acki.flags & AI_ECN ? rechist.acki->ecn_counts : NULL);
        assert(len > 0);
        assert(largest_received == largest_acked(&test->acki));
        assert((size_t) len == test->encoded_sz);
        assert(0 == memcmp(test->encoded, buf, test->encoded_sz));
    }

    /* Test that shorter buffers cannot get parsed */
    for (sz = 1; sz < test->encoded_sz; ++sz)
    {
        len = pf->pf_parse_ack_frame(test->encoded, sz, &acki,
                                                        TP_DEF_ACK_DELAY_EXP);
        assert(len < 0);
    }

    len = pf->pf_parse_ack_frame(test->encoded, sizeof(test->encoded), &acki,
                                                        TP_DEF_ACK_DELAY_EXP);
    assert(len > 0);
    assert((size_t) len == test->encoded_sz);
    compare_ackis(&test->acki, &acki);
}


static void
test_ack_block_count_expansion_regression (void)
{
    const struct parse_funcs *pf;
    struct ack_info acki;
    struct rechist rechist;
    lsquic_packno_t largest_received;
    unsigned char buf[140];
    size_t outbuf_sz;
    unsigned i;
    int len, has_missing;

    pf = select_pf_by_ver(LSQVER_I001);
    memset(&acki, 0, sizeof(acki));
    acki.n_ranges = 65;
    for (i = 0; i < acki.n_ranges; ++i)
        acki.ranges[i].high = acki.ranges[i].low = 128 - i * 2;

    memset(buf, 0xA5, sizeof(buf));
    outbuf_sz = 134;
    rechist.acki = &acki;
    len = pf->pf_gen_ack_frame(buf, outbuf_sz, rechist_first, rechist_next,
        rechist_largest_recv, &rechist, now, &has_missing, &largest_received,
        NULL);
    assert(len > 0);
    assert((size_t) len <= outbuf_sz);
    assert(buf[outbuf_sz] == 0xA5);
    assert(largest_received == acki.ranges[0].high);
    assert(has_missing);
}


int
main (void)
{
    const struct test *test;

    for (test = tests; test < tests + sizeof(tests) / sizeof(tests[0]); ++test)
        run_test(test);
    test_ack_block_count_expansion_regression();

    return 0;
}
