From b613e3a8e0dcb8caa7bad97e50e928301ce84434 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 14 Apr 2026 02:03:59 +0300 Subject: [PATCH] serializer --- lib/serialize.c | 355 ++++++++++++++++++++++++----------------- lib/serialize.h | 114 +++++++++---- tests/test_serialize.c | 154 ++++++++++-------- 3 files changed, 385 insertions(+), 238 deletions(-) diff --git a/lib/serialize.c b/lib/serialize.c index d2ca49a0..6836d7ea 100644 --- a/lib/serialize.c +++ b/lib/serialize.c @@ -2,6 +2,9 @@ #include "serialize.h" #include +#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size схемы +#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип + static uint16_t length_encode(uint8_t* out, uint16_t val) { if (out) { out[0] = (uint8_t)(val >> 8); @@ -19,41 +22,21 @@ static uint16_t length_decode(const uint8_t* in, size_t in_len, size_t* consumed 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->len_type == 0) return 0; +static uint32_t get_var_count(const uint8_t* data, const struct SerializeField* f) { + if (!data || !f) return 0; const uint8_t* len_ptr = data + f->len_offset; - if (f->len_type == 1) { - return *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; - } + if (f->type == SERIALIZE_TYPE_ARRAY_U8) return *len_ptr; + if (f->type == SERIALIZE_TYPE_ARRAY_U16) return *(const uint16_t*)len_ptr; + if (f->type == SERIALIZE_TYPE_ARRAY_U32) 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->len_type == 0) return; +static void set_var_count(uint8_t* data, const struct SerializeField* f, uint16_t count) { + if (!data || !f) 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 >= 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; + if (f->type == SERIALIZE_TYPE_ARRAY_U8) *len_ptr = (uint8_t)count; + else if (f->type == SERIALIZE_TYPE_ARRAY_U16) *(uint16_t*)len_ptr = count; + else if (f->type == SERIALIZE_TYPE_ARRAY_U32) *(uint32_t*)len_ptr = count; } int serialize_encode(const void* structure, @@ -68,30 +51,45 @@ int serialize_encode(const void* structure, 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 { - size_t raw_len; - if (f->elem_size == 1) { // string + + if (f->type == SERIALIZE_TYPE_FIXED || f->type == SERIALIZE_TYPE_ARRAY_FIXED) { + total += f->elem_size; + } else if (f->type == SERIALIZE_TYPE_ASCIIZ || + f->type == SERIALIZE_TYPE_ARRAY_U8 || + f->type == SERIALIZE_TYPE_ARRAY_U16 || + f->type == SERIALIZE_TYPE_ARRAY_U32) { + uint32_t count = 0; + if (f->type == SERIALIZE_TYPE_ASCIIZ) { 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; + count = str ? (uint32_t)strlen(str) : 0; + } else { + count = get_var_count(data, f); } - uint16_t len = (raw_len > f->max_size) ? f->max_size : (uint16_t)raw_len; - total += length_encode(NULL, len); + uint16_t len = (count > 0xFFFFu) ? 0xFFFFu : (uint16_t)count; + total += 2; if (f->elem_size == 1) { - total += (size_t)len + 1; // data + null terminator + total += (size_t)len + 1; // данные + '\0' } else { total += (size_t)len * f->elem_size; } + } else if (f->type == SERIALIZE_TYPE_LINKED) { + // подсчёт узлов (максимум 65535) + void* cur = *(void**)(data + f->offset); + uint16_t count = 0; + while (cur && count < 0xFFFFu) { + count++; + cur = *(void**)((uint8_t*)cur + f->len_offset); + } + total += 2 + (size_t)count * f->elem_size; + } else { + return SERIALIZE_ERR_NOTSUP; } } + if (schema->max_size > 0 && total > schema->max_size) { + return SERIALIZE_ERR_SIZE; + } + uint8_t* buf = u_malloc(total); if (!buf) return SERIALIZE_ERR_BUF; @@ -105,36 +103,68 @@ int serialize_encode(const void* structure, 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); - if (sz > 0) { - memcpy(ptr, fld, sz); - ptr += sz; + if (f->type == SERIALIZE_TYPE_FIXED) { + if (f->elem_size > 0) { + memcpy(ptr, fld, f->elem_size); + ptr += f->elem_size; + } + } else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) { + const uint8_t* arr = *(const uint8_t**)fld; + if (f->elem_size > 0) { + if (arr) memcpy(ptr, arr, f->elem_size); + else memset(ptr, 0, f->elem_size); + } + ptr += f->elem_size; + } else if (f->type == SERIALIZE_TYPE_ASCIIZ || + f->type == SERIALIZE_TYPE_ARRAY_U8 || + f->type == SERIALIZE_TYPE_ARRAY_U16 || + f->type == SERIALIZE_TYPE_ARRAY_U32) { + uint32_t raw_count = 0; + if (f->type == SERIALIZE_TYPE_ASCIIZ) { + const char* str = *(const char**)fld; + raw_count = str ? (uint32_t)strlen(str) : 0; + } else { + raw_count = get_var_count(data, f); + } + uint16_t len = (raw_count > 0xFFFFu) ? 0xFFFFu : (uint16_t)raw_count; + if (len > 0 && f->type == SERIALIZE_TYPE_ASCIIZ) { + if (!*(const char**)fld) len = 0; + } else if (len > 0 && f->type != SERIALIZE_TYPE_ASCIIZ) { + if (!*(const uint8_t**)fld) len = 0; } - } else if (f->elem_size == 1) { // string - const char* str = *(const char**)fld; - 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; + + if (f->elem_size == 1) { // ASCIIZ + if (len > 0) { + const char* str = *(const char**)fld; + if (str) memcpy(ptr, str, len); + ptr += len; + } + *ptr++ = '\0'; + } else { // array + if (len > 0) { + const uint8_t* arr = *(const uint8_t**)fld; + if (arr) memcpy(ptr, arr, (size_t)len * f->elem_size); + ptr += (size_t)len * f->elem_size; + } + } + } else if (f->type == SERIALIZE_TYPE_LINKED) { + void* cur = *(void**)(data + f->offset); + uint16_t count = 0; + while (cur && count < 0xFFFFu) { + count++; + cur = *(void**)((uint8_t*)cur + f->len_offset); } - *ptr++ = '\0'; - } else { // array - const uint8_t* arr = *(const uint8_t**)fld; - 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; + ptr += length_encode(ptr, count); + + cur = *(void**)(data + f->offset); + for (uint16_t i = 0; i < count; i++) { + if (!cur) break; + memcpy(ptr, cur, f->elem_size); + ptr += f->elem_size; + cur = *(void**)((uint8_t*)cur + f->len_offset); } } } @@ -168,94 +198,121 @@ int serialize_decode(const uint8_t* in_buf, 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) { - serialize_free(schema, &obj); - *structure = NULL; - return SERIALIZE_ERR_BUF; + if (f->type == SERIALIZE_TYPE_FIXED) { + if (ptr + f->elem_size > end) goto decode_error; + if (f->elem_size > 0) { + memcpy(data + f->offset, ptr, f->elem_size); + ptr += f->elem_size; } - if (sz > 0) { - memcpy(data + f->offset, ptr, sz); - ptr += sz; + } else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) { + if (ptr + f->elem_size > end) goto decode_error; + uint8_t* arr = NULL; + if (f->elem_size > 0) { + arr = u_malloc(f->elem_size); + if (arr) memcpy(arr, ptr, f->elem_size); } - } else if (f->elem_size == 1) { // string + *(uint8_t**)(data + f->offset) = arr; + if (f->elem_size > 0 && !arr) goto decode_error; + ptr += f->elem_size; + } else if (f->type == SERIALIZE_TYPE_ASCIIZ || + f->type == SERIALIZE_TYPE_ARRAY_U8 || + f->type == SERIALIZE_TYPE_ARRAY_U16 || + f->type == SERIALIZE_TYPE_ARRAY_U32) { uint16_t len = length_decode(ptr, end - ptr, &consumed); - if (consumed == 0) { - serialize_free(schema, &obj); - *structure = NULL; - return SERIALIZE_ERR_BUF; - } + if (consumed == 0) goto decode_error; ptr += consumed; - 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) { - str = u_malloc(1); - if (str) str[0] = '\0'; + int is_str = (f->elem_size == 1); + size_t needed = is_str ? (size_t)len + 1 : (size_t)len * f->elem_size; + + if ((size_t)(end - ptr) < needed) goto decode_error; + + if (is_str) { + char* str = NULL; + if (len == 0) { + str = u_malloc(1); + if (str) str[0] = '\0'; + } else { + str = u_malloc(len + 1); + if (str) { + memcpy(str, ptr, len); + str[len] = '\0'; + } + } + *(char**)(data + f->offset) = str; + if (!str) goto decode_error; + ptr += needed; } else { - str = u_malloc(len + 1); - if (str) { - memcpy(str, ptr, len); - str[len] = '\0'; + uint8_t* arr = NULL; + if (needed > 0) { + arr = u_malloc(needed); + if (arr) memcpy(arr, ptr, needed); } + *(uint8_t**)(data + f->offset) = arr; + if (needed > 0 && !arr) goto decode_error; + ptr += needed; } - *(char**)(data + f->offset) = str; - if (f->len_type != 0) set_len_field(data, f, len); - - if (str == NULL) { - serialize_free(schema, &obj); - *structure = NULL; - return SERIALIZE_ERR_BUF; + if (f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32) { + set_var_count(data, f, len); } - ptr += needed; - } else { // array + } else if (f->type == SERIALIZE_TYPE_LINKED) { uint16_t len = length_decode(ptr, end - ptr, &consumed); - if (consumed == 0) { - serialize_free(schema, &obj); - *structure = NULL; - return SERIALIZE_ERR_BUF; - } + if (consumed == 0) goto decode_error; ptr += consumed; - 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; - } + void* head = NULL; + void* prev = NULL; + + for (uint16_t i = 0; i < len; i++) { + if (ptr + f->elem_size > end) { + // освободить уже созданную часть списка + void* n = head; + while (n) { + void* nxt = *(void**)((uint8_t*)n + f->len_offset); + u_free(n); + n = nxt; + } + goto decode_error; + } - uint8_t* arr = NULL; - if (arr_sz > 0) { - arr = u_malloc(arr_sz); - if (arr) memcpy(arr, ptr, arr_sz); - } + void* new_node = u_malloc(f->elem_size); + if (!new_node) { + void* n = head; + while (n) { + void* nxt = *(void**)((uint8_t*)n + f->len_offset); + u_free(n); + n = nxt; + } + goto decode_error; + } - *(uint8_t**)(data + f->offset) = arr; - if (f->len_type != 0) set_len_field(data, f, len); + memcpy(new_node, ptr, f->elem_size); + // обязательно обнуляем next в новом узле + *(void**)((uint8_t*)new_node + f->len_offset) = NULL; - if (arr_sz > 0 && arr == NULL) { - serialize_free(schema, &obj); - *structure = NULL; - return SERIALIZE_ERR_BUF; + if (!head) { + head = new_node; + } else { + *(void**)((uint8_t*)prev + f->len_offset) = new_node; + } + prev = new_node; + ptr += f->elem_size; } - ptr += arr_sz; + + *(void**)(data + f->offset) = head; + } else { + goto decode_error; } } *structure = obj; return SERIALIZE_ERR_OK; + +decode_error: + serialize_free(schema, &obj); + *structure = NULL; + return SERIALIZE_ERR_BUF; } void serialize_free(const struct SerializeSchema* schema, void** structure) { @@ -265,17 +322,23 @@ void serialize_free(const struct SerializeSchema* schema, void** 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) { - u_free(str); - *(char**)(data + f->offset) = NULL; + + if (f->type == SERIALIZE_TYPE_LINKED) { + void** headp = (void**)(data + f->offset); + void* node = *headp; + while (node) { + void* next = *(void**)((uint8_t*)node + f->len_offset); + u_free(node); + node = next; } - } else if (f->elem_size > 1) { - uint8_t* arr = *(uint8_t**)(data + f->offset); - if (arr) { - u_free(arr); - *(uint8_t**)(data + f->offset) = NULL; + *headp = NULL; + } else if (f->type == SERIALIZE_TYPE_ASCIIZ || + f->type == SERIALIZE_TYPE_ARRAY_FIXED || + (f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32)) { + uint8_t** p = (uint8_t**)(data + f->offset); + if (*p) { + u_free(*p); + *p = NULL; } } } diff --git a/lib/serialize.h b/lib/serialize.h index cffa0f2b..eee638c4 100644 --- a/lib/serialize.h +++ b/lib/serialize.h @@ -2,26 +2,77 @@ * @file serialize.h * @brief Бинарная сериализация структур с динамическим выделением памяти * - * Поддерживает структуры с полями переменной длины (ASCIIZ, ARRAY) через указатели. - * При десериализации автоматически выделяет память через u_malloc. + * Библиотека преобразует C-структуру, содержащую: + * - фиксированные поля, + * - ASCIIZ-строки (char*), + * - динамические массивы (uint8_t* / char*), + * - singly-linked списки + * в компактный бинарный буфер и обратно. * - * Формат буфера: - * [header_len байт][сериализованные данные] + * Поддерживаемые типы полей (см. SERIALIZE_TYPE_*): + * 0 — фиксированные данные (встроены в структуру) + * 1 — ASCIIZ-строка (char*, длина = strlen) + * 2 — массив ровно из 1 элемента (указатель) + * 3 — массив, количество элементов в UINT8 (по len_offset) + * 4 — массив, количество элементов в UINT16 (по len_offset) + * 5 — массив, количество элементов в UINT32 (по len_offset) + * 6 — singly-linked list (next-указатель по смещению len_offset) * - * Пример использования: - * @code - * // Сериализация + * Особенности: + * • Все динамические данные (строки, массивы, списки) выделяются через u_malloc. + * • При encode длина переменных полей кодируется всегда 2 байтами (uint16_t, big-endian). + * • Для linked list в буфере сохраняется только количество узлов + сырые данные узлов + * (next-указатели НЕ сериализуются, они восстанавливаются при decode). + * • serialize_decode принимает буфер БЕЗ заголовка. + * • serialize_free освобождает ВСЮ структуру и все вложенные динамические объекты. + * • Поля в schema.fields должны идти в порядке возрастания offset. + * • max_size (если > 0) — жёсткий лимит размера выходного буфера. + * + * @example + * // 1. Описание структуры + * typedef struct Node { + * uint32_t value; + * struct Node* next; // next по offset = 4 + * } Node; + * + * typedef struct { + * uint8_t id; + * char* name; + * uint8_t name_len; + * uint8_t* data; + * uint16_t data_cnt; + * Node* list; // linked list + * } MyStruct; + * + * // 2. Схема + * static const struct SerializeField my_fields[] = { + * {offsetof(MyStruct, id), 1, SERIALIZE_TYPE_FIXED, 0}, + * {offsetof(MyStruct, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(MyStruct, name_len)}, + * {offsetof(MyStruct, data), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(MyStruct, data_cnt)}, + * {offsetof(MyStruct, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)} + * }; + * + * static const struct SerializeSchema my_schema = { + * .field_count = 4, + * .struct_size = sizeof(MyStruct), + * .max_size = 4096, // опционально + * .header_len = 4, + * .fields = my_fields + * }; + * + * // 3. Использование * uint8_t header[] = {0x01, 0x02, 0x03, 0x04}; - * serialize_encode(&orig, &schema, header, &buf, &len); + * uint8_t *buf = NULL; + * size_t len; + * + * serialize_encode(&orig, &my_schema, header, &buf, &len); * - * // Десериализация (данные без заголовка) - * uint8_t* data = buf + schema.header_len; - * serialize_decode(data, len - schema.header_len, &schema, &restored); + * MyStruct *restored = NULL; + * uint8_t *data = buf + my_schema.header_len; + * serialize_decode(data, len - my_schema.header_len, &my_schema, (void**)&restored); * - * // Освобождение - * serialize_free(&schema, &restored); + * serialize_free(&my_schema, (void**)&restored); * u_free(buf); - * @endcode */ #ifndef SERIALIZE_H #define SERIALIZE_H @@ -29,16 +80,22 @@ #include #include "../lib/mem.h" +#define SERIALIZE_TYPE_FIXED 0 ///< фиксированные данные, elem_size = размер +#define SERIALIZE_TYPE_ASCIIZ 1 ///< char* (ASCIIZ, длина = strlen) +#define SERIALIZE_TYPE_ARRAY_FIXED 2 ///< *array ровно 1 элемент +#define SERIALIZE_TYPE_ARRAY_U8 3 ///< *array, счётчик UINT8 +#define SERIALIZE_TYPE_ARRAY_U16 4 ///< *array, счётчик UINT16 +#define SERIALIZE_TYPE_ARRAY_U32 5 ///< *array, счётчик UINT32 +#define SERIALIZE_TYPE_LINKED 6 ///< linked_list (next по смещению len_offset) + /** * @brief Описание одного поля структуры для сериализации */ struct SerializeField { - uint16_t offset; ///< смещение указателя в структуре - uint8_t elem_size; ///< 0=fixed, 1=ASCIIZ, >1=ARRAY (размер элемента в байтах) - uint16_t max_size; ///< макс. длина для ASCIIZ / макс. кол-во для ARRAY - 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) + uint16_t offset; ///< смещение поля в структуре + uint8_t elem_size; ///< размер элемента (для fixed = размер данных, для array/linked = размер одного элемента) + uint8_t type; ///< SERIALIZE_TYPE_* + uint16_t len_offset; ///< смещение поля-счётчика (игнорируется для type 0,1,2) }; /** @@ -46,14 +103,17 @@ struct SerializeField { */ struct SerializeSchema { uint16_t field_count; ///< число полей - uint16_t struct_size; ///< размер структуры для allocate - uint16_t header_len; ///< длина заголовка (произвольные байты в начале) - const struct SerializeField* fields; ///< массив описаний полей + uint16_t struct_size; ///< размер основной структуры + uint16_t max_size; ///< максимальный размер сериализованных данных (0 = без ограничения) + 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-указатель +#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size +#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип int serialize_encode(const void* structure, const struct SerializeSchema* schema, @@ -67,4 +127,4 @@ int serialize_decode(const uint8_t* in_buf, size_t in_len, void serialize_free(const struct SerializeSchema* schema, void** structure); -#endif // SERIALIZE_H \ No newline at end of file +#endif // SERIALIZE_H diff --git a/tests/test_serialize.c b/tests/test_serialize.c index b8f0bcc9..360bfa52 100644 --- a/tests/test_serialize.c +++ b/tests/test_serialize.c @@ -1,8 +1,8 @@ -// test_serialize.c - Unit tests for serialize library with dynamic allocation +// test_serialize.c - Unit tests for serialize library (with linked list + error cases) #include #include #include -#include // для offsetof +#include #include #include "../lib/serialize.h" @@ -13,6 +13,11 @@ #define FAIL(msg) do { printf("FAIL: %s\n", msg); exit(1); } while(0) #pragma pack(push, 1) +typedef struct TestNode { + uint32_t value; + struct TestNode* next; +} TestNode; + typedef struct { uint8_t id; uint16_t port; @@ -24,23 +29,26 @@ typedef struct { char* name; uint8_t addr_cnt; uint8_t* addrs; + TestNode* list; // linked list (type 6) } TestStruct; #pragma pack(pop) static const struct SerializeField test_fields[] = { - {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 + {offsetof(TestStruct, id), 1, SERIALIZE_TYPE_FIXED, 0}, + {offsetof(TestStruct, port), 2, SERIALIZE_TYPE_FIXED, 0}, + {offsetof(TestStruct, ip), 4, SERIALIZE_TYPE_FIXED, 0}, + {offsetof(TestStruct, big_val), 8, SERIALIZE_TYPE_FIXED, 0}, + {offsetof(TestStruct, signed_val),1, SERIALIZE_TYPE_FIXED, 0}, + {offsetof(TestStruct, signed32), 4, SERIALIZE_TYPE_FIXED, 0}, + {offsetof(TestStruct, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(TestStruct, name_len)}, + {offsetof(TestStruct, addrs), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(TestStruct, addr_cnt)}, + {offsetof(TestStruct, list), sizeof(TestNode), SERIALIZE_TYPE_LINKED, offsetof(TestNode, next)} }; static const struct SerializeSchema test_schema = { .field_count = sizeof(test_fields) / sizeof(test_fields[0]), .struct_size = sizeof(TestStruct), + .max_size = 0, .header_len = 4, .fields = test_fields }; @@ -57,22 +65,26 @@ static void test_alloc_roundtrip(void) { .big_val = 100, .signed_val = 10, .signed32 = 5000, + .list = NULL }; orig.name = strdup("hello"); orig.name_len = 5; orig.addrs = u_malloc(8); - orig.addrs[0] = 192; - orig.addrs[1] = 168; - orig.addrs[2] = 1; - orig.addrs[3] = 1; - orig.addrs[4] = 10; - orig.addrs[5] = 0; - orig.addrs[6] = 0; - orig.addrs[7] = 1; + orig.addrs[0] = 192; orig.addrs[1] = 168; orig.addrs[2] = 1; orig.addrs[3] = 1; + orig.addrs[4] = 10; orig.addrs[5] = 0; orig.addrs[6] = 0; orig.addrs[7] = 1; orig.addr_cnt = 8; - + + // === linked list === + TestNode* n1 = u_malloc(sizeof(TestNode)); + TestNode* n2 = u_malloc(sizeof(TestNode)); + n1->value = 100; + n1->next = n2; + n2->value = 200; + n2->next = NULL; + orig.list = n1; + uint8_t header[] = {0x01, 0x02, 0x03, 0x04}; - TEST("serialize_encode with header"); + TEST("serialize_encode with header + linked list"); int r = serialize_encode(&orig, &test_schema, header, &buf, &len); if (r != SERIALIZE_ERR_OK) FAIL("serialize_encode failed"); PASS(); @@ -88,12 +100,8 @@ static void test_alloc_roundtrip(void) { 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"); + TEST("check fixed fields"); + if (restored->id != orig.id || restored->port != orig.port) FAIL("fixed fields mismatch"); PASS(); TEST("check asciiz"); @@ -101,71 +109,87 @@ static void test_alloc_roundtrip(void) { PASS(); TEST("check array"); - if (!restored->addrs || restored->addrs[0] != 192) FAIL("addrs[0] mismatch"); + if (!restored->addrs || restored->addrs[0] != 192) FAIL("addrs mismatch"); PASS(); - TEST("check name_len"); - if (restored->name_len != 5) FAIL("name_len mismatch"); + TEST("check name_len / addr_cnt"); + if (restored->name_len != 5 || restored->addr_cnt != 8) FAIL("len fields mismatch"); PASS(); - TEST("check addr_cnt"); - if (restored->addr_cnt != 8) FAIL("addr_cnt mismatch"); + TEST("check linked list"); + if (!restored->list || restored->list->value != 100) FAIL("list head mismatch"); + if (!restored->list->next || restored->list->next->value != 200) FAIL("list next mismatch"); + if (restored->list->next->next != NULL) FAIL("list tail not NULL"); PASS(); - TEST("serialize_free"); + TEST("serialize_free (full structure with list)"); serialize_free(&test_schema, (void**)&restored); - if (restored) FAIL("ptr should be NULL"); + if (restored) FAIL("ptr should be NULL after free"); PASS(); u_free(buf); + + // очистка orig + free(orig.name); + u_free(orig.addrs); + u_free(n1); + u_free(n2); } -static void test_free(void) { +static void test_decode_error(void) { uint8_t* buf = NULL; size_t len; - TestStruct orig = { - .id = 2, - .port = 9000, - }; - orig.name = strdup("test"); - orig.name_len = 4; - orig.addrs = u_malloc(4); - orig.addrs[0] = 10; - orig.addrs[1] = 0; - 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 orig = { .id = 42, .port = 1234, .list = NULL }; + orig.name = strdup("error_test"); + orig.name_len = 10; + orig.addrs = u_malloc(2); + orig.addrs[0] = 0xAA; orig.addrs[1] = 0xBB; + orig.addr_cnt = 2; + + uint8_t header[] = {0xAA, 0xBB, 0xCC, 0xDD}; + int r = serialize_encode(&orig, &test_schema, header, &buf, &len); + if (r != SERIALIZE_ERR_OK) FAIL("encode for error test 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"); + uint8_t* data_ptr = buf + test_schema.header_len; + size_t short_len = (len - test_schema.header_len) / 2; // намеренно неполный буфер + + TEST("serialize_decode on short buffer (should free internally)"); + r = serialize_decode(data_ptr, short_len, &test_schema, (void**)&restored); + if (r != SERIALIZE_ERR_BUF) FAIL("expected SERIALIZE_ERR_BUF on truncated data"); + if (restored != NULL) FAIL("restored must be NULL after error (free was called)"); PASS(); - + u_free(buf); free(orig.name); u_free(orig.addrs); } +static void test_free_null_and_double(void) { + TestStruct* p = NULL; + TEST("serialize_free on NULL pointer"); + serialize_free(&test_schema, (void**)&p); // должно ничего не делать + if (p != NULL) FAIL("NULL pointer was changed"); + PASS(); + + // уже освобождённая структура + TestStruct* dummy = u_malloc(sizeof(TestStruct)); + memset(dummy, 0, sizeof(TestStruct)); + TEST("serialize_free after already freed"); + serialize_free(&test_schema, (void**)&dummy); + if (dummy != NULL) FAIL("pointer not set to NULL"); + PASS(); +} + /* ------------------------------------------------------------------ */ int main(void) { - printf("=== Serialize Library Tests (updated for new len_type) ===\n\n"); + printf("=== Serialize Library Tests (linked list + error handling) ===\n\n"); test_alloc_roundtrip(); - test_free(); + test_decode_error(); + test_free_null_and_double(); printf("\n=== All tests passed ===\n"); return 0;