Add serialize library for binary serialization with dynamic allocation

- serialize.h/c: universal binary serialization for structures with
  variable-length fields (ASCIIZ, ARRAY) using pointers
- Auto allocation via u_malloc during deserialization
- Support for header (arbitrary bytes at buffer start)
- serialize_encode(): serialize structure to buffer
- serialize_decode(): deserialize from buffer (without header)
- serialize_free(): free structure and all its pointers
- tests/test_serialize.c: unit tests
This commit is contained in:
Evgeny committed 2026-04-13 23:08:54 +03:00
1 parent 3b31821316
commit dba092d88b
3 files changed
+510

No files matched your search

+234
View File
@@ -0,0 +1,234 @@
// serialize.c - Universal binary serialization with dynamic allocation
#include "serialize.h"
#include <string.h>
static uint16_t varint_encode(uint8_t* out, uint16_t val) {
if (val < 0x80) {
if (out) out[0] = (uint8_t)val;
return 1;
}
if (out) {
out[0] = (uint8_t)(0x80 | (val >> 8));
out[1] = (uint8_t)(val & 0xFF);
}
return 2;
}
static uint16_t varint_decode(const uint8_t* in, size_t in_len, size_t* consumed) {
if (!in || in_len < 1) {
if (consumed) *consumed = 0;
return 0;
}
if ((in[0] & 0x80) == 0) {
if (consumed) *consumed = 1;
return in[0];
}
if (in_len < 2) {
if (consumed) *consumed = 0;
return 0;
}
if (consumed) *consumed = 2;
return ((in[0] & 0x3F) << 8) | in[1];
}
static size_t get_len_field(const uint8_t* data, const struct SerializeField* f) {
if (!f || !data || !f->has_len) return 0;
const uint8_t* len_ptr = data + f->len_offset;
if (f->len_type == 1) {
return *len_ptr;
}
if (f->len_type == 2) {
return *(uint16_t*)len_ptr;
}
return 0;
}
static void set_len_field(uint8_t* data, const struct SerializeField* f, size_t len) {
if (!f || !data || !f->has_len) return;
uint8_t* len_ptr = data + f->len_offset;
if (f->len_type == 1) {
*len_ptr = (uint8_t)len;
} else if (f->len_type == 2) {
*(uint16_t*)len_ptr = (uint16_t)len;
}
}
static size_t fixed_field_size(const struct SerializeSchema* schema, size_t idx) {
if (idx + 1 < schema->field_count) {
return schema->fields[idx + 1].offset - schema->fields[idx].offset;
}
return 0;
}
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len) {
if (!schema || !structure || !out_buf || !out_len) return SERIALIZE_ERR_NULL;
const uint8_t* data = (const uint8_t*)structure;
size_t total = schema->header_len;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->elem_size == 0) {
total += fixed_field_size(schema, i);
} else if (f->elem_size == 1) {
const char* str = *(const char**)(data + f->offset);
size_t len = f->has_len ? get_len_field(data, f) : (str ? strlen(str) : 0);
if (len > f->max_size) len = f->max_size;
total += varint_encode(NULL, (uint16_t)len);
total += len + 1;
} else {
size_t len = f->has_len ? get_len_field(data, f) : f->max_size;
if (len > f->max_size) len = f->max_size;
total += varint_encode(NULL, (uint16_t)len);
total += len * f->elem_size;
}
}
uint8_t* buf = u_malloc(total);
if (!buf) return SERIALIZE_ERR_BUF;
uint8_t* ptr = buf;
if (header && schema->header_len > 0) {
memcpy(ptr, header, schema->header_len);
ptr += schema->header_len;
}
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
const uint8_t* fld = data + f->offset;
if (f->elem_size == 0) {
size_t sz = fixed_field_size(schema, i);
memcpy(ptr, fld, sz);
ptr += sz;
} else if (f->elem_size == 1) {
const char* str = *(const char**)fld;
size_t len = f->has_len ? get_len_field(data, f) : (str ? strlen(str) : 0);
if (len > f->max_size) len = f->max_size;
ptr += varint_encode(ptr, (uint16_t)len);
if (len > 0) {
memcpy(ptr, str, len);
ptr += len;
}
*ptr++ = 0;
} else {
const uint8_t* arr = *(const uint8_t**)fld;
size_t len = f->has_len ? get_len_field(data, f) : f->max_size;
if (len > f->max_size) len = f->max_size;
ptr += varint_encode(ptr, (uint16_t)len);
if (arr && len > 0) {
memcpy(ptr, arr, len * f->elem_size);
ptr += len * f->elem_size;
}
}
}
*out_buf = buf;
*out_len = ptr - buf;
return SERIALIZE_ERR_OK;
}
int serialize_decode(const uint8_t* in_buf,
size_t in_len,
const struct SerializeSchema* schema,
void** structure) {
if (!in_buf || !schema || !structure) return SERIALIZE_ERR_NULL;
void* obj = u_malloc(schema->struct_size);
if (!obj) {
*structure = NULL;
return SERIALIZE_ERR_BUF;
}
const uint8_t* ptr = in_buf;
const uint8_t* end = in_buf + in_len;
uint8_t* data = (uint8_t*)obj;
size_t consumed;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->elem_size == 0) {
size_t sz = fixed_field_size(schema, i);
if (ptr + sz > end) {
u_free(obj);
return SERIALIZE_ERR_BUF;
}
memcpy(data + f->offset, ptr, sz);
ptr += sz;
} else if (f->elem_size == 1) {
uint16_t len = varint_decode(ptr, end - ptr, &consumed);
if (len > f->max_size) len = f->max_size;
ptr += consumed;
size_t copy_len = len < (size_t)(end - ptr) ? len : (size_t)(end - ptr);
char* str = NULL;
if (len > 0) {
str = u_malloc(len + 1);
if (str) {
if (copy_len > 0) memcpy(str, ptr, copy_len);
str[copy_len] = 0;
}
} else {
str = u_malloc(1);
if (str) str[0] = 0;
}
*(char**)(data + f->offset) = str;
if (f->has_len) {
set_len_field(data, f, len);
}
ptr += len + 1;
} else {
uint16_t len = varint_decode(ptr, end - ptr, &consumed);
if (len > f->max_size) len = f->max_size;
ptr += consumed;
size_t arr_sz = len * f->elem_size;
uint8_t* arr = NULL;
if (arr_sz > 0) {
arr = u_malloc(arr_sz);
if (arr && ptr + arr_sz <= end) {
memcpy(arr, ptr, arr_sz);
}
}
*(uint8_t**)(data + f->offset) = arr;
if (f->has_len) {
set_len_field(data, f, len);
}
ptr += arr_sz;
}
}
*structure = obj;
return SERIALIZE_ERR_OK;
}
void serialize_free(const struct SerializeSchema* schema, void** structure) {
if (!schema || !structure || !*structure) return;
uint8_t* data = (uint8_t*)*structure;
for (size_t i = 0; i < schema->field_count; i++) {
const struct SerializeField* f = &schema->fields[i];
if (f->elem_size == 1) {
char* str = *(char**)(data + f->offset);
if (str) {
u_free(str);
*(char**)(data + f->offset) = NULL;
}
} else if (f->elem_size > 1) {
uint8_t* arr = *(uint8_t**)(data + f->offset);
if (arr) {
u_free(arr);
*(uint8_t**)(data + f->offset) = NULL;
}
}
}
u_free(*structure);
*structure = NULL;
}
+102
View File
@@ -0,0 +1,102 @@
/**
* @file serialize.h
* @brief Бинарная сериализация структур с динамическим выделением памяти
*
* Поддерживает структуры с полями переменной длины (ASCIIZ, ARRAY) через указатели.
* При десериализации автоматически выделяет память через u_malloc.
*
* Формат буфера:
* [header_len байт][сериализованные данные]
*
* Пример использования:
* @code
* // Сериализация
* uint8_t header[] = {0x01, 0x02, 0x03, 0x04};
* serialize_encode(&orig, &schema, header, &buf, &len);
*
* // Десериализация (данные без заголовка)
* uint8_t* data = buf + schema.header_len;
* serialize_decode(data, len - schema.header_len, &schema, &restored);
*
* // Освобождение
* serialize_free(&schema, &restored);
* u_free(buf);
* @endcode
*/
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/mem.h"
/**
* @brief Описание одного поля структуры для сериализации
*/
struct SerializeField {
uint16_t offset; ///< смещение указателя в структуре
uint8_t elem_size; ///< 0=fixed, 1=ASCIIZ, >1=ARRAY (размер элемента в байтах)
uint16_t max_size; ///< макс. длина для ASCIIZ / макс. кол-во для ARRAY
uint8_t has_len; ///< 1 если есть length field
uint8_t len_type; ///< 1=UINT8, 2=UINT16
uint16_t len_offset; ///< смещение length поля в структуре
};
/**
* @brief Схема структуры для сериализации
*/
struct SerializeSchema {
uint16_t field_count; ///< число полей
uint16_t struct_size; ///< размер структуры для allocate
uint16_t header_len; ///< длина заголовка (произвольные байты в начале)
const struct SerializeField* fields; ///< массив описаний полей
};
#define SERIALIZE_ERR_OK 0 ///< успех
#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти
#define SERIALIZE_ERR_NULL -2 ///< null указатель
/**
* @brief Сериализация структуры в бинарный буфер
* @param structure указатель на исходную структуру
* @param schema схема структуры
* @param header заголовок (или NULL если header_len=0)
* @param out_buf [out] выходной буфер (выделяется через u_malloc)
* @param out_len [out] размер буфера
* @return SERIALIZE_ERR_OK или код ошибки
*
* Формат выходного буфера:
* [header_len байт][сериализованные данные]
*/
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len);
/**
* @brief Десериализация из буфера в структуру
* @param in_buf буфер БЕЗ заголовка (уже смещённый на header_len)
* @param in_len размер данных (без header)
* @param schema схема структуры
* @param structure [out] указатель на восстановленную структуру
* @return SERIALIZE_ERR_OK или код ошибки
*
* Внимание: in_buf должен быть без заголовка!
* Буфер со смещением: uint8_t* data = buf + schema.header_len;
* Длина без header: size_t data_len = total_len - schema.header_len;
*/
int serialize_decode(const uint8_t* in_buf, size_t in_len,
const struct SerializeSchema* schema,
void** structure);
/**
* @brief Освобождение структуры и всех её указателей
* @param schema схема структуры
* @param structure [inout] указатель на структуру (устанавливается в NULL)
*
* Освобождает структуру и все её динамические указатели (ASCIIZ, ARRAY).
*/
void serialize_free(const struct SerializeSchema* schema, void** structure);
#endif // SERIALIZE_H