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 |
|---|---|---|
|
Copied (strdup’d) — table owns + frees |
Borrowed |
|
Borrowed |
Borrowed |
|
Owned if |
Owned if |
“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 AxlHashTable — in-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. Allaxl_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_strndupaxl_strsplit(free withaxl_strfreev)axl_strjoin,axl_strstripaxl_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-growingAxlString. 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_endon 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 commentsTrailing commas in objects and arrays
Single-quoted strings (
'text')Unquoted (identifier-name) object keys
Hex number literals (
0x...) and+/-number prefixExtended 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
// texton 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 nolibm. There’s noaxl_json_get_doubleaccessor, so IEEE 754 special values would be lex-only and unretrievable.axl_json_get_inton a fractional or scientific-notation token truncates at the first non-digit character (pre-existing strict-mode behavior, unchanged):{x: 1.5}returns1.Unicode
IdentifierNamefor 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_Separatorcharacters) — only the ASCII whitespace set plus\vand\fis 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-ownedAxlString. MirrorsAxlJsonWriter’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. OneAXL_XML_TOKEN_{START_ELEMENT, END_ELEMENT, TEXT, END_DOCUMENT}peraxl_xml_reader_nextcall. Attribute lookup viaaxl_xml_reader_attrwhile positioned at aSTART_ELEMENT. Entity decoding (5 named +&#NNN;/&#xHH;) into a reader-owned scratch buffer reused per token. CDATA passes through withis_cdataset. 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 & 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 (& < >
" ') 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
AxlFind — Byte-Substring Search
A Boyer-Moore-Horspool substring search that runs over an abstract
AxlByteReader — a tiny function table over whatever holds the
bytes. The one engine (axl_find_in_source) therefore drives a flat
memory block (the built-in AxlMemReader), an AxlTextBuffer (gap
buffer), and an AxlPieceTree (out-of-core piece table); the
axl_text_buffer_find / axl_piece_tree_find wrappers just build the
right reader. The reader pulls overlapping windows, so a match
straddling the source’s internal boundaries (a piece edge, the gap) is
never missed; a contiguous source that supplies the optional peek is
scanned in place with no copy. Forward and backward, case-insensitive,
and whole-word variants. A hit is an AxlMatch — start + length,
with the length carried explicitly so the same result shape fits
variable-length matchers (see AxlRegex).
AxlMemReader r;
axl_mem_reader_init(&r, text, len);
AxlMatch m;
if (axl_find_in_source(&r.reader, "needle", 6, 0, AXL_FIND_DEFAULT, &m))
use(text + m.start, m.length);
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/-1both 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)
-
enumerator AXL_HASH_TABLE_NEW
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
intpointed to bykey. (GLib: g_int_hash)
-
bool axl_int_equal(const void *a, const void *b)
Equality of the
ints pointed to byaandb. (GLib: g_int_equal)
-
size_t axl_int64_hash(const void *key)
Hash the
int64_tpointed to bykey. (GLib: g_int64_hash)
-
bool axl_int64_equal(const void *a, const void *b)
Equality of the
int64_ts pointed to byaandb. (GLib: g_int64_equal)
-
size_t axl_double_hash(const void *key)
Hash the
doublepointed to bykey. (GLib: g_double_hash)
-
bool axl_double_equal(const void *a, const void *b)
Equality of the
doubles pointed to byaandb. (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_destroyon 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_funcandequal_functo 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
keywith its value set tokey, 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
predicatefor 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 aaxl_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
satisfy this. Tables built viaaxl_hash_table_new_full(..., axl_free_impl,
axl_free_impl)
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_rangehelper that strdups both sides of theContent-Rangeheader).- 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
funcfor 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.
keyandvalueare 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>
Stack-allocated iterator. Matches GHashTableIter. Fields prefixed with _ are private.
AxlArray
Typedefs
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.indexmay 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
indexis 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.indexmay 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
indexis 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
indexby 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
lenelements starting atindex, 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
lenelements.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.
-
AxlList *axl_list_remove_link(AxlList *list, AxlList *link)
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
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.
-
AxlSList *axl_slist_remove_link(AxlSList *list, AxlSList *link)
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.
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.
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
keyand returns its value. Sets*suffixto point intokeyat the first character after the matched prefix. The caller can compute the prefix length as*suffix-key.On no match,
*suffixis not modified. Pass NULL forsuffixif 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
funcis 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
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, …)
-
enumerator AXL_NTREE_PRE_ORDER
-
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
-
enumerator AXL_NTREE_LEAVES
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
-
void axl_ntree_unlink(AxlNTree *node)
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).
-
struct AxlNTreeIter
- #include <axl-ntree.h>
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_reversewalks 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.
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
-
struct AxlTreeIter
- #include <axl-tree.h>
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.
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_freefunction 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
sizeis 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
sizeis 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_lenis 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
-
uint8_t *buf
-
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.
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)
-
enumerator AXL_CHECKSUM_MD5
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,*lenis 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_lenkey 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 highiterationsmakes 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
iterationsis 0,out_lenis 0,outis 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
dataand folds it intocrc, returning the new running value. Matches zlib’scrc32()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.
datamay be NULL only whenlenis 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
dataintoadler. Matches zlib’sadler32()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)
datamay be NULL only whenlenis 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.
Functions
-
AxlHmac *axl_hmac_new(AxlChecksumType type, const void *key, size_t key_len)
Create an HMAC context for
typekeyed withkey.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_lenof 0 is valid (empty key).keymay be NULL only whenkey_lenis 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, orkey== NULL withkey_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
his 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
hand 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
his 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
*lenis 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*lenbytes are written but*lenstill 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
Functions
-
AxlBytes *axl_bytes_new(const void *data, size_t size)
Create a byte buffer by COPYING
sizebytes fromdata.The caller’s buffer is independent afterwards.
sizemay be 0 (datamay then be NULL), yielding a valid empty buffer.- Parameters:
data – source bytes (may be NULL iff
sizeis 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:
datamust be a heap block from axl_malloc/axl_calloc/ axl_realloc, and is freed with axl_free() when the last reference is dropped.datamay be NULL only whensizeis 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
datais NOT freed) or ifdatais NULL withsize> 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:
datamust 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
datais NULL withsize> 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
lengthbytes ofparentstarting atoffsetand holds a reference toparent, so the parent’s storage stays alive for as long as the slice does.offset+lengthmust not exceed the parent’s size. As an optimization, a slice that spans the whole parent returns a new reference toparentitself.- Parameters:
parent – buffer to slice
offset – start offset into
parentlength – slice length
- Returns:
a new AxlBytes with refcount 1 (or a ref to
parent), or NULL ifparentis 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 ifbis 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
band 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
sizeif 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
bis 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;
bis anconst 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;
aandbareconst 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.
aandbareconst 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
-
enumerator AXL_COMPRESS_GZIP
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
fmtstream: for GZIP a standalone.gzmember (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
*outpoints to a heap buffer of*out_lenbytes that the caller frees with axl_free(). On failure*outis set to NULL and*out_lento 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_lenexceeding 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
fmtstream 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 the1f 8bmagic themselves and pick the format). gzip optional header fields (FEXTRA / FNAME / FCOMMENT / FHCRC) are skipped, so members written by thegzipcommand (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
*outpoints to a heap buffer of*out_lenbytes (caller frees with axl_free); on failure*outis NULL and*out_lenis 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
sinkas a compressing write stream.Bytes written to the returned stream are buffered; the framed
fmtstream is produced and written tosinkwhen the writer is finalized — either explicitly via axl_compress_writer_finish() (which surfaces compression/write errors) or implicitly on axl_fclose().sinkis 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
sis not a compressing writer, or on compression / sink-write failure.
-
AxlStream *axl_compress_reader(AxlCompressFormat fmt, AxlStream *src)
Wrap
srcas a decompressing read stream.Eagerly drains
src, decompresses the wholefmtstream, 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.srcis 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.