From 1c4010ebed41b4ebfbaa467fbb3448be84904262 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 17:54:20 +0200 Subject: [PATCH] Consolidate serialization format and ownership documentation in header --- lib/serialize.h | 16 +++++- lib/serialize_doc.md | 117 ------------------------------------------- 2 files changed, 15 insertions(+), 118 deletions(-) delete mode 100644 lib/serialize_doc.md diff --git a/lib/serialize.h b/lib/serialize.h index 5d2c8788..b324b61a 100644 --- a/lib/serialize.h +++ b/lib/serialize.h @@ -32,6 +32,8 @@ * • elem_size=1 добавляет NUL и для byte-массивов. * • Linked nodes копируются целиком, без рекурсивного кодирования вложенных указателей. * • Этот формат зависит от ABI сырых полей; сериализация не нормализует размер/выравнивание структур. + * • Схема доверенная: offsets, типы, размеры и выравнивание полей/next проверяет её автор, не decode. + * • Независимые операции не используют общее изменяемое состояние; доступ к одной структуре согласует владелец. * * @example * // 1. Описание структуры @@ -116,7 +118,7 @@ struct SerializeSchema { uint16_t struct_size; ///< размер основной структуры uint16_t max_size; ///< максимальный размер сериализованных данных (0 = без ограничения) uint16_t header_len; ///< длина заголовка - const struct SerializeField* fields; + const struct SerializeField* fields; ///< field_count описателей в порядке записи; схема заимствуется на время вызова }; #define SERIALIZE_ERR_OK 0 ///< успех @@ -125,16 +127,28 @@ struct SerializeSchema { #define SERIALIZE_ERR_SIZE -3 ///< encode: превышен max_size #define SERIALIZE_ERR_NOTSUP -4 ///< encode: неподдерживаемый тип поля +/* Кодирует structure по schema; при header_len > 0 header указывает минимум на header_len байт. + * При успехе *out_buf — новый блок u_malloc (освободить u_free), *out_len — фактически записанная длина с header. + * Входная структура и её вложенные данные остаются у владельца. На ошибке выходные значения не меняются. + * Возврат: OK, NULL (обязательные аргументы), BUF (OOM), SIZE (max_size), NOTSUP (неизвестный тип). + * ASCIIZ требует elem_size=1; порядок fields задаёт формат, сортировка по offset не требуется. */ int serialize_encode(const void* structure, const struct SerializeSchema* schema, const uint8_t* header, uint8_t** out_buf, size_t* out_len); +/* Декодирует in_len байт БЕЗ header: передавать buf + header_len, len - header_len. + * При успехе *structure — новая структура с выделенными полями; очищать serialize_free с той же схемой. + * При ошибке частично созданные объекты освобождаются, *structure = NULL. Старый *structure не освобождает. + * max_size не ограничивает decode; хвост после последнего поля игнорируется. + * Возврат: OK, NULL (аргументы), BUF (OOM, обрезанные данные или неизвестный тип). */ int serialize_decode(const uint8_t* in_buf, size_t in_len, const struct SerializeSchema* schema, void** structure); +// Освобождает выделенную через u_malloc структуру, поля схемы и узлы списков, обнуляет *structure; NULL допустим. +// Вложенные указатели внутри элементов массивов/узлов не обходит; к структуре на стеке применять нельзя. void serialize_free(const struct SerializeSchema* schema, void** structure); diff --git a/lib/serialize_doc.md b/lib/serialize_doc.md deleted file mode 100644 index 25153cbb..00000000 --- a/lib/serialize_doc.md +++ /dev/null @@ -1,117 +0,0 @@ -# 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)` — неподдерживаемый тип поля.