- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
118 lines
6.9 KiB
Markdown
118 lines
6.9 KiB
Markdown
# 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)` — неподдерживаемый тип поля.
|