litespeed-quic/tests/test_rechist.c
Paul Jakma 188c055a9c
Test case and fix for lsquic rechist list problem (#410)
* Test case and debug for lsquic rechist list problem

Add:
- test case
- debug rechist print function (not called)
Modify:
- rechist_test_sanity to be more robust

Test case:
Added test case which produces a corrupt rechist.  The rechist
grows and then is emptied, then a rechist is produced that is nominally
empty (rh_n_used == 0) but has a circular list in rh_elems.  E.g.  (using
debug print at the problem site):

lsquic_rechist_received: insert before done
0x7ffe65ce75a0: cutoff 9 l. acked 12087061905995 masks 1 alloced 4 used 1 max ranges 0 head 0
  (0)[9-9] (0)[9-9] (0)[9-9] (0)[9-9] (0)[9-9] (0)[9-9] (0)[9-9]
test_rechist: /tmp/lsquic/src/liblsquic/lsquic_rechist.c:272: rechist_test_sanity: Assertion `rechist->rh_n_used == n_elems' failed.

Debug print:
Added 'rechist_dump' for ease of debugging, marked as unused.
Useful for calling from gdb or adding to code temporarily.

rechist_test_sanity:
Make it more robust to bad rechists by limiting
how many list items it will follow, to prevent infinite loops or walking off
the end of the list.

* rechist: Fix bug in lsquic_rechist_received causing circular list to be created

Fix bug in lsquic_rechist_received demonstrated by the test6 test-case.

When the list is empty, i.e.  rh_n_used == 0, the 'first_elem' path should
be used.  However, the test for this was using rh_n_alloced, which would
cause the code to continue on incorrectly.  One possibility is that it goes
to insert_before and creates a circular list in the rechist with the head at
index 0 having a next of 0.

This causes history to be lost I think.

The list at this point is still 'empty', however a following call could go
to the insert_before path and then insert an elem pointing to the circular
list, and bump up rh_n_used. Now you have a circular list.

Weird things can happen now.  Notably, ACK generation will exhaust the
packet buffer and generate an error, and so cause connections to be
prematurely aborted.

Fix lsquic_rechist_received to use rh_n_used. Also modify
rechist_alloc_elem to NULL the next pointer for robustness.

It would be nice for rechist_free_elem to also invalidate the elem, but it
is meant for relinking elems in the list and should preserve the next
pointer.
2022-08-26 09:15:35 -04:00

447 lines
13 KiB
C

/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include "vc_compat.h"
#endif
#include "lsquic_int_types.h"
#include "lsquic_rechist.h"
#include "lsquic_util.h"
static void
test4 (void)
{
lsquic_rechist_t rechist;
const struct lsquic_packno_range *range;
lsquic_packno_t packno;
lsquic_rechist_init(&rechist, 0, 0);
for (packno = 11917; packno <= 11941; ++packno)
lsquic_rechist_received(&rechist, packno, 0);
for (packno = 11946; packno <= 11994; ++packno)
lsquic_rechist_received(&rechist, packno, 0);
range = lsquic_rechist_first(&rechist);
assert(range);
assert(range->high == 11994);
assert(range->low == 11946);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_rechist_next(&rechist);
assert(!range);
lsquic_rechist_received(&rechist, 11995, 0);
lsquic_rechist_received(&rechist, 11996, 0);
range = lsquic_rechist_first(&rechist);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_rechist_next(&rechist);
assert(!range);
lsquic_rechist_received(&rechist, 11912, 0);
lsquic_rechist_stop_wait(&rechist, 11860);
range = lsquic_rechist_first(&rechist);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11912);
assert(range->low == 11912);
range = lsquic_rechist_next(&rechist);
assert(!range);
for (packno = 12169; packno <= 12193; ++packno)
lsquic_rechist_received(&rechist, packno, 0);
range = lsquic_rechist_first(&rechist);
assert(range);
assert(range->high == 12193);
assert(range->low == 12169);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11996);
assert(range->low == 11946);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11941);
assert(range->low == 11917);
range = lsquic_rechist_next(&rechist);
assert(range);
assert(range->high == 11912);
assert(range->low == 11912);
range = lsquic_rechist_next(&rechist);
assert(!range);
lsquic_rechist_cleanup(&rechist);
}
static void
rechist2str (lsquic_rechist_t *rechist, char *buf, size_t bufsz)
{
const struct lsquic_packno_range *range;
size_t off;
int n;
for (off = 0, range = lsquic_rechist_first(rechist);
range && off < bufsz;
off += n, range = lsquic_rechist_next(rechist))
{
n = snprintf(buf + off, bufsz - off, "[%"PRIu64"-%"PRIu64"]",
range->high, range->low);
if (n < 0 || (size_t) n >= bufsz - off)
break;
}
}
static void
test_range_copy (struct lsquic_rechist *orig, int ietf)
{
char orig_str[0x1000], new_str[0x1000];
struct lsquic_rechist new;
size_t len;
rechist2str(orig, orig_str, sizeof(orig_str));
lsquic_rechist_init(&new, ietf, 0);
lsquic_rechist_copy_ranges(&new, orig,
(const struct lsquic_packno_range * (*) (void *)) lsquic_rechist_first,
(const struct lsquic_packno_range * (*) (void *)) lsquic_rechist_next);
rechist2str(&new, new_str, sizeof(new_str));
assert(0 == strcmp(orig_str, new_str));
lsquic_rechist_cleanup(&new);
/* This tests that lower-numbered ranges do not overwrite higher-numbered
* ranges.
*/
lsquic_rechist_init(&new, ietf, 10);
lsquic_rechist_copy_ranges(&new, orig,
(const struct lsquic_packno_range * (*) (void *)) lsquic_rechist_first,
(const struct lsquic_packno_range * (*) (void *)) lsquic_rechist_next);
rechist2str(&new, new_str, sizeof(new_str));
len = strlen(new_str);
assert(0 == strncmp(orig_str, new_str, len));
lsquic_rechist_cleanup(&new);
}
static void
test5 (void)
{
lsquic_rechist_t rechist;
char buf[100];
lsquic_rechist_init(&rechist, 0, 0);
lsquic_rechist_received(&rechist, 1, 0);
/* Packet 2 omitted because it could not be decrypted */
lsquic_rechist_received(&rechist, 3, 0);
lsquic_rechist_received(&rechist, 12, 0);
rechist2str(&rechist, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][3-3][1-1]"));
lsquic_rechist_received(&rechist, 4, 0);
lsquic_rechist_received(&rechist, 10, 0);
rechist2str(&rechist, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][4-3][1-1]"));
lsquic_rechist_received(&rechist, 6, 0);
rechist2str(&rechist, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][6-6][4-3][1-1]"));
lsquic_rechist_received(&rechist, 7, 0);
lsquic_rechist_received(&rechist, 8, 0);
rechist2str(&rechist, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-10][8-6][4-3][1-1]"));
lsquic_rechist_received(&rechist, 9, 0);
test_range_copy(&rechist, 0);
rechist2str(&rechist, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-12][10-6][4-3][1-1]"));
lsquic_rechist_received(&rechist, 5, 0);
lsquic_rechist_received(&rechist, 11, 0);
rechist2str(&rechist, buf, sizeof(buf));
assert(0 == strcmp(buf, "[12-3][1-1]"));
lsquic_rechist_cleanup(&rechist);
}
/* Regression test for bug where rechist ends up empty (rh_used == 0)
* with invalid head entry with a self-referential next, and
* lsquic_rechist_received would follow it because it didn't check rh_used.
*/
static void
test6 (void)
{
lsquic_rechist_t rechist;
char buf[256];
long int time = 12087061905875;
lsquic_rechist_init(&rechist, 0, 0);
for (int i = 0; i <= 3; i++)
lsquic_rechist_received(&rechist, i, (time += (i*10)));
lsquic_rechist_stop_wait(&rechist, 2);
lsquic_rechist_received(&rechist, 4, (time += 10));
lsquic_rechist_stop_wait(&rechist, 3);
lsquic_rechist_received(&rechist, 5, (time += 10));
lsquic_rechist_stop_wait(&rechist, 3);
lsquic_rechist_received(&rechist, 6, (time += 10));
lsquic_rechist_stop_wait(&rechist, 9);
lsquic_rechist_received(&rechist, 7, (time += 10));
lsquic_rechist_received(&rechist, 8, (time += 10));
lsquic_rechist_received(&rechist, 9, (time += 10));
rechist2str(&rechist, buf, sizeof(buf));
lsquic_rechist_cleanup(&rechist);
}
static void
test_rand_sequence (unsigned seed, unsigned max)
{
struct lsquic_rechist rechist;
const struct lsquic_packno_range *range;
lsquic_packno_t prev_low;
enum received_st st;
unsigned i, count;
lsquic_rechist_init(&rechist, 1, max);
srand(seed);
for (i = 0; i < 10000; ++i)
{
st = lsquic_rechist_received(&rechist, (unsigned) rand(), 0);
assert(st == REC_ST_OK || st == REC_ST_DUP);
}
test_range_copy(&rechist, 1);
range = lsquic_rechist_first(&rechist);
assert(range);
assert(range->high >= range->low);
prev_low = range->low;
count = 1;
while (range = lsquic_rechist_next(&rechist), range != NULL)
{
++count;
assert(range->high >= range->low);
assert(range->high < prev_low);
prev_low = range->low;
}
if (max)
assert(count <= max);
lsquic_rechist_cleanup(&rechist);
}
struct shuffle_elem {
unsigned packno;
int rand;
};
static int
comp_els (const void *a_p, const void *b_p)
{
const struct shuffle_elem *a = a_p, *b = b_p;
if (a->rand < b->rand)
return -1;
if (a->rand > b->rand)
return 1;
return (a->packno > b->packno) - (b->packno > a->packno);
}
static void
test_shuffle_1000 (unsigned seed)
{
struct lsquic_rechist rechist;
const struct lsquic_packno_range *range;
enum received_st st;
unsigned i;
struct shuffle_elem *els;
els = malloc(sizeof(els[0]) * 10000);
lsquic_rechist_init(&rechist, 1, 0);
srand(seed);
for (i = 0; i < 10000; ++i)
{
els[i].packno = i;
els[i].rand = rand();
}
qsort(els, 10000, sizeof(els[0]), comp_els);
for (i = 0; i < 10000; ++i)
{
st = lsquic_rechist_received(&rechist, els[i].packno, 0);
assert(st == REC_ST_OK || st == REC_ST_DUP);
}
test_range_copy(&rechist, 1);
range = lsquic_rechist_first(&rechist);
assert(range);
assert(range->high == 9999);
assert(range->low == 0);
range = lsquic_rechist_next(&rechist);
assert(!range);
lsquic_rechist_cleanup(&rechist);
free(els);
}
int
main (void)
{
enum received_st st;
lsquic_rechist_t rechist;
unsigned i;
const struct lsquic_packno_range *range;
lsquic_rechist_init(&rechist, 0, 0);
lsquic_time_t now = 1234;
st = lsquic_rechist_received(&rechist, 0, now);
assert(("inserting packet number zero results in error", st == REC_ST_ERR));
st = lsquic_rechist_received(&rechist, 1, now);
assert(("inserting packet number one is successful", st == REC_ST_OK));
st = lsquic_rechist_received(&rechist, 1, now);
assert(("inserting packet number one again results in duplicate error",
st == REC_ST_DUP));
range = lsquic_rechist_first(&rechist);
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_rechist_next(&rechist);
assert(("second range does not exist", !range));
for (i = 3; i <= 5; ++i)
{
st = lsquic_rechist_received(&rechist, i, now);
assert(("inserting packet", st == REC_ST_OK));
}
range = lsquic_rechist_first(&rechist);
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));
range = lsquic_rechist_next(&rechist);
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_rechist_next(&rechist);
assert(("third range does not exist", !range));
lsquic_rechist_stop_wait(&rechist, 3);
st = lsquic_rechist_received(&rechist, 1, now);
assert(("inserting packet number one is unsuccessful after cutoff 3",
st == REC_ST_DUP));
range = lsquic_rechist_first(&rechist);
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));
range = lsquic_rechist_next(&rechist);
assert(("second range does not exist", !range));
for (i = 9; i >= 7; --i)
{
st = lsquic_rechist_received(&rechist, i, now);
assert(("inserting packet", st == REC_ST_OK));
}
range = lsquic_rechist_first(&rechist);
assert(("first range returned correctly", range));
assert(("first range low value checks out", range->low == 7));
assert(("first range high value checks out", range->high == 9));
range = lsquic_rechist_next(&rechist);
assert(("second range returned correctly", range));
assert(("second range low value checks out", range->low == 3));
assert(("second range high value checks out", range->high == 5));
range = lsquic_rechist_next(&rechist);
assert(("third range does not exist", !range));
lsquic_rechist_stop_wait(&rechist, 5);
range = lsquic_rechist_first(&rechist);
range = lsquic_rechist_next(&rechist);
assert(("second range returned correctly", range));
assert(("second range low value checks out", range->low == 5));
assert(("second range high value checks out", range->high == 5));
range = lsquic_rechist_next(&rechist);
assert(("third range does not exist", !range));
lsquic_rechist_stop_wait(&rechist, 8);
range = lsquic_rechist_first(&rechist);
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 == 9));
range = lsquic_rechist_next(&rechist);
assert(("second range does not exist", !range));
lsquic_rechist_cleanup(&rechist);
test4();
test5();
test6();
for (i = 0; i < 10; ++i)
test_rand_sequence(i, 0);
for (i = 0; i < 10; ++i)
test_rand_sequence(i, 111 + i * 3 /* Just something arbitrary */);
for (i = 0; i < 10; ++i)
test_shuffle_1000(i);
return 0;
}