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-ring-buf.h> — Ring buffer (circular byte buffer, zero-copy, overwrite mode)

  • <axl/axl-digest.h> — Message digest checksums (MD5, SHA-1, SHA-256)

  • <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

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");

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.

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.

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);

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.

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)

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_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.

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.

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.

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.

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 using insertion sort.

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 array elements in place with a context-aware comparator.

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

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.