You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6.9 KiB

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. Как пользоваться

#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) — неподдерживаемый тип поля.