A MessagePack-based configuration library for embedded systems.
Design constraints: no heap allocation. All buffers are caller-owned. Hard caps: max 128 schema entries. Schema descriptions are ignored/dropped.
- Defines a fixed-cap schema (up to 128 entries) with typed values (u/i 8–64, f32/f64, str/fstr) and 5-char names.
- Parses
.map, JSON, and MessagePack binary schemas into caller-owned buffers; no heap allocations. - Supports default values for schema entries, automatically applied at initialization.
- Supports set/get by index and by schema name with type/length validation.
- Encodes/decodes MessagePack maps; pageout to buffer or file, pagein from buffer or file, with size caps.
- Schema versioning: Embeds schema name in serialized blobs for version detection.
- Remapping: Migrates config between schema versions with index remapping, type widening, and automatic default restoration for new entries.
The intended workflow is to author schemas as .map text files, convert them
to compact MessagePack binary at build time, and optionally compress them.
Your application then loads the binary blob directly — no text parsing on the
device.
# 1. Write your schema as a .map file (see Map Format below)
# 2. Convert to MessagePack binary (50-60% smaller than JSON, no tokenizer needed)
./build/out/cfgpack-schema-pack vehicle.map vehicle.msgpack
# 3. (Optional) Compress with LZ4 or heatshrink
./build/out/cfgpack-compress lz4 vehicle.msgpack vehicle.msgpack.lz4On the device:
/* Decompress if needed */
cfgpack_pagein_lz4(&ctx, compressed_data, compressed_len,
scratch, scratch_cap);
/* Or load the uncompressed msgpack binary directly */
cfgpack_schema_measure_msgpack(data, len, &m, &err); /* measure first */
/* ... allocate buffers from m ... */
cfgpack_schema_parse_msgpack(data, len, &opts); /* then parse */This keeps the .map files human-readable in your repo while shipping the
smallest possible binary to the device. See examples/fleet_gateway/
for a complete example with LZ4-compressed msgpack schemas and multi-version
migration.
- API Reference — Complete API documentation (errors, values, schema, runtime, typed functions)
- Schema Versioning — Version detection, migration, and type widening
- Compression — LZ4/heatshrink decompression support
- LittleFS — LittleFS flash storage wrappers
- Stack Analysis — Per-function stack frame sizes for embedded budgeting
- Fuzz Testing — libFuzzer harnesses for parser and decode robustness
- First line:
<name> <version>header (e.g.,vehicle 1wherevehicleis the schema name and1is the version). The schema name is embedded at reserved index 0 in serialized blobs for version detection during firmware upgrades. - Lines follow the format:
INDEX NAME TYPE DEFAULT # optional descriptionINDEX: 0–65535NAME: up to 5 charactersTYPE: one of the supported numeric/float/string types (str max 64, fstr max 16)DEFAULT: default value for this entry (see below)# description: optional trailing comment for documentation (not stored in binary)
- Comments: lines starting with
#are ignored; inline#comments after the default value are also ignored.
Each schema entry requires a default value specification:
NIL— no default; value must be explicitly set before use- Integer literals:
0,42,-5,0xFF,0b1010 - Float literals:
3.14,-1.5e-3,0.0 - Quoted strings:
"hello","","default value"
Entries with defaults are automatically marked as present when cfgpack_init() is called.
# vehicle.map - Configuration schema for a vehicle control system
# NOTE: Index 0 is reserved for schema name embedding; user entries start at 1.
vehicle 1
# IDENTIFICATION
1 id u32 0 # Unique vehicle identifier
2 model fstr "MX500" # Model code (max 16 chars)
3 vin str NIL # Vehicle identification number (max 64 chars)
# OPERATIONAL LIMITS
10 maxsp u16 120 # Maximum speed in km/h
11 minsp u16 5 # Minimum speed before idle shutdown
12 accel f32 2.5 # Acceleration limit in m/s^2
13 decel f32 -3.0 # Deceleration limit in m/s^2
# SENSOR CALIBRATION
20 toff i8 0 # Temperature sensor offset in degrees C
21 pscal f64 1.0 # Pressure sensor scale factor
22 flags u8 0x07 # Sensor enable bitmask
include/cfgpack/— public headerscfgpack.h— umbrella header; include just this to get all public APIs.config.h— build configuration (CFGPACK_EMBEDDED/CFGPACK_HOSTEDmodes).error.h— error codes enum.value.h— value types and limits (CFGPACK_STR_MAX,CFGPACK_FSTR_MAX).schema.h— schema structs, parser/JSON APIs, and measure functions.msgpack.h— minimal MessagePack buffer + encode/decode helpers.api.h— main cfgpack runtime API (set/get/pagein/pageout/print/version/size).decompress.h— optional LZ4/heatshrink decompression support.io_file.h— optional FILE*-based convenience wrappers for desktop/POSIX systems.io_littlefs.h— optional LittleFS-based convenience wrappers for flash storage.
src/— library implementation (core.c,io.c,io_file.c,io_littlefs.c,msgpack.c,schema_parser.c,tokens.c,wbuf.c,decompress.c).tests/— C test programs plus sample data undertests/data/.tools/— CLI tools source (cfgpack-compress.cfor LZ4/heatshrink compression,cfgpack-schema-pack.cfor converting schemas to msgpack binary).examples/— complete usage examples (allocate-once/,datalogger/,flash_config/,fleet_gateway/,low_memory/,sensor_hub/).third_party/— vendored dependencies (lz4/,heatshrink/,littlefs/).Makefile— buildsbuild/out/libcfgpack.a, test binaries, and tools.
make # builds build/out/libcfgpack.a
make tests # builds all test binaries
make tools # builds CLI tools (cfgpack-compress, cfgpack-schema-pack)| Mode | Default | stdio | Print Functions | Float Formatting |
|---|---|---|---|---|
CFGPACK_EMBEDDED |
Yes | Not linked | Silent no-ops | Minimal (9 digits) |
CFGPACK_HOSTED |
No | Linked | Full printf | snprintf (%.17g) |
To compile in hosted mode:
$(CC) -DCFGPACK_HOSTED -Iinclude myapp.c -Lbuild/out -lcfgpackmake tests
./scripts/run-tests.shOutput:
Running tests...
basic: 4/4 passed
core_edge: 11/11 passed
decompress: 8/8 passed
io_edge: 16/16 passed
io_littlefs: 8/8 passed
json_edge: 8/8 passed
json_remap: 10/10 passed
measure: 15/15 passed
msgpack: 16/16 passed
msgpack_schema: 17/17 passed
null_args: 40/40 passed
parser_bounds: 23/23 passed
parser: 3/3 passed
runtime: 24/24 passed
TOTAL: 203/203 passed
Six libFuzzer harnesses exercise the parsers and decode paths with randomized input. AddressSanitizer and UndefinedBehaviorSanitizer are enabled by default.
Prerequisites (macOS): Apple Clang does not ship libFuzzer. Install Homebrew LLVM:
brew install llvmThe build auto-detects Homebrew LLVM when the default clang lacks libFuzzer support.
Build and run:
make fuzz # build harnesses + generate seed corpus
scripts/run-fuzz.sh # 60s per target (default)
scripts/run-fuzz.sh 10 # 10s per target
scripts/run-fuzz.sh 0 # run indefinitely (Ctrl-C to stop)| Target | What it fuzzes |
|---|---|
fuzz_parse_map |
.map text schema parser |
fuzz_parse_json |
JSON schema parser |
fuzz_parse_msgpack |
MessagePack binary schema parser |
fuzz_parse_msgpack_mutator |
Structure-aware msgpack schema fuzzer (custom mutator) |
fuzz_pagein |
cfgpack_pagein_buf() against a fixed schema |
fuzz_msgpack_decode |
All low-level msgpack decode functions |
See Fuzz Testing for detailed documentation on the harness architecture, seed corpus generation, and crash investigation.
Six complete examples are provided in the examples/ directory:
Dynamic allocation example demonstrating two-phase init: cfgpack_schema_measure() to learn sizes (32 bytes of stack), then right-sized malloc. This replaces the old discovery-parse pattern that required ~8KB of temporary stack buffers.
cd examples/allocate-once && make runBasic data logger demonstrating schema parsing, typed convenience functions, and serialization.
cd examples/datalogger && make runIndustrial sensor node demonstrating LittleFS flash storage with LZ4-compressed msgpack binary schemas and a v1 → v2 schema migration. Uses a RAM-backed LittleFS block device for desktop testing. Shows the composable I/O pattern: manual LFS read + cfgpack_pagein_remap() for cross-version migration, and cfgpack_pageout_lfs() / cfgpack_pagein_lfs() for same-version round-trips. Covers all five migration scenarios: keep, widen, move, remove, and add.
cd examples/flash_config && make runFleet management gateway demonstrating LZ4-compressed msgpack binary schemas (pre-compiled from .map files via cfgpack-schema-pack and compressed with cfgpack-compress lz4) and a three-version migration chain (v1 -> v2 -> v3). Shows runtime LZ4 decompression of schema data before parsing, and uses cfgpack_schema_measure_msgpack() for right-sized heap allocation with no static buffer guessing. Covers all five migration scenarios: keep, widen, move, remove, and add.
cd examples/fleet_gateway && make runHVAC zone controller demonstrating the measure API for right-sized allocation and a full v1 -> v2 schema migration covering all five migration scenarios (keep, widen, move, remove, add). Shows how cfgpack_schema_measure() eliminates compile-time guessing of buffer sizes.
cd examples/low_memory && make runIoT sensor hub demonstrating compressed schema loading with heatshrink (~22% compression ratio).
cd examples/sensor_hub && make run