# Binary Serialization Library ## 1. Назначение Библиотека бинарной сериализации C-структур с динамическими полями (строки, массивы, singly-linked списки) в компактный бинарный буфер и обратно. Предназначена для кодирования/декодирования сообщений сетевого протокола (контрольный сервер, синхронизация БД, обмен конфигурацией). Что сериализуется: - Фиксированные поля (целые, флаги, структуры) — копируются «как есть». - ASCIIZ-строки (`char*`) — длина определяется через `strlen`, сохраняется с терминальным нулём. - Динамические массивы (`uint8_t*`) — счётчик элементов в поле `UINT8`/`UINT16`/`UINT32` или ровно 1 элемент (`ARRAY_FIXED`). - Singly-linked списки — `next`-указатели не сериализуются, сохраняются только `count+data`. Формат: header (`header_len` байт, передаётся отдельно) + данные полей. Длина переменных полей кодируется 2 байтами (`uint16_t`, big-endian). Потокобезопасность: нет, однопоточное использование. ## 2. Как пользоваться ```c #include "../lib/serialize.h" // --- 1. Определяем структуру --- typedef struct Node { uint32_t id; struct Node *next; } Node; typedef struct { uint8_t version; uint8_t name_len; char *name; // ASCIIZ (elem_size=1) uint16_t addrs_cnt; uint8_t *addrs; // массив байт (elem_size=1) Node *list; // linked list } Message; // --- 2. Описываем схему --- static const struct SerializeField msg_fields[] = { {offsetof(Message, version), sizeof(uint8_t), SERIALIZE_TYPE_FIXED, 0}, {offsetof(Message, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(Message, name_len)}, {offsetof(Message, addrs), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(Message, addrs_cnt)}, {offsetof(Message, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)} }; static const struct SerializeSchema msg_schema = { .field_count = 4, .struct_size = sizeof(Message), .max_size = 4096, // 0 = без лимита .header_len = 4, // байты заголовка (напр. версия протокола) .fields = msg_fields }; // --- 3. Сериализация --- Message msg = { .version = 1, .name = "test", .name_len = 4, .list = NULL }; msg.addrs_cnt = 2; msg.addrs = u_malloc(2); msg.addrs[0] = 0xAA; msg.addrs[1] = 0xBB; uint8_t header[] = {0x01, 0x00, 0x00, 0x00}; // 4-байтный заголовок uint8_t *buf = NULL; size_t len; if (serialize_encode(&msg, &msg_schema, header, &buf, &len) != SERIALIZE_ERR_OK) goto fail; // buf содержит: header (4) + version (1) + [len=2]name(4) + '\0' + [len=2]addrs(2) // --- 4. Десериализация (buf без заголовка!) --- Message *restored = NULL; uint8_t *data_ptr = buf + msg_schema.header_len; // пропускаем header if (serialize_decode(data_ptr, len - msg_schema.header_len, &msg_schema, (void**)&restored) != SERIALIZE_ERR_OK) goto fail; // restored->name == "test", restored->name_len == 4 // restored->addrs[0] == 0xAA, restored->addrs_cnt == 2 // restored->list == NULL (список пуст — не падает) // --- 5. Очистка --- serialize_free(&msg_schema, (void**)&restored); // освобождает всё: name, addrs, list, структуру u_free(buf); ``` **Ключевые нюансы:** - Поля в `schema.fields` должны идти в порядке возрастания `offset` в структуре. - `serialize_decode` принимает буфер **без заголовка** — передаётся `buf + schema.header_len`. - `serialize_free` освобождает структуру и все вложенные динамические поля (строки, массивы, списки). - При ошибке `serialize_decode` **автоматически освобождает** всю частично выделенную память → `*structure = NULL`. - `max_size > 0` — жёсткий лимит, при превышении `SERIALIZE_ERR_SIZE`. - Длина переменных полей всегда 2 байта (максимум 65535 элементов). - ASCIIZ-строки (`elem_size=1`) сохраняются с терминальным `\0`; пустая строка → 1 байт (`\0`). - Для linked list поле `len_offset` указывает смещение `next`-указателя внутри узла. - Передавать буфер в `serialize_decode` **без заголовка** — он не знает про `header_len`. ## 3. API | Функция | Назначение | |---------|-----------| | `serialize_encode(structure, schema, header, &buf, &len)` | Кодирует структуру. `header` копируется в начало `buf`. | | `serialize_decode(in_buf, in_len, schema, &structure)` | Декодирует буфер **без заголовка**. При ошибке всё освобождает. | | `serialize_free(schema, &structure)` | Рекурсивно освобождает структуру и динамические поля, затем обнуляет указатель. | **Типы полей (`SERIALIZE_TYPE_*`):** | Тип | `data` | `elem_size` | `len_offset` | |-----|--------|-------------|--------------| | `FIXED` (0) | Встроенные данные | Размер поля | Не исп. | | `ASCIIZ` (1) | `char*` | 1 | Не исп. (длина = `strlen`) | | `ARRAY_FIXED` (2) | `uint8_t*` (ровно 1 элемент) | Размер элемента | Не исп. | | `ARRAY_U8` (3) | `uint8_t*` | Размер элемента | Смещение `uint8_t`-счётчика | | `ARRAY_U16` (4) | `uint8_t*` | Размер элемента | Смещение `uint16_t`-счётчика | | `ARRAY_U32` (5) | `uint8_t*` | Размер элемента | Смещение `uint32_t`-счётчика | | `LINKED` (6) | `void*` (голова списка) | Размер узла | Смещение `next` внутри узла | **Коды возврата:** - `SERIALIZE_ERR_OK (0)` — успех. - `SERIALIZE_ERR_BUF (-1)` — ошибка выделения памяти. - `SERIALIZE_ERR_NULL (-2)` — NULL-указатель во входных параметрах. - `SERIALIZE_ERR_SIZE (-3)` — превышен `max_size`. - `SERIALIZE_ERR_NOTSUP (-4)` — неподдерживаемый тип поля.