Consolidate serialization format and ownership documentation in header

This commit is contained in:
evgeny committed 2026-10-21 17:54:20 +02:00
1 parent ace9cb70d0
commit 1c4010ebed
2 files changed
+15 -118

No files matched your search

+15 -1
View File
@@ -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);
-117
View File
@@ -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)` — неподдерживаемый тип поля.