tests: Add tests for dynamic dechunker

This commit is contained in:
matt335672
2026-08-06 12:22:56 +01:00
parent 0a986869cc
commit e4b0621229
+399 -40
View File
@@ -143,13 +143,24 @@ static const char frankenstein[] =
"all your love and kindness.\n\n"
"Your affectionate brother,\nR. Walton ";
// Number of CHANNEL_CHUNK_LENGTH chunks required to send the
// above text into the dechunker
#define FRANKENSTEIN_CHUNK_COUNT \
// Number of CHANNEL_CHUNK_LENGTH (1600) chunks required to send the
// above text into the vc dechunker
#define FRANKENSTEIN_VC_CHUNK_COUNT \
((sizeof(frankenstein) + (CHANNEL_CHUNK_LENGTH - 1)) \
/ CHANNEL_CHUNK_LENGTH)
// See the private E_MAX_CHUNK_SIZE_LOWER_LIMIT in dechunker.c
// The dynamic dechunker works on total data block sizes of 1600 bytes,
// including the block header as well.
// The FIRST block header is 6-12 bytes long, and the DATA block header
// is 5-8 bytes long. For simplicity we're assume a header size of 8
// bytes, and hence a data size of 1592 bytes.
#define FRANKENSTEIN_DYN_CHUNK_SIZE 1592
#define FRANKENSTEIN_DYN_CHUNK_COUNT \
((sizeof(frankenstein) + (FRANKENSTEIN_DYN_CHUNK_SIZE - 1)) \
/ FRANKENSTEIN_DYN_CHUNK_SIZE)
// See the private E_MAX_VC_CHUNK_SIZE_LOWER_LIMIT in dechunker.c
#define PAD50 " "
/******************************************************************************/
@@ -175,7 +186,7 @@ make_stream_from_data(const char *data, int data_len)
/*
* Check bad parameters passed to the dechunker functions
*/
START_TEST(test_dechunker_bad_params)
START_TEST(test_vc_dechunker_bad_params)
{
struct vc_dechunker *dc;
const char data[] = "Some stream data";
@@ -216,7 +227,7 @@ START_TEST(test_dechunker_bad_params)
* immediately returned to the caller with E_VC_INLINE_CHUNK. If
* however, we are currently dechunking, a passthrough chunk is not allowed
*/
START_TEST(test_dechunker_passthrough)
START_TEST(test_vc_dechunker_passthrough)
{
const char data[] = "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
@@ -272,7 +283,7 @@ END_TEST
* An intermediate chunk (neither first of last) must be rejected if we
* are not dechunking
*/
START_TEST(test_dechunker_intermediate)
START_TEST(test_vc_dechunker_intermediate)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
@@ -297,7 +308,7 @@ END_TEST
/*
* A LAST chunk must be rejected if we are not dechunking
*/
START_TEST(test_dechunker_last)
START_TEST(test_vc_dechunker_last)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
@@ -322,7 +333,7 @@ END_TEST
/**
* Two consecutive FIRST chunks are not allowed
*/
START_TEST(test_dechunker_first_first)
START_TEST(test_vc_dechunker_first_first)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
@@ -367,7 +378,7 @@ END_TEST
* > Virtual channel data that fits in a single Virtual Channel PDU MUST
* > specify both flags
*/
START_TEST(test_dechunker_chunk_overflow)
START_TEST(test_vc_dechunker_chunk_overflow)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
@@ -408,15 +419,15 @@ END_TEST
// up to CHANNEL_CHUNK_LENGTH bytes from Frankenstein chapter 1
// The stream pointer will be positioned at the start of the text.
static struct stream *
make_bigtest_chunk(unsigned int chunk_num, int *flags)
make_vc_bigtest_chunk(unsigned int chunk_num, int *flags)
{
struct stream *s = NULL;
if (chunk_num < FRANKENSTEIN_CHUNK_COUNT)
if (chunk_num < FRANKENSTEIN_VC_CHUNK_COUNT)
{
int chunk_size;
// Work out the size of this chunk
if (chunk_num == (FRANKENSTEIN_CHUNK_COUNT - 1))
if (chunk_num == (FRANKENSTEIN_VC_CHUNK_COUNT - 1))
{
chunk_size = sizeof(frankenstein) % CHANNEL_CHUNK_LENGTH;
if (chunk_size == 0)
@@ -456,7 +467,7 @@ make_bigtest_chunk(unsigned int chunk_num, int *flags)
// Sort out the flags
*flags =
(chunk_num == 0) ? XR_CHANNEL_FLAG_FIRST :
(chunk_num == (FRANKENSTEIN_CHUNK_COUNT - 1)) ? XR_CHANNEL_FLAG_LAST :
(chunk_num == (FRANKENSTEIN_VC_CHUNK_COUNT - 1)) ? XR_CHANNEL_FLAG_LAST :
0;
}
@@ -468,7 +479,7 @@ make_bigtest_chunk(unsigned int chunk_num, int *flags)
* Streams a lot of data through the dechunker and checks it's all
* assembled correctly at the end
*/
START_TEST(test_dechunker_big_test)
START_TEST(test_vc_dechunker_big_test)
{
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", CHANNEL_CHUNK_LENGTH);
@@ -476,15 +487,15 @@ START_TEST(test_dechunker_big_test)
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_CHUNK_COUNT; ++i)
for (i = 0 ; i < FRANKENSTEIN_VC_CHUNK_COUNT; ++i)
{
int flags;
s = make_bigtest_chunk(i, &flags);
s = make_vc_bigtest_chunk(i, &flags);
stat = vc_dechunker_process_chunk(
dc, s,
flags,
sizeof(frankenstein));
if (i < FRANKENSTEIN_CHUNK_COUNT - 1)
if (i < FRANKENSTEIN_VC_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
}
@@ -513,8 +524,8 @@ START_TEST(test_dechunker_big_test)
}
/******************************************************************************/
// Like test_dechunker_big_test, but the last chunk is oversized
START_TEST(test_dechunker_big_test_oversize_fail)
// Like test_vc_dechunker_big_test, but the last chunk is oversized
START_TEST(test_vc_dechunker_big_test_oversize_fail)
{
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", CHANNEL_CHUNK_LENGTH);
@@ -522,11 +533,11 @@ START_TEST(test_dechunker_big_test_oversize_fail)
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_CHUNK_COUNT; ++i)
for (i = 0 ; i < FRANKENSTEIN_VC_CHUNK_COUNT; ++i)
{
int flags;
s = make_bigtest_chunk(i, &flags);
if (i == (FRANKENSTEIN_CHUNK_COUNT - 1))
s = make_vc_bigtest_chunk(i, &flags);
if (i == (FRANKENSTEIN_VC_CHUNK_COUNT - 1))
{
// Add a byte to the end of the text in the chunk
struct stream *s2;
@@ -546,7 +557,7 @@ START_TEST(test_dechunker_big_test_oversize_fail)
dc, s,
flags,
sizeof(frankenstein));
if (i < FRANKENSTEIN_CHUNK_COUNT - 1)
if (i < FRANKENSTEIN_VC_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
}
@@ -561,8 +572,8 @@ START_TEST(test_dechunker_big_test_oversize_fail)
}
/******************************************************************************/
// Like test_dechunker_big_test, but the last chunk is undersized
START_TEST(test_dechunker_big_test_undersize_fail)
// Like test_vc_dechunker_big_test, but the last chunk is undersized
START_TEST(test_vc_dechunker_big_test_undersize_fail)
{
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", CHANNEL_CHUNK_LENGTH);
@@ -570,11 +581,11 @@ START_TEST(test_dechunker_big_test_undersize_fail)
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_CHUNK_COUNT; ++i)
for (i = 0 ; i < FRANKENSTEIN_VC_CHUNK_COUNT; ++i)
{
int flags;
s = make_bigtest_chunk(i, &flags);
if (i == (FRANKENSTEIN_CHUNK_COUNT - 1))
s = make_vc_bigtest_chunk(i, &flags);
if (i == (FRANKENSTEIN_VC_CHUNK_COUNT - 1))
{
// Skip a byte in the stream, so there is one
// less byte than expected
@@ -584,7 +595,7 @@ START_TEST(test_dechunker_big_test_undersize_fail)
dc, s,
flags,
sizeof(frankenstein));
if (i < FRANKENSTEIN_CHUNK_COUNT - 1)
if (i < FRANKENSTEIN_VC_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
}
@@ -600,6 +611,344 @@ START_TEST(test_dechunker_big_test_undersize_fail)
/******************************************************************************/
/******************************************************************************/
/*
* Check bad parameters passed to the dechunker functions
*/
START_TEST(test_dyn_dechunker_bad_params)
{
struct dyn_dechunker *dc;
const char data[] = "Some stream data";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum dyn_dechunker_status stat;
// dyn_dechunker_init
dc = dyn_dechunker_init(NULL); // No channel name
ck_assert_ptr_eq(dc, NULL);
dc = dyn_dechunker_init("test"); // Should be OK
ck_assert_ptr_ne(dc, NULL);
// dyn_dechunker_free
dyn_dechunker_free(NULL); // Musn't crash!
// dyn_dechunker_get_stream
dyn_dechunker_get_stream(NULL); // Musn't crash!
// dyn_dechunker_process_first_chunk
stat = dyn_dechunker_process_first_chunk(NULL, s, 1600); // No dechunker
ck_assert_int_eq(stat, E_DYN_ERROR);
stat = dyn_dechunker_process_first_chunk(dc, NULL, 1600); // No stream
ck_assert_int_eq(stat, E_DYN_ERROR);
stat = dyn_dechunker_process_first_chunk(dc, s, -1); // bad total_size
ck_assert_int_eq(stat, E_DYN_ERROR);
// dyn_dechunker_process_data_chunk
stat = dyn_dechunker_process_data_chunk(NULL, s); // No dechunker
ck_assert_int_eq(stat, E_DYN_ERROR);
stat = dyn_dechunker_process_data_chunk(dc, NULL); // No stream
ck_assert_int_eq(stat, E_DYN_ERROR);
free_stream(s);
dyn_dechunker_free(dc);
}
/******************************************************************************/
/*
* Check passthrough DATA chunks (i.e. those not following a FIRST)
*
* When the dechunker is in normal operation, these chunks are
* immediately returned to the caller with E_DYN_INLINE_CHUNK.
*/
START_TEST(test_dyn_dechunker_passthrough)
{
const char data[] = "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum dyn_dechunker_status stat;
struct dyn_dechunker *dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
// Save the stream pointer so we can reset the stream in between calls
s_push_layer(s, iso_hdr, 0);
// Check a DATA chunk is normally recognised immediately
stat = dyn_dechunker_process_data_chunk(
dc, s);
ck_assert_int_eq(stat, E_DYN_INLINE_CHUNK);
dyn_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/**
* Two consecutive FIRST chunks are not allowed
*/
START_TEST(test_dyn_dechunker_first_first)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum dyn_dechunker_status stat;
struct dyn_dechunker *dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
// Save the stream pointer so we can reset the stream in between calls
s_push_layer(s, iso_hdr, 0);
// Check a FIRST chunk is accepted...
stat = dyn_dechunker_process_first_chunk(
dc, s, 1600);
ck_assert_int_eq(stat, E_DYN_IN_PROGRESS);
// ... and another FIRST chunk is an error
s_pop_layer(s, iso_hdr);
stat = dyn_dechunker_process_first_chunk(
dc, s, 1600);
ck_assert_int_eq(stat, E_DYN_ERROR);
dyn_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/**
* A FIRST chunk bigger than 1590 bytes but less than 1600 is passed
* through to the application, if it is the total length
*/
START_TEST(test_dyn_dechunker_first_inline)
{
const char data[1591] = {0};
enum dyn_dechunker_status stat;
struct dyn_dechunker *dc;
struct stream *s;
// Check a FIRST chunk of 1590 bytes with a total of 1590 is rejected
// (too small to fragment)
dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
s = make_stream_from_data(data, 1590);
ck_assert_ptr_ne(s, NULL);
stat = dyn_dechunker_process_first_chunk( dc, s, 1590);
ck_assert_int_eq(stat, E_DYN_ERROR);
dyn_dechunker_free(dc);
free_stream(s);
// Check a FIRST chunk of 1591 bytes with a total size of 1591 is
// accepted as inline
dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
s = make_stream_from_data(data, 1591);
ck_assert_ptr_ne(s, NULL);
stat = dyn_dechunker_process_first_chunk( dc, s, 1591);
ck_assert_int_eq(stat, E_DYN_INLINE_CHUNK);
dyn_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/**
* Checks that a FIRST chunk cannot be bigger than the total size
*/
START_TEST(test_dyn_dechunker_chunk_overflow)
{
const char data[1592] = {0};
enum dyn_dechunker_status stat;
struct dyn_dechunker *dc;
struct stream *s;
// We know a FIRST chunk of size 1591 for a total of 1591 is
// inline (see test_dyn_dechunker_first_inline()). Check if the
// first chunk is 1592, it is rejected
dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
s = make_stream_from_data(data, 1592);
ck_assert_ptr_ne(s, NULL);
stat = dyn_dechunker_process_first_chunk( dc, s, 1591);
ck_assert_int_eq(stat, E_DYN_ERROR);
dyn_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
// Returns a stream with some random data, then a chunk of
// up to FRANKENSTEIN_DYN_CHUNK_COUNT bytes from Frankenstein chapter 1
// The stream pointer will be positioned at the start of the text.
static struct stream *
make_dyn_bigtest_chunk(unsigned int chunk_num)
{
struct stream *s = NULL;
if (chunk_num < FRANKENSTEIN_DYN_CHUNK_COUNT)
{
int chunk_size;
// Work out the size of this chunk
if (chunk_num == (FRANKENSTEIN_DYN_CHUNK_COUNT - 1))
{
chunk_size = sizeof(frankenstein) % FRANKENSTEIN_DYN_CHUNK_SIZE;
if (chunk_size == 0)
{
chunk_size = FRANKENSTEIN_DYN_CHUNK_SIZE;
}
}
else
{
chunk_size = FRANKENSTEIN_DYN_CHUNK_SIZE;
}
// Add some random data at the start of the stream, so we
// can check the dechunker works for non-zero positioned
// streams
int rand_size = 4 * 4 * chunk_num;
make_stream(s);
init_stream(s, rand_size + chunk_size);
// Write the random data
int i;
for (i = 0 ; i < rand_size; ++i)
{
out_uint8(s, rand() & 255);
}
s_push_layer(s, iso_hdr, 0);
// Copy the chapter data
out_uint8a(s, &frankenstein[chunk_num * FRANKENSTEIN_DYN_CHUNK_SIZE],
chunk_size);
// Get the stream ready for reading from the Frankenstein text
s_mark_end(s);
s_pop_layer(s, iso_hdr);
}
return s;
}
/******************************************************************************/
/*
* Streams a lot of data through the dechunker and checks it's all
* assembled correctly at the end
*/
START_TEST(test_dyn_dechunker_big_test)
{
enum dyn_dechunker_status stat;
struct dyn_dechunker *dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
struct stream *s;
int pending;
int i;
for (i = 0 ; i < FRANKENSTEIN_DYN_CHUNK_COUNT; ++i)
{
s = make_dyn_bigtest_chunk(i);
if (i == 0)
{
stat = dyn_dechunker_process_first_chunk(
dc, s, sizeof(frankenstein));
}
else
{
stat = dyn_dechunker_process_data_chunk( dc, s);
}
pending = dyn_dechunker_pending(dc);
ck_assert_int_ne(pending, 0);
if (i < FRANKENSTEIN_DYN_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_DYN_IN_PROGRESS);
}
else
{
ck_assert_int_eq(stat, E_DYN_READY);
}
free_stream(s);
}
// Check we have a result
s = dyn_dechunker_get_stream(dc);
ck_assert_ptr_ne(s, NULL);
pending = dyn_dechunker_pending(dc);
ck_assert_int_eq(pending, 0);
// Is it the right size?
int stream_size = s_rem(s);
ck_assert_int_eq(stream_size, sizeof(frankenstein));
// Check the data
const char *p;
in_uint8p(s, p, stream_size);
ck_assert_mem_eq(frankenstein, p, stream_size);
free_stream(s);
dyn_dechunker_free(dc);
}
/******************************************************************************/
// Like test_dyn_dechunker_big_test, but the last chunk is oversized
START_TEST(test_dyn_dechunker_big_test_oversize_fail)
{
enum dyn_dechunker_status stat;
struct dyn_dechunker *dc = dyn_dechunker_init("test");
ck_assert_ptr_ne(dc, NULL);
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_DYN_CHUNK_COUNT; ++i)
{
s = make_dyn_bigtest_chunk(i);
if (i == 0)
{
stat = dyn_dechunker_process_first_chunk(
dc, s, sizeof(frankenstein));
}
else
{
if (i == (FRANKENSTEIN_DYN_CHUNK_COUNT - 1))
{
// Add a byte to the end of the text in the chunk
struct stream *s2;
make_stream(s2);
init_stream(s2, s_rem(s) + 1);
s_push_layer(s2, iso_hdr, 0);
out_uint8p(s2, s->p, s_rem(s));
out_uint8(s2, 'x');
s_mark_end(s2);
s_pop_layer(s2, iso_hdr); // Rewind for reading
// Swap s2 and s and delete the original stream
struct stream *tmp = s;
s = s2;
free_stream(tmp);
}
stat = dyn_dechunker_process_data_chunk( dc, s);
}
if (i < FRANKENSTEIN_DYN_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_DYN_IN_PROGRESS);
}
else
{
ck_assert_int_eq(stat, E_DYN_ERROR);
}
free_stream(s);
}
// Check we do not have a result
s = dyn_dechunker_get_stream(dc);
ck_assert_ptr_eq(s, NULL);
dyn_dechunker_free(dc);
}
/******************************************************************************/
Suite *
make_suite_test_dechunker(void)
{
@@ -608,17 +957,27 @@ make_suite_test_dechunker(void)
s = suite_create("dechunker");
rc_dechunker = tcase_create("dechunker_basic");
rc_dechunker = tcase_create("vc_dechunker");
suite_add_tcase(s, rc_dechunker);
tcase_add_test(rc_dechunker, test_dechunker_bad_params);
tcase_add_test(rc_dechunker, test_dechunker_passthrough);
tcase_add_test(rc_dechunker, test_dechunker_intermediate);
tcase_add_test(rc_dechunker, test_dechunker_last);
tcase_add_test(rc_dechunker, test_dechunker_first_first);
tcase_add_test(rc_dechunker, test_dechunker_chunk_overflow);
tcase_add_test(rc_dechunker, test_dechunker_big_test);
tcase_add_test(rc_dechunker, test_dechunker_big_test_oversize_fail);
tcase_add_test(rc_dechunker, test_dechunker_big_test_undersize_fail);
tcase_add_test(rc_dechunker, test_vc_dechunker_bad_params);
tcase_add_test(rc_dechunker, test_vc_dechunker_passthrough);
tcase_add_test(rc_dechunker, test_vc_dechunker_intermediate);
tcase_add_test(rc_dechunker, test_vc_dechunker_last);
tcase_add_test(rc_dechunker, test_vc_dechunker_first_first);
tcase_add_test(rc_dechunker, test_vc_dechunker_chunk_overflow);
tcase_add_test(rc_dechunker, test_vc_dechunker_big_test);
tcase_add_test(rc_dechunker, test_vc_dechunker_big_test_oversize_fail);
tcase_add_test(rc_dechunker, test_vc_dechunker_big_test_undersize_fail);
rc_dechunker = tcase_create("dyn_dechunker");
suite_add_tcase(s, rc_dechunker);
tcase_add_test(rc_dechunker, test_dyn_dechunker_bad_params);
tcase_add_test(rc_dechunker, test_dyn_dechunker_passthrough);
tcase_add_test(rc_dechunker, test_dyn_dechunker_first_first);
tcase_add_test(rc_dechunker, test_dyn_dechunker_first_inline);
tcase_add_test(rc_dechunker, test_dyn_dechunker_chunk_overflow);
tcase_add_test(rc_dechunker, test_dyn_dechunker_big_test);
tcase_add_test(rc_dechunker, test_dyn_dechunker_big_test_oversize_fail);
return s;
}