Merge pull request #3839 from matt335672/add_dechunker
Add dechunker module
This commit is contained in:
@@ -49,6 +49,8 @@ libcommon_la_SOURCES = \
|
||||
base64.c \
|
||||
channel_defs.h \
|
||||
defines.h \
|
||||
dechunker.c \
|
||||
dechunker.h \
|
||||
fifo.c \
|
||||
fifo.h \
|
||||
file.c \
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* 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 dechunker.c
|
||||
* @brief Dechunker functions for chunks on virtual channels - definitions
|
||||
* @author Matt Burt
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(HAVE_CONFIG_H)
|
||||
#include <config_ac.h>
|
||||
#endif
|
||||
|
||||
#include "dechunker.h"
|
||||
#include "parse.h"
|
||||
#include "os_calls.h"
|
||||
#include "string_calls.h"
|
||||
|
||||
enum vc_dechunker_state
|
||||
{
|
||||
E_NO_DATA = 0,
|
||||
E_READING,
|
||||
E_DATA,
|
||||
E_SKIPPING
|
||||
};
|
||||
|
||||
enum vc_chunk_type
|
||||
{
|
||||
CT_INTERMEDIATE = 0,
|
||||
CT_FIRST = 1, // CHANNEL_FLAG_FIRST
|
||||
CT_LAST = 2, // CHANNEL_FLAG_LAST
|
||||
CT_FIRST_LAST = 3 // CHANNEL_FLAG_FIRST | CHANNEL_FLAG_LAST
|
||||
};
|
||||
|
||||
struct vc_dechunker
|
||||
{
|
||||
char name[64];
|
||||
int max_chunk_size;
|
||||
enum vc_dechunker_state state;
|
||||
struct stream *reassembly_s;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
// This is a rather arbitrary figure, but one we are unlikely to
|
||||
// go below. It's a sanity check for vc_dechunker_init()
|
||||
E_MAX_CHUNK_SIZE_LOWER_LIMIT = 50
|
||||
};
|
||||
/*****************************************************************************/
|
||||
struct vc_dechunker *
|
||||
vc_dechunker_init(const char *chan_name, int max_chunk_size)
|
||||
{
|
||||
struct vc_dechunker *self = NULL;
|
||||
if (chan_name == NULL)
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "vc_dechunker_init() called with no channel name");
|
||||
}
|
||||
else if (max_chunk_size < E_MAX_CHUNK_SIZE_LOWER_LIMIT)
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "Dechunker: Max chunk size for %s is too small",
|
||||
chan_name);
|
||||
}
|
||||
else if ((self = g_new(struct vc_dechunker, 1)) == NULL)
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "Dechunker: no memory for %s", chan_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
strlcpy(self->name, chan_name, sizeof(self->name));
|
||||
self->max_chunk_size = max_chunk_size;
|
||||
self->state = E_NO_DATA;
|
||||
self->reassembly_s = NULL;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
void
|
||||
vc_dechunker_free(struct vc_dechunker *self)
|
||||
{
|
||||
if (self != NULL)
|
||||
{
|
||||
free_stream(self->reassembly_s);
|
||||
free(self);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
static void
|
||||
vc_dechunker_reset(struct vc_dechunker *self)
|
||||
{
|
||||
if (self != NULL)
|
||||
{
|
||||
free_stream(self->reassembly_s);
|
||||
self->reassembly_s = NULL;
|
||||
self->state = E_NO_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
static void
|
||||
log_unexpected_vc_chunk_type(struct vc_dechunker *self,
|
||||
enum vc_chunk_type ct)
|
||||
{
|
||||
static const char *chunk_type_str[4] =
|
||||
{
|
||||
"CT_INTERMEDIATE",
|
||||
"CT_FIRST",
|
||||
"CT_LAST",
|
||||
"CT_FIRST_LAST"
|
||||
};
|
||||
|
||||
int index = (int)ct & 3; // Guarantee to be 0..3
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: unexpected chunk type %s received on %s",
|
||||
chunk_type_str[index], self->name);
|
||||
self->state = E_SKIPPING; // Look for the next PDU
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
static enum vc_dechunker_status
|
||||
handle_no_data_state(struct vc_dechunker *self,
|
||||
struct stream *s,
|
||||
int chunk_size,
|
||||
int total_size,
|
||||
enum vc_chunk_type ct)
|
||||
{
|
||||
enum vc_dechunker_status rv = E_VC_ERROR;
|
||||
switch (ct)
|
||||
{
|
||||
case CT_FIRST:
|
||||
// See [MS-RDPBCGR] 3.1.5.2.1 Sending of Virtual Channel PDU
|
||||
if (total_size <= self->max_chunk_size)
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: short first chunk received on %s",
|
||||
self->name);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
else if (chunk_size >= total_size)
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: malformed first chunk received on %s",
|
||||
self->name);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
else
|
||||
{
|
||||
make_stream(self->reassembly_s);
|
||||
if (self->reassembly_s)
|
||||
{
|
||||
init_stream(self->reassembly_s, total_size);
|
||||
}
|
||||
if (self->reassembly_s == NULL ||
|
||||
self->reassembly_s->data == NULL)
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: out-of-memory on %s",
|
||||
self->name);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
else
|
||||
{
|
||||
out_uint8p(self->reassembly_s, s->p, chunk_size);
|
||||
in_uint8s(s, chunk_size);
|
||||
self->state = E_READING;
|
||||
rv = E_VC_IN_PROGRESS;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CT_FIRST_LAST:
|
||||
rv = E_VC_INLINE_CHUNK;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_unexpected_vc_chunk_type(self, ct);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
static enum vc_dechunker_status
|
||||
handle_reading_state(struct vc_dechunker *self,
|
||||
struct stream *s,
|
||||
int chunk_size,
|
||||
int total_size,
|
||||
enum vc_chunk_type ct)
|
||||
{
|
||||
enum vc_dechunker_status rv = E_VC_ERROR;
|
||||
if (ct == CT_INTERMEDIATE || ct == CT_LAST)
|
||||
{
|
||||
/* Data to add to the reassembly stream
|
||||
*
|
||||
* [MS-RDPBCGR] 3.1.5.2.2.1 imposes no requirement to check
|
||||
* the total_size field is consistent between chunks. Only
|
||||
* the total_size value for CT_FIRST is important
|
||||
*/
|
||||
if (chunk_size > s_rem_out(self->reassembly_s))
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: oversized chunk received on %s",
|
||||
self->name);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
else
|
||||
{
|
||||
out_uint8p(self->reassembly_s, s->p, chunk_size);
|
||||
in_uint8s(s, chunk_size);
|
||||
if (ct == CT_LAST)
|
||||
{
|
||||
if (s_rem_out(self->reassembly_s) == 0)
|
||||
{
|
||||
// Make the stream ready for reading
|
||||
s_mark_end(self->reassembly_s);
|
||||
self->reassembly_s->p = self->reassembly_s->data;
|
||||
// Tell the caller the stream is available.
|
||||
self->state = E_DATA;
|
||||
rv = E_VC_READY;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: undersized last chunk received on %s",
|
||||
self->name);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = E_VC_IN_PROGRESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_unexpected_vc_chunk_type(self, ct);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
enum vc_dechunker_status
|
||||
vc_dechunker_process_chunk(struct vc_dechunker *self,
|
||||
struct stream *s, int flags,
|
||||
int total_size)
|
||||
{
|
||||
enum vc_dechunker_status rv = E_VC_ERROR;
|
||||
enum vc_chunk_type ct = (enum vc_chunk_type)(flags & 3);
|
||||
int chunk_size = s ? s_rem(s) : 0; // Chunk is remainder of stream
|
||||
|
||||
if (self == NULL || s == NULL)
|
||||
{
|
||||
; // Nothing to do
|
||||
}
|
||||
else if (total_size < 0)
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: out-of-range length received on %s",
|
||||
self->name);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
else if (chunk_size > self->max_chunk_size)
|
||||
{
|
||||
// [MS-RDPBCGR] 2.2.6.1
|
||||
// > ... This field MUST NOT be larger than CHANNEL_CHUNK_size
|
||||
// > (1600) bytes in size unless the maximum virtual channel
|
||||
// > chunk size is specified in the optional VCChunkSize field
|
||||
// > of the Virtual Channel Capability Set (section 2.2.7.1.10).
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: oversize chunk received on %s (%d octets)",
|
||||
self->name, chunk_size);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for a restart after an error
|
||||
if (self->state == E_SKIPPING)
|
||||
{
|
||||
switch (ct)
|
||||
{
|
||||
case CT_FIRST:
|
||||
case CT_FIRST_LAST:
|
||||
// Clean up the dechunker and start again
|
||||
vc_dechunker_reset(self);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (self->state)
|
||||
{
|
||||
case E_NO_DATA:
|
||||
rv = handle_no_data_state(self, s, chunk_size,
|
||||
total_size, ct);
|
||||
break;
|
||||
|
||||
case E_READING:
|
||||
rv = handle_reading_state(self, s, chunk_size,
|
||||
total_size, ct);
|
||||
break;
|
||||
|
||||
case E_DATA:
|
||||
// If we get here, we've not cleared the existing buffer.
|
||||
// This is a serious problem and we continue returning
|
||||
// a error until the buffer is cleared.
|
||||
LOG(LOG_LEVEL_ALWAYS,
|
||||
"Dechunker: unprocessed PDU on %s", self->name);
|
||||
break;
|
||||
|
||||
case E_SKIPPING:
|
||||
rv = E_VC_IN_PROGRESS; // Ignore this chunk
|
||||
break;
|
||||
|
||||
default:
|
||||
// Shouldn't get here.
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: called when %s has an unknown state %d",
|
||||
self->name, (int)self->state);
|
||||
self->state = E_SKIPPING;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
struct stream *
|
||||
vc_dechunker_get_stream(struct vc_dechunker *self)
|
||||
{
|
||||
struct stream *s;
|
||||
const char *name = (self != NULL) ? self->name : "<unknown>";
|
||||
if (self == NULL || self->state != E_DATA)
|
||||
{
|
||||
LOG (LOG_LEVEL_ERROR,
|
||||
"Dechunker: get stream called for %s with no data available",
|
||||
name);
|
||||
s = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass ownership of the stream to the caller
|
||||
s = self->reassembly_s;
|
||||
self->reassembly_s = NULL; // So we don't free it ourselves!
|
||||
vc_dechunker_reset(self);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* xrdp: A Remote Desktop Protocol server.
|
||||
*
|
||||
* Copyright (C) Jay Sorg 2004-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 dechunker.h
|
||||
* @brief Dechunker functions for chunks on virtual channels - declarations
|
||||
*
|
||||
* Many places in xrdp need to dechunk data received from virtual channels.
|
||||
* This process is described in [MS-RDPBCGR] 3.1.5.2.1 (sending) and
|
||||
* 3.1.5.2.2.1 (reassembly).
|
||||
*
|
||||
*
|
||||
* @author Matt Burt
|
||||
*/
|
||||
|
||||
#if !defined(DECHUNKER_H)
|
||||
#define DECHUNKER_H
|
||||
|
||||
struct stream;
|
||||
|
||||
/* Private type */
|
||||
struct vc_dechunker;
|
||||
|
||||
/**
|
||||
* Returned from dechunker_process_vc_chunk()
|
||||
*/
|
||||
enum vc_dechunker_status
|
||||
{
|
||||
E_VC_INLINE_CHUNK = 0, ///< This chunk is complete in itself
|
||||
E_VC_IN_PROGRESS, ///< The dechunker is processing chunks
|
||||
E_VC_READY, ///< A dechunked stream is now complete
|
||||
E_VC_ERROR ///< An error occurred (logged)
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise a virtual channel dechunker
|
||||
*
|
||||
* @param chan_name - Name of channel
|
||||
* @param max_chunk_size - Max size of chunks allowed on channel
|
||||
* @return vc_dechunker
|
||||
*/
|
||||
struct vc_dechunker *
|
||||
vc_dechunker_init(const char *chan_name, int max_chunk_size);
|
||||
|
||||
/**
|
||||
* Free a virtual channel dechunker
|
||||
* @param self vc dechunker to free
|
||||
*/
|
||||
void
|
||||
vc_dechunker_free(struct vc_dechunker *self);
|
||||
|
||||
/**
|
||||
* Process a virtual channel chunk
|
||||
*
|
||||
* @param self dechunker
|
||||
* @param s Stream for chunk, positioned at start of chunk
|
||||
* @param flags from CHANNEL_PDU_HEADER
|
||||
* @param total_size length from CHANNEL_PDU_HEADER
|
||||
* @return status of dechunker
|
||||
*
|
||||
* If E_VC_ERROR is returned, the dechunker will ignore further PDUs
|
||||
* until the start of the next one is detected. This can be used to
|
||||
* recover from chunking errors without losing the channel entirely. It
|
||||
* is up to the caller whether to treat a dechunking error as fatal for
|
||||
* the channel or not.
|
||||
*/
|
||||
enum vc_dechunker_status
|
||||
vc_dechunker_process_chunk(struct vc_dechunker *self,
|
||||
struct stream *s, int flags, int total_size);
|
||||
/**
|
||||
* Get the stream from a ready dechunker
|
||||
*
|
||||
* @param self virtual channel dechunker
|
||||
* @return input stream containing completed chunk
|
||||
*
|
||||
* Ownership of the stream passes to the caller
|
||||
*
|
||||
* Resets the dechunker state so that further chunks can be processed.
|
||||
*/
|
||||
struct stream *
|
||||
vc_dechunker_get_stream(struct vc_dechunker *self);
|
||||
|
||||
|
||||
#endif // DECHUNKER_H
|
||||
+3
-1
@@ -151,6 +151,8 @@ struct xrdp_drdynvc
|
||||
int (*data)(struct xrdp_process *id, int chan_id, char *data, int bytes);
|
||||
};
|
||||
|
||||
struct vc_dechunker; // Forward declaration
|
||||
|
||||
/* channel */
|
||||
struct xrdp_channel
|
||||
{
|
||||
@@ -158,7 +160,7 @@ struct xrdp_channel
|
||||
struct xrdp_mcs *mcs_layer;
|
||||
int drdynvc_channel_id;
|
||||
int drdynvc_state;
|
||||
struct stream *s;
|
||||
struct vc_dechunker *drdynvc_dc;
|
||||
struct xrdp_drdynvc drdynvcs[256];
|
||||
};
|
||||
|
||||
|
||||
+38
-59
@@ -23,7 +23,7 @@
|
||||
#endif
|
||||
|
||||
#include "libxrdp.h"
|
||||
#include "parse.h"
|
||||
#include "dechunker.h"
|
||||
#include "string_calls.h"
|
||||
#include "xrdp_channel.h"
|
||||
|
||||
@@ -78,7 +78,7 @@ xrdp_channel_delete(struct xrdp_channel *self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
free_stream(self->s);
|
||||
vc_dechunker_free(self->drdynvc_dc);
|
||||
g_memset(self, 0, sizeof(struct xrdp_channel));
|
||||
g_free(self);
|
||||
}
|
||||
@@ -550,11 +550,12 @@ xrdp_channel_process_drdynvc(struct xrdp_channel *self,
|
||||
struct stream *s)
|
||||
{
|
||||
int total_length;
|
||||
int length;
|
||||
int flags;
|
||||
int cmd;
|
||||
int rv;
|
||||
struct stream *ls;
|
||||
struct stream *ls = NULL;
|
||||
int free_ls = 0;
|
||||
enum vc_dechunker_status dechunker_status;
|
||||
|
||||
if (!s_check_rem_and_log(s, 8, "Parsing [MS-RDPBCGR] CHANNEL_PDU_HEADER"))
|
||||
{
|
||||
@@ -564,65 +565,32 @@ xrdp_channel_process_drdynvc(struct xrdp_channel *self,
|
||||
in_uint32_le(s, flags); /* flags */
|
||||
LOG_DEVEL(LOG_LEVEL_TRACE, "Received header [MS-RDPBCGR] CHANNEL_PDU_HEADER "
|
||||
"length %d, flags 0x%8.8x", total_length, flags);
|
||||
ls = NULL;
|
||||
switch (flags & 3)
|
||||
dechunker_status = vc_dechunker_process_chunk(
|
||||
self->drdynvc_dc,
|
||||
s, flags, total_length);
|
||||
switch (dechunker_status)
|
||||
{
|
||||
case 0: /* not first chunk and not last chunk */
|
||||
length = (int) (s->end - s->p);
|
||||
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPBCGR] data chunk (middle) "
|
||||
"length %d", length);
|
||||
if (length > s_rem_out(self->s))
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "[MS-RDPBCGR] Data chunk length is bigger than "
|
||||
"the remaining chunk buffer size. length %d, remaining %d",
|
||||
length, s_rem_out(self->s));
|
||||
return 1;
|
||||
}
|
||||
out_uint8a(self->s, s->p, length); /* append data to chunk buffer */
|
||||
in_uint8s(s, length); /* virtualChannelData */
|
||||
return 0;
|
||||
case 1: /* CHANNEL_FLAG_FIRST */
|
||||
free_stream(self->s);
|
||||
make_stream(self->s);
|
||||
init_stream(self->s, total_length);
|
||||
length = (int) (s->end - s->p);
|
||||
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPBCGR] data chunk (first) "
|
||||
"length %d", length);
|
||||
if (length > s_rem_out(self->s))
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "[MS-RDPBCGR] Data chunk length is bigger than "
|
||||
"the remaining chunk buffer size. length %d, remaining %d",
|
||||
length, s_rem_out(self->s));
|
||||
return 1;
|
||||
}
|
||||
out_uint8a(self->s, s->p, length); /* append data to chunk buffer */
|
||||
in_uint8s(s, length); /* virtualChannelData */
|
||||
return 0;
|
||||
case 2: /* CHANNEL_FLAG_LAST */
|
||||
length = (int) (s->end - s->p);
|
||||
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPBCGR] data chunk (last) "
|
||||
"length %d", length);
|
||||
if (length > s_rem_out(self->s))
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "[MS-RDPBCGR] Data chunk length is bigger than "
|
||||
"the remaining chunk buffer size. length %d, remaining %d",
|
||||
length, s_rem_out(self->s));
|
||||
return 1;
|
||||
}
|
||||
out_uint8a(self->s, s->p, length); /* append data to chunk buffer */
|
||||
in_uint8s(s, length); /* virtualChannelData */
|
||||
s_mark_end(self->s);
|
||||
self->s->p = self->s->data;
|
||||
ls = self->s;
|
||||
break;
|
||||
case 3: /* CHANNEL_FLAG_FIRST and CHANNEL_FLAG_LAST */
|
||||
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPBCGR] data chunk (first and last) "
|
||||
"length %d", total_length);
|
||||
case E_VC_INLINE_CHUNK:
|
||||
ls = s;
|
||||
break;
|
||||
|
||||
case E_VC_IN_PROGRESS:
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case E_VC_READY:
|
||||
ls = vc_dechunker_get_stream(self->drdynvc_dc);
|
||||
// We now own the stream, so must delete it
|
||||
free_ls = 1;
|
||||
break;
|
||||
|
||||
case E_VC_ERROR:
|
||||
// Error has been logged
|
||||
return 1;
|
||||
|
||||
default:
|
||||
LOG(LOG_LEVEL_ERROR, "Received [MS-RDPBCGR] data chunk with "
|
||||
"unknown flag 0x%8.8x", (int) (flags & 3));
|
||||
LOG(LOG_LEVEL_ERROR, "Dechunker returned unknown error %d",
|
||||
(int)dechunker_status);
|
||||
return 1;
|
||||
}
|
||||
if (ls == NULL)
|
||||
@@ -657,6 +625,10 @@ xrdp_channel_process_drdynvc(struct xrdp_channel *self,
|
||||
"unknown command 0x%2.2x", cmd);
|
||||
break;
|
||||
}
|
||||
if (free_ls)
|
||||
{
|
||||
free_stream(ls);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -806,6 +778,13 @@ xrdp_channel_drdynvc_start(struct xrdp_channel *self)
|
||||
DRDYNVC_SVC_CHANNEL_NAME);
|
||||
rv = -1;
|
||||
}
|
||||
else if ((self->drdynvc_dc =
|
||||
vc_dechunker_init(DRDYNVC_SVC_CHANNEL_NAME,
|
||||
CHANNEL_CHUNK_LENGTH)) == NULL)
|
||||
{
|
||||
LOG(LOG_LEVEL_ERROR, "No memory");
|
||||
rv = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->drdynvc_channel_id = (dci->chanid - MCS_GLOBAL_CHANNEL) - 1;
|
||||
|
||||
@@ -14,6 +14,7 @@ check_PROGRAMS = test_common
|
||||
test_common_SOURCES = \
|
||||
test_common.h \
|
||||
test_common_main.c \
|
||||
test_dechunker.c \
|
||||
test_fifo_calls.c \
|
||||
test_list_calls.c \
|
||||
test_list16_calls.c \
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
char *
|
||||
bin_to_hex(const char *input, int length);
|
||||
|
||||
Suite *make_suite_test_dechunker(void);
|
||||
Suite *make_suite_test_fifo(void);
|
||||
Suite *make_suite_test_list(void);
|
||||
Suite *make_suite_test_list16(void);
|
||||
|
||||
@@ -41,24 +41,75 @@ bin_to_hex(const char *input, int length)
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
run_suite(const char *test_name)
|
||||
{
|
||||
const char *env = getenv("TEST_NAME");
|
||||
return (env == NULL || strcmp(env, test_name) == 0);
|
||||
}
|
||||
|
||||
int main (void)
|
||||
{
|
||||
int number_failed;
|
||||
SRunner *sr;
|
||||
|
||||
sr = srunner_create (make_suite_test_fifo());
|
||||
sr = srunner_create (NULL);
|
||||
if (run_suite("dechunker"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_dechunker());
|
||||
}
|
||||
if (run_suite("fifo"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_fifo());
|
||||
}
|
||||
if (run_suite("list"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_list());
|
||||
}
|
||||
if (run_suite("list16"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_list16());
|
||||
}
|
||||
if (run_suite("parse"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_parse());
|
||||
}
|
||||
if (run_suite("set_int"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_set_int());
|
||||
}
|
||||
if (run_suite("string"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_string());
|
||||
}
|
||||
if (run_suite("unicode"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_string_unicode());
|
||||
}
|
||||
if (run_suite("os_calls"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_os_calls());
|
||||
}
|
||||
if (run_suite("ssl_calls"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_ssl_calls());
|
||||
}
|
||||
if (run_suite("base64"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_base64());
|
||||
}
|
||||
if (run_suite("guid"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_guid());
|
||||
}
|
||||
if (run_suite("scancode"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_scancode());
|
||||
}
|
||||
if (run_suite("timers"))
|
||||
{
|
||||
srunner_add_suite(sr, make_suite_test_timers());
|
||||
}
|
||||
|
||||
srunner_set_tap(sr, "-");
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user