:: minicbor

iczelia

Fast, header-only, public domain, 2500 LoC ANSI C89 implementation of RFC 8949 using arenas.

recent commits

2026-08-12 21:42style refactorKamila Szewczyk
2026-08-12 21:37locKamila Szewczyk
2026-08-12 21:27fuzzing oriented bug fixes, cc0 -> 0bsd, etc.Kamila Szewczyk
2026-01-26 01:09Update README.mdKamila Szewczyk
2026-01-26 00:59Add initial README for MiniCBOR libraryKamila Szewczyk
2026-01-26 00:54clarification in the comment headerKamila Szewczyk
2026-01-26 00:54Add files via uploadKamila Szewczyk
2026-01-26 00:53Initial commitKamila Szewczyk

branches

main (2026-08-12 21:42)

tags

no tags.

README.md

minicbor

MiniCBOR is a header-only ANSI C89 implementation of RFC 8949 (CBOR). Synopsis:

  • Fast: Thanks to the use of an arena allocator over the system malloc, we attain slightly better memory footprint and improved allocation/deallocation speed as compared to other libraries.
  • Small: 1400 LoC. Just about the perfect size for a header-only library.
  • Portable: Works well in freestanding environments with minimal tweaking.
  • Free: The CC0 license allows you to use the library for any purpose and the author (that is, myself) has waived all her copyright on this work.

taste of minicbor

#define MCBOR_IMPLEMENTATION
#include "../minicbor.h"
#include <stdio.h>
#include <string.h>

typedef struct {
  const char * app_name;
  const char * version;
  int port;
  int debug_enabled;
  const char * allowed_hosts[4];
  int num_hosts;
  struct {
    int max_connections;
    int timeout_seconds;
    double retry_factor;
  } network;
} app_config_t;

static size_t encode_config(const app_config_t * cfg, unsigned char * buffer, size_t buflen) {
  mcbor_encoder_t enc;
  mcbor_encoder_init(&enc, buffer, buflen);

  /* Root map with 6 keys */
  mcbor_encode_map_start(&enc, 6);

  /* "app_name": "MyService" */
  mcbor_encode_text_cstr(&enc, "app_name");
  mcbor_encode_text_cstr(&enc, cfg->app_name);

  /* "version": "1.2.3" */
  mcbor_encode_text_cstr(&enc, "version");
  mcbor_encode_text_cstr(&enc, cfg->version);

  /* "port": 8080 */
  mcbor_encode_text_cstr(&enc, "port");
  mcbor_encode_uint(&enc, cfg->port);

  /* "debug": true/false */
  mcbor_encode_text_cstr(&enc, "debug");
  mcbor_encode_bool(&enc, cfg->debug_enabled);

  /* "allowed_hosts": ["localhost", "example.com", ...] */
  mcbor_encode_text_cstr(&enc, "allowed_hosts");
  mcbor_encode_array_start(&enc, cfg->num_hosts);
  for (int i = 0; i < cfg->num_hosts; i++) {
    mcbor_encode_text_cstr(&enc, cfg->allowed_hosts[i]);
  }

  /* "network": { nested object } */
  mcbor_encode_text_cstr(&enc, "network");
  mcbor_encode_map_start(&enc, 3);
  mcbor_encode_text_cstr(&enc, "max_connections");
  mcbor_encode_uint(&enc, cfg->network.max_connections);
  mcbor_encode_text_cstr(&enc, "timeout_seconds");
  mcbor_encode_uint(&enc, cfg->network.timeout_seconds);
  mcbor_encode_text_cstr(&enc, "retry_factor");
  mcbor_encode_float64(&enc, cfg->network.retry_factor);

  return mcbor_encoder_len(&enc);
}

/* Decode and print configuration from CBOR */
static int decode_and_print_config(const unsigned char * data, size_t len) {
  mcbor_arena_t arena;
  mcbor_arena_init(&arena);

  mcbor_decoder_t dec;
  mcbor_decoder_init(&dec, data, len, &arena);

  mcbor_value_t root;
  if (mcbor_decode(&dec, &root) != MCBOR_OK) {
    mcbor_arena_destroy(&arena);
    return -1;
  }

  /* Print diagnostic output. */
  char diag[1024];
  mcbor_to_diagnostic(&root, diag, sizeof(diag));
  printf("Decoded CBOR (diagnostic notation):\n%s\n\n", diag);

  /* Extract and print fields, */
  mcbor_value_t * v;

  printf("Decoded configuration:\n");

  if ((v = mcbor_map_get_text(&root, "app_name")) && mcbor_is_text(v)) {
    printf("  app_name: %s\n", mcbor_get_text(v, NULL));
  }
  if ((v = mcbor_map_get_text(&root, "version")) && mcbor_is_text(v)) {
    printf("  version: %s\n", mcbor_get_text(v, NULL));
  }
  if ((v = mcbor_map_get_text(&root, "port")) && mcbor_is_uint(v)) {
    printf("  port: %d\n", (int)mcbor_get_uint(v));
  }
  if ((v = mcbor_map_get_text(&root, "debug")) && mcbor_is_bool(v)) {
    printf("  debug: %s\n", mcbor_get_bool(v) ? "true" : "false");
  }

  /* Decode allowed_hosts array */
  if ((v = mcbor_map_get_text(&root, "allowed_hosts")) && mcbor_is_array(v)) {
    printf("  allowed_hosts: [");
    size_t num_hosts = mcbor_array_len(v);
    for (size_t i = 0; i < num_hosts; i++) {
      mcbor_value_t* host = mcbor_array_get(v, i);
      if (host && mcbor_is_text(host)) {
        printf("%s%s", mcbor_get_text(host, NULL), i < num_hosts - 1 ? ", " : "");
      }
    }
    printf("]\n");
  }

  /* Decode nested network settings */
  if ((v = mcbor_map_get_text(&root, "network")) && mcbor_is_map(v)) {
    mcbor_value_t* nv;
    if ((nv = mcbor_map_get_text(v, "max_connections")) && mcbor_is_uint(nv)) {
      printf("  network.max_connections: %d\n", (int)mcbor_get_uint(nv));
    }
    if ((nv = mcbor_map_get_text(v, "timeout_seconds")) && mcbor_is_uint(nv)) {
      printf("  network.timeout_seconds: %d\n", (int)mcbor_get_uint(nv));
    }
    if ((nv = mcbor_map_get_text(v, "retry_factor")) && mcbor_is_float(nv)) {
      printf("  network.retry_factor: %.1f\n", mcbor_get_float(nv));
    }
  }

  mcbor_arena_destroy(&arena); /* All objects die here! */
  return 0;
}

int main(void) {
  /* Create a sample configuration */
  app_config_t config = {
    .app_name = "MyApp",
    .version = "1.2.3",
    .port = 8080,
    .debug_enabled = 1,
    .allowed_hosts = {"localhost", "127.0.0.1", "example.com"},
    .num_hosts = 3,
    .network = {
      .max_connections = 100,
      .timeout_seconds = 30,
      .retry_factor = 1.5
    }
  };

  /* Encode to CBOR */
  unsigned char buffer[512];
  size_t encoded_len = encode_config(&config, buffer, sizeof(buffer));

  /* Print hex dump */
  for (size_t i = 0; i < encoded_len; i++) {
    printf("%02x ", buffer[i]);
    if ((i + 1) % 16 == 0) printf("\n");
  }
  printf("\n\n");

  /* Decode back and print */
  decode_and_print_config(buffer, encoded_len);

  return 0;
}

vs libcbor

Performance-wise:

Iterations per test: 100000

1. Integer Encoding (uint64):
  minicbor    : 6.501 ms total, 0.065014 us/op (min: 0.029996, max: 82.234010)
  libcbor     : 7.664 ms total, 0.076636 us/op (min: 0.029996, max: 39.744005)
   Speedup: 1.18x

2. Integer Decoding (uint64):
  minicbor    : 7.336 ms total, 0.073358 us/op (min: 0.029996, max: 4.568011)
  libcbor     : 8.063 ms total, 0.080633 us/op (min: 0.029996, max: 95.828995)
   Speedup: 1.10x

3. String Encoding (59 bytes):
  minicbor    : 7.189 ms total, 0.071887 us/op (min: 0.029996, max: 20.767987)
  libcbor     : 22.446 ms total, 0.224463 us/op (min: 0.169992, max: 79.477996)
   Speedup: 3.12x

4. Array Encoding (100 integers):
  minicbor    : 11.802 ms total, 0.118022 us/op (min: 0.069991, max: 61.655000)
  libcbor     : 219.108 ms total, 2.191082 us/op (min: 1.962990, max: 110.256001)
   Speedup: 18.56x

5. Map Encoding (20 key-value pairs):
  minicbor    : 17.906 ms total, 0.179063 us/op (min: 0.139996, max: 14.857009)
  libcbor     : 125.188 ms total, 1.251884 us/op (min: 1.101986, max: 83.055004)
   Speedup: 6.99x

6. Nested Structure Encoding:
  minicbor    : 11.709 ms total, 0.117093 us/op (min: 0.079989, max: 6.462008)
  libcbor     : 46.466 ms total, 0.464659 us/op (min: 0.399992, max: 154.278010)
   Speedup: 3.97x

7. Float Encoding (double):
  minicbor    : 5.952 ms total, 0.059523 us/op (min: 0.019997, max: 0.310004)
  libcbor     : 6.598 ms total, 0.065977 us/op (min: 0.029996, max: 48.250005)
   Speedup: 1.11x

8. Nested Structure Decoding:
  minicbor    : 17.690 ms total, 0.176901 us/op (min: 0.129998, max: 42.589992)
  libcbor     : 44.875 ms total, 0.448750 us/op (min: 0.389993, max: 51.174998)
   Speedup: 2.54x

minicbor is consistently faster, especially for collections (arrays/maps) where libcbor's reference counting and per-item allocation overhead becomes significant.

Feature parity is at a decent level: both libraries fully implement the core RFC. minicbor doesn't provide interfaces for deep clones, explicit ownership transfer, definite length checking or UTF-8 codepoint utilities. It also doesn't support RFC 8742 (CBOR Sequences).

tab: 248 wrap: offon