diff --git a/lib/serialize.c b/lib/serialize.c index ab60dc6b..d2ca49a0 100644 --- a/lib/serialize.c +++ b/lib/serialize.c @@ -2,62 +2,58 @@ #include "serialize.h" #include -static uint16_t varint_encode(uint8_t* out, uint16_t val) { - if (val < 0x80) { - if (out) out[0] = (uint8_t)val; - return 1; - } +static uint16_t length_encode(uint8_t* out, uint16_t val) { if (out) { - out[0] = (uint8_t)(0x80 | (val >> 8)); - out[1] = (uint8_t)(val & 0xFF); + out[0] = (uint8_t)(val >> 8); + out[1] = (uint8_t)val; } return 2; } -static uint16_t varint_decode(const uint8_t* in, size_t in_len, size_t* consumed) { - if (!in || in_len < 1) { - if (consumed) *consumed = 0; - return 0; - } - if ((in[0] & 0x80) == 0) { - if (consumed) *consumed = 1; - return in[0]; - } - if (in_len < 2) { +static uint16_t length_decode(const uint8_t* in, size_t in_len, size_t* consumed) { + if (!in || in_len < 2) { if (consumed) *consumed = 0; return 0; } if (consumed) *consumed = 2; - return ((in[0] & 0x3F) << 8) | in[1]; + return ((uint16_t)in[0] << 8) | in[1]; } static size_t get_len_field(const uint8_t* data, const struct SerializeField* f) { - if (!f || !data || !f->has_len) return 0; + if (!f || !data || f->len_type == 0) return 0; const uint8_t* len_ptr = data + f->len_offset; if (f->len_type == 1) { return *len_ptr; - } - if (f->len_type == 2) { - return *(uint16_t*)len_ptr; + } else if (f->len_type == 2) { + return *(const uint16_t*)len_ptr; + } else if (f->len_type == 4) { + return *(const uint32_t*)len_ptr; } return 0; } static void set_len_field(uint8_t* data, const struct SerializeField* f, size_t len) { - if (!f || !data || !f->has_len) return; + if (!f || !data || f->len_type == 0) return; uint8_t* len_ptr = data + f->len_offset; if (f->len_type == 1) { *len_ptr = (uint8_t)len; } else if (f->len_type == 2) { *(uint16_t*)len_ptr = (uint16_t)len; + } else if (f->len_type == 4) { + *(uint32_t*)len_ptr = (uint32_t)len; } } static size_t fixed_field_size(const struct SerializeSchema* schema, size_t idx) { - if (idx + 1 < schema->field_count) { - return schema->fields[idx + 1].offset - schema->fields[idx].offset; - } - return 0; + if (idx >= schema->field_count) return 0; + const struct SerializeField* f = &schema->fields[idx]; + if (f->elem_size != 0) return 0; + + size_t next_offset = (idx + 1 < schema->field_count) ? + schema->fields[idx + 1].offset : + schema->struct_size; + + return (next_offset > f->offset) ? (next_offset - f->offset) : 0; } int serialize_encode(const void* structure, @@ -66,154 +62,209 @@ int serialize_encode(const void* structure, uint8_t** out_buf, size_t* out_len) { if (!schema || !structure || !out_buf || !out_len) return SERIALIZE_ERR_NULL; - + const uint8_t* data = (const uint8_t*)structure; size_t total = schema->header_len; - + for (size_t i = 0; i < schema->field_count; i++) { const struct SerializeField* f = &schema->fields[i]; if (f->elem_size == 0) { total += fixed_field_size(schema, i); - } else if (f->elem_size == 1) { - const char* str = *(const char**)(data + f->offset); - size_t len = f->has_len ? get_len_field(data, f) : (str ? strlen(str) : 0); - if (len > f->max_size) len = f->max_size; - total += varint_encode(NULL, (uint16_t)len); - total += len + 1; } else { - size_t len = f->has_len ? get_len_field(data, f) : f->max_size; - if (len > f->max_size) len = f->max_size; - total += varint_encode(NULL, (uint16_t)len); - total += len * f->elem_size; + size_t raw_len; + if (f->elem_size == 1) { // string + const char* str = *(const char**)(data + f->offset); + raw_len = (f->len_type != 0) ? get_len_field(data, f) + : (str ? strlen(str) : 0); + } else { // array + const uint8_t* arr = *(const uint8_t**)(data + f->offset); + raw_len = (f->len_type != 0) ? get_len_field(data, f) + : f->max_size; + if (raw_len > 0 && !arr) raw_len = 0; + } + uint16_t len = (raw_len > f->max_size) ? f->max_size : (uint16_t)raw_len; + total += length_encode(NULL, len); + if (f->elem_size == 1) { + total += (size_t)len + 1; // data + null terminator + } else { + total += (size_t)len * f->elem_size; + } } } - + uint8_t* buf = u_malloc(total); if (!buf) return SERIALIZE_ERR_BUF; - + uint8_t* ptr = buf; if (header && schema->header_len > 0) { memcpy(ptr, header, schema->header_len); ptr += schema->header_len; } - + for (size_t i = 0; i < schema->field_count; i++) { const struct SerializeField* f = &schema->fields[i]; const uint8_t* fld = data + f->offset; - + if (f->elem_size == 0) { size_t sz = fixed_field_size(schema, i); - memcpy(ptr, fld, sz); - ptr += sz; - } else if (f->elem_size == 1) { + if (sz > 0) { + memcpy(ptr, fld, sz); + ptr += sz; + } + } else if (f->elem_size == 1) { // string const char* str = *(const char**)fld; - size_t len = f->has_len ? get_len_field(data, f) : (str ? strlen(str) : 0); - if (len > f->max_size) len = f->max_size; - ptr += varint_encode(ptr, (uint16_t)len); + size_t raw_len = (f->len_type != 0) ? get_len_field(data, f) + : (str ? strlen(str) : 0); + uint16_t len = (raw_len > f->max_size) ? f->max_size : (uint16_t)raw_len; + if (len > 0 && !str) len = 0; + + ptr += length_encode(ptr, len); if (len > 0) { memcpy(ptr, str, len); ptr += len; } - *ptr++ = 0; - } else { + *ptr++ = '\0'; + } else { // array const uint8_t* arr = *(const uint8_t**)fld; - size_t len = f->has_len ? get_len_field(data, f) : f->max_size; - if (len > f->max_size) len = f->max_size; - ptr += varint_encode(ptr, (uint16_t)len); - if (arr && len > 0) { - memcpy(ptr, arr, len * f->elem_size); - ptr += len * f->elem_size; + size_t raw_len = (f->len_type != 0) ? get_len_field(data, f) + : f->max_size; + uint16_t len = (raw_len > f->max_size) ? f->max_size : (uint16_t)raw_len; + if (len > 0 && !arr) len = 0; + + ptr += length_encode(ptr, len); + if (len > 0 && arr) { + memcpy(ptr, arr, (size_t)len * f->elem_size); + ptr += (size_t)len * f->elem_size; } } } - + *out_buf = buf; *out_len = ptr - buf; return SERIALIZE_ERR_OK; } int serialize_decode(const uint8_t* in_buf, - size_t in_len, - const struct SerializeSchema* schema, - void** structure) { - if (!in_buf || !schema || !structure) return SERIALIZE_ERR_NULL; - + size_t in_len, + const struct SerializeSchema* schema, + void** structure) { + if (!in_buf || !schema || !structure) { + if (structure) *structure = NULL; + return SERIALIZE_ERR_NULL; + } + void* obj = u_malloc(schema->struct_size); if (!obj) { *structure = NULL; return SERIALIZE_ERR_BUF; } - + memset(obj, 0, schema->struct_size); + const uint8_t* ptr = in_buf; const uint8_t* end = in_buf + in_len; uint8_t* data = (uint8_t*)obj; size_t consumed; - + for (size_t i = 0; i < schema->field_count; i++) { const struct SerializeField* f = &schema->fields[i]; - + if (f->elem_size == 0) { size_t sz = fixed_field_size(schema, i); if (ptr + sz > end) { - u_free(obj); + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; + } + if (sz > 0) { + memcpy(data + f->offset, ptr, sz); + ptr += sz; + } + } else if (f->elem_size == 1) { // string + uint16_t len = length_decode(ptr, end - ptr, &consumed); + if (consumed == 0) { + serialize_free(schema, &obj); + *structure = NULL; return SERIALIZE_ERR_BUF; } - memcpy(data + f->offset, ptr, sz); - ptr += sz; - } else if (f->elem_size == 1) { - uint16_t len = varint_decode(ptr, end - ptr, &consumed); - if (len > f->max_size) len = f->max_size; ptr += consumed; - size_t copy_len = len < (size_t)(end - ptr) ? len : (size_t)(end - ptr); - + + if (len > f->max_size) len = f->max_size; + + size_t needed = (size_t)len + 1; + if ((size_t)(end - ptr) < needed) { + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; + } + char* str = NULL; - if (len > 0) { + if (len == 0) { + str = u_malloc(1); + if (str) str[0] = '\0'; + } else { str = u_malloc(len + 1); if (str) { - if (copy_len > 0) memcpy(str, ptr, copy_len); - str[copy_len] = 0; + memcpy(str, ptr, len); + str[len] = '\0'; } - } else { - str = u_malloc(1); - if (str) str[0] = 0; } + *(char**)(data + f->offset) = str; - if (f->has_len) { - set_len_field(data, f, len); + if (f->len_type != 0) set_len_field(data, f, len); + + if (str == NULL) { + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; + } + ptr += needed; + } else { // array + uint16_t len = length_decode(ptr, end - ptr, &consumed); + if (consumed == 0) { + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; } - ptr += len + 1; - } else { - uint16_t len = varint_decode(ptr, end - ptr, &consumed); - if (len > f->max_size) len = f->max_size; ptr += consumed; - size_t arr_sz = len * f->elem_size; - + + if (len > f->max_size) len = f->max_size; + + size_t arr_sz = (size_t)len * f->elem_size; + if ((size_t)(end - ptr) < arr_sz) { + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; + } + uint8_t* arr = NULL; if (arr_sz > 0) { arr = u_malloc(arr_sz); - if (arr && ptr + arr_sz <= end) { - memcpy(arr, ptr, arr_sz); - } + if (arr) memcpy(arr, ptr, arr_sz); } + *(uint8_t**)(data + f->offset) = arr; - if (f->has_len) { - set_len_field(data, f, len); + if (f->len_type != 0) set_len_field(data, f, len); + + if (arr_sz > 0 && arr == NULL) { + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; } ptr += arr_sz; } } - + *structure = obj; return SERIALIZE_ERR_OK; } void serialize_free(const struct SerializeSchema* schema, void** structure) { if (!schema || !structure || !*structure) return; + uint8_t* data = (uint8_t*)*structure; - + for (size_t i = 0; i < schema->field_count; i++) { const struct SerializeField* f = &schema->fields[i]; - if (f->elem_size == 1) { char* str = *(char**)(data + f->offset); if (str) { @@ -228,7 +279,7 @@ void serialize_free(const struct SerializeSchema* schema, void** structure) { } } } - + u_free(*structure); *structure = NULL; -} \ No newline at end of file +} diff --git a/lib/serialize.h b/lib/serialize.h index 5f942f55..cffa0f2b 100644 --- a/lib/serialize.h +++ b/lib/serialize.h @@ -1,31 +1,30 @@ /** * @file serialize.h * @brief Бинарная сериализация структур с динамическим выделением памяти - * + * * Поддерживает структуры с полями переменной длины (ASCIIZ, ARRAY) через указатели. * При десериализации автоматически выделяет память через u_malloc. - * + * * Формат буфера: - * [header_len байт][сериализованные данные] - * + * [header_len байт][сериализованные данные] + * * Пример использования: * @code - * // Сериализация - * uint8_t header[] = {0x01, 0x02, 0x03, 0x04}; - * serialize_encode(&orig, &schema, header, &buf, &len); - * - * // Десериализация (данные без заголовка) - * uint8_t* data = buf + schema.header_len; - * serialize_decode(data, len - schema.header_len, &schema, &restored); - * - * // Освобождение - * serialize_free(&schema, &restored); - * u_free(buf); + * // Сериализация + * uint8_t header[] = {0x01, 0x02, 0x03, 0x04}; + * serialize_encode(&orig, &schema, header, &buf, &len); + * + * // Десериализация (данные без заголовка) + * uint8_t* data = buf + schema.header_len; + * serialize_decode(data, len - schema.header_len, &schema, &restored); + * + * // Освобождение + * serialize_free(&schema, &restored); + * u_free(buf); * @endcode */ #ifndef SERIALIZE_H #define SERIALIZE_H - #include #include #include "../lib/mem.h" @@ -35,68 +34,37 @@ */ struct SerializeField { uint16_t offset; ///< смещение указателя в структуре - uint8_t elem_size; ///< 0=fixed, 1=ASCIIZ, >1=ARRAY (размер элемента в байтах) + uint8_t elem_size; ///< 0=fixed, 1=ASCIIZ, >1=ARRAY (размер элемента в байтах) uint16_t max_size; ///< макс. длина для ASCIIZ / макс. кол-во для ARRAY - uint8_t has_len; ///< 1 если есть length field - uint8_t len_type; ///< 1=UINT8, 2=UINT16 - uint16_t len_offset; ///< смещение length поля в структуре + uint8_t len_type; ///< 0 = нет length field (для строк — strlen, для массивов — max_size) + ///< 1 = UINT8, 2 = UINT16, 4 = UINT32 (по смещению len_offset) + uint16_t len_offset; ///< смещение length-поля в структуре (игнорируется при len_type=0) }; /** * @brief Схема структуры для сериализации */ struct SerializeSchema { - uint16_t field_count; ///< число полей + uint16_t field_count; ///< число полей uint16_t struct_size; ///< размер структуры для allocate uint16_t header_len; ///< длина заголовка (произвольные байты в начале) const struct SerializeField* fields; ///< массив описаний полей }; -#define SERIALIZE_ERR_OK 0 ///< успех -#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти -#define SERIALIZE_ERR_NULL -2 ///< null указатель +#define SERIALIZE_ERR_OK 0 ///< успех +#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти +#define SERIALIZE_ERR_NULL -2 ///< null указатель -/** - * @brief Сериализация структуры в бинарный буфер - * @param structure указатель на исходную структуру - * @param schema схема структуры - * @param header заголовок (или NULL если header_len=0) - * @param out_buf [out] выходной буфер (выделяется через u_malloc) - * @param out_len [out] размер буфера - * @return SERIALIZE_ERR_OK или код ошибки - * - * Формат выходного буфера: - * [header_len байт][сериализованные данные] - */ int serialize_encode(const void* structure, const struct SerializeSchema* schema, const uint8_t* header, uint8_t** out_buf, size_t* out_len); -/** - * @brief Десериализация из буфера в структуру - * @param in_buf буфер БЕЗ заголовка (уже смещённый на header_len) - * @param in_len размер данных (без header) - * @param schema схема структуры - * @param structure [out] указатель на восстановленную структуру - * @return SERIALIZE_ERR_OK или код ошибки - * - * Внимание: in_buf должен быть без заголовка! - * Буфер со смещением: uint8_t* data = buf + schema.header_len; - * Длина без header: size_t data_len = total_len - schema.header_len; - */ int serialize_decode(const uint8_t* in_buf, size_t in_len, - const struct SerializeSchema* schema, - void** structure); + const struct SerializeSchema* schema, + void** structure); -/** - * @brief Освобождение структуры и всех её указателей - * @param schema схема структуры - * @param structure [inout] указатель на структуру (устанавливается в NULL) - * - * Освобождает структуру и все её динамические указатели (ASCIIZ, ARRAY). - */ void serialize_free(const struct SerializeSchema* schema, void** structure); #endif // SERIALIZE_H \ No newline at end of file diff --git a/tests/test_serialize.c b/tests/test_serialize.c index 6307c6f9..b8f0bcc9 100644 --- a/tests/test_serialize.c +++ b/tests/test_serialize.c @@ -1,57 +1,55 @@ // test_serialize.c - Unit tests for serialize library with dynamic allocation - #include #include #include +#include // для offsetof #include - #include "../lib/serialize.h" #define TEST(name) do { \ printf("TEST: %-40s ", name); fflush(stdout); \ } while(0) - -#define PASS() do { puts("PASS"); } while(0) -#define FAIL(msg) do { printf("FAIL: %s\n", msg); exit(1); } while(0) +#define PASS() do { puts("PASS"); } while(0) +#define FAIL(msg) do { printf("FAIL: %s\n", msg); exit(1); } while(0) #pragma pack(push, 1) typedef struct { - uint8_t id; + uint8_t id; uint16_t port; uint32_t ip; uint64_t big_val; - int8_t signed_val; - int32_t signed32; - uint8_t name_len; - char* name; - uint8_t addr_cnt; + int8_t signed_val; + int32_t signed32; + uint8_t name_len; + char* name; + uint8_t addr_cnt; uint8_t* addrs; } TestStruct; #pragma pack(pop) static const struct SerializeField test_fields[] = { - {offsetof(TestStruct, id), 0, 0, 0, 0, 0}, - {offsetof(TestStruct, port), 0, 0, 0, 0, 0}, - {offsetof(TestStruct, ip), 0, 0, 0, 0, 0}, - {offsetof(TestStruct, big_val), 0, 0, 0, 0, 0}, - {offsetof(TestStruct, signed_val), 0, 0, 0, 0, 0}, - {offsetof(TestStruct, signed32), 0, 0, 0, 0, 0}, - {offsetof(TestStruct, name), 1, 64, 1, 1, offsetof(TestStruct, name_len)}, - {offsetof(TestStruct, addrs), 1, 16, 1, 1, offsetof(TestStruct, addr_cnt)}, + {offsetof(TestStruct, id), 0, 0, 0, 0}, // fixed + {offsetof(TestStruct, port), 0, 0, 0, 0}, // fixed + {offsetof(TestStruct, ip), 0, 0, 0, 0}, // fixed + {offsetof(TestStruct, big_val), 0, 0, 0, 0}, // fixed + {offsetof(TestStruct, signed_val),0, 0, 0, 0}, // fixed + {offsetof(TestStruct, signed32), 0, 0, 0, 0}, // fixed + {offsetof(TestStruct, name), 1, 64, 1, offsetof(TestStruct, name_len)}, // string + uint8 len + {offsetof(TestStruct, addrs), 1, 16, 1, offsetof(TestStruct, addr_cnt)} // byte array + uint8 len }; static const struct SerializeSchema test_schema = { .field_count = sizeof(test_fields) / sizeof(test_fields[0]), .struct_size = sizeof(TestStruct), - .header_len = 4, - .fields = test_fields + .header_len = 4, + .fields = test_fields }; /* ------------------------------------------------------------------ */ static void test_alloc_roundtrip(void) { uint8_t* buf = NULL; size_t len; - + TestStruct orig = { .id = 1, .port = 443, @@ -72,60 +70,60 @@ static void test_alloc_roundtrip(void) { orig.addrs[6] = 0; orig.addrs[7] = 1; orig.addr_cnt = 8; - + uint8_t header[] = {0x01, 0x02, 0x03, 0x04}; TEST("serialize_encode with header"); int r = serialize_encode(&orig, &test_schema, header, &buf, &len); if (r != SERIALIZE_ERR_OK) FAIL("serialize_encode failed"); PASS(); - + TestStruct* restored = NULL; TEST("serialize_decode (data without header)"); uint8_t* data_ptr = buf + test_schema.header_len; r = serialize_decode(data_ptr, len - test_schema.header_len, &test_schema, (void**)&restored); if (r != SERIALIZE_ERR_OK) FAIL("serialize_decode failed"); PASS(); - + TEST("check restored not NULL"); if (!restored) FAIL("restored is NULL"); PASS(); - + TEST("check id"); if (restored->id != orig.id) FAIL("id mismatch"); PASS(); - + TEST("check port"); if (restored->port != orig.port) FAIL("port mismatch"); PASS(); - + TEST("check asciiz"); if (!restored->name || strcmp(restored->name, "hello") != 0) FAIL("name mismatch"); PASS(); - + TEST("check array"); if (!restored->addrs || restored->addrs[0] != 192) FAIL("addrs[0] mismatch"); PASS(); - + TEST("check name_len"); if (restored->name_len != 5) FAIL("name_len mismatch"); PASS(); - + TEST("check addr_cnt"); if (restored->addr_cnt != 8) FAIL("addr_cnt mismatch"); PASS(); - + TEST("serialize_free"); serialize_free(&test_schema, (void**)&restored); if (restored) FAIL("ptr should be NULL"); PASS(); - + u_free(buf); } static void test_free(void) { uint8_t* buf = NULL; size_t len; - + TestStruct orig = { .id = 2, .port = 9000, @@ -138,25 +136,25 @@ static void test_free(void) { orig.addrs[2] = 0; orig.addrs[3] = 1; orig.addr_cnt = 4; - + uint8_t header2[] = {0xFF, 0xEE, 0xDD, 0xCC}; TEST("serialize_encode for free"); int r = serialize_encode(&orig, &test_schema, header2, &buf, &len); if (r != SERIALIZE_ERR_OK) FAIL("serialize_encode failed"); PASS(); - + TestStruct* restored = NULL; TEST("serialize_decode for free"); uint8_t* data_ptr2 = buf + test_schema.header_len; r = serialize_decode(data_ptr2, len - test_schema.header_len, &test_schema, (void**)&restored); if (r != SERIALIZE_ERR_OK) FAIL("serialize_decode failed"); PASS(); - + TEST("serialize_free"); serialize_free(&test_schema, (void**)&restored); if (restored) FAIL("ptr should be NULL"); PASS(); - + u_free(buf); free(orig.name); u_free(orig.addrs); @@ -164,11 +162,11 @@ static void test_free(void) { /* ------------------------------------------------------------------ */ int main(void) { - printf("=== Serialize Library Tests ===\n\n"); - + printf("=== Serialize Library Tests (updated for new len_type) ===\n\n"); + test_alloc_roundtrip(); test_free(); - + printf("\n=== All tests passed ===\n"); return 0; -} \ No newline at end of file +}