Release 2.22.0

- [FEATURE] Extensible HTTP Priorities (HTTP/3 only).
- [FEATURE] Add conn context to packet-out memory interface (PR #175).
- [BUGFIX] gQUIC proof generation: allocate buffer big enough for
  signature (issue #173).
- [BUGFIX] Make library thread-safe: drop use of global variables
  (issue #133, issue #167).
- [BUGFIX] Deactivate only *recent* HQ frame, not any HQ frame.
- [BUGFIX] gQUIC server: associate compressed cert with SSL_CTX,
  instead of keeping them in a separate hash, potentially leading
  to mismatches.
- [BUGFIX] Stream data discard infinite loop: break on FIN.
- cmake: add install target via -DCMAKE_INSTALL_PREFIX (PR #171).
- Support randomized packet number to begin a connection.
- Mini and full IETF connection size optimization.
- http_client: specify HTTP priorities based on stream conditions.
This commit is contained in:
Dmitri Tikhonov 2020-10-07 09:41:26 -04:00
parent cb1e8c1022
commit fbc6cc0413
55 changed files with 6557 additions and 391 deletions

View file

@ -46,6 +46,7 @@ SET(TESTS
goaway_gquic_be
h3_framing
hkdf
hpi
lsquic_hash
packet_out
packet_resize
@ -129,3 +130,6 @@ ADD_TEST(minmax test_minmax)
ADD_EXECUTABLE(test_rechist test_rechist.c ../src/liblsquic/lsquic_rechist.c)
ADD_TEST(rechist test_rechist)
ADD_EXECUTABLE(test_trechist test_trechist.c ../src/liblsquic/lsquic_trechist.c)
ADD_TEST(trechist test_trechist)

View file

@ -1151,6 +1151,17 @@ fuzz_guided_pwritev_testing (const char *input)
}
static unsigned
count_hq_frames (const struct lsquic_stream *stream)
{
const struct stream_hq_frame *shf;
unsigned n_frames = 0;
STAILQ_FOREACH(shf, &stream->sm_hq_frames, shf_next)
++n_frames;
return n_frames;
}
static void
test_frame_header_split (unsigned n_packets, unsigned extra_sz,
int add_one_more)
@ -1162,6 +1173,7 @@ test_frame_header_split (unsigned n_packets, unsigned extra_sz,
unsigned char *buf_in, *buf_out;
const unsigned wsize = 70;
const size_t buf_in_sz = wsize, buf_out_sz = 0x500000;
unsigned n_frames;
struct lsxpack_header header = { XHDR(":method", "GET") };
struct lsquic_http_headers headers = { 1, &header, };
@ -1210,7 +1222,12 @@ test_frame_header_split (unsigned n_packets, unsigned extra_sz,
const ssize_t w = lsquic_stream_write(stream, buf_in, buf_in_sz);
assert(w >= 0 && (size_t) w == buf_in_sz);
n_frames = count_hq_frames(stream);
assert(n_frames == 1 + (w > 0));
lsquic_stream_flush(stream);
n_frames = count_hq_frames(stream);
assert(n_frames == !!stream->sm_n_buffered);
if (add_one_more)
{

View file

@ -18,6 +18,11 @@ struct test
{
int lineno;
enum {
TEST_NO_FLAGS = 0,
TEST_NUL_OUT_LEST_FULL = 1 << 0,
} flags;
unsigned char input[0x100];
size_t input_sz;
@ -30,6 +35,7 @@ static const struct test tests[] =
{
{
__LINE__,
TEST_NO_FLAGS,
{
0x03,
0x04,
@ -42,6 +48,7 @@ static const struct test tests[] =
{
__LINE__,
TEST_NO_FLAGS,
{
HQFT_MAX_PUSH_ID,
0x02,
@ -54,6 +61,7 @@ static const struct test tests[] =
{
__LINE__,
TEST_NO_FLAGS,
{
HQFT_SETTINGS,
0x00,
@ -65,6 +73,7 @@ static const struct test tests[] =
{ /* Frame contents do not match frame length */
__LINE__,
TEST_NO_FLAGS,
{
HQFT_MAX_PUSH_ID,
0x03,
@ -77,6 +86,7 @@ static const struct test tests[] =
{
__LINE__,
TEST_NO_FLAGS,
{
HQFT_SETTINGS,
13,
@ -93,6 +103,58 @@ static const struct test tests[] =
,
},
{
__LINE__,
TEST_NO_FLAGS,
{
0x80, 0x0F, 0x07, 0x00, /* HQFT_PRIORITY_UPDATE_STREAM */
7,
0x52, 0x34,
0x41, 0x42, 0x43, 0x44, 0x45, /* ABCDE */
HQFT_MAX_PUSH_ID,
0x02,
0x41, 0x23,
},
16,
0,
"on_priority_update: stream=0x1234, [ABCDE]\n"
"on_max_push_id: 291\n"
,
},
{
__LINE__,
TEST_NO_FLAGS,
{
0x80, 0x0F, 0x07, 0x01, /* HQFT_PRIORITY_UPDATE_PUSH */
6,
0x08,
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
},
11,
0,
"on_priority_update: push=0x8, [PQRST]\n"
,
},
{
__LINE__,
TEST_NUL_OUT_LEST_FULL,
{
0x80, 0x0F, 0x07, 0x01, /* HQFT_PRIORITY_UPDATE_PUSH */
21,
0x08,
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
0x50, 0x51, 0x52, 0x53, 0x54, /* PQRST */
},
26,
0,
"on_priority_update: push=0x8, [PQRSTPQRSTPQRSTPQRST]\n"
,
},
};
@ -132,6 +194,24 @@ on_unexpected_frame (void *ctx, uint64_t frame_type)
fprintf(ctx, "%s: %"PRIu64"\n", __func__, frame_type);
}
static void
on_priority_update (void *ctx, enum hq_frame_type frame_type,
/* PFV: Priority Field Value */
uint64_t id, const char *pfv, size_t pfv_sz)
{
const char *type;
switch (frame_type)
{
case HQFT_PRIORITY_UPDATE_STREAM: type = "stream"; break;
case HQFT_PRIORITY_UPDATE_PUSH: type = "push"; break;
default: assert(0); return;
}
fprintf(ctx, "%s: %s=0x%"PRIX64", [%.*s]\n", __func__, type, id,
(int) pfv_sz, pfv);
}
static const struct hcsi_callbacks callbacks =
{
.on_cancel_push = on_cancel_push,
@ -140,6 +220,7 @@ static const struct hcsi_callbacks callbacks =
.on_setting = on_setting,
.on_goaway = on_goaway,
.on_unexpected_frame = on_unexpected_frame,
.on_priority_update = on_priority_update,
};
@ -167,7 +248,7 @@ run_test (const struct test *test)
struct lsquic_conn lconn = LSCONN_INITIALIZER_CIDLEN(lconn, 0);
lconn.cn_if = &conn_iface;
for (read_sz = 1; read_sz < test->input_sz; ++read_sz)
for (read_sz = 1; read_sz <= test->input_sz; ++read_sz)
{
out_f = open_memstream(&output, &out_sz);
lsquic_hcsi_reader_init(&reader, &lconn, &callbacks, out_f);
@ -188,7 +269,11 @@ run_test (const struct test *test)
assert(s == test->retval);
fclose(out_f);
assert(0 == strcmp(test->output, output));
if (test->retval == 0 && read_sz < test->input_sz
&& (test->flags & TEST_NUL_OUT_LEST_FULL))
assert(0 == strcmp(output, ""));
else
assert(0 == strcmp(test->output, output));
free(output);
}
}
@ -204,7 +289,7 @@ main (void)
memset(&coalesced_test, 0, sizeof(coalesced_test));
for (test = tests; test < tests + sizeof(tests) / sizeof(tests[0]); ++test)
if (test->retval == 0)
if (test->retval == 0 && !(test->flags & TEST_NUL_OUT_LEST_FULL))
{
memcpy(coalesced_test.input + coalesced_test.input_sz,
test->input, test->input_sz);

351
tests/test_hpi.c Normal file
View file

@ -0,0 +1,351 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_in.h"
#include "lsquic_conn_flow.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_stream.h"
#include "lsquic_types.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_flow.h"
#include "lsquic_conn_public.h"
#include "lsquic_mm.h"
#include "lsquic_min_heap.h"
#include "lsquic_hpi.h"
#include "lsquic_logger.h"
/*
* DSL:
*
* S\d+:\d+:\d+ Create stream and insert it into list. The three numbers
* are stream ID, priority, and incremental boolean flag.
* Priority can be negative, which indicates a critical
* stream. Otherwise, the priorirty should be in the range
* [0, MAX_HTTP_PRIORITY]. The incremental flag is ignored
* for critical streams.
*
* F\d+ Use filter identified by the number. For "F" to take
* effect, it should be called before "I".
*
* I Initialize the iterator.
*
* D[hH] Call drop high (h) or drop non-high (H).
*
* N\d+ Call "next" and verify that the stream ID is this number.
* If "next" returns NULL, the test fails.
*
*/
static const struct test_spec {
int lineno;
const char *prog;
} test_specs[] = {
{ __LINE__,
"S0:3:0;"
"I;"
"N0;"
},
{ __LINE__,
/* Insert non-incremental streams with same priority, check that
* they come back out in the order of stream IDs.
*/
"S1:3:0;" "S0:3:0;" "S2:3:0;"
"I;"
"N0;" "N1;" "N2;"
},
{ __LINE__,
/* Insert incremental streams with same priority, check that they
* come back out in the same order.
*/
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"I;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Insert incremental streams with same priority, filter out out odd
* IDs, check that they come back out in the same order and without
* the odd stream ID"
*/
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"F;"
"I;"
"N0;" "N2;"
},
{ __LINE__,
/* Insert incremental and non-incremental streams with same priority.
* Check that non-incrementals are returned first.
*/
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"S6:3:0;" "S10:3:0;" "S3:3:0;"
"I;"
"N3;N6;N10;"
"N1;N0;N2;"
},
{ __LINE__,
/* Drop high with same priority: nothing should be dropped */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"I;"
"Dh;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Drop non-high with same priority: nothing should be dropped */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"I;"
"DH;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Drop high with same priority: drop non-incrementals */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"S6:3:0;" "S10:3:0;" "S3:3:0;"
"I;"
"Dh;"
"N1;" "N0;" "N2;"
},
{ __LINE__,
/* Drop non-high with same priority: drop incrementals */
"S1:3:1;" "S0:3:1;" "S2:3:1;"
"S6:3:0;" "S10:3:0;" "S3:3:0;"
"I;"
"DH;"
"N3;N6;N10;"
},
{ __LINE__,
/* Insert streams with different priorities */
"S1:1:1;" "S2:2:1;" "S3:3:1;"
"S6:6:0;" "S5:5:0;" "S4:4:0;"
"I;"
"N1;N2;N3;N4;N5;N6;"
},
{ __LINE__,
/* Insert regular and critical streams */
"S1:1:1;" "S2:2:1;" "S3333:-1:1;"
"S6:6:0;" "S2222:-1:0;" "S4:4:0;"
"I;"
"N3333;N2222;N1;N2;N4;N6;"
},
{ __LINE__,
/* Insert regular and critical streams; drop high */
"S1:1:1;" "S2:2:1;" "S3333:-1:1;"
"S6:6:0;" "S2222:-1:0;" "S4:4:0;"
"I;"
"Dh;"
"N1;N2;N4;N6;"
},
{ __LINE__,
/* Insert regular and critical streams; drop non-high */
"S1:1:1;" "S2:2:1;" "S3333:-1:1;"
"S6:6:0;" "S2222:-1:0;" "S4:4:0;"
"I;"
"DH;"
"N3333;N2222;"
},
{ __LINE__,
/* Insert streams with different priorities, non-incremental,
* several per bucket.
*/
"S1:1:0;" "S4:2:0;" "S3:2:0;"
"S6:1:0;" "S5:1:0;" "S2:2:0;"
"I;"
"N1;N5;N6;N2;N3;N4;"
},
};
/* Sharing the same HPI tests safety of reusing the same iterator object
* (no need to deinitialize it).
*/
static struct http_prio_iter hpi;
static struct lsquic_conn lconn = LSCONN_INITIALIZER_CIDLEN(lconn, 0);
static struct lsquic_conn_public conn_pub = { .lconn = &lconn, };
static struct lsquic_stream *
new_stream (lsquic_stream_id_t stream_id, int priority, int incr)
{
struct lsquic_stream *stream = calloc(1, sizeof(*stream));
stream->id = stream_id;
if (priority >= 0)
{
stream->sm_priority = priority;
if (incr)
stream->sm_bflags |= SMBF_INCREMENTAL;
}
else
stream->sm_bflags |= SMBF_CRITICAL;
/* Critical streams are never incremental */
return stream;
}
#define MAGIC 0x12312312U
struct my_filter_ctx
{
unsigned magic;
};
static int
filter_out_odd_stream_ids (void *ctx, struct lsquic_stream *stream)
{
struct my_filter_ctx *fctx = ctx;
assert(fctx->magic == MAGIC);
return 0 == (stream->id & 1);
}
static int (*const filters[])(void *, struct lsquic_stream *) = {
filter_out_odd_stream_ids,
};
/* Just pick one (as long as it's not next_prio_stream) */
#define next_field next_write_stream
static void
run_test (const struct test_spec *spec)
{
struct lsquic_streams_tailq streams;
struct lsquic_stream *stream;
lsquic_stream_id_t stream_id;
long incr, priority, tmp;
const char *pc;
char cmd;
char name[20];
struct my_filter_ctx filter_ctx = { MAGIC };
int (*filter_cb)(void *, struct lsquic_stream *) = NULL;
int first_called = 0;
struct lsquic_mm mm;
lsquic_mm_init(&mm);
conn_pub.mm = &mm;
TAILQ_INIT(&streams);
snprintf(name, sizeof(name), "line-%d", spec->lineno);
for (pc = spec->prog; *pc; ++pc)
{
cmd = *pc++;
switch (cmd)
{
case 'S':
stream_id = strtol(pc, (char **) &pc, 10);
assert(':' == *pc);
priority = strtol(pc + 1, (char **) &pc, 10);
assert(':' == *pc);
incr = strtol(pc + 1, (char **) &pc, 10);
stream = new_stream(stream_id, priority, incr);
TAILQ_INSERT_TAIL(&streams, stream, next_field);
break;
case 'I':
lsquic_hpi_init(&hpi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_field),
&conn_pub, name, filter_cb, &filter_ctx);
break;
case 'N':
stream_id = strtol(pc, (char **) &pc, 10);
if (first_called)
stream = lsquic_hpi_next(&hpi);
else
{
stream = lsquic_hpi_first(&hpi);
first_called = 1;
}
assert(stream);
assert(stream->id == stream_id);
break;
case 'F':
tmp = strtol(pc, (char **) &pc, 10);
assert(tmp >= 0
&& (size_t) tmp < sizeof(filters) / sizeof(filters[0]));
filter_cb = filters[tmp];
break;
case 'D':
switch (*pc++)
{
case 'h':
lsquic_hpi_drop_high(&hpi);
break;
case 'H':
lsquic_hpi_drop_non_high(&hpi);
break;
default:
assert(0);
break;
}
break;
default:
assert(0);
}
assert(*pc == ';');
}
lsquic_hpi_cleanup(&hpi);
while (stream = TAILQ_FIRST(&streams), stream != NULL)
{
TAILQ_REMOVE(&streams, stream, next_field);
free(stream);
}
lsquic_mm_cleanup(&mm);
}
int
main (int argc, char **argv)
{
unsigned n;
lsquic_log_to_fstream(stderr, LLTS_NONE);
lsq_log_levels[LSQLM_HPI] = LSQ_LOG_DEBUG;
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
lsquic_hpi_set_heap_test(LSQUIC_HPI_HEAP_TEST_STACK_OK);
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
lsquic_hpi_set_heap_test(LSQUIC_HPI_HEAP_TEST_4K_OK);
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
lsquic_hpi_set_heap_test(0);
for (n = 0; n < sizeof(test_specs) / sizeof(test_specs[0]); ++n)
run_test(&test_specs[n]);
return 0;
}

View file

@ -22,6 +22,8 @@
#include "lsquic_conn.h"
#include "lsquic_stream.h"
#include "lsquic_types.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_spi.h"
#include "lsquic_logger.h"
@ -33,6 +35,8 @@ static struct stream_prio_iter spi;
static struct lsquic_conn lconn = LSCONN_INITIALIZER_CIDLEN(lconn, 0);
static struct lsquic_conn_public conn_pub = { .lconn = &lconn, };
static lsquic_stream_t *
new_stream (unsigned priority)
@ -73,7 +77,7 @@ test_same_priority (unsigned priority)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
stream = lsquic_spi_first(&spi);
assert(stream == stream_arr[0]);
@ -89,7 +93,7 @@ test_same_priority (unsigned priority)
/* Test reinitialization: */
lsquic_spi_init(&spi, stream_arr[0], stream_arr[1],
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
stream = lsquic_spi_first(&spi);
assert(stream == stream_arr[0]);
stream = lsquic_spi_next(&spi);
@ -121,7 +125,7 @@ test_different_priorities (int *priority)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
for (prev_prio = -1, count = 0, stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi), ++count)
{
@ -214,7 +218,7 @@ test_drop (const struct drop_test *test)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_write_stream),
&lconn, __func__, NULL, NULL);
&conn_pub, __func__, NULL, NULL);
if (drop_high)
lsquic_spi_drop_high(&spi);
@ -275,7 +279,7 @@ test_different_priorities_filter_odd (int *priority)
lsquic_spi_init(&spi, TAILQ_FIRST(&streams),
TAILQ_LAST(&streams, lsquic_streams_tailq),
(uintptr_t) &TAILQ_NEXT((lsquic_stream_t *) NULL, next_send_stream),
&lconn, __func__, filter_out_odd_priorities, &my_filter_ctx);
&conn_pub, __func__, filter_out_odd_priorities, &my_filter_ctx);
for (prev_prio = -1, count = 0, stream = lsquic_spi_first(&spi); stream;
stream = lsquic_spi_next(&spi), ++count)

362
tests/test_trechist.c Normal file
View file

@ -0,0 +1,362 @@
/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/* Tests based on rechist tests */
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include "vc_compat.h"
#endif
#include "lsquic_int_types.h"
#include "lsquic_trechist.h"
static void
test_clone (trechist_mask_t src_mask, struct trechist_elem *src_elems)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *ranges[2];
struct trechist_iter iters[2];
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
lsquic_trechist_iter(&iters[0], src_mask, src_elems);
s = lsquic_trechist_copy_ranges(&hist_mask, hist_elems, &iters[0],
lsquic_trechist_first, lsquic_trechist_next);
assert(s == 0);
lsquic_trechist_iter(&iters[0], src_mask, src_elems);
lsquic_trechist_iter(&iters[1], hist_mask, hist_elems);
for (ranges[0] = lsquic_trechist_first(&iters[0]),
ranges[1] = lsquic_trechist_first(&iters[1]);
ranges[0] && ranges[1];
ranges[0] = lsquic_trechist_next(&iters[0]),
ranges[1] = lsquic_trechist_next(&iters[1]))
{
assert(ranges[0]->low == ranges[1]->low);
assert(ranges[0]->high == ranges[1]->high);
}
assert(!ranges[0] && !ranges[1]);
free(hist_elems);
}
static void
test4 (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *range;
struct trechist_iter iter;
lsquic_packno_t packno;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
test_clone(hist_mask, hist_elems);
for (packno = 11917; packno <= 11941; ++packno)
lsquic_trechist_insert(&hist_mask, hist_elems, packno);
for (packno = 11946; packno <= 11994; ++packno)
lsquic_trechist_insert(&hist_mask, hist_elems, packno);
test_clone(hist_mask, hist_elems);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 11994);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(!range);
lsquic_trechist_insert(&hist_mask, hist_elems, 11995);
lsquic_trechist_insert(&hist_mask, hist_elems, 11996);
test_clone(hist_mask, hist_elems);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(!range);
test_clone(hist_mask, hist_elems);
lsquic_trechist_insert(&hist_mask, hist_elems, 11912);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11912);
assert(range->low == 11912);
range = lsquic_trechist_next(&iter);
assert(!range);
for (packno = 12169; packno <= 12193; ++packno)
lsquic_trechist_insert(&hist_mask, hist_elems, packno);
test_clone(hist_mask, hist_elems);
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(range);
assert(range->high == 12193);
assert(range->low == 12169);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_trechist_next(&iter);
assert(range);
assert(range->high == 11912);
assert(range->low == 11912);
range = lsquic_trechist_next(&iter);
assert(!range);
test_clone(hist_mask, hist_elems);
free(hist_elems);
}
static void
rechist2str (trechist_mask_t hist_mask, const struct trechist_elem *hist_elems,
char *buf, size_t bufsz)
{
const struct lsquic_packno_range *range;
struct trechist_iter iter;
size_t off;
int n;
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
for (off = 0, range = lsquic_trechist_first(&iter);
range && off < bufsz;
off += n, range = lsquic_trechist_next(&iter))
{
n = snprintf(buf + off, bufsz - off, "[%"PRIu64"-%"PRIu64"]",
range->high, range->low);
if (n < 0 || (size_t) n >= bufsz - off)
break;
}
}
static void
test5 (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
char buf[100];
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
lsquic_trechist_insert(&hist_mask, hist_elems, 1);
/* Packet 2 omitted because it could not be decrypted */
lsquic_trechist_insert(&hist_mask, hist_elems, 3);
lsquic_trechist_insert(&hist_mask, hist_elems, 12);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][3-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 4);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][4-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 10);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][4-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 6);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][6-6][4-3][1-1]"));
lsquic_trechist_insert(&hist_mask, hist_elems, 7);
lsquic_trechist_insert(&hist_mask, hist_elems, 8);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][8-6][4-3][1-1]"));
test_clone(hist_mask, hist_elems);
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 0)));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 9)));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 20)));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 4));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 1));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 7));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 8));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 6));
lsquic_trechist_insert(&hist_mask, hist_elems, 9);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-6][4-3][1-1]"));
test_clone(hist_mask, hist_elems);
lsquic_trechist_insert(&hist_mask, hist_elems, 5);
lsquic_trechist_insert(&hist_mask, hist_elems, 11);
rechist2str(hist_mask, hist_elems, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-3][1-1]"));
free(hist_elems);
}
static void
basic_test (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
const struct lsquic_packno_range *range;
struct trechist_iter iter;
unsigned i;
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(!range);
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1);
assert(("inserting packet number one is successful", 0 == s));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1);
assert(("inserting packet number one again results in duplicate error",
s == 1));
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 1));
assert(("first range high value checks out", range->high == 1));
range = lsquic_trechist_next(&iter);
assert(!range);
assert(("second range does not exist", !range));
for (i = 3; i <= 5; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(("inserting packet", s == 0));
}
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 3));
assert(("first range high value checks out", range->high == 5));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 7)));
assert(!(lsquic_trechist_contains(hist_mask, hist_elems, 2)));
assert(lsquic_trechist_contains(hist_mask, hist_elems, 4));
range = lsquic_trechist_next(&iter);
assert(("second range returned correctly", range));
assert(("second range low value checks out", range->low == 1));
assert(("second range high value checks out", range->high == 1));
range = lsquic_trechist_next(&iter);
assert(("third range does not exist", !range));
assert(5 == lsquic_trechist_max(hist_mask, hist_elems));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 10);
assert(("inserting packet", s == 0));
assert(10 == lsquic_trechist_max(hist_mask, hist_elems));
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 10));
assert(("first range high value checks out", range->high == 10));
test_clone(hist_mask, hist_elems);
s = lsquic_trechist_insert(&hist_mask, hist_elems, 8);
assert(("inserting packet", s == 0));
s = lsquic_trechist_insert(&hist_mask, hist_elems, 9);
assert(("inserting packet", s == 0));
/* Check merge */
lsquic_trechist_iter(&iter, hist_mask, hist_elems);
range = lsquic_trechist_first(&iter);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 8));
assert(("first range high value checks out", range->high == 10));
free(hist_elems);
}
static void
test_limits (void)
{
trechist_mask_t hist_mask;
struct trechist_elem *hist_elems;
unsigned i;
int s;
hist_elems = malloc(sizeof(hist_elems[0]) * TRECHIST_MAX_RANGES);
hist_mask = 0;
for (i = 1; i <= UCHAR_MAX; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(s == 0);
}
s = lsquic_trechist_insert(&hist_mask, hist_elems, i);
assert(s == -1); /* Overflow */
for (i = 0; i < TRECHIST_MAX_RANGES - 1; ++i)
{
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1000 + 2 * i);
assert(s == 0);
}
s = lsquic_trechist_insert(&hist_mask, hist_elems, 1000 + 2 * i);
assert(s == -1); /* Out of ranges */
free(hist_elems);
}
int
main (void)
{
basic_test();
test4();
test5();
test_limits();
return 0;
}