Schema Versioning & Remapping

CFGPack supports firmware upgrades where the configuration schema changes between versions. This is handled through:

  1. Schema name embedding: The schema name is automatically stored at reserved index 0 in serialized blobs

  2. Version detection: Read the schema name from a blob to determine which schema version created it

  3. Index remapping: Map old indices to new indices when loading config from an older schema version

  4. Type widening: Automatically coerce values to wider types (e.g., u8 → u16) during remapping

Reserved Index

Index 0 (CFGPACK_INDEX_RESERVED_NAME) is reserved for the schema name. User-defined schema entries should use indices starting at 1.

Detecting Schema Version

Use cfgpack_peek_name() to read the schema name from a serialized blob without fully loading it:

uint8_t blob[4096];
size_t blob_len;
// ... load blob from storage ...

char name[64];
cfgpack_err_t err = cfgpack_peek_name(blob, blob_len, name, sizeof(name));
if (err == CFGPACK_OK) {
    printf("Config was created with schema: %s\n", name);
} else if (err == CFGPACK_ERR_MISSING) {
    printf("No schema name in blob (legacy format)\n");
}

Migrating Between Schema Versions

When loading config from an older schema version, use cfgpack_pagein_remap() with a remap table:

// Old schema "sensor_v1" had:
//   index 1: temp (u8)
//   index 2: humid (u8)
//
// New schema "sensor_v2" has:
//   index 1: temp (u16)   -- widened type, same index
//   index 5: humid (u16)  -- moved to new index, widened type
//   index 6: press (u16)  -- new field

// Define remap table: old_index -> new_index
cfgpack_remap_entry_t remap[] = {
    {1, 1},  // temp: index unchanged (type widening handled automatically)
    {2, 5},  // humid: moved from index 2 to index 5
};

// Load with remapping
cfgpack_err_t err = cfgpack_pagein_remap(&ctx, blob, blob_len, remap, 2);
if (err == CFGPACK_OK) {
    // Old values loaded into new schema positions
    // New fields (like press at index 6) retain their defaults
}

Default Restoration During Remap

After cfgpack_pagein_remap() decodes all entries from the old data, it automatically restores presence for any new-schema entries that have has_default set but were not covered by the incoming data. This means:

  • New entries added in a schema upgrade that have default values are immediately accessible via cfgpack_get() after migration, without any explicit code to set them.

  • Entries without defaults (NIL) that were not in the old data remain absent (cfgpack_get() returns CFGPACK_ERR_MISSING).

  • If old data contains a value for an entry, that decoded value always takes precedence over the schema default.

This applies to all types including str and fstr defaults.

Type Widening Rules

During remapping, values can be automatically widened to larger types:

From

To (allowed)

u8

u16, u32, u64

u16

u32, u64

u32

u64

i8

i16, i32, i64

i16

i32, i64

i32

i64

f32

f64

fstr

str (if length fits)

Narrowing conversions (e.g., u16 → u8) return CFGPACK_ERR_TYPE_MISMATCH.

Migration Workflow

A typical firmware upgrade migration:

// 1. Read schema name from stored config
char stored_name[64];
cfgpack_err_t err = cfgpack_peek_name(flash_data, flash_len, stored_name, sizeof(stored_name));

// 2. Compare with current schema
if (strcmp(stored_name, current_schema.map_name) == 0) {
    // Same schema version - load directly
    cfgpack_pagein_buf(&ctx, flash_data, flash_len);
} else if (strcmp(stored_name, "myapp_v1") == 0) {
    // Old v1 schema - apply v1->v2 remap
    cfgpack_pagein_remap(&ctx, flash_data, flash_len, v1_to_v2_remap, remap_count);
} else {
    // Unknown schema - use defaults
    printf("Unknown config version, using defaults\n");
}

Working Examples

See examples/low_memory/ for a complete v1 -> v2 migration using JSON schemas with cfgpack_schema_measure(), examples/fleet_gateway/ for a three-version migration chain (v1 -> v2 -> v3) using msgpack binary schemas with cfgpack_schema_measure_msgpack(), or examples/flash_config/ for a LittleFS-backed migration with LZ4-compressed msgpack schemas and all five migration scenarios (keep, widen, move, remove, add).