/** * 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; }