Files
xrdp/tests/common/test_dechunker.c
T
2026-07-23 19:20:28 +01:00

625 lines
23 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Jay Sorg 2006-2026
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @file test_dechunker.c
* @brief Test functiond for dechunker code
* @author Matt Burt
*
*/
#if defined(HAVE_CONFIG_H)
#include "config_ac.h"
#endif
#include "dechunker.h"
#include "ms-rdpbcgr.h"
#include "os_calls.h"
#include "parse.h"
#include "test_common.h"
// Chapter 1 of Mary Shelley's Frankenstein (thanks to Project Gutenberg)
static const char frankenstein[] =
"Letter 1\n\n"
"To Mrs. Saville, England.\n\n"
"St. Petersburgh, Dec. 11th, 17—.\n"
"You will rejoice to hear that no disaster has accompanied the "
"commencement of an enterprise which you have regarded with such evil "
"forebodings. I arrived here yesterday, and my first task is to assure "
"my dear sister of my welfare and increasing confidence in the success "
"of my undertaking.\n"
"I am already far north of London, and as I walk in the streets of "
"Petersburgh, I feel a cold northern breeze play upon my cheeks, which "
"braces my nerves and fills me with delight. Do you understand this "
"feeling? This breeze, which has travelled from the regions towards which "
"I am advancing, gives me a foretaste of those icy climes. Inspirited "
"by this wind of promise, my daydreams become more fervent and vivid. I "
"try in vain to be persuaded that the pole is the seat of frost and "
"desolation; it ever presents itself to my imagination as the region "
"of beauty and delight. There, Margaret, the sun is for ever visible, "
"its broad disk just skirting the horizon and diffusing a perpetual "
"splendour. There—for with your leave, my sister, I will put some trust "
"in preceding navigators—there snow and frost are banished; and, sailing "
"over a calm sea, we may be wafted to a land surpassing in wonders and "
"in beauty every region hitherto discovered on the habitable globe. Its "
"productions and features may be without example, as the phenomena of the "
"heavenly bodies undoubtedly are in those undiscovered solitudes. What "
"may not be expected in a country of eternal light? I may there discover "
"the wondrous power which attracts the needle and may regulate a thousand "
"celestial observations that require only this voyage to render their "
"seeming eccentricities consistent for ever. I shall satiate my ardent "
"curiosity with the sight of a part of the world never before visited, "
"and may tread a land never before imprinted by the foot of man. These "
"are my enticements, and they are sufficient to conquer all fear of danger "
"or death and to induce me to commence this laborious voyage with the joy "
"a child feels when he embarks in a little boat, with his holiday mates, "
"on an expedition of discovery up his native river. But supposing all "
"these conjectures to be false, you cannot contest the inestimable benefit "
"which I shall confer on all mankind, to the last generation, by "
"discovering a passage near the pole to those countries, to reach which at "
"present so many months are requisite; or by ascertaining the secret of "
"the magnet, which, if at all possible, can only be effected by an "
" undertaking such as mine.\n"
"These reflections have dispelled the agitation with which I began my "
"letter, and I feel my heart glow with an enthusiasm which elevates me "
"to heaven, for nothing contributes so much to tranquillise the mind as "
"a steady purpose—a point on which the soul may fix its intellectual "
"eye. This expedition has been the favourite dream of my early years. I "
"have read with ardour the accounts of the various voyages which have "
"been made in the prospect of arriving at the North Pacific Ocean through "
"the seas which surround the pole. You may remember that a history of "
"all the voyages made for purposes of discovery composed the whole of "
"our good Uncle Thomas’ library. My education was neglected, yet I was "
"passionately fond of reading. These volumes were my study day and night, "
"and my familiarity with them increased that regret which I had felt, as "
"a child, on learning that my father’s dying injunction had forbidden "
"my uncle to allow me to embark in a seafaring life.\n"
"These visions faded when I perused, for the first time, those poets whose "
"effusions entranced my soul and lifted it to heaven. I also became a poet "
"and for one year lived in a paradise of my own creation; I imagined that "
"I also might obtain a niche in the temple where the names of Homer and "
"Shakespeare are consecrated. You are well acquainted with my failure and "
"how heavily I bore the disappointment. But just at that time I inherited "
"the fortune of my cousin, and my thoughts were turned into the channel "
"of their earlier bent.\n"
"Six years have passed since I resolved on my present undertaking. I can, "
"even now, remember the hour from which I dedicated myself to this great "
"enterprise. I commenced by inuring my body to hardship. I accompanied "
"the whale-fishers on several expeditions to the North Sea; I voluntarily "
"endured cold, famine, thirst, and want of sleep; I often worked harder "
"than the common sailors during the day and devoted my nights to the "
"study of mathematics, the theory of medicine, and those branches of "
"physical science from which a naval adventurer might derive the greatest "
"practical advantage. Twice I actually hired myself as an under-mate in "
"a Greenland whaler, and acquitted myself to admiration. I must own I "
"felt a little proud when my captain offered me the second dignity in "
"the vessel and entreated me to remain with the greatest earnestness, "
"so valuable did he consider my services.\n"
"And now, dear Margaret, do I not deserve to accomplish some great "
"purpose? My life might have been passed in ease and luxury, but I "
"preferred glory to every enticement that wealth placed in my path. Oh, "
"that some encouraging voice would answer in the affirmative! My courage "
"and my resolution is firm; but my hopes fluctuate, and my spirits are "
"often depressed. I am about to proceed on a long and difficult voyage, "
"the emergencies of which will demand all my fortitude: I am required not "
"only to raise the spirits of others, but sometimes to sustain my own, "
"when theirs are failing.\n"
"This is the most favourable period for travelling in Russia. They fly "
"quickly over the snow in their sledges; the motion is pleasant, and, in "
"my opinion, far more agreeable than that of an English stagecoach. The "
"cold is not excessive, if you are wrapped in furs—a dress which I have "
"already adopted, for there is a great difference between walking the deck "
"and remaining seated motionless for hours, when no exercise prevents "
"the blood from actually freezing in your veins. I have no ambition to "
"lose my life on the post-road between St. Petersburgh and Archangel.\n"
"I shall depart for the latter town in a fortnight or three weeks; and my "
"intention is to hire a ship there, which can easily be done by paying "
"the insurance for the owner, and to engage as many sailors as I think "
"necessary among those who are accustomed to the whale-fishing. I do not "
"intend to sail until the month of June; and when shall I return? Ah, "
"dear sister, how can I answer this question? If I succeed, many, many "
"months, perhaps years, will pass before you and I may meet. If I fail, "
"you will see me again soon, or never.\n"
"Farewell, my dear, excellent Margaret. Heaven shower down blessings on "
"you, and save me, that I may again and again testify my gratitude for "
"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 \
((sizeof(frankenstein) + (CHANNEL_CHUNK_LENGTH -1)) \
/ CHANNEL_CHUNK_LENGTH)
// See the private E_MAX_CHUNK_SIZE_LOWER_LIMIT in dechunker.c
#define PAD50 " "
/******************************************************************************/
/*
* Constructs a stream from static data
*
* The returned stream is suitable for reading.
*/
static struct stream *
make_stream_from_data(const char *data, int data_len)
{
struct stream *s;
make_stream(s);
init_stream(s, data_len);
s_push_layer(s, iso_hdr, 0);
out_uint8p(s, data, data_len);
s_mark_end(s);
s_pop_layer(s, iso_hdr);
return s;
}
/******************************************************************************/
/*
* Check bad parameters passed to the dechunker functions
*/
START_TEST(test_dechunker_bad_params)
{
struct vc_dechunker *dc;
const char data[] = "Some stream data";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum vc_dechunker_status stat;
// vc_dechunker_init
dc = vc_dechunker_init(NULL, 1000); // No channel name
ck_assert_ptr_eq(dc, NULL);
dc = vc_dechunker_init("test", 1); // max chunk size too small
ck_assert_ptr_eq(dc, NULL);
dc = vc_dechunker_init("test", 1000); // Should be OK
ck_assert_ptr_ne(dc, NULL);
// vc_dechunker_free
vc_dechunker_free(NULL); // Musn't crash!
// vc_dechunker_get_stream
vc_dechunker_get_stream(NULL); // Musn't crash!
// vc_dechunker_process_chunk
stat = vc_dechunker_process_chunk(NULL, s, 0, 1600); // No dechunker
ck_assert_int_eq(stat, E_VC_ERROR);
stat = vc_dechunker_process_chunk(dc, NULL, 0, 1600); // No stream
ck_assert_int_eq(stat, E_VC_ERROR);
stat = vc_dechunker_process_chunk(dc, s, 0, -1); // bad total_size
ck_assert_int_eq(stat, E_VC_ERROR);
free_stream(s);
vc_dechunker_free(dc);
}
/******************************************************************************/
/*
* Check passthrough chunks (i.e. those with FIRST and LAST bits set)
*
* When the dechunker is in normal operation, these chunks are
* 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)
{
const char data[] = "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", 1000);
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 passthrough chunk is normally recognised immediately
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST,
s->size);
ck_assert_int_eq(stat, E_VC_INLINE_CHUNK);
// Check a passthrough chunk after a first chunk generates an error
// The total size passed for the first chunk must be bigger than the
// dechunker chunking_size
s_pop_layer(s, iso_hdr); // Restore the stream pointer
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST,
2000);
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
s_pop_layer(s, iso_hdr);
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST,
s->size);
ck_assert_int_eq(stat, E_VC_ERROR);
// Check a passthrough chunk is accepted after the error
// (i.e. the dechunker can still be used if required)
s_pop_layer(s, iso_hdr);
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST,
s->size);
ck_assert_int_eq(stat, E_VC_INLINE_CHUNK);
vc_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/*
* An intermediate chunk (neither first of last) must be rejected if we
* are not dechunking
*/
START_TEST(test_dechunker_intermediate)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", sizeof(data));
ck_assert_ptr_ne(dc, NULL);
// Check an intermediate chunk is immediately rejected
stat = vc_dechunker_process_chunk(
dc, s,
0,
s->size + 1);
ck_assert_int_eq(stat, E_VC_ERROR);
vc_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/*
* A LAST chunk must be rejected if we are not dechunking
*/
START_TEST(test_dechunker_last)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", sizeof(data));
ck_assert_ptr_ne(dc, NULL);
// Check a LAST chunk is immediately rejected
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_LAST,
s->size + 1);
ck_assert_int_eq(stat, E_VC_ERROR);
vc_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/**
* Two consecutive FIRST chunks are not allowed
*/
START_TEST(test_dechunker_first_first)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", sizeof(data));
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 = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST,
s->size + 1);
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
// ... and another FIRST chunk is an error
s_pop_layer(s, iso_hdr);
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST,
s->size + 1);
ck_assert_int_eq(stat, E_VC_ERROR);
vc_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
/**
* Checks that a FIRST chunk cannot be the total_size. This follows from
* [MS-RDPBCGR] 3.1.5.2.1:
*
* > If the total size of the virtual channel data is larger than
* > the chunk size, then each chunk MUST be sent in a separate Virtual
* > Channel PDU.
*
* > Virtual channel data that fits in a single Virtual Channel PDU MUST
* > specify both flags
*/
START_TEST(test_dechunker_chunk_overflow)
{
const char data[] = PAD50 "Some data to dechunk";
struct stream *s = make_stream_from_data(data, sizeof(data));
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", sizeof(data));
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 the same size as the total size is rejected
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST,
s->size);
ck_assert_int_eq(stat, E_VC_ERROR);
// Check a FIRST chunk bigger than the total size is rejected
// [MS-RDPBCGR] 3.1.5.2.1
vc_dechunker_free(dc);
dc = vc_dechunker_init("test", sizeof(data));
ck_assert_ptr_ne(dc, NULL);
s_pop_layer(s, iso_hdr);
stat = vc_dechunker_process_chunk(
dc, s,
XR_CHANNEL_FLAG_FIRST,
s->size - 1);
ck_assert_int_eq(stat, E_VC_ERROR);
vc_dechunker_free(dc);
free_stream(s);
}
END_TEST
/******************************************************************************/
// Returns a stream with some random data, then a chunk of
// 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)
{
struct stream *s = NULL;
if (chunk_num < FRANKENSTEIN_CHUNK_COUNT)
{
int chunk_size;
// Work out the size of this chunk
if (chunk_num == (FRANKENSTEIN_CHUNK_COUNT - 1))
{
chunk_size = sizeof(frankenstein) % CHANNEL_CHUNK_LENGTH;
if (chunk_size == 0)
{
chunk_size = CHANNEL_CHUNK_LENGTH;
}
}
else
{
chunk_size = CHANNEL_CHUNK_LENGTH;
}
// 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 * CHANNEL_CHUNK_LENGTH],
chunk_size);
// Get the stream ready for reading from the Frankenstein text
s_mark_end(s);
s_pop_layer(s, iso_hdr);
// Sort out the flags
*flags =
(chunk_num == 0) ? XR_CHANNEL_FLAG_FIRST :
(chunk_num == (FRANKENSTEIN_CHUNK_COUNT - 1)) ? XR_CHANNEL_FLAG_LAST :
0;
}
return s;
}
/******************************************************************************/
/*
* Streams a lot of data through the dechunker and checks it's all
* assembled correctly at the end
*/
START_TEST(test_dechunker_big_test)
{
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", CHANNEL_CHUNK_LENGTH);
ck_assert_ptr_ne(dc, NULL);
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_CHUNK_COUNT; ++i)
{
int flags;
s = make_bigtest_chunk(i, &flags);
stat = vc_dechunker_process_chunk(
dc, s,
flags,
sizeof(frankenstein));
if (i < FRANKENSTEIN_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
}
else
{
ck_assert_int_eq(stat, E_VC_READY);
}
free_stream(s);
}
// Check we have a result
s = vc_dechunker_get_stream(dc);
ck_assert_ptr_ne(s, NULL);
// 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);
vc_dechunker_free(dc);
}
/******************************************************************************/
// Like test_dechunker_big_test, but the last chunk is oversized
START_TEST(test_dechunker_big_test_oversize_fail)
{
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", CHANNEL_CHUNK_LENGTH);
ck_assert_ptr_ne(dc, NULL);
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_CHUNK_COUNT; ++i)
{
int flags;
s = make_bigtest_chunk(i, &flags);
if (i == (FRANKENSTEIN_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 = vc_dechunker_process_chunk(
dc, s,
flags,
sizeof(frankenstein));
if (i < FRANKENSTEIN_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
}
else
{
ck_assert_int_eq(stat, E_VC_ERROR);
}
free_stream(s);
}
vc_dechunker_free(dc);
}
/******************************************************************************/
// Like test_dechunker_big_test, but the last chunk is undersized
START_TEST(test_dechunker_big_test_undersize_fail)
{
enum vc_dechunker_status stat;
struct vc_dechunker *dc = vc_dechunker_init("test", CHANNEL_CHUNK_LENGTH);
ck_assert_ptr_ne(dc, NULL);
struct stream *s;
int i;
for (i = 0 ; i < FRANKENSTEIN_CHUNK_COUNT; ++i)
{
int flags;
s = make_bigtest_chunk(i, &flags);
if (i == (FRANKENSTEIN_CHUNK_COUNT - 1))
{
// Skip a byte in the stream, so there is one
// less byte than expected
in_uint8s(s, 1);
}
stat = vc_dechunker_process_chunk(
dc, s,
flags,
sizeof(frankenstein));
if (i < FRANKENSTEIN_CHUNK_COUNT - 1)
{
ck_assert_int_eq(stat, E_VC_IN_PROGRESS);
}
else
{
ck_assert_int_eq(stat, E_VC_ERROR);
}
free_stream(s);
}
vc_dechunker_free(dc);
}
/******************************************************************************/
Suite *
make_suite_test_dechunker(void)
{
Suite *s;
TCase *rc_dechunker;
s = suite_create("dechunker");
rc_dechunker = tcase_create("dechunker_basic");
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);
return s;
}