AxlData — Data Structures

Core collection types, string utilities, and message digest checksums.

Headers:

  • <axl/axl-hash-table.h> — Hash table with string keys (FNV-1a, chained)

  • <axl/axl-array.h> — Dynamic array (auto-growing, index access)

  • <axl/axl-list.h> — Doubly-linked list

  • <axl/axl-slist.h> — Singly-linked list

  • <axl/axl-queue.h> — FIFO queue

  • <axl/axl-radix-tree.h> — Radix tree (compact prefix tree, longest-prefix lookup)

  • <axl/axl-ntree.h> — N-ary tree (GLib GNode-style hierarchy, public node fields)

  • <axl/axl-tree.h> — Balanced sorted map (GLib GTree, AVL; ordered iteration + range queries)

  • <axl/axl-ring-buf.h> — Ring buffer (circular byte buffer, zero-copy, overwrite mode)

  • <axl/axl-digest.h> — Message digest checksums (MD5, SHA-1, SHA-256) + PBKDF2-HMAC-SHA256 (RFC 8018) + rolling CRC-32 / Adler-32

  • <axl/axl-compress.h> — DEFLATE / gzip / zlib compression (one-shot + AxlStream filters)

  • <axl/axl-sidecar.h> — Common JSON5 sidecar loader (used by AxlPciIds / AxlPciClassDb / AxlSpdIds / AxlUsbIds; see its dedicated module page for the open / schema-check / singleton- with-atexit pattern)

Choosing a Collection

Type

Best for

Lookup

Insert/Remove

AxlHashTable

Key-value mapping

O(1) by key

O(1) amortized

AxlArray

Indexed, sorted data

O(1) by index

O(1) append

AxlList

Frequent insert/remove

O(n) by value

O(1) at position

AxlSList

Simple linked sequences

O(n)

O(1) prepend

AxlQueue

FIFO/LIFO patterns

O(1) head/tail

O(1) push/pop

AxlRadixTree

Prefix-match routing

O(k) by key

O(k) insert

AxlNTree

Parent/child hierarchy

O(depth) navigate

O(1) child insert

AxlTree

Sorted map + range queries

O(log n) by key

O(log n) insert

AxlRingBuf

Streaming I/O, pipes

O(1) push/pop

O(1)

AxlHashTable

GLib-style hash table with FNV-1a hashing, chained collision resolution, and automatic resize at 75% load factor. Supports generic key types via user-provided hash/equal callbacks.

Ownership

All three constructors allocate the AxlHashTable struct. They differ in how the table treats the content (key/value pointers passed to insert/replace):

Constructor

Keys

Values

axl_hash_table_new_str()

Copied (strdup’d) — table owns + frees

Borrowed

axl_hash_table_new(hash, equal)

Borrowed

Borrowed

axl_hash_table_new_full(hash, equal, key_destroy, value_destroy)

Owned if key_destroy non-NULL (no copy, transferred); borrowed otherwise

Owned if value_destroy non-NULL; borrowed otherwise

“Owned” means the table calls the destroy callback when the entry is removed or the table is freed. “Borrowed” means the caller is responsible for the lifetime; the table never copies and never frees.

new_str is the “make this go away easily” choice for literal/borrowed string keys. new_full is for caller-allocated keys/values where the caller wants to transfer ownership at insert time. new is for borrowed-on-both-sides scenarios (e.g. integer keys cast to void *, values that outlive the table).

Convenience Constructor (string keys)

axl_hash_table_new_str() creates a table with string keys that are copied internally. Values are opaque pointers (not freed).

AXL_AUTOPTR(AxlHashTable) h = axl_hash_table_new_str();

axl_hash_table_insert(h, "name", "AXL");
axl_hash_table_insert(h, "version", "0.1.0");

const char *name = axl_hash_table_lookup(h, "name");   // "AXL"
size_t count = axl_hash_table_size(h);               // 2

axl_hash_table_remove(h, "version");
axl_hash_table_foreach(h, my_callback, user_data);

Full Constructor (generic keys, owned entries)

axl_hash_table_new_full(hash, equal, key_free, value_free) creates a table with custom hash/equal functions and optional destructors. Keys are NOT copied — the table takes ownership.

// Owned string keys and values
AxlHashTable *h = axl_hash_table_new_full(
    NULL, NULL,             // NULL = axl_str_hash/axl_str_equal
    axl_free_impl,          // key destructor
    axl_free_impl);         // value destructor

axl_hash_table_replace(h, axl_strdup("key"), axl_strdup("value"));
axl_hash_table_replace(h, axl_strdup("key"), axl_strdup("new"));  // old key+value freed
axl_hash_table_free(h);                                        // remaining entries freed

Pointer Keys

Use axl_direct_hash/axl_direct_equal for pointer or integer keys:

AxlHashTable *h = axl_hash_table_new_full(
    axl_direct_hash, axl_direct_equal, NULL, NULL);

axl_hash_table_insert(h, (void *)42, my_data);
void *data = axl_hash_table_lookup(h, (void *)42);

contains / steal / foreach_remove

// contains: distinguishes NULL-valued key from absent key
axl_hash_table_contains(h, "key");   // true even if value is NULL

// steal: remove without calling destructors (caller takes ownership)
axl_hash_table_steal(h, "key");

// foreach_remove: bulk conditional removal
size_t n = axl_hash_table_foreach_remove(h, predicate, data);

Iterator

Safe removal during iteration:

AxlHashTableIter iter;
void *key, *value;

axl_hash_table_iter_init(&iter, h);
while (axl_hash_table_iter_next(&iter, &key, &value)) {
    if (should_remove(key, value)) {
        axl_hash_table_iter_remove(&iter);   // calls destructors
    }
}

AxlArray

Dynamic array that stores elements by value (not pointers). Auto-grows on append. Supports indexed access, sorting, and removal. Matches GLib’s GArray.

AXL_AUTOPTR(AxlArray) a = axl_array_new(sizeof(int));

int values[] = {50, 20, 40, 10, 30};
for (int i = 0; i < 5; i++) {
    axl_array_append(a, &values[i]);
}

axl_array_sort(a, int_compare);
axl_array_remove_index(a, 0);        // remove first element
axl_array_remove_index_fast(a, 1);   // O(1) swap-with-last removal
axl_array_set_size(a, 10);           // grow (zero-initialized)

AxlList

GLib-style doubly-linked list. Each node has data, next, and prev pointers. Functions return the new head (which may change after prepend, remove, or sort). Matches GLib’s GList.

AxlList *list = NULL;
list = axl_list_append(list, "first");
list = axl_list_append(list, "second");
list = axl_list_prepend(list, "zeroth");

// Insert relative to a node
AxlList *node = axl_list_find(list, "first");
list = axl_list_insert_before(list, node, "half");

// Remove all matching, deep copy, context-aware sort
list = axl_list_remove_all(list, "first");
AxlList *copy = axl_list_copy_deep(list, my_copy_func, NULL);

axl_list_free(list);

AxlSList

Singly-linked list — lighter than AxlList (no prev pointer). Use when you only traverse forward. Matches GLib’s GSList. Same operations as AxlList: insert_before, remove_all, remove_link, sort_with_data, copy_deep.

AxlQueue

FIFO queue with push/pop at both ends and peek. Can also be used as a stack (push/pop from the same end). Matches GLib’s GQueue. Supports find, remove, and stack-allocated initialization.

AxlQueue q = AXL_QUEUE_INIT;    // stack-allocated
axl_queue_push_tail(&q, "first");
axl_queue_push_tail(&q, "second");
axl_queue_push_tail(&q, "first");  // duplicate

axl_queue_remove(&q, "first");     // removes first match
axl_queue_remove_all(&q, "first"); // removes all matches

AxlList *node = axl_queue_find(&q, "second");

AxlNTree

Generic n-ary tree (GLib GNode equivalent) for parent→children hierarchies — UI/device/file trees, a DOM, ACPI/SMBIOS structure. The node is the subtree handle (no separate container) and its fields are public, so traversal is a plain pointer walk:

#include <axl.h>

AxlNTree *root = axl_ntree_new("/");
AxlNTree *etc  = axl_ntree_append_data(root, "etc");
AxlNTree *bin  = axl_ntree_append_data(root, "bin");
axl_ntree_append_data(etc, "hosts");

for (AxlNTree *c = root->children; c != NULL; c = c->next)
    axl_printf("%s\n", (const char *)c->data);          // etc, bin

axl_ntree_traverse(root, AXL_NTREE_PRE_ORDER, AXL_NTREE_ALL, 0,
                   visit_fn, ctx);                       // walk the tree

axl_ntree_free(root);                                    // node + subtree
// axl_ntree_free_full(root, free) also frees each node's data

Insertion (append_child/prepend_child/insert_before/insert_after) attaches an existing root node; append_data is the new+append shortcut. axl_ntree_unlink detaches a subtree (it becomes its own root). move_after/move_before reposition an already-attached node (the insert_* twins, with a cycle guard) — place-above/place-below a sibling, reparent, or (with a NULL sibling) move to first/last; this is how a scene-graph raise/lower is built. Counts and queries: n_children, nth_child, depth (root = 1), max_height, n_nodes(flags), is_ancestor, get_root. Traversal supports pre/post/in/level order, an ALL/LEAVES/NON_LEAVES filter, a depth limit, and early stop (the callback returns true). Data is borrowed; the tree owns only its node objects. Single-threaded, no locking.

For a pull-style walk without a callback, AxlNTreeIter is a stack-allocated pre-order cursor (uses the parent/sibling links — no internal stack, any depth); axl_ntree_iter_init_reverse walks the same nodes in reverse pre-order (topmost-first for a paint-order tree — the hit-test idiom):

AxlNTreeIter it;
axl_ntree_iter_init(&it, root, AXL_NTREE_ALL);
for (AxlNTree *n; (n = axl_ntree_iter_next(&it)) != NULL; )
    use(n->data);

This is the structural hierarchy container — distinct from AxlRadixTree (string-prefix lookup) and AxlTree (balanced sorted map).

AxlTree

Balanced sorted map (GLib GTree equivalent, AVL-backed): ordered key→value storage with O(log n) insert/lookup/remove and — the reason to reach for it over AxlHashTablein-order iteration and range / nearest-key queries. Opaque container; keys ordered by an AxlCompareDataFunc.

#include <axl.h>

static int cmp_int(const void *a, const void *b, void *user) {
    (void)user; intptr_t x = (intptr_t)a, y = (intptr_t)b;
    return (x > y) - (x < y);
}

AxlTree *t = axl_tree_new(cmp_int, NULL);
axl_tree_insert(t, (void *)30, "thirty");
axl_tree_insert(t, (void *)10, "ten");
axl_tree_insert(t, (void *)20, "twenty");

axl_tree_lookup(t, (void *)20);            // "twenty"
axl_tree_lower_bound(t, (void *)15);       // value at key 20 (first >= 15)
axl_tree_upper_bound(t, (void *)20);       // value at key 30 (first > 20)
axl_tree_foreach(t, visit_fn, ctx);        // ascending key order

axl_tree_free(t);
// axl_tree_new_full(cmp, user, key_destroy, value_destroy) owns entries

axl_tree_insert keeps the existing key and replaces the value on a collision; axl_tree_replace swaps both (GTree semantics — destructors run on the dropped key/value). nnodes and height are O(1).

For a pull-style walk without a callback, AxlTreeIter is a stack-allocated ascending-order cursor:

AxlTreeIter it;
void *k, *v;
axl_tree_iter_init(&it, t);
while (axl_tree_iter_next(&it, &k, &v))
    use(k, v);          // ascending key order

Pick this for ordered keys / range scans; AxlHashTable for unordered O(1) maps, AxlRadixTree for string longest-prefix lookup.

AxlStr — String Utilities

String utilities operating on UTF-8 char * strings. Includes length, copy, compare, search, split, join, and case-insensitive operations (ASCII fold only). UCS-2 helpers at the bottom are for UEFI internal use.

All allocated results are freed with axl_free().

Header: <axl/axl-str.h>

Overview

AXL uses UTF-8 (char *) throughout its public API. UEFI firmware uses UCS-2 (unsigned short *) internally, but AXL handles the conversion transparently — you never need to deal with UCS-2 unless making direct UEFI protocol calls.

UTF-8 vs UCS-2

  • Use UTF-8 (char *) for all application code. All axl_str* functions operate on UTF-8.

  • UCS-2 functions (axl_wcslen, axl_wcscmp, axl_str_to_w, axl_str_from_w) exist only for UEFI interop. Consumer code should not need them.

Per-codepoint iteration

axl_utf8_decode walks a UTF-8 string one Unicode codepoint at a time. It is the UTF-8-first walker for new code that needs to inspect characters (e.g. text rendering, syntax highlighting). The existing axl_utf8_to_ucs2 / axl_utf8_to_ucs2_buf helpers remain for UEFI protocol interop where a CHAR16 buffer is required.

const char *p = "Hello, 世界!";
uint32_t    cp;
size_t      n;
while ((n = axl_utf8_decode(p, &cp)) > 0) {
    use(cp);   // U+0048, U+0065, ..., U+4E16, U+754C, U+0021
    p += n;
}

Well-formed 1/2/3/4-byte sequences decode to U+0000..U+10FFFF. Malformed leads, truncated continuations, overlong encodings, surrogate codepoints, and out-of-range values all return 1 with *out_codepoint = 0xFFFD (REPLACEMENT CHARACTER) — the caller advances by 1 byte to resynchronize. End of string (NULL or \0) returns 0.

Case-Insensitive Operations

axl_strcasecmp, axl_strcasestr, and axl_strncasecmp fold ASCII letters only (A-Z -> a-z). They do not handle full Unicode case mapping. This is sufficient for UEFI identifiers, HTTP headers, and file extensions.

Common Patterns

#include <axl.h>

// Split a string
char **parts = axl_strsplit("a,b,c", ',');
for (int i = 0; parts[i] != NULL; i++) {
    axl_printf("  %s\n", parts[i]);
}
axl_strfreev(parts);  // frees the array AND each string

// Join strings
const char *items[] = {"one", "two", "three", NULL};
char *joined = axl_strjoin(", ", items);
axl_printf("%s\n", joined);  // "one, two, three"
axl_free(joined);

// Search
if (axl_str_has_prefix(path, "fs0:")) { ... }
if (axl_str_has_suffix(name, ".efi")) { ... }
const char *found = axl_strcasestr(header, "content-type");

Memory Ownership

Functions that return char * allocate new memory. The caller must free with axl_free():

  • axl_strdup, axl_strndup

  • axl_strsplit (free with axl_strfreev)

  • axl_strjoin, axl_strstrip

  • axl_str_to_w, axl_str_from_w

Functions that return const char * or char * pointing into the input string do NOT allocate:

  • axl_strstr_len, axl_strrstr (return pointer into haystack)

  • axl_strchr (return pointer into string)

AxlStrReader — Cursor-Based String Parser

Symmetric counterpart to AxlString (the builder). A reader BORROWS a const char * (no allocation, no ownership) and tracks a cursor with a sticky-error flag. Operations short-circuit when ok is false, so chains compose naturally without per-call error checking:

AxlStrReader r;
uint64_t     v;
axl_str_reader_init(&r, "N[03A8]");
axl_str_reader_consume_char(&r, 'N');
axl_str_reader_consume_char(&r, '[');
axl_str_reader_take_u64(&r, 16, &v);
axl_str_reader_consume_char(&r, ']');
if (!r.ok || !axl_str_reader_eof(&r)) { /* parse failed */ }

Header: <axl/axl-str.h> (alongside the legacy string utilities and axl_sscanf).

Primitives: init / init_n, eof, peek, remaining, skip_ws, consume_char, consume_str, take_until, take_while, take_u64, take_ident. No allocation; *out slices point directly into the input buffer.

For one-shot fixed-pattern parses (IP addresses, MAC addresses, ASCII numerics with separators), axl_sscanf is built on top of the same primitives and reads cleaner:

unsigned a, b, c, d;
int consumed;
if (axl_sscanf(str, "%u.%u.%u.%u%n", &a, &b, &c, &d, &consumed) != 4
    || str[consumed] != '\0') {
    return -1;   /* malformed */
}

Supports the C99 conversions consumers actually need: `%c %d %i %u %o

hh h l ll z j, and * assignment suppression.

AxlString — String Builder

Mutable auto-growing string builder, like GLib’s GString. All strings are UTF-8. Supports append, prepend, insert, printf-style formatting, and truncation.

Header: <axl/axl-string.h>

Overview

Use AxlString when you need to build a string incrementally (in a loop, with formatting, from multiple sources). For simple one-shot formatting, use axl_asprintf instead.

AXL_AUTOPTR(AxlString) s = axl_string_new("Hello");
axl_string_append(s, " ");
axl_string_append_printf(s, "world #%d", 42);
axl_string_append_c(s, '!');

axl_printf("%s\n", axl_string_str(s));  // "Hello world #42!"
axl_printf("length: %zu\n", axl_string_len(s));

Stealing the Buffer

Transfer ownership of the internal buffer to avoid a copy:

AXL_AUTOPTR(AxlString) b = axl_string_new(NULL);
axl_string_append_printf(b, "key=%s", value);

char *result = axl_string_steal(b);  // b is now empty
// caller owns 'result', must free with axl_free()

The builder can be reused after stealing — it starts empty with its allocated buffer released.

Error Handling

All mutation functions (append, printf, etc.) return int: 0 on success, -1 if the internal realloc fails. This matches the convention used by axl_array_append, axl_hash_table_insert, etc.

AxlJson — JSON / JSON5

JSON reader (jsmn-based) and writer. Parse JSON strings into a token tree, query values by key, and build JSON documents incrementally over an AxlString. A separate colored UEFI-console pretty-printer is provided for debug output.

The reader also accepts the JSON5 grammar superset — comments, trailing commas, single-quoted strings, unquoted keys, hex numbers — via an opt-in flag (see the JSON5 Support section below). The writer can emit trailing commas and JSON5 comments via additional opt-in flags.

Header: <axl/axl-json.h>

Overview

AXL provides three independent JSON APIs:

  • Reader (AxlJsonReader) — parse a JSON string, extract values by key, iterate arrays.

  • Writer (AxlJsonWriter) — build JSON into an auto-growing AxlString. Orthogonal calls (containers, keys, atoms) with a state machine that handles comma placement and string escaping. Optional pretty-print mode with 2-space indent.

  • Console printer (axl_json_console_print) — colored, attribute-based pretty output to the UEFI console. Distinct from the writer’s pretty-print flag (which produces buffer output, no colors).

Reading JSON

const char *json = "{\"name\":\"AXL\",\"version\":1,\"debug\":true}";
AxlJsonReader r;

if (!axl_json_parse(json, axl_strlen(json), &r)) {
    axl_printerr("invalid JSON\n");
    return -1;
}

char    name[64];
int64_t version;
bool    debug;

if (!axl_json_get_string(&r, "name",    name, sizeof(name)) ||
    !axl_json_get_int   (&r, "version", &version) ||
    !axl_json_get_bool  (&r, "debug",   &debug)) {
    axl_printerr("missing or wrong-type field\n");
    axl_json_free(&r);
    return -1;
}

axl_printf("name=%s version=%lld debug=%s\n",
           name, (long long)version, debug ? "true" : "false");

axl_json_free(&r);

axl_json_parse returns false on invalid syntax, unbalanced braces, or token-array allocation failure. Each axl_json_get_* returns false if the key is missing, the value has the wrong type, or (for strings) the caller buffer is too small. String values are copied into the caller buffer — no zero-copy lifetime concerns. Always call axl_json_free on the reader; the token array is heap-allocated.

Writing JSON

The writer builds into a caller-owned AxlString and grows on demand. Comma placement, string escaping, and (optional) indentation are handled internally. Containers, keys, and atoms are independent calls — a single state machine knows whether the current container is an object or an array.

AXL_AUTOPTR(AxlString) out = axl_string_new(NULL);
AxlJsonWriter w;

axl_json_writer_init(&w, out, AXL_JSON_WRITER_DEFAULT);
axl_json_obj_begin(&w);
    axl_json_kv_str (&w, "name",    "AXL");
    axl_json_kv_uint(&w, "version", 1);
    axl_json_kv_bool(&w, "debug",   true);
    axl_json_key(&w, "tags");
    axl_json_arr_begin(&w);
        axl_json_str(&w, "uefi");
        axl_json_str(&w, "embedded");
    axl_json_arr_end(&w);
axl_json_obj_end(&w);
axl_json_writer_finish(&w);

if (!axl_json_writer_error(&w)) {
    axl_printf("%s\n", axl_string_str(out));
    // {"name":"AXL","version":1,"debug":true,"tags":["uefi","embedded"]}
}

For convenience, axl_json_kv_* collapses a key + atomic value into one call (the dominant shape). Use axl_json_key followed by an atom or container when the value is a nested object/array.

Pretty Printing

Pass AXL_JSON_WRITER_PRETTY at init for 2-space-indent output with newlines at every container and member boundary:

axl_json_writer_init(&w, out, AXL_JSON_WRITER_PRETTY);
// ... same writer calls ...
// {
//   "name": "AXL",
//   "version": 1,
//   "tags": [
//     "uefi",
//     "embedded"
//   ]
// }

Iterating Arrays

// Parse: {"items":["a","b","c"]}
AxlJsonArrayIter iter;
if (axl_json_array_begin(&r, "items", &iter)) {
    AxlJsonReader elem;
    while (axl_json_array_next(&iter, &elem)) {
        char value[32];
        axl_json_get_string(&elem, NULL, value, sizeof(value));
        axl_printf("  %s\n", value);
    }
}

For root-level arrays ([{...}, {...}]) use axl_json_root_array_begin instead. Element readers borrow the parent’s token array — do not call axl_json_free on them.

Nested Objects

The flat getters look up keys in the reader’s current object. axl_json_get_object steps into a named child object and hands back a sub-reader scoped to it; chain calls to reach deeper paths.

// Parse: {"server":{"host":"localhost","tls":{"port":443}}}
AxlJsonReader server, tls;
int64_t port = 0;
if (axl_json_get_object(&r, "server", &server) &&
    axl_json_get_object(&server, "tls", &tls)) {
    axl_json_get_int(&tls, "port", &port);   // 443
}

The sub-reader composes with every accessor — axl_json_get_string / _int / _uint / _bool, axl_json_array_begin, and axl_json_get_object itself all operate relative to the nested object. Like array elements, the sub-reader borrows the parent’s token array: do not call axl_json_free on it, and it stays valid only while the parent reader lives.

Round-Trip Transforms

axl_json_write_token splices an already-parsed token into the writer’s output. The bridge writes string and key bytes verbatim from the source — jsmn keeps escape sequences in source form, so this preserves \uXXXX, escaped quotes, etc. without re-escaping. Useful for parse → mutate → re-emit flows:

AxlJsonReader r;
axl_json_parse(input, input_len, &r);

AXL_AUTOPTR(AxlString) out = axl_string_new(NULL);
AxlJsonWriter w;
axl_json_writer_init(&w, out, AXL_JSON_WRITER_PRETTY);

axl_json_obj_begin(&w);
    axl_json_kv_str(&w, "wrapped_in", "envelope");
    axl_json_key(&w, "original");
    axl_json_write_token(&w, &r, 0);   /* splice the entire input doc */
axl_json_obj_end(&w);
axl_json_writer_finish(&w);

axl_json_free(&r);

Error Handling

The writer uses a sticky error flag. The first failure latches it; subsequent calls become no-ops; one check after axl_json_writer_finish is sufficient.

Sources of writer error:

  • AxlString OOM — auto-grow failed.

  • Structural misuse the writer can detect:

    • emit a bare-primitive root (only objects and arrays are valid JSON root values; matches axl_json_parse’s contract)

    • emit a value outside any container after the root has closed

    • emit a key inside an array

    • emit a value when a key was expected in object context

    • axl_json_obj_end on an open array (or vice versa)

    • close when nothing is open

    • mismatched begin/end counts at axl_json_writer_finish

What the writer does not catch:

  • Duplicate keys in the same object — JSON technically allows them; the writer doesn’t track emitted keys.

  • Non-UTF-8 bytes in axl_json_str — passed through escaping unchanged.

  • The contents of axl_json_raw(&w, fragment) — the caller asserts the fragment is valid JSON; the writer splices it as-is.

JSON5 Support

JSON5 is a strict superset of JSON aimed at human-edited config files. AXL accepts the JSON5 grammar on the reader side via an opt-in flag, and emits JSON5-flavored extras (trailing commas, comments) on the writer side via additional flags. Strict callers see no behavior change.

Reader — opt in with AXL_JSON_PARSER_JSON5:

const char *cfg =
    "// jedec.json5 — vendor codes per JEDEC JEP-106\n"
    "{\n"
    "  vendors: [\n"
    "    { code: 0x802C, name: 'Micron'  },\n"
    "    { code: 0x80AD, name: 'Hynix'   },  // trailing comma below\n"
    "    { code: 0x80CE, name: 'Samsung' },\n"
    "  ],\n"
    "}\n";

AxlJsonReader r;
if (!axl_json_parse_flags(cfg, axl_strlen(cfg),
                          AXL_JSON_PARSER_JSON5, &r)) {
    /* parse error */
}
/* All the standard accessors (axl_json_get_string,
   axl_json_array_begin, axl_json_array_next, ...) work
   unchanged — JSON5 is normalized at parse time. */
axl_json_free(&r);

For sidecar files there’s a one-shot:

AxlJsonReader  r;
void          *raw;
size_t         raw_len;

if (axl_json_load_file_flags("jedec.json5", AXL_JSON_PARSER_JSON5,
                             &r, &raw, &raw_len)) {
    /* ... use r ... */
    axl_json_free(&r);
    axl_free(raw);
}

JSON5 features the parser accepts:

  • Line comments (//) and block comments

  • Trailing commas in objects and arrays

  • Single-quoted strings ('text')

  • Unquoted (identifier-name) object keys

  • Hex number literals (0x...) and + / - number prefix

  • Extended string escapes: \', \v, \0, \x##, line continuations

Strict callers (axl_json_parse, no flags) still go through the existing jsmn-based path and reject JSON5 input. The JSON5 path is strictly opt-in.

Writer — emit JSON5 extras with AXL_JSON_WRITER_TRAILING_COMMAS and axl_json_comment:

AXL_AUTOPTR(AxlString) out = axl_string_new(NULL);
AxlJsonWriter w;
axl_json_writer_init(&w, out,
    AXL_JSON_WRITER_PRETTY | AXL_JSON_WRITER_TRAILING_COMMAS);

axl_json_obj_begin(&w);
    axl_json_comment(&w, "generated — do not edit by hand");
    axl_json_kv_str(&w, "name",    "AXL");
    axl_json_kv_int(&w, "version", 1);
axl_json_obj_end(&w);
axl_json_writer_finish(&w);

/* {
 *   // generated — do not edit by hand
 *   "name": "AXL",
 *   "version": 1,
 * }
 */

Comment behavior:

  • Pretty mode emits // text on its own line at the current indent.

  • Compact mode emits an inline /​* text *​/ block; embedded close-comment sequences are split so the comment can’t terminate early.

  • Embedded newlines truncate the comment.

  • Comments don’t disturb the writer’s container state — interleave them freely between values, between key+value pairs, or as the first/last item in a container.

The writer deliberately does not emit unquoted object keys or single-quoted strings. Those are JSON5 input-side conveniences only; emitting them adds escape-correctness footguns with no consumer benefit.

JSON5 conformance notes

AXL’s JSON5 support is scoped to the features that matter for firmware-edited sidecar configs (the in-tree consumer is tools/memspd.c reading share/jedec.json5). The following parts of the json5.org spec are intentionally not supported:

  • Infinity, -Infinity, NaN — AXL is freestanding UEFI with no libm. There’s no axl_json_get_double accessor, so IEEE 754 special values would be lex-only and unretrievable. axl_json_get_int on a fractional or scientific-notation token truncates at the first non-digit character (pre-existing strict-mode behavior, unchanged): {x: 1.5} returns 1.

  • Unicode IdentifierName for unquoted keys — only the ASCII subset ([A-Za-z_$][A-Za-z0-9_$]*) is recognized. Keys with Unicode letters, combining marks, ZWNJ, or ZWJ require quoting (single or double quotes both work).

  • Unicode whitespace (U+00A0 NBSP, U+FEFF BOM, U+2028 LS, U+2029 PS, and other Space_Separator characters) — only the ASCII whitespace set plus \v and \f is treated as insignificant. Documents using Unicode separators as whitespace will fail to parse.

These gaps are deliberate — adding lex support for tokens that can’t be retrieved (floats) or for grammar that no firmware tool actually authors (Unicode identifier keys) would expand the attack surface and surprise consumers without unlocking new use cases. Open an issue if a real consumer needs any of these and they can be revisited.

Console Output

const char *body = http_response_body;
axl_json_console_print(body, axl_strlen(body));

Writes to the UEFI console with cyan keys, green strings, yellow numbers, magenta booleans. Distinct from the writer’s pretty-print flag, which emits to a buffer without color.

AxlXml — Streaming XML writer + pull-token reader

Streaming XML writer over an AxlString plus a pull-token reader over a byte buffer. Caller manages namespaces (qnames like D:multistatus are opaque to the writer; namespace declarations are normal attributes). Out of scope: DTD validation, XSD, RelaxNG, XPath, XSLT, XML signatures. UTF-8 only.

Header: <axl/axl-xml.h>

Overview

Two independent APIs:

  • Writer (AxlXmlWriter) — value-typed state machine that builds XML into a caller-owned AxlString. Mirrors AxlJsonWriter’s shape: init takes flags bitmask, emitters return void, errors are sticky and checked at _finish. Auto-escapes < > & in body text and < & " in attribute values. Tag balance and start-tag-open windows are enforced; misuse sets the sticky error flag.

  • Reader (AxlXmlReader) — opaque pull-token reader. One AXL_XML_TOKEN_{START_ELEMENT, END_ELEMENT, TEXT, END_DOCUMENT} per axl_xml_reader_next call. Attribute lookup via axl_xml_reader_attr while positioned at a START_ELEMENT. Entity decoding (5 named + &#NNN; / &#xHH;) into a reader-owned scratch buffer reused per token. CDATA passes through with is_cdata set. Comments, processing instructions, and DOCTYPE declarations are skipped silently.

Writing XML

AXL_AUTOPTR(AxlString) out = axl_string_new(NULL);
AxlXmlWriter w;

axl_xml_writer_init(&w, out, AXL_XML_WRITER_DEFAULT);
axl_xml_writer_prologue(&w);
axl_xml_writer_start_element(&w, "D:multistatus");
axl_xml_writer_attribute(&w, "xmlns:D", "DAV:");
    axl_xml_writer_start_element(&w, "D:response");
        axl_xml_writer_start_element(&w, "D:href");
        axl_xml_writer_text(&w, "/dav/file.txt");  // auto-escaped
        axl_xml_writer_end_element(&w);
    axl_xml_writer_end_element(&w);
axl_xml_writer_end_element(&w);
axl_xml_writer_finish(&w);

if (!axl_xml_writer_error(&w)) {
    axl_printf("%s\n", axl_string_str(out));
}

Pass AXL_XML_WRITER_PRETTY at init for 2-space indent + newlines between child elements; text-only elements stay on one line.

Reading XML

const char *xml = "<root><greet lang=\"en\">hello &amp; world</greet></root>";
AxlXmlReader *r = axl_xml_reader_new(xml, axl_strlen(xml));
AxlXmlToken t;

while (axl_xml_reader_next(r, &t)) {
    switch (t.type) {
    case AXL_XML_TOKEN_START_ELEMENT:
        axl_printf("START: %.*s\n", (int)t.name_len, t.name);
        const char *lang = axl_xml_reader_attr(r, "lang");
        if (lang != NULL) {
            axl_printf("  lang=%s\n", lang);
        }
        break;
    case AXL_XML_TOKEN_TEXT:
        axl_printf("TEXT: %.*s\n", (int)t.text_len, t.text);
        break;
    case AXL_XML_TOKEN_END_ELEMENT:
        axl_printf("END: %.*s\n", (int)t.name_len, t.name);
        break;
    case AXL_XML_TOKEN_END_DOCUMENT:
        break;
    }
}

uint32_t line, col;
const char *msg;
if (axl_xml_reader_error(r, &line, &col, &msg)) {
    axl_printerr("parse error at %u:%u: %s\n", line, col, msg);
}
axl_xml_reader_free(r);

Token name / text pointers reference reader-owned storage and are invalidated on the next axl_xml_reader_next call. Copy out anything you need to keep.

Entity decoding + safety

The reader decodes the five named entities (&amp; &lt; &gt; &quot; &apos;) plus decimal and hex numeric character references. UTF-8 encoded for codepoints ≥ 0x80. Per XML 1.0 §4.1: references to U+0000 and to UTF-16 surrogates (U+D800..U+DFFF) are rejected as parse errors — both would otherwise corrupt downstream C-string handling or produce invalid UTF-8.

DOCTYPE declarations are tokenized and skipped, but the reader balances [ and ] brackets across the declaration so any internal entity definitions are NEVER processed. This closes the billion-laughs / external-entity attack classes without any content-side checks.

Well-formedness

Strict by construction:

  • Tag balance enforced (mismatched end tag → error).

  • Exactly one root element (content after root → error).

  • Non-whitespace content before / after the root → error.

  • Tag nesting capped at 64 levels in both writer and reader.

Status code

X1 (writer) shipped 2026-05-10. X2 (reader) shipped 2026-05-10. WebDAV axl_http_server_add_webdav’s PROPFIND emit migrated to AxlXmlWriter in X3 the same day; class-2 verb bodies (PROPPATCH / LOCK / UNLOCK) become implementable via the reader when a consumer asks.

AxlCache — TTL Cache

TTL cache with LRU eviction. Fixed-size slots, string keys, opaque fixed-size values. Designed for single-threaded UEFI use.

Header: <axl/axl-cache.h>

Overview

AxlCache is a simple key-value store with time-based expiration and least-recently-used eviction when full. Values are copied into fixed-size slots (not stored as pointers).

Use cases: DNS resolution caching, HTTP response caching, SMBIOS lookup caching.

// Cache up to 16 entries, each 4 bytes (e.g., IPv4 addresses)
AXL_AUTOPTR(AxlCache) cache = axl_cache_new(16, sizeof(uint32_t), 60000);
//                                           ^    ^                ^
//                                     slots  value size      TTL (ms)

// Store a value
uint32_t addr = 0xC0A80101;  // 192.168.1.1
axl_cache_put(cache, "gateway", &addr);

// Retrieve
uint32_t result;
if (axl_cache_get(cache, "gateway", &result) == 0) {
    // hit -- result contains the cached value
}

// Invalidate a specific entry
axl_cache_invalidate(cache, "gateway");

// Invalidate all entries
axl_cache_invalidate_all(cache);

AxlPageCache — LRU Page Cache

A fixed-capacity LRU cache of equal-sized pages backed by a caller-supplied fill function. Unlike AxlCache (TTL, string keys, copy-in/copy-out values), this is capacity-only, integer-indexed, and zero-copy: a lookup returns a borrowed pointer into the resident frame. On a miss the least-recently-used frame is evicted and refilled in place. It knows nothing about files — the fill function decides where the bytes come from — so it windows any large, randomly-addressed backing store where only the hot pages should stay resident. It is the mechanism behind AxlFileView.

// Frame size 4 KiB, 8 resident frames; fill from some backing store.
static int64_t fill(void *user, size_t page, void *dst, size_t cap) {
    return load_page(user, page, dst, cap);   // bytes written, or -1
}

AXL_AUTOPTR(AxlPageCache) pc = axl_page_cache_new(4096, 8, fill, backing);

size_t valid = 0;
const uint8_t *p = axl_page_cache_get(pc, page_index, &valid);
// p points into a resident frame; valid until the next get/clear.

AxlPageCacheStats st;
axl_page_cache_stats(pc, &st);   // hits / misses / evictions / fills

Multi-tenant mode. axl_page_cache_new_shared(page_size, max_frames) makes a fill-less cache that several owners share: axl_page_cache_fetch(pc, owner, page, fill, user, &valid) keys frames by (owner, page) and takes the fill per call, so one bounded frame budget serves many backing stores at once (every open file in an editor). axl_page_cache_drop_owner(pc, owner) returns a closing owner’s frames to the pool; eviction is global LRU across all owners. (The single-tenant axl_page_cache_get is this same primitive with the cache as its own owner.) AxlFileView / AxlPieceTree’s *_open_cached variants are built on it.

AxlTextBuffer — Editable Text Store

A growable, editable byte buffer with an integral line index, tuned for an interactive text editor: load a file once, then many small inserts/deletes near a moving cursor, with O(log n) byte-offset ↔ line mapping queried every keystroke and once per visible line per frame.

Storage is a gap buffer (O(1) amortized edits at the gap). The line index is a sorted array of newline offsets maintained incrementally on every edit — never a full rescan — so line_of_offset and line_bounds are binary searches. The store is byte-oriented: '\n' is the only special byte; UTF-8 / codepoint policy is the caller’s. A gap buffer is not contiguous, so content is read out via axl_text_buffer_get rather than a pointer.

AXL_AUTOPTR(AxlTextBuffer) tb = axl_text_buffer_new(0);
axl_text_buffer_set_bytes(tb, "ab\ncd\nef", 8);   // 3 lines

axl_text_buffer_insert(tb, 2, "X", 1);            // edit near the cursor
size_t line = axl_text_buffer_line_of_offset(tb, 4);

size_t start, end;
axl_text_buffer_line_bounds(tb, line, &start, &end);   // [start,end) excl. '\n'

char out[64];
size_t n = axl_text_buffer_get(tb, start, end - start, out, sizeof(out));

AxlMatch m;                                        // {start, length}
if (axl_text_buffer_find(tb, "cd", 2, 0, AXL_FIND_DEFAULT, &m)) { /* m.start */ }

axl_text_buffer_find mirrors axl_piece_tree_find (case-insensitive / backward / whole-word; matches that straddle the gap are handled) — both are thin wrappers over the shared axl_find_in_source engine (<axl/axl-find.h>).

For very large / out-of-core files, use AxlPieceTree below; the gap buffer is the memory-resident store.

AxlRBTree — Intrusive Augmented Red-Black Tree

A generic, intrusive red-black tree: the caller embeds an AxlRBNode in its own struct (the tree never allocates nodes) and descends to the insertion point itself, so the same tree serves ordered maps, order-statistic trees, and weighted positional trees. An optional recompute callback maintains a cached subtree aggregate (size, byte/newline sums, …) across every structural change in O(log n). Distinct from AxlTree (a non-intrusive key→value AVL map); this is intrusive and augmentable. It is the substrate behind AxlPieceTree. Reimplemented under Apache-2.0 from the textbook algorithm — no GPL source. See docs/AXL-RBTree-Design.md.

typedef struct { AxlRBNode node; int key; size_t sub_count; } Ent;
static void recompute(AxlRBNode *n, void *u) {
    Ent *e = AXL_RB_ENTRY(n, Ent, node);
    e->sub_count = 1
        + (n->left  ? AXL_RB_ENTRY(n->left,  Ent, node)->sub_count : 0)
        + (n->right ? AXL_RB_ENTRY(n->right, Ent, node)->sub_count : 0);
}

AxlRBTree t;
axl_rb_tree_init(&t, recompute, NULL);
// caller descends to the slot, then links + rebalances:
AxlRBNode **link = &t.root, *parent = NULL;
while (*link) { parent = *link;
    link = (e->key < AXL_RB_ENTRY(parent, Ent, node)->key)
         ? &parent->left : &parent->right; }
axl_rb_link_node(&e->node, parent, link);
axl_rb_insert(&t, &e->node);     // recompute propagates the subtree sums

AxlPieceTree — Out-of-Core Editable Buffer

An out-of-core, editable text buffer for large files. The original file is never loaded whole — its bytes are read on demand through AxlFileView — while edits accumulate in an append-only add buffer. A balanced tree of pieces (spans into either source, held in an AxlRBTree augmented with subtree byte and newline sums) presents one logical document with O(log n) offset↔line mapping and O(log n) edits, so editing a multi-gigabyte file costs memory proportional to the edits, not the file. This is the structure VS Code calls a “piece tree.” Line semantics match AxlTextBuffer exactly (interchangeable for a renderer). See docs/AXL-PieceTree-Design.md.

AXL_AUTOPTR(AxlPieceTree) pt = axl_piece_tree_open("fs0:\\big.log", 0, 0);
axl_piece_tree_insert(pt, 50000, "hello\n", 6);   // O(log n), no big copy

char out[64];
size_t n = axl_piece_tree_get(pt, 50000, 6, out, sizeof(out));
size_t line = axl_piece_tree_line_of_offset(pt, 50000);
axl_piece_tree_save(pt, "fs0:\\big.log");          // crash-safe, streamed

size_t at, sel;                                    // unlimited by default
axl_piece_tree_undo(pt, &at, &sel);                // &at/&sel locate the change
axl_piece_tree_redo(pt, NULL, NULL);               // (out-params optional)

Undo/redo is built in and unlimited by default (axl_piece_tree_set_undo_limit to cap or disable). undo/redo report where the change landed (affected_offset + affected_len — non-zero to re-select restored text, zero for a net deletion) so the editor can place the caret/selection at the edit site. Because the original and add buffers are immutable/append-only, the bytes needed to reverse an edit are never discarded — undo records are tiny span deltas, not text copies. Three undo-grouping tools, three distinct jobs: axl_piece_tree_undo_group_begin/_end (nestable, depth-counted) is the explicit atomic-transaction bracket — every edit between them undoes as one step; use it for imperative multi-edit ops (paste, find-replace-all, multi-cursor). axl_piece_tree_apply_edits does the same for a batch you already hold as an AxlEdit[] (one group, with offset adjustment) — prefer it when the edits are known up front. axl_piece_tree_undo_checkpoint is the opposite model: accumulate-until-break keystroke coalescing, where consecutive edits merge until the editor declares a boundary (a pause, a cursor jump, a type↔delete switch) for VS Code-like smart grouping — use it for live typing, not for bracketing a transaction. What to group is always the editor’s policy; the buffer supplies the mechanism.

Editor-substrate helpers layer on top for a full editor: axl_piece_tree_find searches a byte substring across the virtual document (cross-piece) with case-insensitive / backward / whole-word flags and reports an AxlMatch ({start, length}). It is a thin wrapper over the shared search engine axl_find_in_source (<axl/axl-find.h>), which runs Boyer–Moore–Horspool over an abstract AxlByteReader — the same engine axl_text_buffer_find uses, so a gap buffer and a piece tree share one matcher (windowing with overlap so a match straddling a piece boundary or the gap is never missed; a contiguous source is scanned in place via the reader’s peek). axl_piece_tree_is_modified is a save-point-aware dirty flag; axl_piece_tree_apply_edits applies a batch of original-coordinate edits (replace-all, multi-cursor) as one undo group; and the axl_piece_tree_line_iter_* iterator walks every line in one O(n) pass (_line_iter_init_at starts at a given line for a deep viewport). Caret support: undo/redo report the affected range, the axl_piece_tree_cp_align / _cp_next / _cp_prev helpers step UTF-8 codepoint boundaries, and axl_piece_tree_get_alloc copies a range out as a fresh NUL-terminated buffer. For files that aren’t plain UTF-8, axl_piece_tree_load_encoded detects the encoding (UTF-8 ± BOM, UTF-16 LE/BE), decodes to a UTF-8 document (plain UTF-8 stays out-of-core; others transcode in), and reports the encoding + BOM so axl_piece_tree_save_encoded round-trips them. axl_piece_tree_detect_eol classifies the line endings (LF / CRLF / CR / MIXED) and axl_piece_tree_set_eol makes save normalize every terminator to a chosen style while streaming (conversion without materializing the document); line_bounds and the line iterator exclude a CRLF’s trailing \r so a renderer sees clean content. The document stays \n-indexed internally — only \n delimits lines for line_count / line_of_offset. axl_piece_tree_set_read_only freezes the buffer (insert / delete / apply_edits return AXL_ERR; reads, search, and save still work). axl_piece_tree_backing_changed reports whether the backing file’s size or mtime changed on disk since open, so an out-of-core editor can detect an external edit (or deletion) and offer a reload. For many files at once, axl_piece_tree_open_cached(path, cache) opens out-of-core sharing a caller-owned AxlPageCache so one bounded frame budget covers every open document.

Saving over the open file (rebase). There is deliberately no in-place “save over yourself”: for an out-of-core document, overwriting the file it reads from would invalidate the resident ORIGINAL-piece offsets (and the add-buffer-relative undo log) mid-flight. So the consumer composes the existing primitives and chooses the policy:

// Save over the open file — a rebase: write a sibling temp, drop the
// document, move the temp into place, reopen. Undo history resets; the
// reopened document is a clean, single-original-piece buffer (bounded).
axl_piece_tree_save_encoded(pt, "fs0:\\F.savetmp", enc, bom);  // preserve encoding/EOL
axl_piece_tree_free(pt);
axl_file_move("fs0:\\F.savetmp", "fs0:\\F");
pt = axl_piece_tree_load_encoded("fs0:\\F", 0, 0, &enc, &bom); // clean, not modified
// (then axl_piece_tree_set_eol(pt, axl_piece_tree_detect_eol(pt)) to keep the EOL mode)

// Save-As — keep editing: just save to a new path. The document and its
// undo history are untouched; it is marked clean against the new file.
axl_piece_tree_save_encoded(pt, "fs0:\\F-copy", enc, bom);

Use save_encoded + load_encoded (not the plain save/open) for the rebase so the file’s encoding and BOM survive it; re-apply set_eol after the reopen if you converted line endings. (Both saves are crash-safe — they write to <path>.tmp and rename — so the sibling-temp name just needs to differ from the original.) Caret / scroll / selection / read-only state are the editor’s to snapshot and restore around the free→move→reopen; undo necessarily resets (the add-buffer offsets it referenced are gone).

AxlEncoding enc; bool bom;
AXL_AUTOPTR(AxlPieceTree) doc = axl_piece_tree_load_encoded(
    "fs0:\\notes.txt", 0, 0, &enc, &bom);   // detects + decodes
AxlMatch hit;
if (axl_piece_tree_find(doc, "TODO", 4, 0, AXL_FIND_CASE_INSENSITIVE, &hit)) {
    /* hit.start / hit.length locate the match */
}
axl_piece_tree_save_encoded(doc, "fs0:\\notes.txt", enc, bom);  // same form back

AxlRegex — Regular-Expression Matcher

A compiled-pattern regular-expression matcher over the same AxlByteReader seam. Compile once with axl_regex_new, then search many times — the right shape for find-all loops and matching one pattern across many lines or buffers. The engine is a Thompson NFA / Pike VM, not a backtracker, so match time is O(pattern × input) for every pattern: there is no catastrophic (“ReDoS”) blow-up. Backreferences are deliberately unsupported because they are not regular and would force backtracking.

Supported: literals, ., greedy and lazy quantifiers (* + ? *? +? ??), anchors ^ $, alternation |, grouping and capture ( ), classes [...] / [^...] with ranges, and \d \w \s (plus negations). Matching is byte-oriented and leftmost (Perl / grep -P priority, not POSIX leftmost-longest). Compile flags: CASELESS, MULTILINE, DOTALL. Match flags: ANCHORED pins the match to from_offset; NOTBOL / NOTEOL treat from_offset / the buffer end as mid-stream so ^ / $ (and the start/end anchors) don’t match there (POSIX REG_NOTBOL / REG_NOTEOL) — for scanning a larger source in overlapping windows without anchors firing at every boundary.

AXL_AUTOPTR(AxlRegex) re = axl_regex_new("(\\w+)@(\\w+)", AXL_REGEX_DEFAULT);
AxlMatch g[3];                                   // [0]=whole, [1]/[2]=groups
AxlMemReader r;
axl_mem_reader_init(&r, "contact bob@host now", 20);
if (axl_regex_search_captures(re, &r.reader, 0, AXL_REGEX_MATCH_DEFAULT, g, 3)) {
    /* g[0] = "bob@host", g[1] = "bob", g[2] = "host" */
}

Find-all is the same loop literal find uses — re-search from m.start + (m.length ? m.length : 1). The matcher needs a contiguous view of the scanned region: it uses the reader’s zero-copy peek when available, otherwise materializes the region into a temporary buffer (O(region) per call — fine for editor-sized regions; for find-all over a large out-of-core document, read the range out once and search the buffer). The axl_text_buffer_find_regex / axl_piece_tree_find_regex wrappers run a compiled regex over those sources.

AxlRadixTree — Radix Tree

Compact prefix tree (radix tree) with string keys. Supports exact lookup, longest-prefix lookup, insert with automatic edge splitting, remove with node collapse, and depth-first iteration. Lookup is O(k) where k is the key length, independent of the number of entries.

Header: <axl/axl-radix-tree.h>

Overview

Use AxlRadixTree when you need longest-prefix matching — finding the best match for a key that may not be an exact entry. The canonical use case is URL route matching: given routes /api/users and /api, a lookup for /api/users/42 returns the /api/users handler.

AxlRadixTree *tree = axl_radix_tree_new();

axl_radix_tree_insert(tree, "/api/users", handler_users);
axl_radix_tree_insert(tree, "/api",       handler_api);
axl_radix_tree_insert(tree, "/css/",      handler_static);

// Exact lookup
void *h = axl_radix_tree_lookup(tree, "/api/users");  // handler_users

// Longest-prefix lookup (the key feature)
const char *suffix;
h = axl_radix_tree_lookup_prefix(tree, "/api/users/42", &suffix);
// h = handler_users, suffix = "/42"

h = axl_radix_tree_lookup_prefix(tree, "/css/style.css", &suffix);
// h = handler_static, suffix = "style.css"

// Iterate all entries
axl_radix_tree_foreach(tree, print_entry, NULL);

axl_radix_tree_free(tree);

Value Destructor

Use axl_radix_tree_new_full(value_free) to auto-free values on removal or tree destruction:

AxlRadixTree *tree = axl_radix_tree_new_full(axl_free);
axl_radix_tree_insert(tree, "key", axl_strdup("owned value"));
axl_radix_tree_free(tree);  // value freed automatically

AxlRingBuf — Ring Buffer

Byte-oriented circular buffer with power-of-2 sizing and three API layers. Inspired by Linux kfifo: monotonically increasing indices with mask-based wrapping, using all buffer slots with no wasted-slot ambiguity.

Header: <axl/axl-ring-buf.h>

Overview

AxlRingBuf provides three layers, each building on the one below:

  • Layer 1 (Bytes): raw byte stream — push, pop, peek, discard, zero-copy scatter/gather regions

  • Layer 2 (Messages): variable-size length-prefixed frames – push_msg, pop_msg, peek_msg, peek_msg_size

  • Layer 3 (Elements): fixed-size typed entries — push_elem, pop_elem, peek_elem, peek/set_nth_elem

Supports reject-on-full (default) and overwrite-on-full modes.

// Heap-allocated ring buffer
AxlRingBuf *rb = axl_ring_buf_new(1024);

// Layer 1: raw bytes
axl_ring_buf_push(rb, "hello", 5);
char buf[32];
uint32_t n = axl_ring_buf_pop(rb, buf, sizeof(buf));  // n = 5

// Layer 2: variable-size messages
axl_ring_buf_push_msg(rb, "short", 5);
axl_ring_buf_push_msg(rb, "a longer message", 16);
uint32_t actual;
axl_ring_buf_pop_msg(rb, buf, sizeof(buf), &actual);  // "short", actual=5

axl_ring_buf_free(rb);

// Layer 3: fixed-size elements (use new_fixed constructor)
AxlRingBuf *erb = axl_ring_buf_new_fixed(256, sizeof(int), 0);
int vals[] = {10, 20, 30};
for (int i = 0; i < 3; i++) {
    axl_ring_buf_push_elem(erb, &vals[i]);
}
int out;
axl_ring_buf_pop_elem(erb, &out);  // out = 10 (FIFO)
axl_ring_buf_free(erb);

Overwrite Mode

In overwrite mode, writes always succeed by discarding the oldest data:

AxlRingBuf *rb = axl_ring_buf_new_full(8, AXL_RING_BUF_OVERWRITE);
axl_ring_buf_push(rb, "12345678", 8);  // full
axl_ring_buf_push(rb, "AB", 2);        // discards "12", buffer has "345678AB"
axl_ring_buf_free(rb);

Embedded (Stack/Static) Usage

The struct is exposed, so it can be embedded in other structs or allocated on the stack with no heap allocation:

uint8_t buf[256];
AxlRingBuf rb;
axl_ring_buf_init(&rb, buf, sizeof(buf), 0, NULL);

axl_ring_buf_push(&rb, "no heap!", 8);
axl_ring_buf_deinit(&rb);

Zero-Copy Access

For high-performance I/O, access the buffer directly without copying:

AxlRingBufRegion regions[2];
uint32_t count = axl_ring_buf_peek_regions(rb, regions);
for (uint32_t i = 0; i < count; i++) {
    process(regions[i].data, regions[i].len);
}
axl_ring_buf_pop_advance(rb, total_processed);

Push Statistics

Cumulative byte counters track every push attempt, including the ones that don’t survive:

uint64_t pushed = axl_ring_buf_pushes_total(rb);  // bytes attempted
uint64_t lost   = axl_ring_buf_pushes_lost(rb);   // bytes invisible to consumer

pushes_lost covers (a) bytes rejected when reject-mode pushes exceed available space, (b) bytes dropped from the front of an oversized input in overwrite mode, and (c) older bytes displaced by a new overwrite-mode push. Both counters reset on axl_ring_buf_clear and on init.

Element-mode buffers translate trivially: divide by elem_size to get element counts. The kernel POC’s reqlog server uses exactly that pattern to surface “received” and “dropped” totals on its / endpoint.

API Reference

AxlHashTable

Typedefs

typedef struct AxlHashTable AxlHashTable

axl-hash-table.h:

GLib-style hash table with generic keys. FNV-1a hashing, chained collision resolution, automatic resize at 75% load factor.

API mirrors GLib’s GHashTable (g_hash_table_*). See GLib documentation for detailed usage patterns. Key differences:

  • axl_hash_table_new_str() is an AXL convenience (copies string keys)

  • Insert/replace return a tri-state int (1=new, 0=replaced, -1=OOM) instead of GLib’s bool, since GLib aborts on OOM and AXL recovers.

  • No reference counting (use axl_hash_table_free, not unref)

Ownership of keys and values

All three constructors allocate the AxlHashTable struct itself. They differ in how the table treats the key and value pointers passed to insert/replace:

axl_hash_table_new_str() Keys are COPIED internally via strdup; the table owns the copy and frees it on remove/free. Values are borrowed — caller manages their lifetime.

axl_hash_table_new(hash, equal) Keys and values are BORROWED. Caller manages all lifetimes. The table never copies or frees either.

axl_hash_table_new_full(hash, equal, key_destroy, value_destroy) Keys are NOT copied. The table TAKES OWNERSHIP if the corresponding destroy callback is non-NULL — on remove/free it calls the destroy callback on the pointer it stored. If a destroy callback is NULL, that side is treated as borrowed.

Insert vs. replace differ on key collision when ownership is in play (see axl_hash_table_insert / axl_hash_table_replace docs):

  • insert: keep the OLD key, destroy the NEW key

  • replace: destroy the OLD key, keep the NEW key Both destroy the old value either way.

typedef size_t (*AxlHashFunc)(const void *key)

Hash function: compute a hash value from a key.

typedef bool (*AxlEqualFunc)(const void *a, const void *b)

Equality function: return true if two keys are equal.

typedef void (*AxlHashTableForeachFunc)(const void *key, void *value, void *data)

Callback for axl_hash_table_foreach (GLib: GHFunc).

typedef bool (*AxlHashTableForeachRemoveFunc)(const void *key, void *value, void *data)

Predicate for axl_hash_table_foreach_remove (GLib: GHRFunc). Return true to remove the entry.

typedef bool (*AxlHashTableFindFunc)(const void *key, void *value, void *data)

Predicate for axl_hash_table_find (GLib: GHRFunc). Return true when the entry is the one being searched for.

Enums

enum AxlHashTableInsertResult

Outcome of axl_hash_table_insert / axl_hash_table_replace.

Three distinguishable outcomes — the operation either added a new entry, replaced an existing one, or failed allocation. Follows the AxlSidecarStatus precedent for typed multi-outcome status enums.

Numeric values are part of the contract: callers comparing against the named constants AND against literal 1/0/-1 both work. New codes only ever extend the negative range.

Values:

enumerator AXL_HASH_TABLE_NEW

new entry was added

enumerator AXL_HASH_TABLE_REPLACED

existing entry was replaced

enumerator AXL_HASH_TABLE_ERR

allocation failure (also logged)

Functions

size_t axl_direct_hash(const void *key)

Hash a pointer value directly. (GLib: g_direct_hash)

bool axl_direct_equal(const void *a, const void *b)

Pointer equality. (GLib: g_direct_equal)

size_t axl_int_hash(const void *key)

Hash the int pointed to by key. (GLib: g_int_hash)

bool axl_int_equal(const void *a, const void *b)

Equality of the ints pointed to by a and b. (GLib: g_int_equal)

size_t axl_int64_hash(const void *key)

Hash the int64_t pointed to by key. (GLib: g_int64_hash)

bool axl_int64_equal(const void *a, const void *b)

Equality of the int64_ts pointed to by a and b. (GLib: g_int64_equal)

size_t axl_double_hash(const void *key)

Hash the double pointed to by key. (GLib: g_double_hash)

bool axl_double_equal(const void *a, const void *b)

Equality of the doubles pointed to by a and b. (GLib: g_double_equal)

AxlHashTable *axl_hash_table_new_str(void)

Create a string-keyed hash table that COPIES its keys.

Allocates the AxlHashTable struct. Each call to insert/replace also strdup’s the key into a heap-allocated copy that the table owns; that copy is freed on remove and on axl_hash_table_free. Values are borrowed — caller retains ownership and lifetime responsibility.

AXL extension; no GLib equivalent. Use this when keys are borrowed or literal strings and you want zero ceremony around key lifetime.

Returns:

new AxlHashTable, or NULL on allocation failure.

AxlHashTable *axl_hash_table_new(AxlHashFunc hash_func, AxlEqualFunc equal_func)

Create a hash table that BORROWS keys and values.

Allocates the AxlHashTable struct. Keys and values are stored as raw pointers — the table never copies and never frees either side. Caller manages all lifetimes.

Matches g_hash_table_new().

Parameters:
  • hash_func – hash function

  • equal_func – equality function

Returns:

new AxlHashTable, or NULL on allocation failure.

AxlHashTable *axl_hash_table_new_full(AxlHashFunc hash_func, AxlEqualFunc equal_func, AxlDestroyNotify key_destroy, AxlDestroyNotify value_destroy)

Create a hash table that TAKES OWNERSHIP of keys/values.

Allocates the AxlHashTable struct. Keys and values are stored by pointer (NOT copied); the table calls key_destroy / value_destroy on remove/free for any side whose destroy callback is non-NULL. Pass NULL for a side to leave it borrowed (caller retains ownership of that side).

Pass NULL for hash_func and equal_func to default to axl_str_hash / axl_str_equal.

Matches g_hash_table_new_full().

Parameters:
  • hash_func – hash function, or NULL for axl_str_hash

  • equal_func – equality function, or NULL for axl_str_equal

  • key_destroy – key destructor, or NULL to borrow keys

  • value_destroy – value destructor, or NULL to borrow values

Returns:

new AxlHashTable, or NULL on allocation failure.

void axl_hash_table_free(AxlHashTable *h)

Free a hash table and all entries.

Calls key_destroy and value_destroy on each entry (if set). Equivalent to g_hash_table_destroy().

Parameters:
  • h – hash table (NULL-safe)

AxlHashTableInsertResult axl_hash_table_insert(AxlHashTable *h, const void *key, void *value)

Insert a key-value pair, keeping the OLD key on collision.

If the key already exists: the new key is freed via key_destroy (if set), the old key is kept, and the old value is freed via value_destroy (if set). Matches g_hash_table_insert().

For tables created with axl_hash_table_new_str() (copy_keys mode), insert and replace behave identically.

Parameters:
  • h – hash table

  • key – key (copied for new() tables, owned for new_full())

  • value – value pointer

Returns:

one of the AxlHashTableInsertResult values.

AxlHashTableInsertResult axl_hash_table_replace(AxlHashTable *h, const void *key, void *value)

Insert a key-value pair, keeping the NEW key on collision.

If the key already exists: the old key is freed via key_destroy (if set), the new key is kept, and the old value is freed via value_destroy (if set). Matches g_hash_table_replace().

For tables created with axl_hash_table_new_str() (copy_keys mode), insert and replace behave identically.

Parameters:
  • h – hash table

  • key – key (copied for new() tables, owned for new_full())

  • value – value pointer

Returns:

one of the AxlHashTableInsertResult values.

void *axl_hash_table_lookup(AxlHashTable *h, const void *key)

Look up a key.

Matches g_hash_table_lookup().

Parameters:
  • h – hash table

  • key – key to look up

Returns:

value pointer, or NULL if not found.

bool axl_hash_table_contains(AxlHashTable *h, const void *key)

Check if a key exists in the table.

Unlike lookup, this distinguishes between a key with a NULL value and a missing key. Matches g_hash_table_contains().

Parameters:
  • h – hash table

  • key – key to check

Returns:

true if the key exists, false otherwise.

bool axl_hash_table_add(AxlHashTable *h, void *key)

Add a key to a set-style table (value is the key itself).

Stores key with its value set to key, the GLib set idiom. Replaces any existing entry for an equal key (keeping the new key, like axl_hash_table_replace). Intended for set-style tables — tables created with axl_hash_table_new() (borrowed) or axl_hash_table_new_full() with a key destructor only. Do NOT use with a value_destroy callback: the value aliases the key, so a value destructor would double-free it. Likewise not meaningful for axl_hash_table_new_str() (copy_keys) tables — the stored value would alias the caller’s key, not the table’s internal copy.

Matches g_hash_table_add().

Parameters:
  • h – hash table

  • key – key, also stored as the value

Returns:

true if the key was newly added, false if it replaced an existing entry or allocation failed.

bool axl_hash_table_remove(AxlHashTable *h, const void *key)

Remove an entry, calling key_destroy and value_destroy.

Matches g_hash_table_remove().

Parameters:
  • h – hash table

  • key – key to remove

Returns:

true if removed, false if not found.

void axl_hash_table_remove_all(AxlHashTable *h)

Remove every entry, calling key_destroy and value_destroy.

Leaves the table empty and reusable (the bucket array is retained). Matches g_hash_table_remove_all().

Parameters:
  • h – hash table (NULL-safe)

void *axl_hash_table_find(AxlHashTable *h, AxlHashTableFindFunc predicate, void *data)

Find the first value whose entry satisfies a predicate.

Calls predicate for each entry (in unspecified order) until it returns true, then returns that entry’s value. Matches g_hash_table_find().

Parameters:
  • h – hash table

  • predicate – predicate, returns true on match

  • data – opaque data passed to predicate

Returns:

the matching value, or NULL if no entry matches.

bool axl_hash_table_steal(AxlHashTable *h, const void *key)

Remove an entry WITHOUT calling destructors.

The caller takes ownership of the value. For copy_keys tables, the internal key copy is freed (since the caller never had it). Matches g_hash_table_steal().

Parameters:
  • h – hash table

  • key – key to steal

Returns:

true if removed, false if not found.

size_t axl_hash_table_size(AxlHashTable *h)

Get the number of entries.

Matches g_hash_table_size().

Parameters:
  • h – hash table

Returns:

entry count.

bool axl_hash_table_owns_entries(AxlHashTable *h)

Predicate: does this table take ownership of (and free) any pointer key + value passed to insert / replace?

Returns true iff the table satisfies all of:

  • copy_keys == false — caller-provided keys are stored as-is (so a axl_strdup’d key handed to insert isn’t redundantly re-strdup’d).

  • key_destroy != NULL — the table will free those keys on remove / replace / table free.

  • value_destroy != NULL — the table will likewise free stored values.

Tables built via

axl_hash_table_new_full(..., axl_free_impl,

axl_free_impl)

satisfy this. Tables built via axl_hash_table_new_str() (copy_keys=true, both destroys NULL) do NOT — passing strdup’d keys leaks the caller’s copy AND the value isn’t freed on table free.

Useful for helpers that lazy-allocate or compose with a caller’s pre-existing table and want to verify the destroy-func contract before inserting heap-owned entries (e.g. the axl_http_response_set_content_range helper that strdups both sides of the Content-Range header).

Parameters:
  • h – hash table

Returns:

true if both keys and values will be freed on remove and copy_keys is false (so caller-provided pointer keys are stored without redundant strdup); false otherwise.

void axl_hash_table_foreach(AxlHashTable *h, AxlHashTableForeachFunc func, void *data)

Iterate all entries. Order is undefined.

Matches g_hash_table_foreach().

Parameters:
  • h – hash table

  • func – callback

  • data – opaque data passed to callback

size_t axl_hash_table_foreach_remove(AxlHashTable *h, AxlHashTableForeachRemoveFunc func, void *data)

Remove entries matching a predicate.

Calls func for each entry. If it returns true, the entry is removed (key_destroy and value_destroy are called). Matches g_hash_table_foreach_remove().

Parameters:
  • h – hash table

  • func – predicate

  • data – opaque data

Returns:

number of entries removed.

AxlList *axl_hash_table_get_keys(AxlHashTable *h)

Collect all keys into a newly allocated list.

The returned list holds the table’s key pointers (borrowed — the keys themselves still belong to the table). Free only the list spine with axl_list_free(); do not free the keys through it. Order is unspecified. Best-effort under memory pressure: a list-node allocation failure drops that key silently rather than erroring. Matches g_hash_table_get_keys().

Parameters:
  • h – hash table

Returns:

a new AxlList of keys, or NULL if the table is empty.

AxlList *axl_hash_table_get_values(AxlHashTable *h)

Collect all values into a newly allocated list.

The returned list holds the table’s value pointers (borrowed). Free only the list spine with axl_list_free(). Order is unspecified. Matches g_hash_table_get_values().

Parameters:
  • h – hash table

Returns:

a new AxlList of values, or NULL if the table is empty.

void axl_hash_table_iter_init(AxlHashTableIter *iter, AxlHashTable *h)

Initialize an iterator over a hash table.

Matches g_hash_table_iter_init().

Parameters:
  • iter – iterator to initialize

  • h – hash table to iterate

bool axl_hash_table_iter_next(AxlHashTableIter *iter, void **key, void **value)

Advance to the next entry.

key and value are optional (pass NULL to ignore). Matches g_hash_table_iter_next().

Parameters:
  • iter – iterator

  • key – receives key pointer, or NULL

  • value – receives value pointer, or NULL

Returns:

true if an entry was returned, false if exhausted.

void axl_hash_table_iter_remove(AxlHashTableIter *iter)

Remove the current entry, calling destructors.

Must be called after a successful axl_hash_table_iter_next(). Matches g_hash_table_iter_remove().

Parameters:
  • iter – iterator

void axl_hash_table_iter_steal(AxlHashTableIter *iter)

Remove the current entry WITHOUT calling destructors.

Must be called after a successful axl_hash_table_iter_next(). Matches g_hash_table_iter_steal().

Parameters:
  • iter – iterator

struct AxlHashTableIter
#include <axl-hash-table.h>

AxlHashTableIter:

Stack-allocated iterator. Matches GHashTableIter. Fields prefixed with _ are private.

Public Members

AxlHashTable *table
size_t _bucket
size_t _cur_bucket
void *_current
void *_next

AxlArray

Typedefs

typedef struct AxlArray AxlArray

axl-array.h:

Growable dynamic array. Two modes:

  • Value mode: stores copies of fixed-size elements.

  • Pointer mode: stores void* pointers (element_size = sizeof(void*)).

Functions

AxlArray *axl_array_new(size_t element_size)

Create a new dynamic array for value-mode storage.

Parameters:
  • element_size – size of each element in bytes

Returns:

new AxlArray, or NULL on failure. Free with axl_array_free().

void axl_array_free(AxlArray *a)

Free a dynamic array and its internal buffer.

Parameters:
  • a – array (NULL-safe)

int axl_array_append(AxlArray *a, const void *element)

Append an element (value mode).

Parameters:
  • a – array

  • element – pointer to element data to copy (element_size bytes)

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

int axl_array_insert(AxlArray *a, size_t index, const void *element)

Insert an element at index (value mode), shifting the rest right.

index may equal the current length (equivalent to append). Matches g_array_insert_val.

Parameters:
  • a – array

  • index – position to insert at (0..length)

  • element – pointer to element data to copy (element_size bytes)

Returns:

AXL_OK on success, AXL_ERR on allocation failure or if index is past the end (> length).

int axl_array_prepend(AxlArray *a, const void *element)

Prepend an element (value mode) — insert at the front.

Equivalent to axl_array_insert(a, 0, element). Matches g_array_prepend_val. O(n) (shifts all elements).

Parameters:
  • a – array

  • element – pointer to element data to copy (element_size bytes)

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

void *axl_array_get(AxlArray *a, size_t index)

Get a pointer to the element at index (value mode).

Parameters:
  • a – array

  • index – element index

Returns:

pointer into internal buffer, or NULL if out of range.

size_t axl_array_len(AxlArray *a)

Get the number of elements in the array.

Parameters:
  • a – array

Returns:

number of elements.

void axl_array_clear(AxlArray *a)

Remove all elements. Does not free the internal buffer.

Parameters:
  • a – array

int axl_array_append_ptr(AxlArray *a, void *ptr)

Append a pointer (pointer mode).

Parameters:
  • a – array

  • ptr – pointer to store

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

int axl_array_insert_ptr(AxlArray *a, size_t index, void *ptr)

Insert a pointer at index (pointer mode), shifting the rest right.

index may equal the current length (equivalent to append). Matches g_ptr_array_insert.

Parameters:
  • a – array

  • index – position to insert at (0..length)

  • ptr – pointer to store

Returns:

AXL_OK on success, AXL_ERR on allocation failure or if index is past the end (> length).

int axl_array_prepend_ptr(AxlArray *a, void *ptr)

Prepend a pointer (pointer mode) — insert at the front.

Equivalent to axl_array_insert_ptr(a, 0, ptr). O(n).

Parameters:
  • a – array

  • ptr – pointer to store

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

void *axl_array_get_ptr(AxlArray *a, size_t index)

Get stored pointer at index (pointer mode).

Parameters:
  • a – array

  • index – element index

Returns:

stored pointer, or NULL if out of range.

void axl_array_sort(AxlArray *a, AxlCompareFunc compare)

Sort array elements in place (introsort, O(n log n)).

Delegates to axl_qsort(). Not stable: equal elements may be reordered.

Parameters:
  • a – array

  • compare – comparison function (qsort-compatible)

int axl_array_remove_index(AxlArray *a, size_t index)

Remove the element at index, shifting remaining elements left.

Parameters:
  • a – array

  • index – element index to remove

Returns:

AXL_OK on success, AXL_ERR if index is out of range.

int axl_array_remove_index_fast(AxlArray *a, size_t index)

Remove the element at index by swapping with the last element.

O(1) but does not preserve order.

Parameters:
  • a – array

  • index – element index to remove

Returns:

AXL_OK on success, AXL_ERR if index is out of range.

int axl_array_remove_range(AxlArray *a, size_t index, size_t len)

Remove len elements starting at index, shifting remaining left.

Parameters:
  • a – array

  • index – first element to remove

  • len – number of elements to remove

Returns:

AXL_OK on success, AXL_ERR if the range is out of bounds.

int axl_array_set_size(AxlArray *a, size_t len)

Resize the array to exactly len elements.

If growing, new elements are zero-initialized. If growing beyond capacity, the internal buffer is reallocated. If shrinking, length is reduced without reallocating.

Parameters:
  • a – array

  • len – desired number of elements

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

void axl_array_sort_with_data(AxlArray *a, AxlCompareDataFunc compare, void *user_data)

Sort in place with a context-aware comparator (introsort).

Delegates to axl_qsort_with_data(). Not stable: equal elements may be reordered.

Parameters:
  • a – array

  • compare – comparison function with user_data

  • user_data – passed to every compare call

AxlList

Typedefs

typedef void *(*AxlCopyFunc)(const void *src, void *user_data)

axl-list.h:

Doubly-linked list. GLib GList equivalent. Node struct is exposed for direct traversal: for (AxlList *l = list; l; l = l->next) { use(l->data); }

Functions that modify the head return the new head pointer. Always assign back: list = axl_list_append(list, data);

Also defines shared callback types used by AxlSList and AxlQueue. AxlCopyFunc:

Deep copy function (GCopyFunc equivalent). Returns a newly allocated copy of src.

Functions

AxlList *axl_list_append(AxlList *list, void *data)

Append data to the end of the list. O(n).

Parameters:
  • list – current head (NULL for empty list)

  • data – data pointer to store

Returns:

new head of the list.

AxlList *axl_list_prepend(AxlList *list, void *data)

Prepend data to the front of the list. O(1).

Parameters:
  • list – current head (NULL for empty list)

  • data – data pointer to store

Returns:

new head of the list.

AxlList *axl_list_insert(AxlList *list, void *data, int position)

Insert data at the given position. O(n).

Negative or out-of-range position appends to the end.

Parameters:
  • list – current head

  • data – data pointer to store

  • position – 0-based insert position

Returns:

new head of the list.

AxlList *axl_list_insert_sorted(AxlList *list, void *data, AxlCompareFunc func)

Insert data in sorted order. O(n).

The list must already be sorted by the same comparator.

Parameters:
  • list – current head (sorted)

  • data – data pointer to insert

  • func – comparison function

Returns:

new head of the list.

AxlList *axl_list_remove(AxlList *list, const void *data)

Remove the first element matching data. O(n).

Compares by pointer equality. Frees the node, not the data.

Parameters:
  • list – current head

  • data – data pointer to find and remove

Returns:

new head of the list.

AxlList *axl_list_reverse(AxlList *list)

Reverse the list in place. O(n).

Parameters:
  • list – current head

Returns:

new head (was the tail).

AxlList *axl_list_concat(AxlList *list1, AxlList *list2)

Concatenate two lists. O(n) in length of list1.

list2 is appended to list1. Both must be distinct lists.

Parameters:
  • list1 – first list

  • list2 – second list (appended)

Returns:

head of the combined list.

AxlList *axl_list_sort(AxlList *list, AxlCompareFunc func)

Sort the list using merge sort. O(n log n), stable.

Parameters:
  • list – current head

  • func – comparison function

Returns:

new head of the sorted list.

AxlList *axl_list_copy(AxlList *list)

Shallow copy of the list. O(n).

Copies node structure; data pointers are shared, not duplicated.

Parameters:
  • list – list to copy

Returns:

head of the new list, or NULL on failure.

void axl_list_free(AxlList *list)

Free all nodes. Does not free element data.

Parameters:
  • list – head (NULL-safe)

void axl_list_free_full(AxlList *list, AxlDestroyNotify free_func)

Free all nodes, calling free_func on each element’s data.

Parameters:
  • list – head (NULL-safe)

  • free_func – called on each node’s data

size_t axl_list_length(AxlList *list)

Count elements. O(n).

Parameters:
  • list – head

Returns:

number of elements.

AxlList *axl_list_nth(AxlList *list, size_t n)

Get the nth node. O(n).

Parameters:
  • list – head

  • n – 0-based index

Returns:

node at position n, or NULL if out of range.

void *axl_list_nth_data(AxlList *list, size_t n)

Get data from the nth node. O(n).

Parameters:
  • list – head

  • n – 0-based index

Returns:

data pointer, or NULL if out of range.

AxlList *axl_list_first(AxlList *list)

Get the first node (rewind to head). O(n).

Useful when you have a pointer to a middle node.

Parameters:
  • list – any node in the list

Returns:

the first node, or NULL if list is empty.

AxlList *axl_list_last(AxlList *list)

Get the last node. O(n).

Parameters:
  • list – head

Returns:

the last node, or NULL if list is empty.

AxlList *axl_list_find(AxlList *list, const void *data)

Find the first node with matching data (pointer equality). O(n).

Parameters:
  • list – head

  • data – data pointer to find

Returns:

matching node, or NULL.

AxlList *axl_list_find_custom(AxlList *list, const void *data, AxlCompareFunc func)

Find using a custom comparator. O(n).

Calls func(node->data, data) for each node. Returns the first node where func returns 0.

Parameters:
  • list – head

  • data – data to compare against

  • func – comparison function

Returns:

matching node, or NULL.

void axl_list_foreach(AxlList *list, AxlFunc func, void *user_data)

Call func for each element. O(n).

Parameters:
  • list – head

  • func – callback

  • user_data – passed to callback

AxlList *axl_list_insert_before(AxlList *list, AxlList *sibling, void *data)

Insert data before the given sibling node. O(1).

If sibling is NULL, appends to the end.

Parameters:
  • list – current head

  • sibling – node to insert before (NULL = append)

  • data – data pointer to store

Returns:

new head of the list.

AxlList *axl_list_insert_after(AxlList *list, AxlList *sibling, void *data)

Insert data after the given sibling node. O(1).

If sibling is NULL, prepends to the front.

Parameters:
  • list – current head

  • sibling – node to insert after (NULL = prepend)

  • data – data pointer to store

Returns:

new head of the list.

AxlList *axl_list_remove_all(AxlList *list, const void *data)

Remove ALL nodes matching data (pointer equality). O(n).

Frees the nodes, not the data.

Parameters:
  • list – current head

  • data – data pointer to match

Returns:

new head of the list.

Unlink a specific node without freeing it. O(1).

The caller is responsible for freeing the unlinked node. The node’s prev/next pointers are set to NULL after unlinking.

Parameters:
  • list – current head

  • link – node to unlink

Returns:

new head of the list.

AxlList *axl_list_sort_with_data(AxlList *list, AxlCompareDataFunc func, void *user_data)

Sort with context-aware comparator. O(n log n), stable.

Parameters:
  • list – current head

  • func – comparison function with user_data

  • user_data – passed to every compare call

Returns:

new head of the sorted list.

AxlList *axl_list_copy_deep(AxlList *list, AxlCopyFunc func, void *user_data)

Deep copy using a copy function. O(n).

Calls func(node->data, user_data) for each node to produce the data pointer for the new list.

Parameters:
  • list – list to copy

  • func – copy function for each element

  • user_data – passed to func

Returns:

head of the new list, or NULL on failure.

struct AxlList

Public Members

void *data
struct AxlList *next
struct AxlList *prev

AxlSList

Functions

AxlSList *axl_slist_append(AxlSList *list, void *data)

Append data to the end. O(n).

Parameters:
  • list – current head (NULL for empty)

  • data – data pointer

Returns:

new head.

AxlSList *axl_slist_prepend(AxlSList *list, void *data)

Prepend data to the front. O(1).

Parameters:
  • list – current head (NULL for empty)

  • data – data pointer

Returns:

new head.

AxlSList *axl_slist_insert(AxlSList *list, void *data, int position)

Insert at position. O(n).

Parameters:
  • list – current head

  • data – data pointer

  • position – 0-based position

Returns:

new head.

AxlSList *axl_slist_insert_sorted(AxlSList *list, void *data, AxlCompareFunc func)

Insert in sorted order. O(n).

Parameters:
  • list – current head (sorted)

  • data – data to insert

  • func – comparison function

Returns:

new head.

AxlSList *axl_slist_remove(AxlSList *list, const void *data)

Remove first match by pointer equality. O(n).

Parameters:
  • list – current head

  • data – data to find and remove

Returns:

new head.

AxlSList *axl_slist_reverse(AxlSList *list)

Reverse in place. O(n).

Parameters:
  • list – current head

Returns:

new head (was tail).

AxlSList *axl_slist_concat(AxlSList *list1, AxlSList *list2)

Concatenate two lists. O(n) in list1.

Parameters:
  • list1 – first list

  • list2 – appended to list1

Returns:

head of combined list.

AxlSList *axl_slist_sort(AxlSList *list, AxlCompareFunc func)

Sort using merge sort. O(n log n), stable.

Parameters:
  • list – current head

  • func – comparison function

Returns:

new head.

AxlSList *axl_slist_copy(AxlSList *list)

Shallow copy. O(n).

Parameters:
  • list – list to copy

Returns:

new list head, or NULL on failure.

void axl_slist_free(AxlSList *list)

Free all nodes. Does not free element data.

Parameters:
  • list – head (NULL-safe)

void axl_slist_free_full(AxlSList *list, AxlDestroyNotify free_func)

Free all nodes, calling free_func on each element’s data.

Parameters:
  • list – head (NULL-safe)

  • free_func – called on each data

size_t axl_slist_length(AxlSList *list)

Count elements. O(n).

Parameters:
  • list – head

Returns:

number of elements.

AxlSList *axl_slist_nth(AxlSList *list, size_t n)

Get nth node. O(n).

Parameters:
  • list – head

  • n – 0-based index

Returns:

node, or NULL if out of range.

void *axl_slist_nth_data(AxlSList *list, size_t n)

Get data from nth node. O(n).

Parameters:
  • list – head

  • n – 0-based index

Returns:

data pointer, or NULL if out of range.

AxlSList *axl_slist_last(AxlSList *list)

Get last node. O(n).

Parameters:
  • list – head

Returns:

last node, or NULL.

AxlSList *axl_slist_find(AxlSList *list, const void *data)

Find by pointer equality. O(n).

Parameters:
  • list – head

  • data – data to find

Returns:

matching node, or NULL.

AxlSList *axl_slist_find_custom(AxlSList *list, const void *data, AxlCompareFunc func)

Find with custom comparator. O(n).

Parameters:
  • list – head

  • data – data to compare against

  • func – comparison function

Returns:

first node where func(node->data, data) == 0, or NULL.

void axl_slist_foreach(AxlSList *list, AxlFunc func, void *user_data)

Call func for each element. O(n).

Parameters:
  • list – head

  • func – callback

  • user_data – passed to callback

AxlSList *axl_slist_insert_before(AxlSList *list, AxlSList *sibling, void *data)

Insert data before the given sibling node. O(n).

If sibling is NULL, appends to the end.

Parameters:
  • list – current head

  • sibling – node to insert before (NULL = append)

  • data – data pointer to store

Returns:

new head of the list.

AxlSList *axl_slist_remove_all(AxlSList *list, const void *data)

Remove ALL nodes matching data (pointer equality). O(n).

Frees the nodes, not the data.

Parameters:
  • list – current head

  • data – data pointer to match

Returns:

new head of the list.

Unlink a specific node without freeing it. O(n).

The caller is responsible for freeing the unlinked node. The node’s next pointer is set to NULL after unlinking.

Parameters:
  • list – current head

  • link – node to unlink

Returns:

new head of the list.

AxlSList *axl_slist_sort_with_data(AxlSList *list, AxlCompareDataFunc func, void *user_data)

Sort with context-aware comparator. O(n log n), stable.

Parameters:
  • list – current head

  • func – comparison function with user_data

  • user_data – passed to every compare call

Returns:

new head of the sorted list.

AxlSList *axl_slist_copy_deep(AxlSList *list, AxlCopyFunc func, void *user_data)

Deep copy using a copy function. O(n).

Calls func(node->data, user_data) for each node to produce the data pointer for the new list.

Parameters:
  • list – list to copy

  • func – copy function for each element

  • user_data – passed to func

Returns:

head of the new list, or NULL on failure.

struct AxlSList
#include <axl-slist.h>

axl-slist.h:

Singly-linked list. GLib GSList equivalent. Node struct is exposed for direct traversal: for (AxlSList *l = list; l; l = l->next) { use(l->data); }

Functions that modify the head return the new head pointer. Always assign back: list = axl_slist_prepend(list, data);

Note: axl_slist_append is O(n). For frequent appends, use axl_slist_prepend + axl_slist_reverse, or use AxlList instead.

Public Members

void *data
struct AxlSList *next

AxlQueue

Defines

AXL_QUEUE_INIT

Static initializer for stack-allocated queues.

Functions

AxlQueue *axl_queue_new(void)

Allocate a new empty queue.

Returns:

new queue, or NULL on failure. Free with axl_queue_free().

void axl_queue_init(AxlQueue *queue)

Initialize a stack-allocated queue.

Parameters:
  • queue – queue to initialize

void axl_queue_free(AxlQueue *queue)

Free queue and all nodes. Does not free element data.

Parameters:
  • queue – queue (NULL-safe)

void axl_queue_free_full(AxlQueue *queue, AxlDestroyNotify free_func)

Free queue, calling free_func on each element’s data.

Parameters:
  • queue – queue (NULL-safe)

  • free_func – called on each data

void axl_queue_clear(AxlQueue *queue)

Remove all elements. Queue itself is not freed.

Parameters:
  • queue – queue

bool axl_queue_is_empty(AxlQueue *queue)

Check if the queue is empty.

Parameters:
  • queue – queue

Returns:

true if empty.

size_t axl_queue_get_length(AxlQueue *queue)

Get the number of elements. O(1).

Parameters:
  • queue – queue

Returns:

element count.

int axl_queue_push_head(AxlQueue *queue, void *data)

Push data to the front. O(1).

Parameters:
  • queue – queue

  • data – data pointer

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

int axl_queue_push_tail(AxlQueue *queue, void *data)

Push data to the back. O(1).

Parameters:
  • queue – queue

  • data – data pointer

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

void *axl_queue_pop_head(AxlQueue *queue)

Remove and return the front element. O(1).

Parameters:
  • queue – queue

Returns:

data pointer, or NULL if empty.

void *axl_queue_pop_tail(AxlQueue *queue)

Remove and return the back element. O(1).

Parameters:
  • queue – queue

Returns:

data pointer, or NULL if empty.

void *axl_queue_peek_head(AxlQueue *queue)

Peek at the front element without removing. O(1).

Parameters:
  • queue – queue

Returns:

data pointer, or NULL if empty.

void *axl_queue_peek_tail(AxlQueue *queue)

Peek at the back element without removing. O(1).

Parameters:
  • queue – queue

Returns:

data pointer, or NULL if empty.

void *axl_queue_peek_nth(AxlQueue *queue, size_t n)

Peek at the nth element. O(n).

Parameters:
  • queue – queue

  • n – 0-based index from head

Returns:

data pointer, or NULL if out of range.

void axl_queue_foreach(AxlQueue *queue, AxlFunc func, void *user_data)

Call func for each element, head to tail. O(n).

Parameters:
  • queue – queue

  • func – callback

  • user_data – passed to callback

AxlQueue *axl_queue_copy(AxlQueue *queue)

Shallow copy. O(n).

Parameters:
  • queue – queue to copy

Returns:

new queue, or NULL on failure.

void axl_queue_reverse(AxlQueue *queue)

Reverse the queue in place. O(n).

Parameters:
  • queue – queue

void axl_queue_sort(AxlQueue *queue, AxlCompareFunc func)

Sort the queue using merge sort. O(n log n).

Parameters:
  • queue – queue

  • func – comparison function

AxlList *axl_queue_find(AxlQueue *queue, const void *data)

Find the first node matching data (pointer equality). O(n).

Parameters:
  • queue – queue

  • data – data pointer to find

Returns:

matching AxlList node, or NULL.

AxlList *axl_queue_find_custom(AxlQueue *queue, const void *data, AxlCompareFunc func)

Find using a custom comparator. O(n).

Returns the first node where func(node->data, data) == 0.

Parameters:
  • queue – queue

  • data – data to compare against

  • func – comparison function

Returns:

matching AxlList node, or NULL.

bool axl_queue_remove(AxlQueue *queue, const void *data)

Remove the first node matching data (pointer equality). O(n).

Frees the node, not the data. Updates head/tail/length.

Parameters:
  • queue – queue

  • data – data pointer to find and remove

Returns:

true if a match was found and removed.

size_t axl_queue_remove_all(AxlQueue *queue, const void *data)

Remove ALL nodes matching data (pointer equality). O(n).

Frees the nodes, not the data. Updates head/tail/length.

Parameters:
  • queue – queue

  • data – data pointer to match

Returns:

number of nodes removed.

struct AxlQueue
#include <axl-queue.h>

axl-queue.h:

Double-ended queue built on AxlList. GLib GQueue equivalent. O(1) push/pop at both ends. Struct is exposed for direct access.

Can be heap-allocated (axl_queue_new) or stack-allocated: AxlQueue q = AXL_QUEUE_INIT; axl_queue_push_tail(&q, data);

Public Members

AxlList *head
AxlList *tail
size_t length

AxlRadixTree

Typedefs

typedef struct AxlRadixTree AxlRadixTree

axl-radix-tree.h:

Radix tree (compact prefix tree) with string keys. Supports exact lookup, longest-prefix lookup, insert, remove, and iteration.

Ideal for URL routing, path matching, and any scenario where longest-prefix matching is needed. Lookup is O(k) where k is the key length, independent of the number of entries.

API mirrors GLib/AXL conventions (axl_radix_tree_*).

Functions

AxlRadixTree *axl_radix_tree_new(void)

Create a new radix tree.

Values are not freed on removal or tree destruction.

Returns:

new AxlRadixTree, or NULL on allocation failure.

AxlRadixTree *axl_radix_tree_new_full(AxlDestroyNotify value_free)

Create a new radix tree with a value destructor.

Parameters:
  • value_free – value destructor, or NULL

Returns:

new AxlRadixTree, or NULL on allocation failure.

void axl_radix_tree_free(AxlRadixTree *tree)

Free a radix tree and all entries.

Calls value_free on each entry’s value (if set).

Parameters:
  • tree – radix tree (NULL-safe)

int axl_radix_tree_insert(AxlRadixTree *tree, const char *key, void *value)

Insert or replace a key-value pair.

If the key already exists, the old value is freed via value_free (if set) and replaced with the new value.

Parameters:
  • tree – radix tree

  • key – string key (copied internally)

  • value – value pointer

Returns:

AXL_OK on success, AXL_ERR on allocation failure.

void *axl_radix_tree_lookup(AxlRadixTree *tree, const char *key)

Exact lookup of a key.

Parameters:
  • tree – radix tree

  • key – key to look up

Returns:

value pointer, or NULL if not found.

void *axl_radix_tree_lookup_prefix(AxlRadixTree *tree, const char *key, const char **suffix)

Longest-prefix lookup.

Finds the longest inserted key that is a prefix of key and returns its value. Sets *suffix to point into key at the first character after the matched prefix. The caller can compute the prefix length as *suffix - key.

On no match, *suffix is not modified. Pass NULL for suffix if the suffix pointer is not needed.

Parameters:
  • tree – radix tree

  • key – key to match against

  • suffix – receives pointer into key after matched prefix

Returns:

value pointer, or NULL if no prefix matches.

bool axl_radix_tree_remove(AxlRadixTree *tree, const char *key)

Remove a key.

Calls value_free on the value (if set). Collapses intermediate nodes with a single child.

Parameters:
  • tree – radix tree

  • key – key to remove

Returns:

true if removed, false if not found.

size_t axl_radix_tree_size(AxlRadixTree *tree)

Get the number of entries.

Parameters:
  • tree – radix tree

Returns:

entry count.

void axl_radix_tree_foreach(AxlRadixTree *tree, AxlHashTableForeachFunc func, void *data)

Iterate all entries in depth-first order.

The key passed to func is a reconstructed NUL-terminated string valid only for the duration of the callback.

Parameters:
  • tree – radix tree

  • func – callback(key, value, data)

  • data – opaque data passed to callback

AxlNTree

Typedefs

typedef bool (*AxlNTreeTraverseFunc)(AxlNTree *node, void *user)

AxlNTreeTraverseFunc:

Per-node callback for axl_ntree_traverse. Return true to STOP the walk early (GLib semantics), false to continue.

typedef void (*AxlNTreeForeachFunc)(AxlNTree *node, void *user)

AxlNTreeForeachFunc:

Per-node callback for axl_ntree_children_foreach (cannot stop early).

Enums

enum AxlNTreeTraverseType

AxlNTreeTraverseType:

Visiting order for axl_ntree_traverse. For an n-ary node, IN_ORDER visits the first child, then the node, then the remaining children (GLib G_IN_ORDER semantics).

Values:

enumerator AXL_NTREE_PRE_ORDER

node, then children left-to-right

enumerator AXL_NTREE_POST_ORDER

children left-to-right, then node

enumerator AXL_NTREE_IN_ORDER

first child, node, then remaining children

enumerator AXL_NTREE_LEVEL_ORDER

breadth-first (root, then depth 2, …)

enum AxlNTreeTraverseFlags

AxlNTreeTraverseFlags:

Which nodes a traversal / count visits. Combine LEAVES and NON_LEAVES, or use ALL.

Values:

enumerator AXL_NTREE_LEAVES

only leaf nodes (no children)

enumerator AXL_NTREE_NON_LEAVES

only internal nodes (>= 1 child)

enumerator AXL_NTREE_ALL

every node

Functions

AxlNTree *axl_ntree_new(void *data)

Allocate a new, parentless leaf node holding data.

Parameters:
  • data – payload pointer to store (borrowed)

Returns:

the node, or NULL on allocation failure.

void axl_ntree_free(AxlNTree *node)

Free node and its entire subtree. Data pointers are left untouched (borrowed). NULL-safe.

node must be a root (unlink it first if it has a parent), otherwise its parent is left with a dangling child pointer.

Parameters:
  • node – subtree root to free (NULL-safe)

void axl_ntree_free_full(AxlNTree *node, AxlDestroyNotify free_func)

Free node and its entire subtree, calling free_func on every node’s data first. NULL-safe; free_func may be NULL (same as axl_ntree_free).

Parameters:
  • node – subtree root to free (NULL-safe)

  • free_func – called on each node’s data, or NULL

Detach node from its parent and siblings; the subtree rooted at node stays intact and becomes its own root.

No-op if node is NULL or already a root.

Parameters:
  • node – node to detach

AxlNTree *axl_ntree_append_child(AxlNTree *parent, AxlNTree *child)

Append child as the last child of parent. O(n) in the child count (walks to the tail).

child must be a root (no existing parent).

Parameters:
  • parent – parent to receive the child

  • child – root node to attach

Returns:

child, or NULL if parent or child is NULL or child already has a parent.

AxlNTree *axl_ntree_prepend_child(AxlNTree *parent, AxlNTree *child)

Prepend child as the first child of parent. O(1).

Parameters:
  • parent – parent to receive the child

  • child – root node to attach

Returns:

child, or NULL on bad arguments (see axl_ntree_append_child).

AxlNTree *axl_ntree_insert_after(AxlNTree *parent, AxlNTree *sibling, AxlNTree *child)

Insert child after sibling among parent’s children. O(1).

If sibling is NULL, child becomes the first child (prepend).

Parameters:
  • parent – parent owning sibling

  • sibling – existing child to insert after (NULL = prepend)

  • child – root node to attach

Returns:

child, or NULL on bad arguments (NULL parent/, child already attached, or sibling not a child of parent).

AxlNTree *axl_ntree_insert_before(AxlNTree *parent, AxlNTree *sibling, AxlNTree *child)

Insert child before sibling among parent’s children. O(1).

If sibling is NULL, child becomes the last child (append, O(n)).

Parameters:
  • parent – parent owning sibling

  • sibling – existing child to insert before (NULL = append)

  • child – root node to attach

Returns:

child, or NULL on bad arguments (NULL parent/, child already attached, or sibling not a child of parent).

AxlNTree *axl_ntree_append_data(AxlNTree *parent, void *data)

Convenience: allocate a node for data and append it to parent.

Parameters:
  • parent – parent to receive the new child

  • data – payload for the new child

Returns:

the new child, or NULL on allocation failure / NULL parent.

AxlNTree *axl_ntree_move_after(AxlNTree *parent, AxlNTree *sibling, AxlNTree *node)

Reposition node to sit immediately after sibling among parent’s children, detaching it from its current position first.

sibling == NULL moves node to the first position. No-op-safe if the node is already there.

Parameters:
  • parent – destination parent

  • sibling – child to position after (NULL = first)

  • node – node to move (may already be attached)

Returns:

node, or NULL on bad arguments (NULL parent/, sibling not a child of parent, node == sibling, or the move would create a cycle — node is parent or an ancestor of parent).

AxlNTree *axl_ntree_move_before(AxlNTree *parent, AxlNTree *sibling, AxlNTree *node)

Reposition node to sit immediately before sibling among parent’s children, detaching it from its current position first.

sibling == NULL moves node to the last position.

Parameters:
  • parent – destination parent

  • sibling – child to position before (NULL = last)

  • node – node to move (may already be attached)

Returns:

node, or NULL on bad arguments (see axl_ntree_move_after).

AxlNTree *axl_ntree_first_child(const AxlNTree *node)

First child of node, or NULL if node is a leaf / NULL.

Parameters:
  • node – node to inspect

AxlNTree *axl_ntree_last_child(const AxlNTree *node)

Last child of node. O(n). NULL if a leaf / NULL.

Parameters:
  • node – node to inspect

AxlNTree *axl_ntree_nth_child(const AxlNTree *node, uint32_t n)

Nth (0-based) child of node. O(n). NULL if out of range / NULL.

Parameters:
  • node – parent node

  • n – 0-based child index

uint32_t axl_ntree_n_children(const AxlNTree *node)

Number of immediate children of node. O(n).

Parameters:
  • node – node to inspect

AxlNTree *axl_ntree_get_root(AxlNTree *node)

Walk up to the root of node’s tree. O(depth).

Parameters:
  • node – any node in the tree

Returns:

the root (== node if it has no parent), or NULL if node is NULL.

bool axl_ntree_is_ancestor(const AxlNTree *node, const AxlNTree *descendant)

True iff node is an ancestor of descendant (strict — a node is not its own ancestor). O(depth).

Parameters:
  • node – candidate ancestor

  • descendant – candidate descendant

uint32_t axl_ntree_depth(const AxlNTree *node)

Depth of node: the root is 1, its children 2, … (GLib convention). O(depth). 0 if node is NULL.

Parameters:
  • node – node to measure

uint32_t axl_ntree_n_nodes(const AxlNTree *node, AxlNTreeTraverseFlags flags)

Count the nodes in the subtree rooted at node that match flags. O(n).

Parameters:
  • node – subtree root

  • flags – ALL / LEAVES / NON_LEAVES

uint32_t axl_ntree_max_height(const AxlNTree *node)

Height of the subtree rooted at node: a single node is 1, a node with children is 1 + max child height. O(n). 0 if NULL.

Parameters:
  • node – subtree root

void axl_ntree_children_foreach(AxlNTree *node, AxlNTreeTraverseFlags flags, AxlNTreeForeachFunc func, void *user)

Call func for each immediate child of node matching flags. O(n). Cannot stop early.

Parameters:
  • node – parent whose children are visited

  • flags – ALL / LEAVES / NON_LEAVES

  • func – per-child callback

  • user – passed to func

void axl_ntree_traverse(AxlNTree *root, AxlNTreeTraverseType order, AxlNTreeTraverseFlags flags, int max_depth, AxlNTreeTraverseFunc func, void *user)

Traverse the subtree rooted at root in order, visiting nodes that match flags, to a maximum relative depth of max_depth.

func returns true to stop the walk early (its node still counts as visited). max_depth <= 0 means unlimited; 1 visits only root.

Parameters:
  • root – subtree root

  • order – PRE / POST / IN / LEVEL order

  • flags – ALL / LEAVES / NON_LEAVES

  • max_depth – max depth (<= 0 = unlimited)

  • func – per-node callback (true = stop)

  • user – passed to func

void axl_ntree_iter_init(AxlNTreeIter *iter, AxlNTree *root, AxlNTreeTraverseFlags flags)

Position iter before the first matching node of the subtree rooted at root, visiting in pre-order, filtered by flags. NULL root yields an immediately-exhausted iterator.

Parameters:
  • iter – [out] iterator to initialize

  • root – subtree root (boundary; not its siblings)

  • flags – ALL / LEAVES / NON_LEAVES

void axl_ntree_iter_init_reverse(AxlNTreeIter *iter, AxlNTree *root, AxlNTreeTraverseFlags flags)

Position iter to walk the subtree rooted at root in reverse pre-order (the exact reverse of axl_ntree_iter_init) — topmost-first for a paint-order tree. Advance with axl_ntree_iter_next, same as the forward iterator. NULL root yields an immediately-exhausted iterator.

Parameters:
  • iter – [out] iterator to initialize

  • root – subtree root (boundary; not its siblings)

  • flags – ALL / LEAVES / NON_LEAVES

AxlNTree *axl_ntree_iter_next(AxlNTreeIter *iter)

Advance to and return the next node in pre-order matching the iterator’s flags.

Parameters:
  • iter – iterator

Returns:

the next node, or NULL when the subtree is exhausted.

struct AxlNTree

axl-ntree.h:

Generic n-ary tree. GLib GNode equivalent.

The node IS the subtree handle — there is no separate container object; you hold a root node and the struct fields are public, so traversal is a plain pointer walk: for (AxlNTree *c = node->children; c; c = c->next) { use(c->data); }

Intrusive sibling/child links (no per-link allocation beyond the node itself). Single-threaded, no locking. The tree owns its node objects; the caller owns each node’s data unless a free function is passed to axl_ntree_free_full.

Distinct from axl-radix-tree (a string-prefix lookup tree) and axl-tree (a balanced sorted key->value map): this is the general parent->children hierarchy (UI/device/DOM trees).

Public Members

void *data

caller payload (borrowed unless freed via _free_full)

AxlNTree *parent

parent node (NULL at the root)

AxlNTree *prev

previous sibling (NULL = first child)

AxlNTree *next

next sibling (NULL = last child)

AxlNTree *children

first child (NULL = leaf)

struct AxlNTreeIter
#include <axl-ntree.h>

AxlNTreeIter:

Stack-allocated, pull-style cursor over a subtree in pre-order (node before children) — the no-callback alternative to axl_ntree_traverse. No internal stack: it walks via the parent/sibling links, so it is O(1) per step and any depth.

AxlNTreeIter it; axl_ntree_iter_init(&it, root, AXL_NTREE_ALL); for (AxlNTree *n; (n = axl_ntree_iter_next(&it)) != NULL; ) { … }

axl_ntree_iter_init_reverse walks the SAME nodes in the exact reverse of pre-order (deepest-last child first, a node after all its descendants) — i.e. topmost-first for a paint-order tree, the hit-test idiom: pull until the first node whose region contains the point, then stop.

Treat the fields as opaque. Restructuring the subtree (unlink / insert / free) during iteration invalidates the iterator. For post/in/level order, use axl_ntree_traverse.

Public Members

AxlNTree *root

private: subtree boundary

AxlNTree *current

private: last visited node

AxlNTreeTraverseFlags flags

private: node filter

bool started

private: has iteration begun

bool reverse

private: reverse pre-order

AxlTree

Defines

AXL_TREE_ITER_MAX_DEPTH

Max tree height the stack-based iterator handles without heap — the AVL height bound (~1.44·log2 n) stays under this for any tree that fits in addressable memory (n up to ~10^9).

Typedefs

typedef struct AxlTree AxlTree

opaque AVL sorted map

axl-tree.h:

Balanced sorted map. GLib GTree equivalent, AVL-backed.

An ordered key->value store with O(log n) insert / lookup / remove and, unlike a hash table, in-order (sorted) iteration plus range / nearest-key queries (lower_bound / upper_bound). Keys are ordered by a caller-supplied AxlCompareDataFunc.

Opaque container (the node internals stay private). Single-threaded, no locking. Choose this over:

  • axl-hash-table when you need ordered iteration or range queries (the hash table is unordered, O(1));

  • axl-radix-tree when keys are not strings or you need general ordering rather than longest-prefix lookup;

  • axl-ntree when you need a parent/child hierarchy, not a map.

typedef bool (*AxlTreeForeachFunc)(void *key, void *value, void *user)

AxlTreeForeachFunc:

Per-entry callback for axl_tree_foreach, called in ascending key order. Return true to STOP iteration early, false to continue.

Functions

AxlTree *axl_tree_new(AxlCompareDataFunc cmp, void *cmp_user)

Create an empty tree ordered by cmp. Keys and values are borrowed (not freed by the tree).

Parameters:
  • cmp – key comparator (required)

  • cmp_user – context passed to cmp

Returns:

the tree, or NULL on allocation failure / NULL cmp.

AxlTree *axl_tree_new_full(AxlCompareDataFunc cmp, void *cmp_user, AxlDestroyNotify key_destroy, AxlDestroyNotify value_destroy)

Create an empty tree that OWNS keys and/or values: the matching destroy callback (if non-NULL) is called when an entry is removed/replaced or the tree is freed.

Parameters:
  • cmp – key comparator (required)

  • cmp_user – context passed to cmp

  • key_destroy – key destructor, or NULL to borrow

  • value_destroy – value destructor, or NULL to borrow

Returns:

the tree, or NULL on allocation failure / NULL cmp.

void axl_tree_free(AxlTree *tree)

Free the tree and every node, running the key/value destroy callbacks (if set) on each entry. NULL-safe.

Parameters:
  • tree – tree to free (NULL-safe)

void axl_tree_insert(AxlTree *tree, void *key, void *value)

Insert key -> value, keeping the EXISTING key on a collision (GTree semantics).

If an equal key is already present: the stored key is kept and the new key is destroyed (if a key destructor is set); the old value is destroyed (if a value destructor is set) and replaced by value. Otherwise a new node is created.

Parameters:
  • tree – target tree

  • key – key (ownership taken iff new + key_destroy set)

  • value – value (ownership taken iff value_destroy set)

void axl_tree_replace(AxlTree *tree, void *key, void *value)

Insert key -> value, replacing BOTH key and value on a collision (GTree semantics).

If an equal key is already present: the OLD key and value are both destroyed (if destructors are set) and replaced by key / value.

Parameters:
  • tree – target tree

  • key – key (replaces the stored key)

  • value – value (replaces the stored value)

bool axl_tree_remove(AxlTree *tree, const void *key)

Remove the entry equal to key, running the key/value destroy callbacks (if set).

Parameters:
  • tree – target tree

  • key – key to remove

Returns:

true if an entry was removed, false if key was absent.

void *axl_tree_lookup(AxlTree *tree, const void *key)

Look up the value stored for key.

Parameters:
  • tree – tree to search

  • key – key to find

Returns:

the value, or NULL if key is absent (NULL is also a valid stored value — use axl_tree_lookup_extended to disambiguate).

bool axl_tree_lookup_extended(AxlTree *tree, const void *key, void **found_key, void **value)

Look up key, reporting the stored key and value separately.

Parameters:
  • tree – tree to search

  • key – key to find

  • found_key – [out] stored key, or NULL to ignore

  • value – [out] stored value, or NULL to ignore

Returns:

true if found (then *found_key / *value are filled, if non-NULL); false otherwise (outputs untouched).

uint32_t axl_tree_nnodes(const AxlTree *tree)

Number of entries. O(1).

Parameters:
  • tree – tree to measure (NULL-safe → 0)

uint32_t axl_tree_height(const AxlTree *tree)

Height of the tree (0 if empty, 1 for a single node). O(1).

Parameters:
  • tree – tree to measure (NULL-safe → 0)

void axl_tree_foreach(AxlTree *tree, AxlTreeForeachFunc func, void *user)

Call func for every entry in ascending key order; stops early if func returns true.

Parameters:
  • tree – tree to iterate

  • func – per-entry callback (true = stop)

  • user – passed to func

void axl_tree_iter_init(AxlTreeIter *iter, AxlTree *tree)

Position iter before the first (smallest-key) entry of tree. NULL tree yields an immediately-exhausted iterator.

Parameters:
  • iter – [out] iterator to initialize

  • tree – tree to iterate (borrowed for the iterator’s life)

bool axl_tree_iter_next(AxlTreeIter *iter, void **key, void **value)

Advance to the next entry in ascending key order.

Writes the entry’s key/value to key / value (each may be NULL to ignore) and returns true; returns false when the iteration is exhausted (outputs untouched).

Parameters:
  • iter – iterator

  • key – [out] entry key, or NULL to ignore

  • value – [out] entry value, or NULL to ignore

void *axl_tree_lower_bound(AxlTree *tree, const void *key)

Value of the first entry whose key is >= key (the lower bound).

Parameters:
  • tree – tree to search

  • key – bound key

Returns:

the value, or NULL if every key is < key (or the tree is empty).

void *axl_tree_upper_bound(AxlTree *tree, const void *key)

Value of the first entry whose key is > key (the upper bound — strictly greater).

Parameters:
  • tree – tree to search

  • key – bound key

Returns:

the value, or NULL if every key is <= key (or the tree is empty).

struct AxlTreeIter
#include <axl-tree.h>

AxlTreeIter:

Stack-allocated, pull-style cursor over a tree’s entries in ascending key order — the no-callback alternative to axl_tree_foreach. Declare one on the stack, axl_tree_iter_init it, then loop on axl_tree_iter_next:

AxlTreeIter it; void *k, *v; axl_tree_iter_init(&it, tree); while (axl_tree_iter_next(&it, &k, &v)) { … }

Treat the fields as opaque. The iterator holds raw pointers to the tree’s nodes, so any insert/remove/free on the tree during iteration invalidates it — a later axl_tree_iter_next would then read a moved or freed node (use-after-free). Don’t mutate the tree while iterating.

Public Members

void *stack[48]

private: path of pending nodes

int top

private: stack depth

AxlRingBuf

Defines

AXL_RING_BUF_OVERWRITE

Overwrite oldest data when buffer is full.

axl-ring-buf.h:

Byte-oriented ring buffer (circular buffer) with power-of-2 sizing. Three API layers, each building on the one below:

Layer 1 (Bytes): push, pop, peek, discard, regions Layer 2 (Messages): push_msg, pop_msg, peek_msg (variable-size) Layer 3 (Elements): push_elem, pop_elem, peek_elem (fixed-size)

Supports partial writes, peek without consuming, zero-copy scatter/gather, optional overwrite-on-full, and user-provided backing buffers for embedded/stack use.

Naming follows GLib’s GQueue conventions: push (produce), pop (consume), peek (read without consuming), peek_nth (indexed access).

Inspired by Linux kfifo: monotonically increasing indices with mask-based wrapping. Uses all buffer slots — no wasted-slot ambiguity.

Functions

int axl_ring_buf_init(AxlRingBuf *rb, void *buf, uint32_t size, uint32_t flags, void (*buf_free)(void*))

Initialize an embedded ring buffer (byte mode).

No heap allocation. The caller owns both the AxlRingBuf struct and the backing buffer. Pass a buf_free function to have deinit free the buffer, or NULL if the caller manages the buffer lifetime.

Parameters:
  • rb – caller-allocated struct

  • buf – backing buffer (must be power-of-2 sized)

  • size – buffer size in bytes (must be power of 2)

  • flags – AXL_RING_BUF_OVERWRITE or 0

  • buf_free – buffer deallocator, or NULL

Returns:

AXL_OK on success, AXL_ERR if size is not a power of 2 or args NULL.

int axl_ring_buf_init_fixed(AxlRingBuf *rb, void *buf, uint32_t size, uint32_t elem_size, uint32_t flags, void (*buf_free)(void*))

Initialize an embedded ring buffer (fixed-element mode).

Same as axl_ring_buf_init but enables Layer 3 element functions (push_elem, pop_elem, peek_elem, peek_nth_elem, set_nth_elem).

Parameters:
  • rb – caller-allocated struct

  • buf – backing buffer (must be power-of-2 sized)

  • size – buffer size in bytes (must be power of 2)

  • elem_size – fixed element size in bytes

  • flags – AXL_RING_BUF_OVERWRITE or 0

  • buf_free – buffer deallocator, or NULL

Returns:

AXL_OK on success, AXL_ERR if size is not a power of 2 or args NULL.

void axl_ring_buf_deinit(AxlRingBuf *rb)

Release resources for an initialized ring buffer.

Calls the buf_free function (if set) to free the backing buffer. Does NOT free the AxlRingBuf struct itself (use axl_ring_buf_free for heap-allocated ring buffers).

Parameters:
  • rb – ring buffer (NULL-safe)

AxlRingBuf *axl_ring_buf_new(uint32_t min_size)

Create a ring buffer in byte mode.

Capacity is rounded up to the next power of 2. Rejects pushes when full (use axl_ring_buf_new_full for overwrite mode).

Parameters:
  • min_size – minimum capacity in bytes (rounded up to power of 2)

Returns:

new AxlRingBuf, or NULL on allocation failure.

AxlRingBuf *axl_ring_buf_new_full(uint32_t min_size, uint32_t flags)

Create a ring buffer in byte mode with flags.

Parameters:
  • min_size – minimum capacity in bytes (rounded up to power of 2)

  • flags – AXL_RING_BUF_OVERWRITE or 0

Returns:

new AxlRingBuf, or NULL on allocation failure.

AxlRingBuf *axl_ring_buf_new_fixed(uint32_t min_size, uint32_t elem_size, uint32_t flags)

Create a ring buffer in fixed-element mode.

Enables Layer 3 element functions (push_elem, pop_elem, etc.).

Parameters:
  • min_size – minimum capacity in bytes (rounded up to power of 2)

  • elem_size – fixed element size in bytes

  • flags – AXL_RING_BUF_OVERWRITE or 0

Returns:

new AxlRingBuf, or NULL on allocation failure.

AxlRingBuf *axl_ring_buf_new_with_buffer(void *buf, uint32_t size, uint32_t flags)

Create a ring buffer using a caller-provided backing buffer.

The struct is heap-allocated but the backing buffer is owned by the caller. axl_ring_buf_free will NOT free the buffer.

Parameters:
  • buf – caller-provided buffer (must be power-of-2 sized)

  • size – buffer size in bytes (must be power of 2)

  • flags – AXL_RING_BUF_OVERWRITE or 0

Returns:

new AxlRingBuf, or NULL on failure.

void axl_ring_buf_free(AxlRingBuf *rb)

Free a heap-allocated ring buffer.

Calls buf_free on the backing buffer if set, then frees the struct. For embedded ring buffers, use axl_ring_buf_deinit instead.

Parameters:
  • rb – ring buffer (NULL-safe)

uint32_t axl_ring_buf_push(AxlRingBuf *rb, const void *data, uint32_t len)

Push bytes into the ring buffer.

In reject mode (default), pushes up to the available space and returns the number of bytes actually written (may be less than len). In overwrite mode, always pushes all bytes, advancing the read position to discard the oldest data as needed.

Parameters:
  • rb – ring buffer

  • data – source data

  • len – number of bytes to push

Returns:

number of bytes pushed.

uint32_t axl_ring_buf_pop(AxlRingBuf *rb, void *dest, uint32_t len)

Pop bytes from the ring buffer.

Parameters:
  • rb – ring buffer

  • dest – destination buffer

  • len – maximum bytes to pop

Returns:

number of bytes popped (may be less than len).

uint32_t axl_ring_buf_peek(AxlRingBuf *rb, void *dest, uint32_t len)

Peek at bytes without consuming them.

Parameters:
  • rb – ring buffer

  • dest – destination buffer

  • len – maximum bytes to peek

Returns:

number of bytes copied to dest.

uint32_t axl_ring_buf_discard(AxlRingBuf *rb, uint32_t len)

Discard bytes from the read side without copying.

Parameters:
  • rb – ring buffer

  • len – maximum bytes to discard

Returns:

number of bytes discarded.

uint32_t axl_ring_buf_peek_regions(AxlRingBuf *rb, AxlRingBufRegion regions[2])

Get contiguous readable regions for zero-copy access.

Returns up to 2 regions covering all readable data. After processing, call axl_ring_buf_pop_advance to consume.

Parameters:
  • rb – ring buffer

  • regions – receives up to 2 regions

Returns:

number of regions (0, 1, or 2).

uint32_t axl_ring_buf_push_regions(AxlRingBuf *rb, AxlRingBufRegion regions[2])

Get contiguous writable regions for zero-copy access.

Returns up to 2 regions covering all writable space. After filling, call axl_ring_buf_push_advance to commit.

Parameters:
  • rb – ring buffer

  • regions – receives up to 2 regions

Returns:

number of regions (0, 1, or 2).

void axl_ring_buf_pop_advance(AxlRingBuf *rb, uint32_t len)

Advance the read position after zero-copy peek.

Parameters:
  • rb – ring buffer

  • len – bytes consumed

void axl_ring_buf_push_advance(AxlRingBuf *rb, uint32_t len)

Advance the write position after zero-copy push.

Parameters:
  • rb – ring buffer

  • len – bytes produced

int axl_ring_buf_push_msg(AxlRingBuf *rb, const void *data, uint32_t len)

Push a length-prefixed message atomically.

Stores [uint32_t len][data] in the ring buffer. The push is all-or-nothing in reject mode. In overwrite mode, the push always succeeds but may corrupt the oldest message framing.

Parameters:
  • rb – ring buffer

  • data – message payload

  • len – payload length in bytes

Returns:

AXL_OK on success, AXL_ERR if not enough space.

int axl_ring_buf_pop_msg(AxlRingBuf *rb, void *dest, uint32_t max_len, uint32_t *actual_len)

Pop the next length-prefixed message.

Consumes the length header and payload. If max_len is too small for the message, returns -1 without consuming.

Parameters:
  • rb – ring buffer

  • dest – destination buffer

  • max_len – destination buffer size

  • actual_len – receives actual message length (may be NULL)

Returns:

AXL_OK on success, AXL_ERR if no message or buffer too small.

int axl_ring_buf_peek_msg(AxlRingBuf *rb, void *dest, uint32_t max_len, uint32_t *actual_len)

Peek at the next message without consuming.

Same as pop_msg but leaves the message in the buffer.

Parameters:
  • rb – ring buffer

  • dest – destination buffer

  • max_len – destination buffer size

  • actual_len – receives actual message length (may be NULL)

Returns:

AXL_OK on success, AXL_ERR if no message or buffer too small.

uint32_t axl_ring_buf_peek_msg_size(AxlRingBuf *rb)

Peek at the size of the next message without consuming.

Parameters:
  • rb – ring buffer

Returns:

message payload size, or 0 if no complete header available.

int axl_ring_buf_push_elem(AxlRingBuf *rb, const void *elem)

Push a fixed-size element (all-or-nothing).

Requires elem_size set at creation (new_fixed or init_fixed). In reject mode, fails if insufficient space. In overwrite mode, always succeeds by discarding the oldest data.

Parameters:
  • rb – ring buffer (must be in element mode)

  • elem – element to push

Returns:

AXL_OK on success, AXL_ERR if not enough space or not in element mode.

int axl_ring_buf_pop_elem(AxlRingBuf *rb, void *elem)

Pop a fixed-size element (all-or-nothing).

Parameters:
  • rb – ring buffer (must be in element mode)

  • elem – receives the element

Returns:

AXL_OK on success, AXL_ERR if not enough data or not in element mode.

int axl_ring_buf_peek_elem(AxlRingBuf *rb, void *dest)

Peek at the head element without consuming.

Parameters:
  • rb – ring buffer (must be in element mode)

  • dest – receives the element

Returns:

AXL_OK on success, AXL_ERR if empty or not in element mode.

int axl_ring_buf_peek_nth_elem(AxlRingBuf *rb, uint32_t index, void *dest)

Peek at an element by index without consuming.

Index 0 is the oldest element, get_length - 1 is the newest.

Parameters:
  • rb – ring buffer (must be in element mode)

  • index – element index (0 = oldest)

  • dest – receives the element

Returns:

AXL_OK on success, AXL_ERR if index out of range or not in element mode.

int axl_ring_buf_set_nth_elem(AxlRingBuf *rb, uint32_t index, const void *src)

Overwrite an element by index.

Index 0 is the oldest element, get_length - 1 is the newest.

Parameters:
  • rb – ring buffer (must be in element mode)

  • index – element index (0 = oldest)

  • src – element data to write

Returns:

AXL_OK on success, AXL_ERR if index out of range or not in element mode.

uint32_t axl_ring_buf_get_length(AxlRingBuf *rb)

Get the number of elements (element mode) or bytes (byte mode).

In element mode (elem_size > 0): returns readable / elem_size. In byte mode (elem_size == 0): returns readable byte count.

Parameters:
  • rb – ring buffer

Returns:

element count or byte count.

uint32_t axl_ring_buf_get_readable(AxlRingBuf *rb)

Get the number of readable bytes.

Parameters:
  • rb – ring buffer

Returns:

byte count available for reading.

uint32_t axl_ring_buf_get_writable(AxlRingBuf *rb)

Get the number of writable bytes.

Parameters:
  • rb – ring buffer

Returns:

byte count available for writing.

uint32_t axl_ring_buf_get_capacity(AxlRingBuf *rb)

Get the total buffer capacity.

Parameters:
  • rb – ring buffer

Returns:

capacity in bytes (always a power of 2).

bool axl_ring_buf_is_empty(AxlRingBuf *rb)

Check if the ring buffer is empty.

Parameters:
  • rb – ring buffer

Returns:

true if no data is available to read.

bool axl_ring_buf_is_full(AxlRingBuf *rb)

Check if the ring buffer is full.

Parameters:
  • rb – ring buffer

Returns:

true if no space is available for writing.

void axl_ring_buf_clear(AxlRingBuf *rb)

Discard all data and reset to empty.

Also resets the cumulative push counters (pushes_total / pushes_lost).

Parameters:
  • rb – ring buffer

uint64_t axl_ring_buf_pushes_total(const AxlRingBuf *rb)

Cumulative bytes the producer has attempted to push.

Increments on every push call (push, push_msg, push_elem, push_advance) by the number of bytes the producer asked to commit — including bytes that were rejected (reject mode) or dropped because the input exceeded ring capacity. Reset to 0 by axl_ring_buf_clear and on init.

Unit is BYTES regardless of mode. For element-mode buffers, divide by the element size to get an element count. For message-mode buffers each message contributes (sizeof(uint32_t) + payload_len) bytes (the header counts).

Parameters:
  • rb – ring buffer

Returns:

cumulative attempted push bytes since last init/clear.

uint64_t axl_ring_buf_pushes_lost(const AxlRingBuf *rb)

Cumulative bytes the consumer cannot see due to overflow.

Counts:

  • bytes from new pushes that were rejected (reject mode)

  • bytes dropped from the front of an oversized input (overwrite mode, when a single push exceeds ring capacity)

  • bytes of older data displaced by new pushes (overwrite mode)

Reset to 0 by axl_ring_buf_clear and on init. Unit is BYTES.

Note: pushes_total - pushes_lost is the cumulative byte count the consumer was at any point able to observe; it is NOT a proxy for “currently in the ring” (that’s axl_ring_buf_get_readable).

Parameters:
  • rb – ring buffer

Returns:

cumulative lost bytes since last init/clear.

struct AxlRingBuf
#include <axl-ring-buf.h>

Ring buffer with power-of-2 sizing and monotonic indices.

Can be heap-allocated (via axl_ring_buf_new) or embedded in another struct/stack (via axl_ring_buf_init). Fields are private — use the API functions, not direct access.

Public Members

uint8_t *buf

backing buffer

uint32_t size

capacity in bytes (power of 2)

uint32_t mask

size - 1

uint32_t read_pos

monotonically increasing read index

uint32_t write_pos

monotonically increasing write index

uint32_t flags

AXL_RING_BUF_OVERWRITE etc.

uint32_t elem_size

fixed element size (0 = byte mode)

uint64_t pushes_total

cumulative bytes the producer attempted to push (private)

uint64_t pushes_lost

cumulative bytes invisible to consumer (private)

void (*buf_free)(void*)

buffer deallocator, or NULL if caller-owned

struct AxlRingBufRegion
#include <axl-ring-buf.h>

Contiguous memory region for zero-copy access.

Ring buffer data may span two regions (before and after the internal wrap point). Use with peek_regions / push_regions.

Public Members

void *data

pointer into the ring buffer

uint32_t len

number of bytes in this region

AxlDigest

Message digest checksums (MD5, SHA-1, SHA-256). Standalone implementations — available even without AXL_TLS=1.

Typedefs

typedef struct AxlChecksum AxlChecksum

Enums

enum AxlChecksumType

Hash algorithm selector.

axl-digest.h:

Message digest checksums: MD5, SHA-1, SHA-256. Mirrors GLib’s GChecksum API. Standalone implementations with no external dependencies (works without AXL_TLS=1).

One-shot:

char *hex = axl_compute_checksum(AXL_CHECKSUM_SHA1, data, len);
axl_printf("SHA-1: %s\n", hex);
axl_free(hex);

Incremental (streaming):

AxlChecksum *cs = axl_checksum_new(AXL_CHECKSUM_SHA1);
axl_checksum_update(cs, part1, len1);
axl_checksum_update(cs, part2, len2);
const char *hex = axl_checksum_get_string(cs);
axl_checksum_free(cs);

Values:

enumerator AXL_CHECKSUM_MD5

MD5 (128-bit / 16-byte digest)

enumerator AXL_CHECKSUM_SHA1

SHA-1 (160-bit / 20-byte digest)

enumerator AXL_CHECKSUM_SHA256

SHA-256 (256-bit / 32-byte digest)

Functions

size_t axl_checksum_type_get_length(AxlChecksumType type)

Get the digest length in bytes for the given algorithm.

Parameters:
  • type – checksum algorithm

Returns:

digest length, or 0 for unknown type.

AxlChecksum *axl_checksum_new(AxlChecksumType type)

Create a new checksum context.

Parameters:
  • type – checksum algorithm

Returns:

new context, or NULL on failure.

void axl_checksum_update(AxlChecksum *cs, const void *data, size_t len)

Feed data into the checksum.

Can be called multiple times. Must not be called after axl_checksum_get_string() or axl_checksum_get_digest().

Parameters:
  • cs – checksum context

  • data – input data

  • len – input length in bytes

const char *axl_checksum_get_string(AxlChecksum *cs)

Get the digest as a hex string.

Returns a pointer to an internal NUL-terminated lowercase hex string. The pointer is valid until axl_checksum_free(). After this call, axl_checksum_update() must not be called.

Parameters:
  • cs – checksum context

Returns:

hex string (e.g. “a9993e36…”), or NULL on error.

void axl_checksum_get_digest(AxlChecksum *cs, uint8_t *buf, size_t *len)

Get the raw digest bytes.

Writes the binary digest into buf. On entry, *len is the buffer size; on return, it is set to the digest length. After this call, axl_checksum_update() must not be called.

Parameters:
  • cs – checksum context

  • buf – output buffer

  • len – [in/out] buffer size / digest length

void axl_checksum_reset(AxlChecksum *cs)

Reset a checksum for reuse.

Clears the state so axl_checksum_update() can be called again.

Parameters:
  • cs – checksum context

void axl_checksum_free(AxlChecksum *cs)

Free a checksum context. NULL-safe.

Parameters:
  • cs – checksum context

char *axl_compute_checksum(AxlChecksumType type, const void *data, size_t len)

Compute a checksum and return it as a hex string.

Convenience wrapper for new + update + get_string + free.

Parameters:
  • type – checksum algorithm

  • data – input data

  • len – input length in bytes

Returns:

newly allocated hex string (caller frees with axl_free), or NULL on error.

int axl_compute_checksum_digest(AxlChecksumType type, const void *data, size_t len, uint8_t *out, size_t out_len)

Compute a checksum and write raw digest bytes.

Parameters:
  • type – checksum algorithm

  • data – input data

  • len – input length in bytes

  • out – output buffer (must be large enough)

  • out_len – output buffer size

Returns:

AXL_OK on success, AXL_ERR on error.

int axl_pbkdf2_hmac_sha256(const uint8_t *password, size_t password_len, const uint8_t *salt, size_t salt_len, uint32_t iterations, uint8_t *out, size_t out_len)

Derive a key with PBKDF2-HMAC-SHA256 (RFC 8018 / RFC 2898).

Stretches a password into out_len key bytes against a salt and an iteration count, using HMAC-SHA256 as the PRF. The standard primitive for storing a password verifier (e.g. SCRAM credentials, axl-scram.h) or deriving a key from a passphrase: a high iterations makes brute-forcing the stored value expensive.

Layered on the dependency-free axl-hmac.h, so it works in every build (no AXL_TLS required). The output is deterministic and RFC-conformant regardless of how the library was configured.

Parameters:
  • password – password bytes (may be NULL iff password_len==0)

  • password_len – password length in bytes

  • salt – salt bytes (may be NULL iff salt_len==0)

  • salt_len – salt length in bytes

  • iterations – PBKDF2 iteration count (>= 1)

  • out – [out] derived key

  • out_len – number of key bytes to derive

Returns:

AXL_OK on success; AXL_INVALID if iterations is 0, out_len is 0, out is NULL, or a length argument is non-zero with a NULL buffer; AXL_ERR on an internal HMAC/allocation failure.

uint32_t axl_crc32(uint32_t crc, const void *data, size_t len)

Update a running CRC-32 (gzip / zlib / PNG polynomial).

Computes the CRC-32 of data and folds it into crc, returning the new running value. Matches zlib’s crc32() contract exactly: seed the first call with 0, chain the result through subsequent calls, and the final return is the CRC-32 of the concatenated input.

uint32_t crc = axl_crc32(0, NULL, 0);   // 0 — the seed
crc = axl_crc32(crc, part1, len1);
crc = axl_crc32(crc, part2, len2);      // == CRC-32(part1 || part2)

Reflected polynomial 0xEDB88320, as used by the gzip trailer. data may be NULL only when len is 0.

Parameters:
  • crc – running CRC (0 to start)

  • data – input bytes (may be NULL iff len == 0)

  • len – number of bytes

Returns:

the updated CRC-32 value.

uint32_t axl_adler32(uint32_t adler, const void *data, size_t len)

Update a running Adler-32 (RFC 1950 / zlib).

Folds the Adler-32 of data into adler. Matches zlib’s adler32() contract: seed the first call with 1, chain the result, and the final return is the Adler-32 of the concatenated input.

uint32_t a = axl_adler32(1, NULL, 0);   // 1 — the seed
a = axl_adler32(a, part1, len1);
a = axl_adler32(a, part2, len2);        // == Adler-32(part1 || part2)

data may be NULL only when len is 0.

Parameters:
  • adler – running Adler-32 (1 to start)

  • data – input bytes (may be NULL iff len == 0)

  • len – number of bytes

Returns:

the updated Adler-32 value.

AxlHmac

Keyed-hash message authentication (HMAC, RFC 2104) over the digest engine — mirrors GLib’s GHmac. For API tokens, signed cookies, webhook signatures. No AXL_TLS=1 required. Prefer HMAC-SHA256 for new designs.

Keyed-hash message authentication code (HMAC, RFC 2104).

Mirrors GLib’s GHmac, layered on the AxlChecksum digest engine (axl-digest.h) — MD5, SHA-1, or SHA-256, no external dependency (works without AXL_TLS=1). Use it to authenticate a message with a shared secret: API tokens, signed cookies, webhook signatures, Redfish/IPMI session integrity.

The API matches AxlChecksum: create with a key + algorithm, feed data incrementally, then read the result once as a hex string or raw bytes. After a get, the context is finalized — do not update again.

One-shot:

char *mac = axl_compute_hmac(AXL_CHECKSUM_SHA256,
                             key, key_len, msg, msg_len);
axl_printf("HMAC-SHA256: %s\n", mac);
axl_free(mac);

Incremental:

AXL_AUTOPTR(AxlHmac) h = axl_hmac_new(AXL_CHECKSUM_SHA256, key, key_len);
axl_hmac_update(h, part1, len1);
axl_hmac_update(h, part2, len2);
const char *hex = axl_hmac_get_string(h);

NOTE: HMAC-MD5 and HMAC-SHA1 are provided for interoperability with legacy protocols; prefer HMAC-SHA256 for new designs.

Typedefs

typedef struct AxlHmac AxlHmac

Functions

AxlHmac *axl_hmac_new(AxlChecksumType type, const void *key, size_t key_len)

Create an HMAC context for type keyed with key.

The key may be any length: keys longer than the hash block size (64 bytes for MD5/SHA-1/SHA-256) are hashed down per RFC 2104, and a key_len of 0 is valid (empty key). key may be NULL only when key_len is 0.

Parameters:
  • type – digest algorithm

  • key – secret key bytes

  • key_len – key length in bytes

Returns:

a new HMAC context, or NULL on allocation failure, an unsupported type, or key == NULL with key_len > 0. Free with axl_hmac_free().

void axl_hmac_update(AxlHmac *h, const void *data, size_t len)

Feed data into the HMAC.

May be called repeatedly. Must NOT be called after axl_hmac_get_string() or axl_hmac_get_digest() — the context is finalized by the first get. No-op if h is NULL.

Parameters:
  • h – HMAC context

  • data – input data

  • len – input length in bytes

const char *axl_hmac_get_string(AxlHmac *h)

Get the HMAC as a lowercase hex string.

Finalizes the context on first call. The returned string is owned by h and stays valid until axl_hmac_free(); repeated calls return the same pointer. After this call, axl_hmac_update() must not be used.

Parameters:
  • h – HMAC context

Returns:

hex string (e.g. “5bdcc146…”), or NULL if h is NULL or the finalize step hit an allocation failure.

void axl_hmac_get_digest(AxlHmac *h, uint8_t *buf, size_t *len)

Get the raw HMAC digest bytes.

Finalizes the context. On entry *len is the buffer size; on return it is set to the digest length (16/20/32 bytes for MD5/SHA-1/SHA-256), or 0 if the finalize step hit an allocation failure. If the buffer is smaller than the digest, only *len bytes are written but *len still reports the full digest length. After this call, axl_hmac_update() must not be used.

Parameters:
  • h – HMAC context

  • buf – output buffer

  • len – [in/out] buffer size / digest length

void axl_hmac_free(AxlHmac *h)

Free an HMAC context. NULL-safe.

Parameters:
  • h – HMAC context (may be NULL)

char *axl_compute_hmac(AxlChecksumType type, const void *key, size_t key_len, const void *data, size_t data_len)

Compute an HMAC in one call and return it as a hex string.

Convenience wrapper for new + update + get_string + dup. Matches GLib’s g_compute_hmac_for_data().

Parameters:
  • type – digest algorithm

  • key – secret key bytes

  • key_len – key length in bytes

  • data – message bytes

  • data_len – message length in bytes

Returns:

newly allocated lowercase hex string (free with axl_free), or NULL on failure (see axl_hmac_new).

Note

For the full cryptography map — hashing, HMAC, randomness, image verification, TLS, and public-key signature verification (<axl/axl-crypto.h>) — see Cryptography.

AxlBytes

Immutable, reference-counted byte buffer (GLib’s GBytes). A read-only (data, size) blob shared across owners without copying; axl_bytes_new_from_bytes carves a zero-copy sub-range that keeps its parent alive. The shared currency for data flowing between subsystems (HTTP bodies, file contents, shared-memory segments).

Immutable, reference-counted byte buffer.

Mirrors GLib’s GBytes: a read-only (data, size)

blob with a reference count, so the same bytes can be shared across owners without copying and without anyone having to track “who frees

this.” Ideal as the currency for data that flows between subsystems — an HTTP body handed to a parser, file contents passed to a hasher, a shared-memory segment exposed to several readers.

The bytes never change after construction, which is what makes sharing safe: axl_bytes_ref is O(1) and hands back the same object; axl_bytes_new_from_bytes carves out a sub-range that shares the parent’s storage (zero copy) and keeps the parent alive for as long as the slice lives.

Reference counting is not thread-safe (single-threaded UEFI BSP).

AxlBytes *b = axl_bytes_new(buf, len);     // copies buf
size_t n;
const uint8_t *p = axl_bytes_get_data(b, &n);
AxlBytes *head = axl_bytes_new_from_bytes(b, 0, 16);  // zero-copy slice
axl_bytes_unref(b);     // 'head' still keeps the storage alive
// ... use head ...
axl_bytes_unref(head);  // now the storage is freed

Typedefs

typedef struct AxlBytes AxlBytes

Functions

AxlBytes *axl_bytes_new(const void *data, size_t size)

Create a byte buffer by COPYING size bytes from data.

The caller’s buffer is independent afterwards. size may be 0 (data may then be NULL), yielding a valid empty buffer.

Parameters:
  • data – source bytes (may be NULL iff size is 0)

  • size – number of bytes

Returns:

a new AxlBytes with refcount 1, or NULL on allocation failure. Release with axl_bytes_unref().

AxlBytes *axl_bytes_new_take(void *data, size_t size)

Create a byte buffer that TAKES OWNERSHIP of data.

No copy: data must be a heap block from axl_malloc/axl_calloc/ axl_realloc, and is freed with axl_free() when the last reference is dropped. data may be NULL only when size is 0.

Parameters:
  • data – heap buffer to take ownership of

  • size – number of bytes

Returns:

a new AxlBytes with refcount 1, or NULL on allocation failure (in which case data is NOT freed) or if data is NULL with size > 0.

AxlBytes *axl_bytes_new_static(const void *data, size_t size)

Create a byte buffer over STATIC data (never freed).

No copy and no ownership: data must outlive every reference (string literals, embedded blobs, .rodata). Nothing is freed when the last reference is dropped.

Parameters:
  • data – static bytes outliving all references

  • size – number of bytes

Returns:

a new AxlBytes with refcount 1, or NULL on allocation failure, or if data is NULL with size > 0.

AxlBytes *axl_bytes_new_from_bytes(AxlBytes *parent, size_t offset, size_t length)

Create a sub-range that SHARES the parent’s storage (no copy).

The slice covers length bytes of parent starting at offset and holds a reference to parent, so the parent’s storage stays alive for as long as the slice does. offset + length must not exceed the parent’s size. As an optimization, a slice that spans the whole parent returns a new reference to parent itself.

Parameters:
  • parent – buffer to slice

  • offset – start offset into parent

  • length – slice length

Returns:

a new AxlBytes with refcount 1 (or a ref to parent), or NULL if parent is NULL or the range is out of bounds.

AxlBytes *axl_bytes_ref(AxlBytes *b)

Add a reference. O(1), returns the same object.

Parameters:
  • b – buffer

Returns:

b (or NULL if b is NULL).

void axl_bytes_unref(AxlBytes *b)

Drop a reference; free the buffer when the last one goes.

NULL-safe. Dropping the final reference frees owned storage (for axl_bytes_new / _new_take) and releases the parent reference (for a slice); static buffers free only the AxlBytes wrapper.

Parameters:
  • b – buffer (may be NULL)

const void *axl_bytes_get_data(const AxlBytes *b, size_t *size)

Borrow the bytes and (optionally) the size.

The returned pointer is owned by b and valid until the reference that returned it is dropped. Do not write through it or free it.

Parameters:
  • b – buffer

  • size – receives the size, or NULL to ignore

Returns:

pointer to the bytes (NULL for an empty buffer), and the length via size if non-NULL.

size_t axl_bytes_get_size(const AxlBytes *b)

Get the size in bytes.

Parameters:
  • b – buffer

Returns:

the buffer length (0 if b is NULL).

size_t axl_bytes_hash(const void *b)

Hash the contents. (GLib: g_bytes_hash)

Signature matches AxlHashFunc so AxlBytes can key a hash table; b is an const AxlBytes * and must be non-NULL (unlike axl_bytes_equal, which tolerates NULL).

Parameters:
  • b – const AxlBytes *

Returns:

a content-derived hash.

bool axl_bytes_equal(const void *a, const void *b)

Content equality. (GLib: g_bytes_equal)

Signature matches AxlEqualFunc; a and b are const AxlBytes *.

Parameters:
  • a – const AxlBytes *

  • b – const AxlBytes *

Returns:

true if both have the same size and identical bytes.

int axl_bytes_compare(const void *a, const void *b)

Lexicographic ordering by content. (GLib: g_bytes_compare)

Compares the shared prefix byte-by-byte; if one is a prefix of the other, the shorter sorts first. a and b are const AxlBytes * and must be non-NULL (unlike axl_bytes_equal). The magnitude is not normalized to ±1 — only the sign is meaningful.

Parameters:
  • a – const AxlBytes *

  • b – const AxlBytes *

Returns:

<0, 0, or >0.

AxlCompress

DEFLATE-family compression (RFC 1951) with gzip (RFC 1952) and zlib (RFC 1950) framing, backed by a vendored sdefl/sinfl codec. One-shot axl_compress / axl_decompress plus stream filters (axl_compress_writer / axl_compress_reader and the axl_gzip_* wrappers) over AxlStream — so tar.gz, HTTP gzip, and file compression compose for free. Integrity (CRC-32 / Adler-32) is verified on decode via AxlDigest.

Defines

AXL_COMPRESS_LEVEL_DEFAULT

Use the codec’s default effort. Explicit levels run 0 (store/fast) through 9 (best); values are clamped into the codec’s supported range.

Enums

enum AxlCompressFormat

Container/framing format for a DEFLATE stream.

axl-compress.h:

AxlCompress — DEFLATE-family compression (RFC 1951) with gzip (RFC 1952) and zlib (RFC 1950) framing. Backed by a vendored single-header codec (sdefl/sinfl); AXL owns the framing and verifies the CRC-32 / Adler-32 integrity fields via AxlDigest.

This header is the one-shot whole-buffer layer — axl_compress / axl_decompress. (A stream-filter layer over AxlStream — so tar.gz, HTTP gzip, and file compression compose for free — is planned on top of these.)

Format coverage: GZIP, ZLIB, and raw DEFLATE. LZ4 may follow; zstd / xz are deliberately out of scope (their encoders dwarf a UEFI tool).

void  *gz;
size_t gz_len;
if (axl_compress(AXL_COMPRESS_GZIP, data, len, &gz, &gz_len,
                 AXL_COMPRESS_LEVEL_DEFAULT) == AXL_OK) {
    // gz/gz_len is a complete .gz member; ships to disk or the wire.
    axl_free(gz);
}

Values:

enumerator AXL_COMPRESS_GZIP

gzip (RFC 1952): magic + CRC-32 + size

enumerator AXL_COMPRESS_ZLIB

zlib (RFC 1950): 2-byte header + Adler-32

enumerator AXL_COMPRESS_DEFLATE_RAW

bare DEFLATE (RFC 1951): no header/trailer

Functions

int axl_compress(AxlCompressFormat fmt, const void *in, size_t in_len, void **out, size_t *out_len, int level)

Compress a whole buffer into a freshly allocated buffer.

Produces a complete fmt stream: for GZIP a standalone .gz member (10-byte header, DEFLATE body, CRC-32 + ISIZE trailer); for ZLIB a 2-byte header + body + Adler-32; for DEFLATE_RAW the bare compressed body with no framing.

On success *out points to a heap buffer of *out_len bytes that the caller frees with axl_free(). On failure *out is set to NULL and *out_len to 0. An empty input (in_len == 0) is valid and yields the framing for an empty payload.

Parameters:
  • fmt – output framing

  • in – input bytes (may be NULL iff in_len == 0)

  • in_len – input length

  • out – [out] allocated compressed buffer (axl_free)

  • out_len – [out] compressed length

  • level – 0..9, or AXL_COMPRESS_LEVEL_DEFAULT

Returns:

AXL_OK on success; AXL_ERR on allocation failure, NULL output pointers, or in_len exceeding the codec’s limit.

int axl_decompress(AxlCompressFormat fmt, const void *in, size_t in_len, void **out, size_t *out_len)

Decompress a whole fmt stream into a freshly allocated buffer.

The expected framing is selected by fmt — this does not auto-detect (callers that need gzip-vs-plain sniffing should test the 1f 8b magic themselves and pick the format). gzip optional header fields (FEXTRA / FNAME / FCOMMENT / FHCRC) are skipped, so members written by the gzip command (which embed the original filename) decode cleanly.

Integrity: GZIP verifies the trailer CRC-32 and uncompressed size, ZLIB verifies the Adler-32 — a mismatch is an AXL_ERR. DEFLATE_RAW carries no checksum, so truncated raw input can silently yield a short result; prefer a framed format when integrity matters.

On success *out points to a heap buffer of *out_len bytes (caller frees with axl_free); on failure *out is NULL and *out_len is 0. A stream whose payload is empty yields *out_len == 0 with a non-NULL one-byte allocation.

Parameters:
  • fmt – expected input framing

  • in – compressed bytes

  • in_len – compressed length

  • out – [out] allocated plaintext buffer (axl_free)

  • out_len – [out] plaintext length

Returns:

AXL_OK on success; AXL_ERR on malformed framing, a checksum or size mismatch, corrupt DEFLATE data, or allocation failure.

AxlStream *axl_compress_writer(AxlCompressFormat fmt, AxlStream *sink, int level)

Wrap sink as a compressing write stream.

Bytes written to the returned stream are buffered; the framed fmt stream is produced and written to sink when the writer is finalized — either explicitly via axl_compress_writer_finish() (which surfaces compression/write errors) or implicitly on axl_fclose().

sink is borrowed, not owned: the caller closes it after the writer is finalized/closed. Composes with anything — a file stream, an in-memory buffer (axl_bufopen), a tar writer’s backing stream.

Parameters:
  • fmt – output framing

  • sink – destination for the compressed stream (borrowed)

  • level – 0..9, or AXL_COMPRESS_LEVEL_DEFAULT

Returns:

a write stream (free with axl_fclose), or NULL on bad arguments or allocation failure.

int axl_compress_writer_finish(AxlStream *s)

Finalize a compressing writer: compress everything written so far and emit the framed stream to its sink.

Idempotent — a second call is a no-op that returns the same status. axl_fclose() calls this implicitly if it wasn’t called explicitly, but only an explicit call can report a compression or sink-write failure to the caller.

Parameters:
  • s – stream returned by axl_compress_writer

Returns:

AXL_OK on success; AXL_ERR if s is not a compressing writer, or on compression / sink-write failure.

AxlStream *axl_compress_reader(AxlCompressFormat fmt, AxlStream *src)

Wrap src as a decompressing read stream.

Eagerly drains src, decompresses the whole fmt stream, and returns a readable, seekable stream over the plaintext. Integrity is verified during construction (a bad checksum/size yields NULL), so a successful return means the data decoded cleanly.

src is borrowed and read to EOF but not closed — the caller still owns and closes it.

Parameters:
  • fmt – expected input framing

  • src – compressed source (borrowed, read to EOF)

Returns:

a read stream over the plaintext (free with axl_fclose), or NULL on a decode error or allocation failure.

AxlStream *axl_gzip_writer(AxlStream *sink, int level)

Convenience: axl_compress_writer with AXL_COMPRESS_GZIP.

Parameters:
  • sink – destination for the gzip stream (borrowed)

  • level – 0..9, or AXL_COMPRESS_LEVEL_DEFAULT

AxlStream *axl_gzip_reader(AxlStream *src)

Convenience: axl_compress_reader with AXL_COMPRESS_GZIP.

Parameters:
  • src – gzip source (borrowed, read to EOF)