HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcMinor

Parse a contrived message protocol with first byte data length followed by data

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
protocolwithbytemessagelengthfollowedcontrivedparsefirstdata

Problem

I think this code to parse a contrived message protocol with first byte data length followed by data is a little ugly. Has anyone got any suggestions on how to make it more elegant and more robust?

```
#include
#include
#include

#define LENGTHBYTES 1

void splitmessages(unsigned char* bstream, int length);
void printmsg(unsigned char* msg);

int main() {

unsigned char stream[] = {2, 'A', 'A', 1, 'B', 3, 'C', 'C', 'C', 1, 'D' };
int len = sizeof(stream) / sizeof(stream[0]);

printf("\nTest 1 - four messages together\n");
splitmessages(stream, len); //works

printf("\nTest 2 - two complete msgs, then 2 complete msgs\n");
splitmessages(stream, len-2); //works
splitmessages(stream+9, 2);

//split across a message - in data
printf("\nTest 3 - messages split across data part\n");
splitmessages(stream, len-4); //works
splitmessages(stream+7, 4);

//split across a message - just after size
printf("\nTest 4 - messages split just after size part\n");
splitmessages(stream, len-5);
splitmessages(stream+6, 5);

//split across a message - just before size
printf("\nTest 5 - messages split just before size part\n");
splitmessages(stream, len-6);
splitmessages(stream+5, 6);

return 0;
}

void splitmessages(unsigned char* bstream, int length) {
//Create msg as soon as you have size and save as static
static unsigned char* msg = 0;
static int tempbufposition = 0;
static int tocompletemsg = 0;

enum STAGE { SIZE, DATA };
STAGE stage = SIZE;
int size = 0;

//adjust stage to DATA if already have start of msg
if (tocompletemsg != 0)
stage = DATA;

//get cached size
if(tempbufposition != 0 && msg)
size = msg[0];

for(int i=0; i<length; ++i){
switch(stage) {
case SIZE:
size = bstream[i];
if(msg) {
free(msg);
msg = 0;
tempbufposition = 0;
tocompletemsg = 0;
}
//re-create complete ms

Solution

Below is an alternative example of how messages could be extracted. Instead of a state machine with saved-state inside your splitmessages function, I have moved the saved-state out of the function into a structure that is passed-in. I have also checked for unexpected data at the start of the function. The return value is 0 on failure or the number of bytes consumed from the input stream on success. I didn't check for size bytes occurring in the wrong place - replace the memcpy with a function that checks the bytes copied for that.

Note that your protocol has to distinguish between data bytes and size bytes. I have arbitrarily made the boundary 32, the first printable char.

#include 
#include 

#define DATA_MIN_VALUE 32
#define DATA_MAX_SIZE  (DATA_MIN_VALUE)

struct message {
    size_t size;
    size_t remaining;
    char data[DATA_MAX_SIZE];
};  
typedef struct message Message;

static inline size_t min(size_t a, size_t b)
{
    return a = DATA_MIN_VALUE;
}

static size_t
get_message(Message *msg, const unsigned char *bstream, size_t length)
{
    size_t len;
    assert(length);

    if ((msg->remaining == 0) && is_data(bstream[0])) {
        /* Data instead of message; caller must handle - eg. discard data up
         * to next message */
        return 0;
    }

    if (is_data(bstream[0])) {
        msg->size = 0;
        msg->remaining = bstream[0];
    }

    len = min(length-1, msg->remaining);

    memcpy(&msg->data[msg->size], &bstream[1], len);
    msg->size += len;
    msg->remaining -= len;

    return len + 1;
}


The code should compile but is untested. Hope it helps to have another perspective.

Code Snippets

#include <string.h>
#include <assert.h>

#define DATA_MIN_VALUE 32
#define DATA_MAX_SIZE  (DATA_MIN_VALUE)

struct message {
    size_t size;
    size_t remaining;
    char data[DATA_MAX_SIZE];
};  
typedef struct message Message;

static inline size_t min(size_t a, size_t b)
{
    return a < b ? a : b;
}

static inline int is_data(unsigned char ch)
{
    return  ch >= DATA_MIN_VALUE;
}

static size_t
get_message(Message *msg, const unsigned char *bstream, size_t length)
{
    size_t len;
    assert(length);

    if ((msg->remaining == 0) && is_data(bstream[0])) {
        /* Data instead of message; caller must handle - eg. discard data up
         * to next message */
        return 0;
    }

    if (is_data(bstream[0])) {
        msg->size = 0;
        msg->remaining = bstream[0];
    }

    len = min(length-1, msg->remaining);

    memcpy(&msg->data[msg->size], &bstream[1], len);
    msg->size += len;
    msg->remaining -= len;

    return len + 1;
}

Context

StackExchange Code Review Q#16269, answer score: 2

Revisions (0)

No revisions yet.