Код протокола для кодирования/декодирования различных типов данных


Мне надо создать протокол для передачи данных различных типов через сокетное соединение. поэтому мне нужно, чтобы serialise данных в байтовый поток. Мне нужно только знаковых и беззнаковых 32-битных целых чисел, 64-разрядных целых чисел, строк и двоичных. Это только начало, но если кто может код этот комментарий я буду очень благодарен.

/* custom tlv protocol. byte1=type, bytes2-5=size, remaining=payload
   Following types:
   int32_t  //signed int 32 bit
   uint32_t //unsigned int 32 bit
   uint64_t //unsigned int 64 bit
   string - char* - int8_t*
   byte*  - unsigned char* -anything binary

   Everything uses TLV.  each message must contain type as first byte

   currently a message is one tlv but expand so a message is a linked list of tlvs
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>



/* include stdint.h but as this is demo just use these here */
typedef unsigned char        uint8_t;
typedef signed int           int32_t;
typedef unsigned int        uint32_t;

#ifdef WIN32
typedef unsigned __int64   uint64_t;
#else
typedef unsigned int64_t   uint64_t;
#endif

enum data_type {DTYPE_S32 = 0, DTYPE_U32 = 1, DTYPE_U64 = 2, DTYPE_STRING = 3, DTYPE_BINARY = 4 };

struct tlv_msg
{
 data_type datatype;   /* datatypes - 5 types */
 /* payload stored in a union */
 union{
    int32_t        s32val;   /*  signed int 1 */
    uint32_t       u32val;   /*  2 */
    uint64_t       u64val;   /*  3 */
    char*          strval;   /*  4 strings */
    unsigned char* binval;   /*  5 any binary data */
 };

 uint32_t bytelen;  /* no. bytes of union/data part */
};

size_t tlv_encode(data_type type, uint64_t input_length, tlv_msg* inputdata, 
                            unsigned char** outputdata, size_t *output_length);

/* allocation/de-allocation of memory */
size_t alloc_encode(data_type type, unsigned char** outputdata, size_t datasize = 0);
size_t alloc_decode(unsigned char* inputdata, tlv_msg** msg);

void free_encode(unsigned char** outputdata);
void free_decode(tlv_msg** msg);


size_t tlv_decode(unsigned char* inputdata, tlv_msg** msg);

void printbytes(unsigned char* bytes, size_t length);
void printmessage(tlv_msg* msg);

/* testing functions */
void tests32();
void testu32();
void testu64();
void teststring();
void testbinary();


int main() 
{ 
   //test encode/decond of various data values
   tests32();
   testu32();
   testu64();
   teststring();
   testbinary();

   return 0; 
} 



size_t tlv_encode(data_type type, uint64_t input_length, tlv_msg* inputdata, 
                            unsigned char** outputdata, size_t *output_length)
{
    if(!outputdata)
        return 0;  

    //1 byte for type, 4 for length, plus data size
   *output_length = (uint32_t)(1 + sizeof(uint32_t) + input_length);

    unsigned char* p = *outputdata;

   *p++ = (unsigned char)type;

   for (int i = sizeof(uint32_t) - 1; i >= 0; --i)
      *p++ = (unsigned char) ((input_length >> (i * 8)) & 0xFF);

   /* next job is to append actual data on end */
   while(input_length--) {
       switch(inputdata->datatype) {
       case  DTYPE_S32: *p++ = (unsigned char)(inputdata->s32val >> (input_length * 8) & 0xFF); break;
       case  DTYPE_U32: *p++ = (unsigned char)(inputdata->u32val >> (input_length * 8) & 0xFF); break;
       case  DTYPE_U64: *p++ = (unsigned char)(inputdata->u64val >> (input_length * 8) & 0xFF); break;
       case  DTYPE_STRING: *p++ = *inputdata->strval++; break;
       case  DTYPE_BINARY: *p++ = *inputdata->binval++; break;
      }
    }

    return *output_length;
}


size_t tlv_decode(unsigned char* inputdata, tlv_msg** msg) {
    if(!msg)
        alloc_decode(inputdata, msg);

    unsigned char* p = inputdata;

    /* skip first 5 bytes */
    for(int i = 0; i < 5; ++i)
       *p++;


   int length = (*msg)->bytelen - 5;

   while(length--) {

     switch((*msg)->datatype) {
        case DTYPE_S32:
          (*msg)->s32val += *p++ << (length * 8);
        break;
        case DTYPE_U32:
          (*msg)->u32val +=  *p++ << (length * 8);
        break;
        case DTYPE_U64:
          (*msg)->u64val += *p++ << (length * 8);
        break;
        case DTYPE_STRING:
         *(*msg)->strval++ = (char)*p++;
        break;
        case DTYPE_BINARY:
         *(*msg)->binval++ = (unsigned char)*p++;
        break;
        default:
          printf("tlv_decodegeneric error!!! unrecognised datatype %u\n", (*msg)->datatype);
        break;
     }
  }

  /* rewind to beginning of strings */
  switch((*msg)->datatype) {
  case DTYPE_STRING:
      length = (*msg)->bytelen-5;
      while(length--)
        *(*msg)->strval--;

      break;
  case DTYPE_BINARY:
      length = (*msg)->bytelen-5;
      while(length--)
        *(*msg)->binval--;          
      break;

  }


  return 0;
}



/* allocate for new message to be encoded */
size_t alloc_encode(data_type type, unsigned char** outputdata, size_t datasize) {
  size_t allosize = datasize;

  if(allosize == 0) {
    switch(type) {
      case  DTYPE_S32: allosize = 4; break;
      case  DTYPE_U32: allosize = 4; break;
      case  DTYPE_U64: allosize = 8; break;
      default:
         printf("alloc_encode error!!! no datasize for data type %u\n", type);
         return 0;
    }
  }

  allosize += 5;  /* append type and size fields */


  *outputdata = (unsigned char*)malloc(allosize);
  return allosize;
}

size_t alloc_decode(unsigned char* inputdata, tlv_msg** msg) {

   /* check if already malloc'd and if so free and realloc */
   if(*msg)
      printf("alloc_decode already called");

   /* length of data is in bytes 1,2,3,4 */
   size_t sz = 0;

   unsigned char* p = inputdata;

   /*first byte is type */
   unsigned char type = *p++;

   for(int i = 0; i < 4; ++i)
     sz += (unsigned char) (*p++ >> ((3-i) * 8) & 0xFF);

   if(!sz)
       printf("ERROR! zero bytes size found in input data\n");

   *msg = (tlv_msg*)malloc(sizeof(tlv_msg));

   if(*msg) {
       (*msg)->bytelen = sz + 5; /* plus size for header type and length */
       (*msg)->datatype = (data_type)type;

       if(type == DTYPE_STRING)
           (*msg)->strval = (char*)malloc((*msg)->bytelen);
       else if(type == DTYPE_BINARY)
           (*msg)->binval = (unsigned char*)malloc((*msg)->bytelen);


       /* set safe defaults */
       switch(type) {
        case 0: (*msg)->s32val = 0; break; 
        case 1: (*msg)->u32val = 0; break; 
        case 2: (*msg)->u64val = 0; break; 
        case 3: *(*msg)->strval = 0; break; 
        case 4: *(*msg)->binval = 0; break; 
      }
   }

   /* add on 5 for type and length */
   sz += 5;


   return sz;
}

void free_encode(unsigned char** outputdata) {
  free(*outputdata);
}

void free_decode(tlv_msg** msg) {
  switch((*msg)->datatype) {
    case DTYPE_STRING:  free((*msg)->strval); break;
    case DTYPE_BINARY:  free((*msg)->binval); break;
  }
  free(*msg);
}



void printmessage(tlv_msg* msg) {
   switch(msg->datatype) {
   case DTYPE_S32: printf("%i\n", msg->s32val); break;
   case DTYPE_U32: printf("%u\n", msg->u32val); break;
   case DTYPE_U64: printf("%u\n", msg->u64val); break;
   case DTYPE_STRING: printf("%s\n", msg->strval); break;
   case DTYPE_BINARY: printbytes(msg->binval, msg->bytelen-5); break;
   default:  printf("unknown data type\n"); break;
   }
}

void printbytes(unsigned char* bytes, size_t length) {
   for(size_t i = 0; i < length; ++i) {
     char c = 0;
     div_t stResult = div(bytes[i], 16);
     printf("%X%X ", stResult.quot, stResult.rem);
   }
   printf("\n");
}

void teststring() {
  /* test string type encode/decode */

tlv_msg msgstr; 
msgstr.datatype = DTYPE_STRING;  /* ie string type */

char* hellomsg = "Hello World!";

printf("testing string encode/decode, value to encode: %s\n", hellomsg);

size_t szlen = strlen(hellomsg)+1;
msgstr.strval = (char*)malloc(szlen);
strcpy(msgstr.strval, hellomsg);
msgstr.bytelen = szlen;

size_t output_length = 0;
unsigned char* outputmsg = 0;
size_t ret = alloc_encode(DTYPE_STRING, &outputmsg, msgstr.bytelen);
tlv_encode(msgstr.datatype, msgstr.bytelen, &msgstr, &outputmsg, &output_length);

/* print bytes as hex */
printbytes(outputmsg, output_length);


tlv_msg* decode_msg = 0;
size_t bytes = alloc_decode(outputmsg, &decode_msg);
tlv_decode(outputmsg, &decode_msg);
printmessage(decode_msg);

printf("completed testing string encode/decode, decoded value: %s\n", decode_msg->strval);


free_encode(&outputmsg);
free_decode(&decode_msg);


}

void tests32() {
  /* test signed int 32 bit type encode/decode */
  tlv_msg msg; 
  msg.datatype = DTYPE_S32;

  msg.bytelen = 4;
  msg.s32val = -65999;

  printf("testing s32 encode/decode, input value: %d\n", msg.s32val);


  size_t output_length = 0;
  unsigned char* outputmsg = 0;
  size_t ret = alloc_encode(DTYPE_S32, &outputmsg, msg.bytelen);
  tlv_encode(msg.datatype, msg.bytelen, &msg, &outputmsg, &output_length);

  /* print bytes as hex */
  printf("Hex string: ");
  printbytes(outputmsg, output_length);


  tlv_msg* decode_msg = 0;
  size_t bytes = alloc_decode(outputmsg, &decode_msg);
  tlv_decode(outputmsg, &decode_msg);
  printmessage(decode_msg);

  printf("decoded u32 value: %d\n", decode_msg->s32val);

  free_encode(&outputmsg);
  free_decode(&decode_msg);
}

void testu32() {
  /* test unsigned int 32 bit type encode/decode */
   tlv_msg msg; 
   msg.datatype = DTYPE_U32;

   msg.bytelen = 4;
   msg.u32val = 0xFFFFFFFF;  //257;

   printf("testing u32 encode/decode, input value: %u\n", msg.u32val);

   size_t output_length = 0;
   unsigned char* outputmsg = 0;
   size_t ret = alloc_encode(DTYPE_U32, &outputmsg, msg.bytelen);
   tlv_encode(msg.datatype, msg.bytelen, &msg, &outputmsg, &output_length);

   /* print bytes as hex */
   printf("Hex string: ");
   printbytes(outputmsg, output_length);

   tlv_msg* decode_msg = 0;
   size_t bytes = alloc_decode(outputmsg, &decode_msg);
   tlv_decode(outputmsg, &decode_msg);
   printmessage(decode_msg);

   printf("decoded u32 value: %u\n", decode_msg->u32val);

   free_encode(&outputmsg);
   free_decode(&decode_msg);
}

void testu64() {
  /* test unsigned int 64 bit type encode/decode */
   tlv_msg msg; 
   msg.datatype = DTYPE_U64;

   msg.bytelen = 8;
   msg.u64val = 0xFFFF;

   printf("testing u64 encode/decode, input value: %u\n", msg.u64val);


   size_t output_length = 0;
   unsigned char* outputmsg = 0;
   size_t ret = alloc_encode(DTYPE_U64, &outputmsg, msg.bytelen);
   tlv_encode(msg.datatype, msg.bytelen, &msg, &outputmsg, &output_length);

  /* print bytes as hex */
  printf("Hex string: ");
  printbytes(outputmsg, output_length);


  tlv_msg* decode_msg = 0;
  size_t bytes = alloc_decode(outputmsg, &decode_msg);
  tlv_decode(outputmsg, &decode_msg);
  printmessage(decode_msg);

  printf("decoded u64 value: %u\n", decode_msg->u64val);

  free_encode(&outputmsg);
  free_decode(&decode_msg);
}

void testbinary(){
   tlv_msg msgstr; 
   msgstr.datatype = DTYPE_BINARY;  /* ie binary data type */

   unsigned char binmsg[] = { 'a', 'b', 'c', '\0', 'a', 'b', 'c', '\0' };

   printf("testing binary encode/decode, value to encode is abcnullabcnull\n");

   msgstr.binval = (unsigned char*)malloc(sizeof(binmsg));
   memcpy(msgstr.binval, binmsg, sizeof(binmsg));
   msgstr.bytelen = sizeof(binmsg);

   size_t output_length = 0;
   unsigned char* outputmsg = 0;
   size_t ret = alloc_encode(DTYPE_BINARY, &outputmsg, msgstr.bytelen);
   tlv_encode(msgstr.datatype, msgstr.bytelen, &msgstr, &outputmsg, &output_length);

   /* print bytes as hex */
   printbytes(outputmsg, output_length);


   tlv_msg* decode_msg = 0;
   size_t bytes = alloc_decode(outputmsg, &decode_msg);
   tlv_decode(outputmsg, &decode_msg);
   printmessage(decode_msg);

   //not sure a printout is required for binary

   free_encode(&outputmsg);
   free_decode(&decode_msg);
}


3992
5
c
задан 26 ноября 2011 в 07:11 Источник Поделиться
Комментарии
1 ответ

Если я вижу смысл в преобразовании типов данных в сообщение, что может только быть, чтобы иметь возможность отправлять такие сообщения по сети, без необходимости выполнения какой-либо дополнительной интерпретации их внутренней структуры. Однако, ваша схема производит сообщения, которые еще требуют интерпретации, прежде чем они могут быть отправлены: тип сообщения должен быть осмотрен с целью выяснить, как передать его грузоподъемность, поскольку сообщение будет содержать фактические данные в случае типов с фиксированной длиной, а указатель на данные в случае типов переменной длины.

Я думаю, что это сделало бы намного больше смысла, чтобы построить свои сообщения так, что они могут быть отправлены в виде двоичных блоков, которые ничего не знают больше, чем их длина.

Сообщение должно быть ничего, кроме двух вещей: длина, и объем данных. Когда вы думаете на уровне сообщений и написание функций, отправлять и получать сообщения, Вы должны не быть связаны с внутренней структурой данных, содержащихся в сообщениях. Таким образом, понятие "тип данных" не имеет отношения на этом уровне.

На уровне, где вы только что получили блоке данных известной длины, теперь вы можете начать интерпретировать для того, чтобы извлечь полезные данные из него. Первый байт может содержать тип данных enum, а остальные байты могут быть реальное значение.


  • В случае типов данных с фиксированной длиной, утверждения могут быть в месте, чтобы гарантировать, что длина данных равна величина плюс одна для первого байта, в котором содержится тип данных enum.

  • Для типов данных переменной длины, длины значение длины данных минус один первый байт, который содержит тип данных enum.

Дополнительным преимуществом поступая таким образом, что у вас есть только одно выделение памяти в сообщение, а не два, один для структуры сообщений и один для данных переменной длины, содержащиеся в нем. Меньше ассигнований, равных снижение фрагментации памяти, которая может быть чрезвычайно важно, если приложение выполняется в течение длительного времени.

Еще одно соображение следует иметь в виду, является ли ваш базовый транспортный механизм обеспечивает эффективную буферизацию или нет. Если, случайно, он делает предложение эффективная буферность, то вы можете повысить производительность, читать/писать байты напрямую в/из транспортного механизма, вместо создания и заполнения структуры сообщения.

Так что, я не имею определенной точки в коде, потому что на мой взгляд он должен быть полностью переписан.

В следующий раз вы это пишите, пожалуйста, воздержитесь от использования жестко заданных констант, таких как 4, где вы должны как sizeof(int32_t), 8 в некоторых местах, где вы должны иметь оператор sizeof(int64_t), 8 в других местах, где вы должны иметь константы манифеста под названием "BITS_PER_BYTE" (или БПБ для краткости) и т. д. Почему мы используем констант манифеста, вовсе не потому, что их значения могут меняться, а потому, что их имена документов наши намерения и уточнить код.

Кроме того, для фиксированной длины типы данных, это хорошая идея, чтобы внять @Локи и изящные советы, и обратить должное внимание на порядок байтов данных: всегда используйте какой-либо из байтов для данных, которые должны быть разделены между разными машинами, и что лучше там чем в сетевом порядке байтов.

3
ответ дан 27 ноября 2011 в 08:11 Источник Поделиться