TAAFT
Free mode
100% free
Freemium
Free Trial
Deals

BaseMax / c-binary-serialization

A lightweight, portable binary serialization library for C with zero external dependencies. Provides a clean API for converting structured data to compact binary format and back, with built-in support for endianness handling, versioning, schema evolution, and validation.

0 0 Language: C License: MIT Updated: 2mo ago

README

C Binary Serialization Library

A lightweight, portable binary serialization library for C with zero external dependencies. Provides a clean API for converting structured data to compact binary format and back, with built-in support for endianness handling, versioning, schema evolution, and validation.

Features

  • Compact Binary Format: Efficient space utilization with minimal overhead
  • Endianness Safe: Automatic conversion to network byte order (big-endian)
  • Schema Versioning: Built-in version tracking for format evolution
  • Schema Evolution: Read old data with new code, with sensible defaults
  • Data Validation: CRC32 checksum for integrity verification
  • File & Network I/O: Ready-to-use functions for common use cases
  • Type Safety: Explicit type encoding for validation
  • Zero Dependencies: Pure C99, only standard library required
  • Clean API: Simple, consistent interface for all operations

Supported Data Types

  • Integers: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
  • Floating Point: float, double
  • Boolean: bool
  • Strings: Null-terminated C strings (with length prefix)
  • Raw Bytes: Arbitrary byte arrays (with length prefix)

Building

make all

This builds:

  • libbinserde.a - Static library
  • Example programs: example_file, example_network, example_evolution

Running Examples

make run-examples

Or run individually:

./example_file
./example_network
./example_evolution

Cleaning

make clean

Quick Start

Basic Serialization/Deserialization

#include "binserde.h"

/* Serialization */
uint8_t buffer[1024];
binserde_writer_t writer;
binserde_writer_init(&writer, buffer, sizeof(buffer), 1); // version 1

binserde_write_header(&writer);
binserde_write_int32(&writer, 42);
binserde_write_string(&writer, "Hello, World!");
binserde_write_checksum(&writer);

size_t data_size = binserde_writer_position(&writer);

/* Deserialization */
binserde_reader_t reader;
binserde_reader_init(&reader, buffer, data_size);

uint32_t version;
binserde_read_header(&reader, &version);

int32_t value;
binserde_read_int32(&reader, &value);

char *text;
binserde_read_string(&reader, &text);

binserde_validate_checksum(&reader);

free(text);

File I/O

/* Write to file */
binserde_write_file("data.bin", buffer, data_size);

/* Read from file */
uint8_t *file_buffer;
size_t file_size;
binserde_read_file("data.bin", &file_buffer, &file_size);

/* ... use data ... */

free(file_buffer);

Binary Format Specification

The binary format consists of:

[Header]
  - Magic Number (4 bytes): 0x42534445 ("BSDE")
  - Version (4 bytes): Schema version number

[Data]
  - Type-specific encoding (see below)

[Footer]
  - Checksum (4 bytes): CRC32 of all preceding data

Type Encoding

  • Integers: Network byte order (big-endian)

    • 1 byte: int8_t, uint8_t
    • 2 bytes: int16_t, uint16_t
    • 4 bytes: int32_t, uint32_t
    • 8 bytes: int64_t, uint64_t
  • Floating Point: IEEE 754, network byte order

    • 4 bytes: float
    • 8 bytes: double
  • Boolean: 1 byte (0 = false, 1 = true)

  • String: Length-prefixed

    • Length (4 bytes, uint32_t)
    • Data (length bytes, UTF-8)
  • Bytes: Length-prefixed

    • Length (4 bytes, uint32_t)
    • Data (length bytes)

Schema Evolution

The library supports schema evolution through version numbers:

  1. Version in Header: Each serialized object includes schema version
  2. Forward Compatibility: Old readers can detect new versions
  3. Backward Compatibility: New readers can handle old data
  4. Default Values: Missing fields get sensible defaults

Example:

/* Version 1 */
typedef struct {
    int32_t id;
    char name[64];
} UserV1;

/* Version 2 (with new fields) */
typedef struct {
    int32_t id;
    char name[64];
    char email[64];      // New field
    uint32_t created_at; // New field
} UserV2;

/* Deserializer handles both versions */
err = binserde_read_header(&reader, &version);
if (version >= 2) {
    // Read new fields
} else {
    // Use defaults for new fields
}

See example_evolution.c for a complete example.

Error Handling

All functions return binserde_error_t:

  • BINSERDE_OK (0): Success
  • BINSERDE_ERR_NULL_PTR: Null pointer argument
  • BINSERDE_ERR_BUFFER_TOO_SMALL: Output buffer too small
  • BINSERDE_ERR_INVALID_DATA: Invalid or corrupted data
  • BINSERDE_ERR_VERSION_MISMATCH: Schema version mismatch
  • BINSERDE_ERR_CHECKSUM_FAILED: Checksum validation failed
  • BINSERDE_ERR_IO: File I/O error

Use binserde_error_string() to get human-readable error messages.

Network Usage

The library uses network byte order (big-endian) for all multi-byte values, making it suitable for network protocols:

/* Sender */
binserde_write_uint16(&writer, packet_id);
binserde_write_string(&writer, payload);
binserde_write_checksum(&writer);

size_t packet_size = binserde_writer_position(&writer);
send(socket, buffer, packet_size, 0); // POSIX sockets

/* Receiver */
recv(socket, recv_buffer, sizeof(recv_buffer), 0);

binserde_reader_init(&reader, recv_buffer, received_bytes);
binserde_read_header(&reader, &version);
binserde_read_uint16(&reader, &packet_id);
// ... read rest ...
binserde_validate_checksum(&reader);

See example_network.c for a complete simulation.

API Reference

Initialization

binserde_error_t binserde_writer_init(binserde_writer_t *writer,
                                      uint8_t *buffer,
                                      size_t capacity,
                                      uint32_t version);

binserde_error_t binserde_reader_init(binserde_reader_t *reader,
                                      const uint8_t *buffer,
                                      size_t size);

Header Operations

binserde_error_t binserde_write_header(binserde_writer_t *writer);
binserde_error_t binserde_read_header(binserde_reader_t *reader, uint32_t *version);

Write Operations

binserde_error_t binserde_write_int8(binserde_writer_t *writer, int8_t value);
binserde_error_t binserde_write_uint8(binserde_writer_t *writer, uint8_t value);
binserde_error_t binserde_write_int16(binserde_writer_t *writer, int16_t value);
binserde_error_t binserde_write_uint16(binserde_writer_t *writer, uint16_t value);
binserde_error_t binserde_write_int32(binserde_writer_t *writer, int32_t value);
binserde_error_t binserde_write_uint32(binserde_writer_t *writer, uint32_t value);
binserde_error_t binserde_write_int64(binserde_writer_t *writer, int64_t value);
binserde_error_t binserde_write_uint64(binserde_writer_t *writer, uint64_t value);
binserde_error_t binserde_write_float(binserde_writer_t *writer, float value);
binserde_error_t binserde_write_double(binserde_writer_t *writer, double value);
binserde_error_t binserde_write_bool(binserde_writer_t *writer, bool value);
binserde_error_t binserde_write_string(binserde_writer_t *writer, const char *value);
binserde_error_t binserde_write_bytes(binserde_writer_t *writer, 
                                      const uint8_t *data, uint32_t length);

Read Operations

binserde_error_t binserde_read_int8(binserde_reader_t *reader, int8_t *value);
binserde_error_t binserde_read_uint8(binserde_reader_t *reader, uint8_t *value);
binserde_error_t binserde_read_int16(binserde_reader_t *reader, int16_t *value);
binserde_error_t binserde_read_uint16(binserde_reader_t *reader, uint16_t *value);
binserde_error_t binserde_read_int32(binserde_reader_t *reader, int32_t *value);
binserde_error_t binserde_read_uint32(binserde_reader_t *reader, uint32_t *value);
binserde_error_t binserde_read_int64(binserde_reader_t *reader, int64_t *value);
binserde_error_t binserde_read_uint64(binserde_reader_t *reader, uint64_t *value);
binserde_error_t binserde_read_float(binserde_reader_t *reader, float *value);
binserde_error_t binserde_read_double(binserde_reader_t *reader, double *value);
binserde_error_t binserde_read_bool(binserde_reader_t *reader, bool *value);
binserde_error_t binserde_read_string(binserde_reader_t *reader, char **value);
binserde_error_t binserde_read_bytes(binserde_reader_t *reader,
                                     uint8_t **data, uint32_t *length);

Note: binserde_read_string() and binserde_read_bytes() allocate memory.
Caller must free the returned pointers.

Validation

binserde_error_t binserde_write_checksum(binserde_writer_t *writer);
binserde_error_t binserde_validate_checksum(binserde_reader_t *reader);

File I/O

binserde_error_t binserde_write_file(const char *filename,
                                     const uint8_t *buffer,
                                     size_t size);

binserde_error_t binserde_read_file(const char *filename,
                                    uint8_t **buffer,
                                    size_t *size);

Note: binserde_read_file() allocates memory. Caller must free the buffer.

Utilities

size_t binserde_writer_position(const binserde_writer_t *writer);
size_t binserde_reader_position(const binserde_reader_t *reader);
const char *binserde_error_string(binserde_error_t error);

Examples

Three complete examples are provided:

  1. example_file.c - File I/O with serialization
  2. example_network.c - Network packet serialization (simulated)
  3. example_evolution.c - Schema versioning and evolution

Build and run with:

make run-examples

Design Principles

  1. Simplicity: Clean, minimal API that's easy to understand
  2. Safety: Comprehensive bounds checking, no buffer overruns
  3. Portability: Pure C99, works on any platform
  4. Efficiency: Compact encoding, minimal overhead
  5. Reliability: Checksum validation, version tracking
  6. Flexibility: Support for schema evolution

Use Cases

  • Configuration Files: Compact, versioned config storage
  • Network Protocols: Custom binary protocols with integrity checks
  • Data Persistence: Save/load application state
  • IPC: Inter-process communication with structured data
  • Embedded Systems: Minimal footprint serialization
  • Game Development: Save files, network sync

Performance

The library is designed for efficiency:

  • Zero-copy where possible
  • Minimal memory allocations
  • Fast bitwise operations for endianness
  • Inline functions for hot paths
  • No dynamic dispatch overhead

License

See LICENSE file for details.

Contributing

Contributions welcome! Please ensure:

  • Code follows existing style (C99)
  • All examples still build and run
  • No new external dependencies
  • Documentation is updated

Roadmap

Potential future enhancements:

  • Compression support (optional)
  • Stream API for large data
  • Schema definition format
  • Code generation from schemas
  • Additional checksum algorithms
0 AIs selected
Clear selection
#
Name
Task