diff --git a/lib/serialize.c b/lib/serialize.c new file mode 100644 index 00000000..ab60dc6b --- /dev/null +++ b/lib/serialize.c @@ -0,0 +1,234 @@ +// serialize.c - Universal binary serialization with dynamic allocation +#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; + } + if (out) { + out[0] = (uint8_t)(0x80 | (val >> 8)); + out[1] = (uint8_t)(val & 0xFF); + } + 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) { + if (consumed) *consumed = 0; + return 0; + } + if (consumed) *consumed = 2; + return ((in[0] & 0x3F) << 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; + 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; + } + return 0; +} + +static void set_len_field(uint8_t* data, const struct SerializeField* f, size_t len) { + if (!f || !data || !f->has_len) 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; + } +} + +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; +} + +int serialize_encode(const void* structure, + const struct SerializeSchema* schema, + const uint8_t* header, + 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; + } + } + + 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) { + 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); + if (len > 0) { + memcpy(ptr, str, len); + ptr += len; + } + *ptr++ = 0; + } else { + 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; + } + } + } + + *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; + + void* obj = u_malloc(schema->struct_size); + if (!obj) { + *structure = NULL; + return SERIALIZE_ERR_BUF; + } + + 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); + 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); + + char* str = NULL; + if (len > 0) { + str = u_malloc(len + 1); + if (str) { + if (copy_len > 0) memcpy(str, ptr, copy_len); + str[copy_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); + } + 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; + + uint8_t* arr = NULL; + if (arr_sz > 0) { + arr = u_malloc(arr_sz); + if (arr && ptr + arr_sz <= end) { + memcpy(arr, ptr, arr_sz); + } + } + *(uint8_t**)(data + f->offset) = arr; + if (f->has_len) { + set_len_field(data, f, len); + } + 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) { + u_free(str); + *(char**)(data + f->offset) = NULL; + } + } 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; + } + } + } + + u_free(*structure); + *structure = NULL; +} \ No newline at end of file diff --git a/lib/serialize.h b/lib/serialize.h new file mode 100644 index 00000000..5f942f55 --- /dev/null +++ b/lib/serialize.h @@ -0,0 +1,102 @@ +/** + * @file serialize.h + * @brief Бинарная сериализация структур с динамическим выделением памяти + * + * Поддерживает структуры с полями переменной длины (ASCIIZ, ARRAY) через указатели. + * При десериализации автоматически выделяет память через u_malloc. + * + * Формат буфера: + * [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); + * @endcode + */ +#ifndef SERIALIZE_H +#define SERIALIZE_H + +#include +#include +#include "../lib/mem.h" + +/** + * @brief Описание одного поля структуры для сериализации + */ +struct SerializeField { + uint16_t offset; ///< смещение указателя в структуре + 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 поля в структуре +}; + +/** + * @brief Схема структуры для сериализации + */ +struct SerializeSchema { + 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 указатель + +/** + * @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); + +/** + * @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 new file mode 100644 index 00000000..6307c6f9 --- /dev/null +++ b/tests/test_serialize.c @@ -0,0 +1,174 @@ +// test_serialize.c - Unit tests for serialize library with dynamic allocation + +#include +#include +#include +#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) + +#pragma pack(push, 1) +typedef struct { + 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; + 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)}, +}; + +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 +}; + +/* ------------------------------------------------------------------ */ +static void test_alloc_roundtrip(void) { + uint8_t* buf = NULL; + size_t len; + + TestStruct orig = { + .id = 1, + .port = 443, + .ip = 0x01020304, + .big_val = 100, + .signed_val = 10, + .signed32 = 5000, + }; + 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.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, + }; + 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* 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); +} + +/* ------------------------------------------------------------------ */ +int main(void) { + printf("=== Serialize Library Tests ===\n\n"); + + test_alloc_roundtrip(); + test_free(); + + printf("\n=== All tests passed ===\n"); + return 0; +} \ No newline at end of file