AxlNet — Networking

TCP sockets, UDP sockets, socket abstraction layer, URL parsing, HTTP server, HTTP client, TLS, and network utilities (IPv4 address helpers, interface enumeration, ping).

Individual headers can be included separately or use the umbrella <axl/axl-net.h>.

Headers:

  • <axl/axl-net.h> — Umbrella + network utilities

  • <axl/axl-inet-address.h> — IP address and socket address types

  • <axl/axl-socket.h> — Unified socket (stream/datagram)

  • <axl/axl-socket-client.h> — High-level DNS + connect helper

  • <axl/axl-tcp.h> — TCP sockets (low-level)

  • <axl/axl-udp.h> — UDP sockets (low-level)

  • <axl/axl-url.h> — URL parsing

  • <axl/axl-http-core.h> — Low-level HTTP/1.1 parsing (shared by server + client)

  • <axl/axl-http-server.h> — HTTP server

  • <axl/axl-http-client.h> — HTTP client

  • <axl/axl-tls.h> — TLS support (optional, requires AXL_TLS=1)

Overview

AXL networking is built on UEFI’s TCP4/UDP4 protocol stack. The stack must be initialized before use — either by the UEFI Shell (ifconfig) or by calling axl_net_auto_init.

Application
├─ axl_http_server / axl_http_client
├─ axl_socket_client (DNS + connect)
├─ axl_socket (stream/datagram)
│   ├─ axl_tcp / axl_udp
│   └─ axl_socket_address / axl_inet_address
├─ axl_tls (optional, wraps TCP)
└─ UEFI TCP4 / UDP4 protocols
    └─ IP4 → ARP → SNP (NIC driver)

Network Initialization

The recommended one-call shape is axl_net_bring_up — load drivers, acquire an IP (DHCP or static), optionally read it back. Used by HTTP services, REST tools, and one-shot fetch utilities — they all open with the same preamble:

// DHCP — most common case
if (axl_net_bring_up(SIZE_MAX, NULL, NULL, NULL, 10, NULL) != AXL_OK) {
    axl_printf("Network not available\n");
    return -1;
}

// Static IP — pass the address (NULL netmask = /24 default,
// NULL gateway = none); also reads the resolved address back.
uint8_t  ip[] = { 192, 168, 1, 100 };
AxlIPv4Address addr;
if (axl_net_bring_up(SIZE_MAX, ip, NULL, NULL, 0, &addr) != AXL_OK) {
    return -1;
}

Standard option helpers

Most consumers (tools, services) take the same NIC / local-IP / port options on the command line and run the same DHCP bring-up preamble. <axl/axl-net-opts.h> ships a canonical option bag plus a one-call init helper:

typedef struct {
    AxlNetOpts net;          // embed as sub-struct
    const char *url;
    bool        verbose;
} MyOpts;

// DHCP bring-up driven by the bag — maps AXL_NET_NIC_AUTO to
// SIZE_MAX and runs axl_net_bring_up under the hood:
if (axl_net_init_from_opts(&opts.net, 10) != AXL_OK) {
    axl_printf("network unavailable\n");
    return 1;
}

// Use opts.net.local_ip for the local socket bind — outbound
// source for clients, listen address for servers (same bind(2)).

The bag carries three fields:

  • nic_index — which NIC to DHCP on; AXL_NET_NIC_AUTO picks the first usable one.

  • local_ip — IPv4 to bind(2) the local socket end to. Outbound source for clients (curl --interface-style), listen address for servers — same syscall, role implied by what the consumer does next.

  • portuint16_t; consumers define their own domain default.

Out of scope by design: installing a static IPv4 on the NIC. That’s a firmware-ifconfig-layer concern (UEFI Shell ifconfig, or axl_net_set_static_ip for tools that genuinely need to mutate IP4Config2 policy). The options bag is for stateless connection-side selectors only.

Pair with the descriptor-table composition helpers in <axl/axl-config.h> (axl_config_descs_net, axl_config_descs_append) to also inject the matching CLI / config descriptors into a consumer’s own table without copy-paste — see the AxlConfig docs. The AXL_NET_OPT_SOURCE_IP and AXL_NET_OPT_LISTEN_IP selector bits both target the same local_ip field; they differ only in CLI vocabulary (--source-ip vs --listen-ip).

Three layered primitives sit underneath if a consumer needs finer control:

  • axl_net_drivers_up() — load NIC drivers, connect SNP, wait for link-up (5 s budget). No DHCP, no IP assignment.

  • axl_net_auto_init(nic, dhcp_timeout) — drivers_up + DHCP wait. Used by the DHCP path of bring_up internally. Event-driven via EFI_IP4_CONFIG2_PROTOCOL.RegisterDataNotify — sub-millisecond wakeup after DHCP completes (firmware that doesn’t support the notify falls back to a 1 s tick).

  • axl_net_set_static_ip(nic, ip, netmask, gateway) — raw IP4Config2 setter; static path of bring_up calls it after drivers_up.

Socket Layer

AxlSocket is the recommended socket API for new code — a GLib-GSocket-shaped abstraction over both stream (TCP) and datagram (UDP) transports, with rich address types and blocking and async forms. It delegates to the low-level AxlTcp / AxlUdp primitives under the hood (see the Low-Level TCP / UDP section below).

Address Types

AxlInetAddress wraps an IPv4 address with parsing, formatting, and comparison:

// Create from string or bytes
AxlInetAddress *addr = axl_inet_address_new_from_string("192.168.1.1");
AxlInetAddress *lo   = axl_inet_address_new_loopback();

const char    *str   = axl_inet_address_to_string(addr);  // "192.168.1.1"
const uint8_t *bytes = axl_inet_address_to_bytes(addr);

axl_inet_address_free(addr);
axl_inet_address_free(lo);

AxlSocketAddress pairs an address with a port:

// From address + port (takes ownership of the AxlInetAddress)
AxlSocketAddress *sa = axl_socket_address_new(
    axl_inet_address_new_from_string("10.0.0.1"), 8080);

// Or parse "host:port"
AxlSocketAddress *sa2 = axl_socket_address_new_from_string("10.0.0.1:8080", 0);

axl_socket_address_free(sa);
axl_socket_address_free(sa2);

Unified Socket

TCP client:

AXL_AUTOPTR(AxlSocket) sock = axl_socket_new(AXL_SOCKET_STREAM);
AxlSocketAddress *remote = axl_socket_address_new(
    axl_inet_address_new_from_string("192.168.1.1"), 8080);

if (axl_socket_connect(sock, remote) == 0) {
    axl_socket_send(sock, "hello", 5, 0);

    char buf[64];
    size_t len = sizeof(buf);
    axl_socket_receive(sock, buf, &len, 5000);
}
axl_socket_address_free(remote);

TCP server (blocking):

AXL_AUTOPTR(AxlSocket) listener = axl_socket_new(AXL_SOCKET_STREAM);
axl_socket_listen(listener, 8080);

AxlSocket *client;
if (axl_socket_accept(listener, &client) == 0) {
    // handle client...
    axl_socket_free(client);
}

UDP send:

AXL_AUTOPTR(AxlSocket) sock = axl_socket_new(AXL_SOCKET_DATAGRAM);
AXL_AUTOPTR(AxlSocketAddress) dest = axl_socket_address_new(
    axl_inet_address_new_from_string("192.168.1.100"), 514);
axl_socket_send_to(sock, msg, msg_len, dest);

Socket Client

AxlSocketClient combines DNS resolution and TCP connection:

AXL_AUTOPTR(AxlSocketClient) client = axl_socket_client_new();
AxlSocket *sock;

if (axl_socket_client_connect_to_host(client, "example.com", 80, &sock) == 0) {
    axl_socket_send(sock, request, req_len, 0);
    axl_socket_free(sock);
}

Or connect to a resolved address:

AxlSocketAddress *addr = axl_socket_address_new(
    axl_inet_address_new_from_string("10.0.0.1"), 8080);
AxlSocket *sock;
axl_socket_client_connect(client, addr, &sock);
axl_socket_address_free(addr);

Async Operations

The socket layer supports async operations via AxlLoop. Callbacks return booltrue keeps the op armed (accept-the-next-client or re-issue-recv-on-same-buffer), false tears down. Returning false permits closing the socket inside the callback: the loop does not touch the socket again after a false return.

bool on_client(AxlSocket *client, AxlStatus status, void *data) {
    if (status != 0) return true;  // transient error; keep listening
    // handle client...
    axl_socket_free(client);
    return true;  // keep accepting more clients
}

AXL_AUTOPTR(AxlSocket) listener = axl_socket_new(AXL_SOCKET_STREAM);
axl_socket_listen(listener, 8080);
axl_socket_accept_async(listener, loop, on_client, NULL);
axl_loop_run(loop);

See sdk/examples/echo-server.c for a complete async echo server built on this layer — it uses axl_socket_receive_async in stays-armed mode (callback returns true to keep receiving).

Low-Level TCP / UDP

Most applications should use the Socket Layer above. AxlTcp and AxlUdp are the primitives underneath – thin wrappers over UEFI’s TCP4_PROTOCOL / UDP4_PROTOCOL. Reach for them only when you need raw access to UEFI tokens, want the session-scoped cancellation pattern shown below, or are minimizing wrapper overhead. See sdk/examples/tcp-echo-server.c for the low-level counterpart to the socket-based echo-server.c.

TCP Sockets

Blocking and async TCP sockets. The blocking API is simpler; the async API integrates with the event loop for non-blocking I/O.

Client (blocking):

AxlTcp *sock;
if (axl_tcp_connect("192.168.1.1", 8080, &sock) == 0) {
    axl_tcp_send(sock, "GET / HTTP/1.0\r\n\r\n", 18, 5000);

    char buf[4096];
    size_t len = sizeof(buf);
    axl_tcp_recv(sock, buf, &len, 5000);

    axl_tcp_close(sock);
}

Server (async with event loop):

bool on_client(AxlTcp *client, AxlStatus status, void *data) {
    if (status != 0) return true;  // transient error; keep listening
    // handle client connection...
    axl_tcp_close(client);
    return true;  // keep accepting more clients
}

AxlTcp *listener;
axl_tcp_listen(8080, &listener);
axl_tcp_accept_async(listener, loop, /*cancel=*/NULL, on_client, NULL);
axl_loop_run(loop);

Session-scoped cancellation:

Every axl_tcp_*_async call accepts an optional AxlCancellable *. Share one cancellable across all ops tied to a session — closing the session cancels every in-flight op at once, each firing its callback with status == AXL_CANCELLED.

typedef struct { AxlCancellable *cancel; AxlTcp *sock; } Session;

static bool on_connected(AxlTcp *sock, AxlStatus status, void *data) {
    Session *s = data;
    if (status == AXL_CANCELLED) return true;  // session closed before connect
    s->sock = sock;
    axl_tcp_recv_async(sock, s->rxbuf, sizeof(s->rxbuf),
                       loop, s->cancel, on_data, s);
    return true;  // connect fires once; return value ignored for connect
}

Session *s = axl_new0(Session);
s->cancel = axl_cancellable_new();
axl_tcp_connect_async(host, port, loop, s->cancel, on_connected, s);

// Later, from any handler -- user closes the tab, subsystem shuts
// down, a parent cancellable fires: every op tagged with s->cancel
// stops and its callback fires exactly once with AXL_CANCELLED.
axl_cancellable_cancel(s->cancel);

UDP Sockets

Fire-and-forget datagram sending, request-response patterns, plus async receive / send and connection-style peer locking. Mirrors AxlTcp’s async / cancellable / source-IP-pinning shape.

AXL_AUTOPTR(AxlUdp) sock = NULL;
axl_udp_open(&sock, 0);                 // ephemeral local port
// or pin to a specific NIC:
// axl_udp_open_via(&sock, 0, &source_ip);

uint16_t bound;
char     bound_addr[16];
axl_udp_get_local_addr(sock, bound_addr, sizeof(bound_addr), &bound);

AxlIPv4Address dest;
axl_ipv4_parse("192.168.1.100", dest.addr);

// Fire-and-forget (sync, 2 s timeout)
axl_udp_send(sock, &dest, 514, msg, msg_len);

// Request-response (e.g., DNS query)
char reply[512];
size_t reply_len;
axl_udp_sendrecv(sock, &dest, 53, query, query_len,
                 reply, sizeof(reply), &reply_len, 3000);

Async send + receive, with optional AxlCancellable:

bool on_recv(AxlUdp *s, AxlStatus status, const void *data, size_t len,
             const AxlIPv4Address *from, uint16_t from_port, void *udata) {
    if (status != AXL_OK) return false;   // err / cancel — stop
    process(data, len, from);
    return true;                          // re-arm for next datagram
}
axl_udp_recv_async(sock, loop, /*cancel=*/NULL, on_recv, NULL);

bool on_sent(AxlUdp *s, AxlStatus status, void *udata) {
    if (status != AXL_OK) log_warn("send failed");
    return true;                          // ignored for send
}
axl_udp_send_async(sock, &dest, 514, msg, msg_len,
                   loop, /*cancel=*/NULL, on_sent, NULL);

Connection-style peer lock — kernel-side recv filter plus NULL-dest shorthand on send:

axl_udp_connect(sock, &peer, 9999);
axl_udp_send(sock, NULL, 0, msg, msg_len);   // uses configured peer
axl_udp_disconnect(sock);                    // back to "send anywhere"

Multicast / broadcast:

AxlIPv4Address mdns = { .addr = {224, 0, 0, 251} };
axl_udp_join_multicast(sock, &mdns);      // mDNS group
axl_udp_set_broadcast(sock, true);        // accept inbound broadcasts
// ... receive ...
axl_udp_leave_multicast(sock, NULL);      // leave all groups

HTTP Server

Create an HTTP server with route handlers:

void on_hello(AxlHttpRequest *req, AxlHttpResponse *resp, void *data) {
    axl_http_respond_text(resp, 200, "Hello from AXL!\n");
}

AXL_AUTOPTR(AxlHttpServer) s = axl_http_server_new(8080);
axl_http_server_add_route(s, "GET", "/hello", on_hello, NULL);
axl_http_server_run(s);  // blocks, serving requests

For multiple routes, the variadic batch form collapses the per-call error checks into one:

axl_http_server_add_routes(s,
    "GET",  "/version", on_version, NULL,
    "GET",  "/health",  on_health,  NULL,
    "POST", "/echo",    on_echo,    NULL,
    NULL);   // sentinel — required

REST request helpers

For REST-shaped handlers, three helpers route through the existing HTTP machinery so routes don’t reinvent content negotiation or JSON body parsing:

int handle_request(AxlHttpRequest *req, AxlHttpResponse *resp, void *data) {
    if (axl_http_request_wants_json(req)) {
        // ...emit JSON
    }

    AxlJsonReader r;
    if (axl_http_request_get_json(req, &r)) {
        int64_t value;
        if (axl_json_get_int(&r, "key", &value)) { /* ... */ }
        axl_json_free(&r);
    }
    return 0;
}

axl_http_request_accepts(req, mime) is the underlying primitive (case-insensitive, multi-type lists, wildcards, q-value tolerant). _wants_json is the application/json shorthand. _get_json parses req->body into a caller-owned AxlJsonReader (caller frees with axl_json_free).

The server supports middleware, WebSocket endpoints, authentication, response caching, streaming uploads, and WebDAV mounts. See the API reference for details.

WebDAV class-1 + MOVE/COPY

axl_http_server_add_webdav(s, prefix, &ops, user_data) mounts a WebDAV handler at the given URL prefix. Verb scope: OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, MOVE, COPY — covers class-1 plus MOVE and COPY. PROPPATCH, LOCK, UNLOCK, and If-header conditionals remain out of v1 scope (Windows Explorer, macOS Finder, davfs2, cadaver work without them when the server doesn’t advertise the lock class).

The consumer fills in an AxlWebDavOps callback table mapped onto its own filesystem; the SDK owns the protocol — verb dispatch, PROPFIND 207 Multi-Status XML emit (driven by AxlXmlWriter; see <axl/axl-xml.h>), Depth / Destination / Overwrite header parsing, RFC 1123 Last-Modified date formatting, RFC 3230 Want-Digest / Digest header negotiation (opt-in via the consumer’s optional digest callback), DAV: 1 advertisement on every WebDAV-method response. GET inherits axl_http_response_set_streamer (multi-GB safe, Range requests via axl_http_response_set_content_range); PUT inherits the upload-route chunk handler (write_open / chunk / close(aborted)). Per-mount single-in-flight PUT — concurrent PUTs to the same mount are refused rather than silently trampling each other’s state.

static int my_stat(void *user, const char *path, AxlWebDavEntry *out);
static int my_list_dir(void *user, const char *path,
                       AxlWebDavEntry *out, size_t max, size_t *count);
static int my_read_open (void *user, const char *path,
                         uint64_t offset, void **out_ctx);
static int my_read_chunk(void *ctx, void *buf, size_t cap, size_t *got);
static void my_read_close(void *ctx);
/* ... write_open / write_chunk / write_close / mkdir / remove
       / move / copy / content_type ... */

static const AxlWebDavOps my_ops = {
    .stat         = my_stat,
    .list_dir     = my_list_dir,
    .read_open    = my_read_open,
    .read_chunk   = my_read_chunk,
    .read_close   = my_read_close,
    /* ... */
};

axl_http_server_add_webdav(server, "/dav", &my_ops, my_user_data);

Up to 4 WebDAV mounts per server. The ops struct is COPIED into the server; the consumer may free or re-use it after add_webdav returns. user_data is borrowed and must outlive the server.

Streaming uploads

axl_http_server_add_upload_route(server, method, path, handler, data) registers a route that streams the body to handler in chunks instead of buffering — required for multi-GB uploads (the body never materializes in RAM, bypasses body.limit).

The AxlUploadHandler callback distinguishes three terminating shapes by the chunk and aborted arguments:

chunk

aborted

meaning

!= NULL

false

body chunk arrived; process it and return AXL_OK

NULL, 0

false

clean EOF — set resp fields, response is sent

NULL, 0

true

TCP disconnect / recv error — release per-request state

The abort call is mutually exclusive with the clean-EOF call: a handler that received the clean-EOF call will NOT also receive an abort, even if the response send subsequently fails. On abort the handler MUST NOT touch the connection or call any response setter — it exists only to release per-request state (open file handles, accumulators, allocations) accumulated across earlier chunk calls. Without this signal, that state leaks across requests.

Middleware registered via axl_http_server_use runs before the upload handler sees a single byte. On rejection the connection is force-closed (clients almost always send body bytes before reading the rejection — staying in keep-alive desyncs the next request). Header-based gating only — the body isn’t materialized so middleware that needs the body can’t apply to upload routes.

HTTP Client

AXL_AUTOPTR(AxlHttpClient) c = axl_http_client_new();
AXL_AUTOPTR(AxlHttpClientResponse) resp = NULL;

if (axl_http_get(c, "http://192.168.1.1:8080/api/status", &resp) == 0) {
    axl_printf("HTTP %zu\n", resp->status_code);
    if (resp->body != NULL) {
        axl_printf("%.*s\n", (int)resp->body_size, (char *)resp->body);
    }
}

HTTPS URLs are automatically detected when built with AXL_TLS=1.

TLS (HTTPS)

Optional TLS 1.2 support using mbedTLS 3.6. Provides HTTPS server/client, self-signed certificate generation, and transparent TCP encryption.

Build requirement: make AXL_TLS=1 (adds ~200KB to the binary). Without this flag, all TLS functions return -1/NULL/false.

Header: <axl/axl-tls.h>

AXL’s TLS module wraps mbedTLS to provide:

  • Self-signed ECDSA P-256 certificate generation

  • TLS 1.2 server contexts (for HTTPS)

  • TLS 1.2 client contexts (for HTTPS GET/POST)

  • Transparent integration with AxlHttpServer and AxlHttpClient

HTTPS Server

Generate a certificate and enable TLS on the HTTP server:

#include <axl.h>

axl_tls_init();

// Generate self-signed cert (valid 10 years, ECDSA P-256)
void *cert, *key;
size_t cert_len, key_len;
axl_tls_generate_self_signed("MyServer", NULL, 0,
                             &cert, &cert_len, &key, &key_len);

// Create HTTPS server
AxlHttpServer *s = axl_http_server_new(8443);
axl_http_server_use_tls(s, cert, cert_len, key, key_len);
axl_http_server_add_route(s, "GET", "/", handler, NULL);

axl_free(cert);
axl_free(key);

axl_http_server_run(s);  // serves HTTPS

HTTPS Client

HTTPS is automatic — just use an https:// URL:

AXL_AUTOPTR(AxlHttpClient) c = axl_http_client_new();
AXL_AUTOPTR(AxlHttpClientResponse) resp = NULL;

// TLS handshake happens automatically
axl_http_get(c, "https://192.168.1.1:8443/api/status", &resp);

Certificate Generation

axl_tls_generate_self_signed creates an ECDSA P-256 certificate with SHA-256 signature:

  • Subject: CN=<name>,O=AximCode

  • Validity: current year to +10 years

  • SubjectAltName: DNS:localhost, IP:127.0.0.1, plus any provided IP addresses

  • Output: DER-encoded certificate and private key (caller frees)

Entropy

mbedTLS needs random numbers for key generation and TLS handshakes. AXL provides entropy via:

  1. EFI_RNG_PROTOCOL (hardware RNG) — preferred, used when available

  2. Software fallback — system time + monotonic counter mixing. A warning is logged when the fallback is used.

Security Considerations

  • Self-signed certificates are not trusted by browsers or standard TLS clients. Use curl --insecure or configure trust-on-first-use.

  • Certificate verification is disabled for client connections (MBEDTLS_SSL_VERIFY_NONE). This is appropriate for BMC/embedded use but not for public internet TLS.

  • The software entropy fallback is not cryptographically strong. For production use on hardware without an RNG, consider providing your own entropy source.

API Reference

Network Utilities

Defines

AXL_NET_DRIVERS_OK

axl_net_ensure_drivers() return codes.

SNP is registered (already, or after load)

AXL_NET_DRIVERS_NOT_FOUND

no NIC drivers found on any mounted volume

drivers loaded, but no SNP came up

Functions

int axl_net_get_ip_address(AxlIPv4Address *addr)

Get the local IPv4 address of the first configured NIC.

axl-net.h:

Networking umbrella header. Includes socket layer, TCP, UDP, URL, HTTP server, HTTP client, and network utilities.

Individual headers can be included separately:

  • #include <axl/axl-inet-address.h> — IP address + socket address

  • #include <axl/axl-socket.h> — Unified socket

  • #include <axl/axl-socket-client.h> — DNS + connect helper

  • #include <axl/axl-tcp.h> — TCP sockets (low-level)

  • #include <axl/axl-udp.h> — UDP sockets (low-level)

  • #include <axl/axl-url.h> — URL parsing only

  • #include <axl/axl-http-core.h> — HTTP raw-buffer parsers

  • #include <axl/axl-http-server.h> — HTTP server

  • #include <axl/axl-http-client.h> — HTTP client

Parameters:
  • addr – receives the IPv4 address

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_net_ping(AxlIPv4Address *target, size_t timeout_ms, size_t *out_rtt_ms)

Send an ICMP echo request and measure round-trip time.

Parameters:
  • target – target IPv4 address

  • timeout_ms – timeout in milliseconds

  • out_rtt_ms – receives round-trip time in milliseconds

Returns:

AXL_OK on success, AXL_ERR on failure or timeout.

int axl_net_resolve(const char *hostname, AxlIPv4Address *addr)

Resolve a hostname to an IPv4 address via DNS4. Falls back to parsing the hostname as a dotted-decimal IP.

Parameters:
  • hostname – hostname or IP string

  • addr – receives the resolved address

Returns:

AXL_OK on success, AXL_ERR on failure.

bool axl_net_is_available(void)

Check whether any IPv4 network is available.

Returns:

true if at least one NIC has an IP address.

int axl_net_auto_init(size_t nic_index, size_t dhcp_timeout_sec)

Bring up networking: load drivers, run DHCP, wait for IP.

Performs a best-effort network initialization sequence:

  1. Calls axl_net_ensure_drivers() to locate and load NIC drivers from the standard driver search path.

  2. Connects all SNP handles to trigger protocol stack creation.

  3. Selects a NIC (by nic_index, or first available if SIZE_MAX).

  4. Waits up to dhcp_timeout_sec for an IPv4 address via DHCP.

Parameters:
  • nic_index – NIC index (SIZE_MAX = auto-select first)

  • dhcp_timeout_sec – DHCP timeout in seconds (0 = 10s default)

Returns:

AXL_OK on success (IP address acquired), AXL_ERR on failure.

int axl_net_ensure_drivers(void)

Ensure network drivers are loaded and SNP is up.

Locates and loads NetworkCommon.efi plus a known list of NIC drivers (Realtek, Intel/iPXE, Broadcom/iPXE, USB-CDC ECM/NCM, USB-RNDIS, ASIX-USB) from the standard driver search path used by axl_driver_ensure() — drivers/<arch>/<name> on the booted volume, the image’s own directory, drivers/<name> at the volume root, then drivers/<arch>/<name> on every other mounted FAT volume. After loading, ConnectController is run globally to wire the SNP/MNP/ IP4/TCP4 stack.

Drivers absent from the volume are skipped silently — the cost of a missing entry is one file existence check. Drivers whose hardware isn’t present register their binding but never bind to a controller, which is also fine.

Short-circuits if an SNP handle already exists. Idempotent — safe to call multiple times.

Same trust caveat as axl_driver_ensure: this loads .efi files off any mounted FAT volume with full firmware privileges.

Typical use, before touching any networking:

if (axl_net_ensure_drivers() != AXL_NET_DRIVERS_OK) {
    axl_printf("MyTool: networking unavailable\n");
    return 1;
}

Returns:

AXL_NET_DRIVERS_OK on success; AXL_NET_DRIVERS_NOT_FOUND if no NIC drivers were found on any mounted volume; AXL_NET_DRIVERS_NO_LINK if drivers were loaded but no SNP came up (likely no NIC plugged in).

int axl_net_drivers_up(void)

Load drivers, connect SNP, wait for link.

Decoupled from address assignment — does NOT run DHCP and does NOT touch IP4Config2. Composes axl_net_ensure_drivers + per-handle SNP reconnect + a 5 s link-up poll, which is the front half of axl_net_auto_init.

Used directly by axl_net_bring_up’s static-IP path (where the DHCP wait that auto_init would otherwise burn is dead time) and internally by axl_net_auto_init. Consumers that want IP assignment should call axl_net_bring_up or axl_net_auto_init — those layer DHCP / static configuration on top of this primitive.

Returns:

AXL_OK on success (at least one NIC link came up); AXL_ERR if no NIC link was detected within the 5 s wait. Drivers and SNP reconnect are best-effort — failures there are not surfaced.

int axl_net_bring_up(size_t nic_index, const uint8_t *static_ipv4, const uint8_t *netmask, const uint8_t *gateway, size_t timeout_sec, AxlIPv4Address *addr_out)

Bring up networking with a single call — drivers + DHCP or static IP + address read-back.

Composes the typical “what every networked tool does at startup” sequence into one call so consumers don’t reinvent it. Behavior is controlled by static_ipv4:

  • static_ipv4 == NULL → DHCP. Calls axl_net_auto_init (which itself runs axl_net_drivers_up and waits up to timeout_sec for a lease).

  • static_ipv4 != NULL → static. Calls axl_net_drivers_up (load drivers + link wait, no DHCP timeout), then axl_net_set_static_ip with netmask (defaulting to 255.255.255.0 if NULL) and gateway (NULL = no gateway). Sleeps 500 ms after to let IP4Config2 apply the change — the firmware applies the policy + address asynchronously and a subsequent GetData can still report the prior state without the settle.

In either case, on success addr_out is populated via axl_net_get_ip_address (skipped if addr_out is NULL).

Used by HTTP services (axl-webfs and similar), REST tools, and one-shot fetch-style utilities — they all open with the same “load drivers, get an IP, here’s my address” preamble. AxlService is NOT on the call path; this is plain network bring-up, callable from any AXL-consuming code.

Parameters:
  • nic_index – NIC index (SIZE_MAX = auto-select)

  • static_ipv4 – NULL = DHCP; non-NULL = 4-byte static IPv4

  • netmask – 4-byte netmask (NULL = 255.255.255.0); ignored on DHCP path

  • gateway – 4-byte gateway (NULL = none); ignored on DHCP path

  • timeout_sec – DHCP wait (0 = 10 s default; ignored on static path)

  • addr_out – [out] resolved IPv4 (NULL = caller doesn’t care)

Returns:

AXL_OK on success (network up, IP acquired, addr_out populated if non-NULL); AXL_ERR if drivers couldn’t be loaded, no NIC came up, DHCP timed out, or static-IP configuration failed.

int axl_net_set_static_ip(size_t nic_index, const uint8_t ip[4], const uint8_t netmask[4], const uint8_t *gateway)

Configure a static IPv4 address on a NIC.

Sets the IP4Config2 policy to static and assigns the given address, subnet mask, and optional gateway. Pass NULL for gateway to leave it unconfigured.

Parameters:
  • nic_index – NIC index (from axl_net_list_interfaces)

  • ip – IPv4 address

  • netmask – subnet mask (e.g. {255,255,255,0})

  • gateway – gateway address (NULL = none)

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_ipv4_parse(const char *str, uint8_t octets[4])

Parse a dotted-decimal IPv4 address string.

Accepts strings like “192.168.1.1”. Each octet must be 0-255. No leading zeros validation — “01.02.03.04” is accepted.

Parameters:
  • str – IPv4 string (e.g. “192.168.1.1”)

  • octets – receives the four octets

Returns:

AXL_OK on success, AXL_ERR on invalid input.

int axl_ipv4_format(const uint8_t octets[4], char *buf, size_t size)

Format an IPv4 address as a dotted-decimal string.

Writes at most size bytes (including NUL). 16 bytes is always sufficient (“255.255.255.255” + NUL).

Parameters:
  • octets – four octets

  • buf – output buffer

  • size – buffer size (16 bytes sufficient)

Returns:

AXL_OK on success, AXL_ERR if buffer is too small or args are NULL.

int axl_ipv6_format(const uint8_t octets[16], char *buf, size_t size)

Format 16 IPv6 octets to a colon-separated text representation.

Emits the canonical lowercase form with :: collapsing the longest run of all-zero 16-bit groups, per RFC 5952. Single zero groups are not collapsed; ties go to the leftmost run.

Writes at most size bytes (including NUL). 40 bytes is always sufficient (max form: “ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff” + NUL, 39 chars + 1).

Parameters:
  • octets – sixteen octets

  • buf – output buffer

  • size – buffer size (40 bytes sufficient)

Returns:

AXL_OK on success, AXL_ERR if buffer is too small or args are NULL.

bool axl_ipv4_equals(const uint8_t a[4], const uint8_t b[4])

True if a equals b byte-for-byte.

bool axl_ipv4_in_subnet(const uint8_t dest[4], const uint8_t station[4], const uint8_t mask[4])

True if dest is in the same subnet as station given mask. A zero mask is treated as “no policy” and returns false rather than the technically-true “every IP matches” — callers using this for routing decisions don’t want an unconfigured interface to claim every destination.

int axl_net_list_interfaces(AxlNetInterface *out, size_t *count)

List available network interfaces.

Fills out with up to *count interface descriptors. On return, *count is set to the number of entries filled. Call with out=NULL to query the number of interfaces.

Parameters:
  • out – output array (NULL to query count)

  • count – [in/out] capacity / entries filled

Returns:

AXL_OK on success, AXL_ERR on error.

struct AxlNetInterface
#include <axl-net.h>

Network interface descriptor.

Public Members

char name[32]

interface name (“eth0”, “eth1”, …)

uint8_t mac[6]

MAC address.

true if link is up

uint32_t mtu

maximum transmission unit

bool has_ipv4

true if IPv4 is configured

uint8_t ipv4[4]

IPv4 address (valid if has_ipv4)

uint8_t netmask[4]

subnet mask (valid if has_ipv4)

uint8_t gateway[4]

default gateway (valid if has_ipv4)

AxlNetOpts

Canonical NIC / static-IP / port / listen-IP options bag, embedded as a sub-struct in a consumer’s own options type and paired with the axl_config_descs_net group-injection helper in <axl/axl-config.h>.

Defines

AXL_NET_NIC_AUTO

NIC index sentinel meaning “auto-detect first usable NIC”.

axl-net-opts.h:

Canonical option bag and bring-up helper for AXL-consuming tools and services that take the same NIC / local-IP / port options on the command line. Every networked consumer (web servers, REST / IPMI clients, one-shot fetch utilities) was reinventing the same flag-set + sentinel-mapping + bring-up plumbing; AxlNetOpts factors it into a sub-struct the consumer embeds in its own options type, and axl_net_init / axl_net_init_from_opts are the matching one-call DHCP bring-up.

Pair with the axl_config_descs_net group-injection helper in <axl/axl-config.h> to also pull the matching CLI / config descriptors into a consumer’s own table without copy-paste.

Out of scope by design: setting a static IPv4 onto the NIC (the ifconfig-equivalent layer). Call axl_net_set_static_ip directly if a tool genuinely needs to mutate IP4Config2 policy — that’s stateful system config, not a per-invocation connection option. Source / listen IP selection (local_ip below) is the connection-side knob and stays here.

IPv4 only for v1. An IPv6 / family-tagged variant is a clean future addition.

Maps to SIZE_MAX when passed through to the bring-up call. axl_config_descs_net uses AXL_NET_NIC_AUTO_STR (the stringified value of this sentinel) as the descriptor default, so axl_config_new auto-applies the sentinel into the embedded nic_index field with no extra wiring from the consumer.

AXL_NET_NIC_AUTO_STR

Decimal-string form of AXL_NET_NIC_AUTO, suitable as an AxlConfigDesc.default_value for an AXL_CFG_UINT field.

The string is the unsigned decimal representation of UINT64_MAX — pre-stringified rather than computed at runtime because AxlConfigDesc.default_value is a const char * that must be a compile-time constant. Surfaced on the public API so consumers synthesizing their own descriptors keep the “auto” semantics consistent with axl_config_descs_net.

AXL_NET_OPT_CLIENT

Client-side preset: NIC selector + outbound source-IP bind.

AXL_NET_OPT_SERVER

Server-side preset: NIC selector + port + listen-IP bind.

Enums

enum AxlNetOptKind

Bitmask of which AxlNetOpts fields a consumer wants descriptors for. Pass to axl_config_descs_net.

AXL_NET_OPT_SOURCE_IP and AXL_NET_OPT_LISTEN_IP both target the same underlying local_ip field — they differ only in CLI vocabulary (--source-ip for clients, --listen-ip for servers). A consumer normally sets at most one of the two, matching its role; the CLIENT and SERVER presets below pick the conventional name for each.

Values:

enumerator AXL_NET_OPT_NIC

&#8212;nic → nic_index

enumerator AXL_NET_OPT_SOURCE_IP

&#8212;source-ip → local_ip (client)

enumerator AXL_NET_OPT_PORT

&#8212;port → port

enumerator AXL_NET_OPT_LISTEN_IP

&#8212;listen-ip → local_ip (server)

Functions

int axl_net_init(uint64_t nic_index, size_t timeout_sec)

Bring up networking via DHCP on a chosen NIC.

Maps nic_index == AXL_NET_NIC_AUTO to SIZE_MAX and delegates to axl_net_bring_up with a NULL static address (DHCP path). Address read-back is implicit; this helper does not surface the resolved IP — call axl_net_get_ip_address separately if you need it.

Static-IP configuration is intentionally out of scope: it mutates IP4Config2 policy, which is the firmware’s ifconfig layer, not a per-tool connection option. Tools that need to install a static IP call axl_net_set_static_ip directly, or defer to UEFI Shell ifconfig.

Parameters:
  • nic_index – AXL_NET_NIC_AUTO or 0-based NIC index

  • timeout_sec – DHCP wait (0 = 10 s default)

Returns:

AXL_OK on success, AXL_ERR on driver-load / link / DHCP failure.

int axl_net_init_from_opts(const AxlNetOpts *opts, size_t timeout_sec)

Bring up networking from an AxlNetOpts bag.

Thin thunk over axl_net_init reading opts->nic_index. The local_ip / port fields are consumer-owned and unused at bring-up — they parameterize subsequent socket operations.

Parameters:
  • opts – options bag

  • timeout_sec – DHCP wait (0 = 10 s default)

Returns:

AXL_OK on success, AXL_ERR on bring-up failure (or if opts is NULL).

struct AxlNetOpts
#include <axl-net-opts.h>

Canonical network options bag.

Embed as a sub-struct of the consumer’s own options type, then use axl_config_descs_net(out, cap, kinds, offsetof(MyOpts, net)) to emit matching CLI / config descriptors without copy-paste.

Field semantics:

  • nic_indexAXL_NET_NIC_AUTO (the default) means auto-detect the first usable NIC; any other value is a 0-based index into the SNP handle list as returned by axl_net_list_interfaces. Used at bring-up time to pick which NIC to run DHCP against. Post-bring-up, prefer local_ip for routing — you can name the interface you want by its station IP without knowing its handle index.

  • local_ip — IPv4 to bind the local end of the socket to. Empty string means “let the kernel pick” (0.0.0.0). For clients this is the outbound source IP (curl --interface); for servers this is the listen / accept address. One field because both roles bind(2) the same way — the role is implied by whether the consumer subsequently calls connect() or listen(). CLI vocabulary is selected by the AXL_NET_OPT_SOURCE_IP / _LISTEN_IP enum bits below.

  • portuint16_t so it round-trips through the descriptor’s AXL_CFG_UINT parser without truncation. 0 means “consumer-defined default” — AXL ships no canonical port, since every consumer’s domain default differs.

Sub-struct (not flat fields) so future option additions don’t collide with the consumer’s own field names.

Public Members

uint64_t nic_index

AXL_NET_NIC_AUTO = auto-detect first usable NIC.

const char *local_ip

IPv4 to bind() the local socket end; “” = any.

uint16_t port

0 = consumer-defined default

AxlInetAddress / AxlSocketAddress

Typedefs

typedef struct AxlInetAddress AxlInetAddress

axl-inet-address.h:

IPv4 address and socket address types. AxlInetAddress wraps an IP address with parsing, formatting, and comparison. AxlSocketAddress pairs an address with a port number for use with AxlSocket.

AxlInetAddress *addr = axl_inet_address_new_from_string("192.168.1.1");
AxlSocketAddress *sa = axl_socket_address_new(addr, 8080);
// sa now owns addr — do not free addr separately
axl_socket_address_free(sa);
typedef struct AxlSocketAddress AxlSocketAddress

Functions

AxlInetAddress *axl_inet_address_new_from_string(const char *str)

Create an address from a dotted-decimal string.

Parses strings like “192.168.1.1”. Each octet must be 0-255.

Parameters:
  • str – dotted-decimal IPv4 string (e.g. “10.0.0.1”)

Returns:

new address, or NULL on invalid input.

AxlInetAddress *axl_inet_address_new_from_bytes(const uint8_t *bytes)

Create an address from raw bytes.

Parameters:
  • bytes – 4-byte IPv4 address in network order

Returns:

new address, or NULL on allocation failure.

AxlInetAddress *axl_inet_address_new_any(void)

Create the any-address (0.0.0.0).

Returns:

new address, or NULL on allocation failure.

AxlInetAddress *axl_inet_address_new_loopback(void)

Create the loopback address (127.0.0.1).

Returns:

new address, or NULL on allocation failure.

void axl_inet_address_free(AxlInetAddress *addr)

Free an address. NULL-safe.

Parameters:
  • addr – address to free

const char *axl_inet_address_to_string(AxlInetAddress *addr)

Get the dotted-decimal string representation.

The string is lazily cached — the first call formats it, subsequent calls return the cached buffer. The pointer is valid for the lifetime of addr.

Parameters:
  • addr – address

Returns:

internal string (e.g. “192.168.1.1”), or NULL on error.

const uint8_t *axl_inet_address_to_bytes(const AxlInetAddress *addr)

Get the raw 4-byte address.

Parameters:
  • addr – address

Returns:

pointer to internal 4-byte array, valid for lifetime of addr.

bool axl_inet_address_equal(const AxlInetAddress *a, const AxlInetAddress *b)

Check if two addresses are equal.

Parameters:
  • a – first address

  • b – second address

Returns:

true if both addresses have the same octets.

bool axl_inet_address_is_any(const AxlInetAddress *addr)

Check if this is the any-address (0.0.0.0).

Parameters:
  • addr – address

Returns:

true if all octets are zero.

bool axl_inet_address_is_loopback(const AxlInetAddress *addr)

Check if this is the loopback address (127.0.0.1).

Parameters:
  • addr – address

Returns:

true if addr is 127.0.0.1.

AxlSocketAddress *axl_socket_address_new(AxlInetAddress *addr, uint16_t port)

Create a socket address from an IP address and port.

Takes ownership of addr — the caller must not free it after this call. Free the socket address with axl_socket_address_free(), which also frees the contained AxlInetAddress.

Parameters:
  • addr – IP address (ownership transferred)

  • port – port number

Returns:

new socket address, or NULL on failure (addr is freed on failure).

AxlSocketAddress *axl_socket_address_new_from_string(const char *str, uint16_t default_port)

Create a socket address by parsing “host:port” or “host”.

If no port is present in the string, default_port is used. The host part must be a dotted-decimal IPv4 address.

Parameters:
  • str – “192.168.1.1:8080” or “192.168.1.1”

  • default_port – port used when string has no “:port”

Returns:

new socket address, or NULL on parse failure.

void axl_socket_address_free(AxlSocketAddress *sa)

Free a socket address and its contained AxlInetAddress. NULL-safe.

Parameters:
  • sa – socket address to free

AxlInetAddress *axl_socket_address_get_address(const AxlSocketAddress *sa)

Get the IP address component.

The returned pointer is borrowed — do not free it. It remains valid for the lifetime of sa.

Parameters:
  • sa – socket address

Returns:

borrowed pointer, valid for lifetime of sa.

uint16_t axl_socket_address_get_port(const AxlSocketAddress *sa)

Get the port number.

Parameters:
  • sa – socket address

Returns:

port number.

void axl_socket_address_to_ipv4(const AxlSocketAddress *sa, AxlIPv4Address *out_addr, uint16_t *out_port)

Extract as legacy AxlIPv4Address + port.

Convenience for interop with existing UDP APIs that take AxlIPv4Address and port separately.

Parameters:
  • sa – socket address

  • out_addr – [out] receives IPv4 address

  • out_port – [out] receives port number

struct AxlIPv4Address
#include <axl-inet-address.h>

IPv4 address (4 bytes). Legacy — prefer AxlInetAddress for new code.

Public Members

uint8_t addr[4]

AxlSocket

Typedefs

typedef struct AxlLoop AxlLoop

axl-socket.h:

Unified socket abstraction. AxlSocket wraps AxlTcp (stream) and AxlUdp (datagram) behind a single API. Blocking and async (event-loop integrated) operations are both supported.

AxlSocket *sock = axl_socket_new(AXL_SOCKET_STREAM);
AxlSocketAddress *remote = axl_socket_address_new(
    axl_inet_address_new_from_string("192.168.1.1"), 8080);
axl_socket_connect(sock, remote);
axl_socket_send(sock, "hello", 5, 0);
axl_socket_address_free(remote);
axl_socket_free(sock);
typedef struct AxlTcp AxlTcp
typedef struct AxlSocket AxlSocket
typedef struct AxlSocketAddress AxlSocketAddress
typedef bool (*AxlSocketCallback)(AxlSocket *sock, AxlStatus status, void *data)

Callback for async socket operations.

sock is the socket for the completed operation (new socket for accept, or the original socket for send/recv/connect). status is an AxlStatus value: AXL_OK on success, AXL_ERR on UEFI error, AXL_CANCELLED if the cancellable on the *_async call was signalled before completion. On error, sock may be NULL (accept) or half-initialized (connect). Always check status before using sock.

Return value controls re-arming for ops that support it:

  • axl_socket_receive_async: true = re-arm with same buffer, false = stop

  • axl_socket_accept_async: true = keep listening (default), false = stop accepting

  • axl_socket_connect_async, axl_socket_send_async: ignored (one-shot ops). Convention: return true;.

Enums

enum AxlSocketType

Socket type: stream (TCP) or datagram (UDP).

Values:

enumerator AXL_SOCKET_STREAM

TCP stream socket

enumerator AXL_SOCKET_DATAGRAM

UDP datagram socket

Functions

AxlSocket *axl_socket_new(AxlSocketType type)

Create a new socket.

For AXL_SOCKET_STREAM, the socket is unconnected — call axl_socket_connect() or axl_socket_listen() next. For AXL_SOCKET_DATAGRAM, a UDP socket is opened immediately on an ephemeral port.

Parameters:
  • type – AXL_SOCKET_STREAM or AXL_SOCKET_DATAGRAM

Returns:

new socket, or NULL on failure.

AxlSocket *axl_socket_new_from_tcp(AxlTcp *tcp)

Wrap an existing AxlTcp as a stream socket.

Takes ownership of tcp — the caller must not close it. Useful for wrapping accepted connections from the raw TCP API.

Parameters:
  • tcp – connected TCP socket (ownership transferred)

Returns:

new socket, or NULL on failure (tcp is closed on failure).

void axl_socket_free(AxlSocket *sock)

Close and free a socket. NULL-safe.

Parameters:
  • sock – socket to free

int axl_socket_bind(AxlSocket *sock, uint16_t port)

Bind a datagram socket to a specific local port.

Closes the current ephemeral UDP socket and reopens on port. Pass 0 to rebind to a new ephemeral port.

Parameters:
  • sock – datagram socket

  • port – local port (0 = ephemeral)

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_socket_connect(AxlSocket *sock, AxlSocketAddress *addr)

Connect a stream socket to a remote address. Blocking.

Parameters:
  • sock – stream socket (unconnected)

  • addr – remote address (borrowed, not consumed)

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_socket_listen(AxlSocket *sock, uint16_t port)

Start listening on a stream socket.

Parameters:
  • sock – stream socket

  • port – port to listen on

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_socket_accept(AxlSocket *sock, AxlSocket **out_client, size_t timeout_ms)

Accept a connection on a listening stream socket. Blocking.

Unlike axl_socket_send/_receive where timeout_ms=0 defaults to 10s, a sync accept’s semantic role is “wait for an incoming

client,” so timeout_ms=0 means wait forever (no timeout source). Pass a positive value to bound the wait; Ctrl-C ends the wait either way via the loop’s break observation.

Parameters:
  • sock – listening stream socket

  • out_client – [out] receives accepted client socket

  • timeout_ms – timeout in ms (0 = wait forever)

Returns:

AXL_OK on success, AXL_ERR on failure. timeout, or cancel.

int axl_socket_send(AxlSocket *sock, const void *data, size_t size, size_t timeout_ms)

Send data on a connected stream socket. Blocking.

Parameters:
  • sock – connected stream socket

  • data – buffer to send

  • size – number of bytes

  • timeout_ms – timeout in ms (0 = default 10s)

Returns:

AXL_OK on success, AXL_ERR on failure or timeout.

int axl_socket_send_to(AxlSocket *sock, const void *data, size_t size, AxlSocketAddress *dest)

Send a datagram to a specific address. Datagram sockets only.

Parameters:
  • sock – datagram socket

  • data – buffer to send

  • size – number of bytes

  • dest – destination address (borrowed)

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_socket_receive(AxlSocket *sock, void *buf, size_t *size, size_t timeout_ms)

Receive data from a connected stream socket. Blocking.

size is in/out: on entry the buffer capacity, on return the number of bytes received.

Parameters:
  • sock – connected stream socket

  • buf – receive buffer

  • size – [in/out] buffer capacity / bytes received

  • timeout_ms – timeout in ms (0 = default 10s)

Returns:

AXL_OK on success, AXL_ERR on failure or timeout.

AxlSocketAddress *axl_socket_get_local_address(AxlSocket *sock)

Get the local address of a connected or listening socket.

Parameters:
  • sock – socket

Returns:

new AxlSocketAddress (caller frees), or NULL.

AxlSocketAddress *axl_socket_get_remote_address(AxlSocket *sock)

Get the remote address of a connected stream socket.

Parameters:
  • sock – connected stream socket

Returns:

new AxlSocketAddress (caller frees), or NULL.

AxlSocketType axl_socket_get_type(AxlSocket *sock)

Get the socket type.

Parameters:
  • sock – socket

Returns:

AXL_SOCKET_STREAM or AXL_SOCKET_DATAGRAM.

int axl_socket_connect_async(AxlSocket *sock, AxlSocketAddress *addr, AxlLoop *loop, AxlSocketCallback cb, void *data)

Async connect — returns immediately, callback fires from loop.

Parameters:
  • sock – stream socket (unconnected)

  • addr – remote address (borrowed)

  • loop – event loop

  • cb – callback on completion (return value ignored)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

int axl_socket_accept_async(AxlSocket *sock, AxlLoop *loop, AxlSocketCallback cb, void *data)

Async accept — callback fires for each accepted client.

Re-arming is controlled by the callback’s return value: true keeps the listener armed, false stops accepting.

Parameters:
  • sock – listening stream socket

  • loop – event loop

  • cb – callback per accepted client (return bool controls re-arm)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

int axl_socket_send_async(AxlSocket *sock, const void *buf, size_t size, AxlLoop *loop, AxlSocketCallback cb, void *data)

Async send — callback fires when send completes.

The buffer must stay valid until the callback fires.

Parameters:
  • sock – connected stream socket

  • buf – send buffer (must remain valid)

  • size – bytes to send

  • loop – event loop

  • cb – callback on completion (return value ignored)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

int axl_socket_receive_async(AxlSocket *sock, void *buf, size_t size, AxlLoop *loop, AxlSocketCallback cb, void *data)

Async receive — callback fires when data arrives.

Works for both stream and datagram sockets. For stream sockets, data is received directly into buf. For datagram sockets, the next datagram is copied into buf (truncated if larger than size).

The buffer must stay valid across re-arms. Re-arming is controlled by the callback’s return value: true re-arms with the same buffer, false stops receiving.

Parameters:
  • sock – stream or datagram socket

  • buf – receive buffer (must remain valid across re-arms)

  • size – buffer size

  • loop – event loop

  • cb – callback when data received (return bool controls re-arm)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

size_t axl_socket_receive_get_size(AxlSocket *sock)

Get bytes received by the last async receive.

Call from inside the receive callback to get the actual byte count. Works for both stream and datagram sockets.

Parameters:
  • sock – socket from receive callback

Returns:

bytes received, or 0 if no receive has completed.

AxlSocketClient

Typedefs

typedef struct AxlSocket AxlSocket

axl-socket-client.h:

High-level socket client. Performs DNS resolution and TCP connection in a single call. Reusable — one client can connect to multiple hosts.

AxlSocketClient *c = axl_socket_client_new();
AxlSocket *sock;
if (axl_socket_client_connect_to_host(c, "192.168.1.1", 80, &sock) == 0) {
    axl_socket_send(sock, "GET / HTTP/1.0\r\n\r\n", 18, 0);
    axl_socket_free(sock);
}
axl_socket_client_free(c);
typedef struct AxlSocketAddress AxlSocketAddress
typedef struct AxlSocketClient AxlSocketClient

Functions

AxlSocketClient *axl_socket_client_new(void)

Create a new socket client.

Returns:

new client, or NULL on allocation failure.

void axl_socket_client_free(AxlSocketClient *client)

Free a socket client. NULL-safe.

Parameters:
  • client – client to free

void axl_socket_client_set_timeout(AxlSocketClient *client, size_t timeout_ms)

Set the connect timeout.

Reserved for future use. Currently the timeout is determined by the underlying TCP stack (typically 10 seconds).

Parameters:
  • client – client

  • timeout_ms – timeout in ms (reserved, not yet applied)

int axl_socket_client_connect(AxlSocketClient *client, AxlSocketAddress *addr, AxlSocket **out_sock)

Connect to a resolved address. Blocking.

Creates a new AxlSocket, connects to addr, and returns it.

Parameters:
  • client – client

  • addr – resolved address (borrowed)

  • out_sock – [out] receives connected socket

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_socket_client_connect_to_host(AxlSocketClient *client, const char *host, uint16_t port, AxlSocket **out_sock)

Resolve a hostname and connect. Blocking.

Performs DNS resolution (or parses a dotted-decimal IP), then creates and connects a stream socket. The resolved address is available via axl_socket_get_remote_address() on the returned socket.

Parameters:
  • client – client

  • host – hostname or dotted-decimal IP

  • port – port number

  • out_sock – [out] receives connected socket

Returns:

AXL_OK on success, AXL_ERR on DNS or connect failure.

AxlTcp

Typedefs

typedef struct AxlLoop AxlLoop

axl-tcp.h:

TCP socket abstraction. Blocking and async (event-driven) APIs. Async functions integrate with AxlLoop for non-blocking I/O.

typedef struct AxlTcp AxlTcp
typedef struct AxlCancellable AxlCancellable
typedef bool (*AxlTcpCallback)(AxlTcp *sock, AxlStatus status, void *data)

AxlTcpCallback:

Callback for async TCP operations.

sock is the resulting socket. On accept/connect success this is a freshly-connected socket; on recv/send success it is the same socket the op was started on. On connect/accept error or cancel sock is NULL (library closes the partial/listener state internally). On recv/send error or cancel sock is the still-valid connected socket — caller retains ownership and can reuse or close it.

status is an AxlStatus value:

  • AXL_OK on success

  • AXL_ERR on UEFI error

  • AXL_CANCELLED if the cancellable passed to the *_async call was signalled before completion

Return value controls re-arming for ops that support it:

  • axl_tcp_recv_async: true = re-arm with same buffer, false = stop

  • axl_tcp_accept_async: true = keep listening (default), false = stop accepting

  • axl_tcp_connect_async, axl_tcp_send_async: ignored (ops are one-shot by nature — connect fires once, each send owns its buffer). Convention: return true; in these callbacks.

Functions

int axl_tcp_connect(const char *host, uint16_t port, AxlTcp **out_sock)

Connect to a remote host via TCP4.

Parameters:
  • host – IPv4 address string or hostname (DNS resolved)

  • port – remote port number

  • out_sock – receives the connected socket handle

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_tcp_connect_via(const char *host, uint16_t port, const AxlIPv4Address *source_ip, AxlTcp **out_sock)

Connect to a remote host via TCP4, with explicit interface selection.

Like axl_tcp_connect but adds optional pinning to a specific local interface by station IP. When source_ip is non-NULL and non-zero, the connect uses only the network interface whose IP4 config matches that station address — useful when the host has multiple NICs and the destination is reachable via a specific one (e.g. an in-band BMC USB-NIC at 169.254.1.0/24).

Pass source_ip = NULL or {0,0,0,0} for the auto-pick path:

  1. skip interfaces whose station IP is 0.0.0.0

  2. prefer an interface whose subnet contains the destination

  3. fall back to the first valid (non-zero) interface

Returns:

AXL_OK on success, AXL_ERR on failure (including “no

interface matches @p source_ip” when forced).

int axl_tcp_listen(uint16_t port, AxlTcp **out_listener)

Create a TCP4 listener on the given port.

Parameters:
  • port – local port to listen on

  • out_listener – receives the listener handle

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_tcp_listen_via(uint16_t port, const AxlIPv4Address *source_ip, AxlTcp **out_listener)

Create a TCP4 listener pinned to a specific local interface.

Like axl_tcp_listen but takes an optional source IP that selects which network interface to bind on a multi-NIC host. When source_ip is NULL or all-zeros, falls through to the auto-pick path used by axl_tcp_listen.

Returns:

AXL_OK on success, AXL_ERR on failure (including “no

interface has station IP @p source_ip” when forced).

int axl_tcp_accept(AxlTcp *listener, AxlTcp **out_client, size_t timeout_ms)

Accept one pending connection on a listener.

Unlike axl_tcp_send/_recv where timeout_ms=0 defaults to 10s, a sync accept’s semantic role is “wait for an incoming client,” so timeout_ms=0 means wait forever (no timeout source). Pass a positive value to bound the wait; Ctrl-C ends the wait either way via the loop’s break observation.

Parameters:
  • listener – listener from axl_tcp_listen

  • out_client – receives the accepted client socket

  • timeout_ms – timeout in ms (0 = wait forever)

Returns:

AXL_OK on success, AXL_ERR on failure. timeout, or cancel.

int axl_tcp_send(AxlTcp *sock, const void *data, size_t size, size_t timeout_ms)

Send data over a connected TCP socket.

Parameters:
  • sock – connected socket

  • data – buffer to send

  • size – number of bytes to send

  • timeout_ms – timeout in ms (0 = default 10s)

Returns:

AXL_OK on success, AXL_ERR on failure or timeout.

int axl_tcp_recv(AxlTcp *sock, void *buf, size_t *size, size_t timeout_ms)

Receive data from a connected TCP socket.

Parameters:
  • sock – connected socket

  • buf – receive buffer

  • size – on entry, buffer size; on return, bytes received

  • timeout_ms – timeout in ms (0 = default 10s)

Returns:

AXL_OK on success, AXL_ERR on failure or timeout.

int axl_tcp_poll(AxlTcp *sock)

Poll a TCP socket to drive its internal state machine.

Parameters:
  • sock – socket to poll

Returns:

AXL_OK on success, AXL_ERR on failure.

void axl_tcp_close(AxlTcp *sock)

Close and free a TCP socket (listener or connected).

For a connected socket, initiates a graceful close (FIN exchange). Returns immediately when called from inside a running event loop: the firmware-level teardown (Configure(NULL) + service-binding DestroyChild + freeing the AxlTcp) is deferred until the firmware signals close-complete (~TIME_WAIT later for an active close), so the caller never blocks on the close path. When called outside a running loop (e.g. from a sync CLI tool, or during shutdown after axl_loop_run returned) it falls back to a bounded synchronous wait (~3 s) and finalizes inline before returning.

Ordering: close before freeing the loop. Always call axl_tcp_close BEFORE axl_loop_free on any loop the socket was registered with. Close has to drop loop sources for the socket, and the async-finalize path posts the close-complete event back to the loop. Freeing the loop first leaves both paths dereferencing freed memory.

**sock outlives this call.** On the async path the AxlTcp struct lives until the firmware signals close-complete. Callers must not touch sock after axl_tcp_close returns; treat the pointer as freed.

Parameters:
  • sock – socket to close (NULL-safe)

int axl_tcp_get_local_addr(AxlTcp *sock, char *addr, size_t size, uint16_t *out_port)

Query the local address of a connected or listening socket.

Parameters:
  • sock – socket

  • addr – buffer for dotted-decimal address (min 16 bytes)

  • size – size of addr buffer

  • out_port – receives local port number

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_tcp_get_remote_addr(AxlTcp *sock, char *addr, size_t size, uint16_t *out_port)

Query the remote address of a connected socket.

Parameters:
  • sock – connected socket

  • addr – buffer for dotted-decimal address (min 16 bytes)

  • size – size of addr buffer

  • out_port – receives remote port number

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_tcp_connect_async(const char *host, uint16_t port, AxlLoop *loop, AxlCancellable *cancel, AxlTcpCallback cb, void *data)

Async connect — initiates TCP connection, returns immediately.

The callback fires from the event loop when the connection completes, fails, or is cancelled via cancel. On success the newly connected socket is passed; on error or cancellation the sock pointer is NULL and the partial socket is closed internally. Status is AXL_OK on success, AXL_ERR on UEFI error, or AXL_CANCELLED if cancel was signalled.

Cancel is terminal for connect. The partial socket is closed by the library — caller has nothing to free on a cancel callback. Contrast with accept/recv/send below, which leave their socket intact on cancel.

Parameters:
  • host – IPv4 address or hostname

  • port – remote port

  • loop – event loop to register with

  • cancel – optional cancel token (NULL = uncancellable)

  • cb – callback on completion (return value ignored)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

int axl_tcp_connect_async_via(const char *host, uint16_t port, const AxlIPv4Address *source_ip, AxlLoop *loop, AxlCancellable *cancel, AxlTcpCallback cb, void *data)

Async sibling of axl_tcp_connect_via.

source_ip semantics match the synchronous variant.

int axl_tcp_accept_async(AxlTcp *listener, AxlLoop *loop, AxlCancellable *cancel, AxlTcpCallback cb, void *data)

Async accept — waits for incoming connection via the event loop.

The callback fires each time a client connects. Re-arming is controlled by the callback’s return value: true keeps the listener armed for the next connection, false stops accepting. Call axl_tcp_close on the listener to tear it down entirely.

Cancel leaves the listener valid. On cancel, the callback fires with (NULL, AXL_CANCELLED, data) and the listener is no longer armed for accepts, but axl_tcp_accept_async can be called again to resume listening. Close the listener with axl_tcp_close when done.

Parameters:
  • listener – listener from axl_tcp_listen

  • loop – event loop

  • cancel – optional cancel token (NULL = uncancellable)

  • cb – callback per accepted client

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

int axl_tcp_recv_async(AxlTcp *sock, void *buf, size_t size, AxlLoop *loop, AxlCancellable *cancel, AxlTcpCallback cb, void *data)

Async receive — waits for data via the event loop.

The callback fires when data arrives in the caller’s buffer. Re-arming is controlled by the callback’s return value: true re-arms the recv with the same buffer (typical for streaming servers); false stops receiving (caller may start a fresh recv with a different buffer, or close the socket).

Cancel leaves the socket connected. On cancel the callback fires with (sock, AXL_CANCELLED, data) — the sock is still a valid connected TCP socket, and the caller can start another recv or send, or close it. Contrast with connect, which destroys the sock on cancel.

Parameters:
  • sock – connected socket

  • buf – receive buffer (must stay valid across re-arms)

  • size – buffer size

  • loop – event loop

  • cancel – optional cancel token (NULL = uncancellable)

  • cb – callback when data received (return bool controls re-arm)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure.

size_t axl_tcp_recv_get_size(AxlTcp *sock)

Get the number of bytes received by the last axl_tcp_recv_async.

Call from inside the recv callback to get the actual byte count.

Parameters:
  • sock – socket from recv callback

Returns:

bytes received, or 0 if no recv has completed.

int axl_tcp_send_async(AxlTcp *sock, const void *buf, size_t size, AxlLoop *loop, AxlCancellable *cancel, AxlTcpCallback cb, void *data)

Async send — initiates send, callback on completion.

The buffer must stay valid until the callback fires.

No preemption. Calling this while a previous send is still in flight returns -1. To interrupt an in-flight send, pass an AxlCancellable to the original call and signal it; the cancel callback fires cleanly with AXL_CANCELLED. Alternatively, axl_tcp_close(sock) tears down every pending op at once.

Cancel leaves the socket connected. On cancel the callback fires with (sock, AXL_CANCELLED, data) — the sock is still a valid connected TCP socket. Partial bytes may have been transmitted before cancel took effect; the caller should treat the send outcome as unknown and resync at the application level if needed.

Parameters:
  • sock – connected socket

  • buf – send buffer (must stay valid until callback)

  • size – bytes to send

  • loop – event loop

  • cancel – optional cancel token (NULL = uncancellable)

  • cb – callback when send completes (return value ignored)

  • data – opaque context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure or if a send is already in flight.

AxlUdp

Typedefs

typedef struct AxlCancellable AxlCancellable

axl-udp.h:

UDP datagram sockets. Fire-and-forget send, request-response send-receive, and async loop-integrated receive.

AxlUdp *sock;
if (axl_udp_open(&sock, 0) == 0) {
    AxlIPv4Address dest;
    axl_ipv4_parse("192.168.1.100", &dest);
    axl_udp_send(sock, &dest, 514, msg, msg_len);
    axl_udp_close(sock);
}
typedef struct AxlUdp AxlUdp
typedef bool (*AxlUdpSendCallback)(AxlUdp *sock, AxlStatus status, void *data)

AxlUdpSendCallback:

Callback for axl_udp_send_async. Mirrors AxlTcpCallback’s shape — send is one-shot, so the return value is ignored (convention: return true).

status is an AxlStatus value:

  • AXL_OK on a successful Transmit

  • AXL_ERR on a UEFI-reported send error

  • AXL_CANCELLED if the cancellable was signalled before completion

typedef bool (*AxlUdpCallback)(AxlUdp *sock, AxlStatus status, const void *data, size_t len, const AxlIPv4Address *from, uint16_t from_port, void *user_data)

AxlUdpCallback:

Callback for async UDP receive. Mirrors AxlTcpCallback shape: the consumer gets per-event status, can stop in-place by returning false, and the op honors an optional AxlCancellable.

status is an AxlStatus value:

  • AXL_OK on a delivered datagram (data / len / from describe the payload)

  • AXL_ERR on a UEFI-reported recv error (token Status non-zero or RxData NULL); data is NULL, len is 0

  • AXL_CANCELLED if the cancellable passed to axl_udp_recv_async was signalled before the next datagram arrived; data NULL, len 0

Return value controls re-arming:

  • true (and status == AXL_OK): re-arm Receive for the next datagram. Sock must remain valid until the callback returns.

  • false: stop receiving. The library cancels the underlying UEFI Receive op and drops loop sources. The socket is NOT closed — caller may start a fresh recv or call axl_udp_close.

  • AXL_ERR / AXL_CANCELLED status: never re-arm regardless of return value (the UEFI op is already torn down on those paths).

Functions

int axl_udp_open(AxlUdp **sock, uint16_t local_port)

Open a UDP socket bound to a local port.

Uses the NIC’s DHCP-assigned or static IP address. Pass 0 for local_port to use an ephemeral port.

Parameters:
  • sock – [out] receives socket handle

  • local_port – local port (0 = ephemeral)

Returns:

AXL_OK on success, AXL_ERR if UDP4 stack is not available.

int axl_udp_open_via(AxlUdp **sock, uint16_t local_port, const AxlIPv4Address *source_ip)

Open a UDP socket with explicit source-NIC selection.

Like axl_udp_open but adds optional pinning to a specific local interface by station IP. When source_ip is non-NULL and non-zero, walks the UDP4 service-binding handles and picks the one whose IP4Config2 station address matches — useful when the host has multiple NICs (e.g. an in-band BMC USB-NIC at 169.254.1.0/24 alongside a regular ethernet) and the consumer needs to control which interface emits the datagram.

Pass source_ip = NULL or {0,0,0,0} for the auto-pick path (same behavior as axl_udp_open). Mirrors the axl_tcp_listen_via shape.

Parameters:
  • sock – [out] receives socket handle

  • local_port – local port (0 = ephemeral)

  • source_ip – NULL or zeros = auto-pick

Returns:

AXL_OK on success, AXL_ERR on failure (including “no

interface has station IP @p source_ip” when forced).

void axl_udp_close(AxlUdp *sock)

Close a UDP socket and release resources. NULL-safe.

Parameters:
  • sock – socket to close

int axl_udp_get_local_addr(AxlUdp *sock, char *addr, size_t size, uint16_t *out_port)

Query the local address of an open UDP socket.

Useful in two cases:

  • After axl_udp_open with local_port = 0 (ephemeral), to read back the kernel-assigned port so it can be advertised to a peer.

  • For diagnostics — confirming the bound interface IP after DHCP / static configuration.

addr is filled with the dotted-decimal station address (min 16 bytes); out_port receives the bound local port.

Parameters:
  • sock – open socket

  • addr – buffer for dotted-decimal address (min 16 bytes)

  • size – size of addr buffer

  • out_port – receives bound local port number

Returns:

AXL_OK on success, AXL_ERR if the socket is unconfigured or the underlying GetModeData call fails.

int axl_udp_send_async(AxlUdp *sock, const AxlIPv4Address *dest, uint16_t port, const void *buf, size_t len, AxlLoop *loop, AxlCancellable *cancel, AxlUdpSendCallback cb, void *data)

Async send — initiates Transmit, callback on completion.

Mirrors axl_tcp_send_async. The buffer at buf must stay valid until the callback fires (the UEFI Transmit op references it directly — no intermediate copy).

No preemption. Calling this while a previous send on sock is still in flight returns AXL_ERR. To interrupt an in-flight send, pass an AxlCancellable to the original call and signal it; the cancel callback fires cleanly with AXL_CANCELLED.

Parameters:
  • sock – socket

  • dest – destination IPv4 address

  • port – destination port

  • buf – send buffer (must stay valid until cb)

  • len – bytes to send

  • loop – event loop

  • cancel – optional cancel token (NULL = uncancellable)

  • cb – callback when send completes (return ignored)

  • data – caller context

Returns:

AXL_OK if initiated, AXL_ERR on immediate failure or if a send is already in flight on this socket.

int axl_udp_connect(AxlUdp *sock, const AxlIPv4Address *peer, uint16_t port)

Pin the socket to a single peer (Linux-style connect() on UDP).

Re-configures the underlying UDP4 instance with RemoteAddress + RemotePort set to peer / port. After this:

  • The kernel filters incoming datagrams to ones from the peer (other senders’ packets are dropped).

  • Subsequent axl_udp_send / axl_udp_send_async calls accept NULL dest — the configured peer is used. Passing a non-NULL dest still overrides the peer for that packet.

Idempotent: calling again with a different peer rebinds. Use axl_udp_disconnect to clear the lock without picking a new peer.

Parameters:
  • sock – socket

  • peer – peer address

  • port – peer port

Returns:

AXL_OK on success, AXL_ERR on Configure failure.

int axl_udp_disconnect(AxlUdp *sock)

Clear a peer lock previously installed by axl_udp_connect.

After this, the socket accepts datagrams from any sender again and axl_udp_send requires an explicit dest.

Parameters:
  • sock – socket

Returns:

AXL_OK on success, AXL_ERR on Configure failure.

int axl_udp_join_multicast(AxlUdp *sock, const AxlIPv4Address *group)

Join an IPv4 multicast group on this socket.

After joining, the socket receives datagrams sent to group (in addition to its station unicast address). Useful for service-discovery protocols (mDNS at 224.0.0.251, SSDP at 239.255.255.250) and other multicast traffic.

Idempotent only if the underlying UEFI driver is — UEFI 2.x doesn’t define behavior for joining the same group twice.

Parameters:
  • sock – socket

  • group – multicast group address (224.0.0.0/4)

Returns:

AXL_OK on success, AXL_ERR if group is not a valid 224.0.0.0/4 multicast address or the UEFI Groups() call fails.

int axl_udp_leave_multicast(AxlUdp *sock, const AxlIPv4Address *group)

Leave a previously-joined multicast group.

Pass group = NULL to leave ALL groups this socket has joined (matches UEFI 2.x UDP4.Groups() semantics for JoinFlag=FALSE with MulticastAddress = NULL).

Parameters:
  • sock – socket

  • group – group to leave, or NULL for all

Returns:

AXL_OK on success, AXL_ERR if the UEFI Groups() call fails.

int axl_udp_set_broadcast(AxlUdp *sock, bool enable)

Enable or disable reception of broadcast datagrams.

Re-Configures the underlying UDP4 instance with AcceptBroadcast = enable. Default for a freshly-opened socket is FALSE (broadcasts dropped). Sending broadcasts (e.g. to 255.255.255.255 or a subnet broadcast) does not require this — it gates the recv-side filter only.

Parameters:
  • sock – socket

  • enable – true to accept inbound broadcasts

Returns:

AXL_OK on success, AXL_ERR on Configure failure.

int axl_udp_send(AxlUdp *sock, const AxlIPv4Address *dest, uint16_t port, const void *data, size_t len)

Send a UDP datagram. Blocking with 2-second timeout.

Fire-and-forget — no response expected.

dest may be NULL only if the socket has been pinned to a peer via axl_udp_connect (the configured peer is used). Otherwise dest is required.

Parameters:
  • sock – socket

  • dest – destination IPv4 address (NULL = use connected peer)

  • port – destination port (ignored if dest is NULL)

  • data – payload

  • len – payload length

Returns:

AXL_OK on success, AXL_ERR on error or timeout.

int axl_udp_sendrecv(AxlUdp *sock, const AxlIPv4Address *dest, uint16_t port, const void *tx_data, size_t tx_len, void *rx_buf, size_t rx_size, size_t *rx_len, size_t timeout_ms)

Send a datagram and wait for a response.

Sends tx_data, then polls for an incoming datagram up to timeout_ms milliseconds. Useful for DNS queries, NTP, etc.

Parameters:
  • sock – socket

  • dest – destination IPv4 address

  • port – destination port

  • tx_data – request payload

  • tx_len – request length

  • rx_buf – [out] response buffer

  • rx_size – response buffer capacity

  • rx_len – [out] bytes received

  • timeout_ms – receive timeout in ms

Returns:

AXL_OK on success, AXL_ERR on error or timeout.

int axl_udp_recv_async(AxlUdp *sock, AxlLoop *loop, AxlCancellable *cancel, AxlUdpCallback cb, void *data)

Async receive — fires cb for each incoming datagram via the event loop, until the callback returns false, the cancellable is signalled, or the socket is closed.

Mirrors axl_tcp_recv_async: takes an optional AxlCancellable, the callback receives per-event status, and the callback’s bool return controls re-arming.

Replaces the pre-parity-sweep axl_udp_recv_start / axl_udp_recv_stop pair: returning false from the callback now stops in-place (no separate stop call needed).

Parameters:
  • sock – socket

  • loop – event loop

  • cancel – optional cancel token (NULL = uncancellable)

  • cb – receive callback

  • data – user data for callback

Returns:

AXL_OK on success, AXL_ERR on error.

AxlUrl

Functions

int axl_url_parse(const char *url, AxlUrl **out_parsed)

Parse a URL string into components.

Supports the RFC 3986 generic URI shape: scheme://[user[:password]@]host[:port][/path][?query][#fragment]

Userinfo is recognized only when an @ appears in the authority (between :// and the first /, ?, or #); an @ later in the path or query is left alone. Percent-decoding is NOT applied to any field — the parser returns raw bytes for round-trip fidelity.

Parameters:
  • url – URL string (e.g. “http://user:pass@host:8080/path?q=1#frag”)

  • out_parsed – receives allocated AxlUrl; free with axl_url_free()

Returns:

AXL_OK on success, AXL_ERR on failure.

void axl_url_free(AxlUrl *url)

Free an AxlUrl returned by axl_url_parse.

Parameters:
  • url – URL to free (NULL-safe)

char *axl_url_build(const char *scheme, const char *host, uint16_t port, const char *path)

Build a URL string from components.

Parameters:
  • scheme – “http” or “https”

  • host – hostname or IP

  • port – port number (0 = use default for scheme)

  • path – path starting with “/” (NULL = “/”)

Returns:

allocated URL string, or NULL on failure. Caller frees.

int axl_url_encode(const char *src, char *out, size_t size)

Percent-encode a string for use in a URL.

Encodes all characters except unreserved characters (A-Z, a-z, 0-9, ‘-’, ‘.’, ‘_’, ‘~’) and optionally ‘/’ (preserved by default for path encoding). Each encoded byte becomes XX.

Parameters:
  • src – input string (UTF-8)

  • out – output buffer

  • size – output buffer size

Returns:

number of bytes written (excluding NUL), or -1 on error or truncation.

int axl_url_decode(const char *src, char *out, size_t size)

Decode a percent-encoded URL string.

Replaces XX sequences with the corresponding byte. Passes through characters that are not percent-encoded.

Parameters:
  • src – percent-encoded string

  • out – output buffer

  • size – output buffer size

Returns:

number of bytes written (excluding NUL), or -1 on error or truncation.

struct AxlUrl
#include <axl-url.h>

axl-url.h:

URL parser and builder. Parsed URL components per RFC 3986 (subset). Heap-allocated by axl_url_parse; freed by axl_url_free. NULL string fields mean the component wasn’t present in the input — distinguish from “present but empty” via empty-string check (e.g. user:@host → password is "", not NULL).

Public Members

char *scheme

URL scheme without :// (“http”, “https”, …)

char *user

userinfo user portion before : (NULL if no user[:pass]@ in authority)

char *password

userinfo password portion after : (NULL if no : in userinfo; “” if user:@host)

char *host

hostname or IP literal (no userinfo, no port)

char *path

path component starting with / (“/” if absent)

char *query

raw query string after ? (NULL if no ?)

char *fragment

fragment after # (NULL if no #)

uint16_t port

port number (default for scheme if not specified)

AxlHttpCore

Low-level HTTP/1.1 parsing helpers shared by the server, the client, and any consumer code (proxies, middleware, custom transports).

Functions

size_t axl_http_find_header_end(const char *buf, size_t len)

Find the end of HTTP headers (CRLF CRLF) in a buffer.

axl-http-core.h:

Low-level HTTP/1.1 parsing helpers shared by axl-http-server, axl-http-client, and consumer code (proxies, middleware, custom transports). All functions operate on raw byte buffers and use AxlHashTable for header storage.

Higher-level header utilities like axl_http_parse_range live in axl-http-server.h.

Scans buf for the “\r\n\r\n” sequence that terminates the request/response header block.

Parameters:
  • buf – raw data buffer

  • len – buffer length in bytes

Returns:

offset of the byte just past the “\r\n\r\n”, or 0 if not found (also 0 if len is less than 4).

int axl_http_parse_request_line(const char *line, size_t line_len, char **method, char **path, char **query)

Parse an HTTP request line: “METHOD PATH?QUERY HTTP/1.x”.

On success, allocates and returns the method, path, and (optional) query string. The caller frees each non-NULL output with axl_free. On error, all outputs are set to NULL.

Parameters:
  • line – start of the request line

  • line_len – length up to (but not including) CRLF

  • method – receives method string (allocated; caller frees)

  • path – receives path string (allocated; caller frees)

  • query – receives query string (allocated; NULL if none)

Returns:

AXL_OK on success, AXL_ERR if the line is malformed or allocation fails.

int axl_http_parse_status_line(const char *line, size_t line_len, size_t *status_code)

Parse an HTTP status line: “HTTP/1.x NNN Reason”.

Parameters:
  • line – start of the status line

  • line_len – length up to (but not including) CRLF

  • status_code – receives the numeric status code (e.g. 200)

Returns:

AXL_OK on success, AXL_ERR if the line is malformed.

int axl_http_parse_headers(const char *data, size_t data_len, AxlHashTable **headers)

Parse HTTP headers from raw bytes into a hash table.

Reads header lines starting at data until the empty line that separates headers from the body. Header names are stored in lowercase to enable case-insensitive lookups. The returned table owns its keys and values; the caller frees it with axl_hash_table_free.

Parameters:
  • data – start of header block (after request/status line + CRLF)

  • data_len – length of data

  • headers – receives populated hash table (caller frees)

Returns:

AXL_OK on success, AXL_ERR on error.(table is left as NULL).

size_t axl_http_get_content_length(AxlHashTable *headers)

Read the Content-Length value from a parsed header table.

Looks up the lowercase “content-length” key. Stops parsing at the first non-digit character.

Parameters:
  • headers – parsed headers (from axl_http_parse_headers)

Returns:

Content-Length value, or 0 if the header is absent or headers is NULL.

AxlHttpServer

Defines

AXL_WS_CONNECT
AXL_WS_TEXT
AXL_WS_BINARY
AXL_WS_DISCONNECT
AXL_ROUTE_NO_AUTH
AXL_ROUTE_AUTH
AXL_ROUTE_ADMIN
AXL_CACHE_FOREVER

Typedefs

typedef struct AxlLoop AxlLoop

axl-http-server.h:

HTTP server with routing, middleware pipeline, WebSocket, authentication, response caching, and upload streaming.

typedef int (*AxlHttpHandler)(AxlHttpRequest *req, AxlHttpResponse *resp, void *data)

Route handler callback.

Return:

AXL_OK on success, AXL_ERR on failure.

typedef int (*AxlHttpMiddleware)(AxlHttpRequest *req, AxlHttpResponse *resp, void *data)

Middleware callback. Return 0 to continue pipeline, -1 to short-circuit.

Return:

AXL_OK to continue, AXL_ERR to abort.

typedef struct AxlHttpServer AxlHttpServer
typedef int (*AxlResponseStreamer)(void *ctx, void *out_buf, size_t out_buf_size, size_t *out_size)

Producer callback for streaming response bodies.

Called repeatedly by the dispatcher to fill outgoing chunks. Implementations read the next chunk from their backing source (file, generated content, network passthrough) into out_buf and report the byte count via out_size. Returning AXL_OK with *out_size == 0 signals end-of-stream.

Re-entrancy: same constraints as AxlUploadHandler — runs on the loop’s normal dispatch level (TPL_APPLICATION foreground, TPL_CALLBACK driver). Don’t block, don’t allocate gratuitously, keep each invocation short — the dispatcher is single-threaded and a slow streamer stalls other connections.

Param ctx:

opaque user data from axl_http_response_set_streamer

Param out_buf:

caller-supplied buffer to fill

Param out_buf_size:

capacity of out_buf in bytes

Param out_size:

[out] bytes written into out_buf this call; 0 = EOF

Return:

AXL_OK to continue (including the EOF case with *out_size == 0); AXL_ERR to abort the response (the dispatcher resets the connection and invokes the cleanup hook).

typedef void (*AxlResponseCleanup)(void *ctx)

Optional finalizer called when a streaming response ends.

Fires exactly once for any response that successfully installs a streamer via axl_http_response_set_streamer, regardless of how the response ended (EOF, streamer error, connection reset before EOF). Use it to close files, free buffers, or release any resource ctx holds onto. Pass NULL if the streamer manages its own lifecycle.

Param ctx:

the same ctx pointer registered with the streamer.

typedef int (*AxlWsHandler)(size_t event, const void *frame, size_t frame_size, void *data)

WebSocket event callback.

Return:

AXL_OK on success, AXL_ERR on failure.

typedef int (*AxlAuthCallback)(AxlHttpRequest *req, AxlAuthInfo *auth_out, void *data)

Authentication callback.

Return:

AXL_OK on success (authenticated), AXL_ERR on failure.

typedef int (*AxlUploadHandler)(AxlHttpRequest *req, AxlHttpResponse *resp, const void *chunk, size_t chunk_size, void *data, bool aborted)

Upload streaming callback, called per chunk as body data arrives.

Called repeatedly with chunks up to the configured upload.chunk.size. Three terminating call shapes — the handler must distinguish them:

  • chunk != NULL, aborted == false: a body chunk arrived. Process it and return AXL_OK to continue, AXL_ERR to abort and send 500.

  • chunk == NULL, chunk_size == 0, aborted == false: clean EOF. Set resp fields here; the response is sent after return.

  • chunk == NULL, chunk_size == 0, aborted == true: the connection was torn down mid-upload (TCP disconnect, recv error, server shutdown). resp is NOT transmitted. The handler MUST NOT touch the connection, send a response, or call any response setter (axl_http_response_set_*) — the call exists only to release per-request state (open file handles, accumulators, allocations) the handler accumulated across earlier chunk calls. Without this signal, that state leaks into the next request on the same handler globals — caused cross-request data corruption in axl-webfs’s PUT path.

Fires exactly once per upload: clean-EOF and abort calls are mutually exclusive — a handler that already received the clean-EOF call will NOT also receive an abort, even if the response send subsequently fails. Return value is ignored on the abort call.

Return:

AXL_OK on success, AXL_ERR to abort the upload and send 500.

Functions

AxlHttpServer *axl_http_server_new(uint16_t port)

Create a new HTTP server bound to the given port.

Parameters:
  • port – TCP port to listen on

Returns:

server instance, or NULL on failure.

void axl_http_server_free(AxlHttpServer *server)

Free an HTTP server and all resources.

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

int axl_http_server_set(AxlHttpServer *s, const char *key, const char *value)

Set a server option by key.

Supported keys: “max.connections”, “body.limit”, “keep.alive.sec”.

Parameters:
  • s – server

  • key – option key

  • value – option value (string)

Returns:

AXL_OK on success, AXL_ERR on unknown key or invalid value.

const char *axl_http_server_get(AxlHttpServer *s, const char *key)

Get a server option value as string.

Parameters:
  • s – server

  • key – option key

Returns:

option value, or NULL for unknown keys.

int axl_http_server_set_max_connections(AxlHttpServer *s, size_t max)

Set maximum simultaneous connections.

Parameters:
  • s – server

  • max – maximum simultaneous connections (default 8)

Returns:

AXL_OK on success, AXL_ERR if s is NULL or the underlying setter rejected the value.

int axl_http_server_set_body_limit(AxlHttpServer *s, size_t max_bytes)

Set maximum request body size.

Parameters:
  • s – server

  • max_bytes – maximum request body size in bytes (default 4 MB)

Returns:

AXL_OK on success, AXL_ERR on error.

int axl_http_server_set_keep_alive(AxlHttpServer *s, size_t timeout_sec)

Set keep-alive timeout.

Parameters:
  • s – server

  • timeout_sec – keep-alive timeout in seconds (default 30)

Returns:

AXL_OK on success, AXL_ERR on error.

int axl_http_server_use(AxlHttpServer *s, AxlHttpMiddleware mw, void *data)

Register middleware executed in registration order. Return 0 from mw to continue pipeline, -1 to short-circuit.

Parameters:
  • s – server

  • mw – middleware function

  • data – context passed to mw

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_add_route(AxlHttpServer *s, const char *method, const char *path, AxlHttpHandler handler, void *data)

Register a route handler.

Parameters:
  • s – server

  • method – HTTP method (“GET”, “POST”, etc.) or NULL for any

  • path – path pattern; trailing slash-star matches prefix

  • handler – handler function

  • data – context passed to handler

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_add_routes(AxlHttpServer *s, ...)

Register multiple route handlers in one call.

Variadic batch form of axl_http_server_add_route. Each route is a four-arg group (method, path, handler, data) repeated until a sentinel NULL method terminates the list. Stops on the first registration failure and returns AXL_ERR — earlier successfully- registered routes stay installed (the server’s route table is append-only and the failure is most likely “table full,” which the caller can surface to the user).

axl_http_server_add_routes(server,
    "GET",    "/",       handle_root, NULL,
    "GET",    "/x",      handle_x,    o,
    "PUT",    "/y",      handle_y,    o,
    "DELETE", "/z",      handle_z,    o,
    NULL);   // sentinel — required

Replaces the equivalent five separate axl_http_server_add_route calls and the per-call error-check chain. Route precedence is the same as for repeated single-route calls — exact path before prefix, method-specific before method-wildcard — independent of the order routes are registered.

Parameters:
  • s – server

Param :

(method, path, handler, data) groups, terminated by NULL method

Returns:

AXL_OK if every route registered; AXL_ERR on the first failure (with that route and all later groups in the list NOT registered).

int axl_http_server_add_static(AxlHttpServer *s, const char *prefix, const char *fs_path)

Serve static files from a filesystem path.

Parameters:
  • s – server

  • prefix – URL prefix (e.g. “/”)

  • fs_path – filesystem path (UTF-8)

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_start(AxlHttpServer *s, AxlLoop *loop)

Bring the server up on a caller-owned event loop.

Allocates the per-connection pool sized from the server’s max.connections config, opens the TCP listener (pinned to listen.ip if set, else auto-pick), and registers the async accept on loop so each incoming connection re-arms automatically. The server is fully wired and listening when this returns; the caller drives loop via axl_loop_run (foreground) or axl_loop_attach_driver (DXE driver mode).

axl_http_server_run is the convenience wrapper that creates its own loop, calls this, and runs the loop to completion.

Parameters:
  • s – server

  • loop – event loop from axl_loop_new

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_run(AxlHttpServer *s)

Run the server standalone — creates a loop, calls axl_http_server_start, and blocks in axl_loop_run until axl_loop_quit.

Parameters:
  • s – server

Returns:

AXL_OK on success, AXL_ERR on failure.

bool axl_http_request_accepts(const AxlHttpRequest *req, const char *mime)

Does the request’s Accept header advertise interest in mime?

Routes from req->headers[“accept”] into axl_http_accepts — the same matcher used elsewhere in the HTTP machinery: case-insensitive, handles multi-type lists, recognizes the catch-all wildcard, and tolerates ;q= parameters (matches regardless of q-value, so an explicit q=0 reject is still treated as “accepts” — a future tightening if a consumer needs strict negotiation). Missing Accept header returns false.

Parameters:
  • req – incoming request

  • mime – MIME type to look for (“application/json”)

Returns:

true if req would accept a response of MIME type mime.

bool axl_http_request_wants_json(const AxlHttpRequest *req)

Convenience: does the request want JSON?

Equivalent to axl_http_request_accepts (req, “application/json”). Common-enough pattern in REST handlers that it earns its own name — if (axl_http_request_wants_json(req)) { ... } reads at the right level. Used when a single endpoint serves both HTML and JSON representations and picks based on the client’s Accept header.

Parameters:
  • req – incoming request

bool axl_http_request_get_json(const AxlHttpRequest *req, AxlJsonReader *out)

Parse the request body as JSON.

Calls axl_json_parse on req->body / req->body_size. The reader references the body buffer directly — do not free req->body while the reader is in use, and call axl_json_free on out when done.

Strict RFC 8259. For JSON5, parse manually with axl_json_parse_flags.

Parameters:
  • req – incoming request

  • out – [out] reader to fill (caller owns; free with axl_json_free)

Returns:

true on success (out populated and ready for axl_json_object_get / etc); false on NULL inputs, empty body, or JSON parse error.

void axl_http_response_set_json(AxlHttpResponse *r, const char *json)

Set JSON body and “application/json” content type (default 200).

Parameters:
  • r – response

  • json – JSON string

void axl_http_response_set_text(AxlHttpResponse *r, const char *text)

Set plain text body and “text/plain” content type (default 200).

Parameters:
  • r – response

  • text – plain text string

void axl_http_response_set_status(AxlHttpResponse *r, size_t code)

Set or override the HTTP status code.

Parameters:
  • r – response

  • code – HTTP status code

void axl_http_response_set_file(AxlHttpResponse *r, const char *path)

Set response body from a file, inferring content type from extension.

Parameters:
  • r – response

  • path – filesystem path (UTF-8)

void axl_http_response_set_static(AxlHttpResponse *r, const void *body, size_t size, const char *content_type)

Set response body to a borrowed static buffer that the SDK must NOT free.

Use this for embedded read-only assets — .rodata C string literals, static const arrays of HTML / JS / CSS, immutable binary blobs xxd’d into the binary. Sets AxlHttpResponse.body to body, body_size to size, marks AxlHttpResponse.body_static = true so the dispatch loop skips its post-send axl_free. Passing such a pointer to the axl_http_response_set_text / _json family would force a copy (waste); assigning it to AxlHttpResponse.body directly causes heap corruption (the dispatch loop would treat the literal as an axl_malloc’d buffer and free it).

content_type is borrowed (typically a string literal). NULL leaves the existing content-type unchanged.

If a previous body was set via the copy-based helpers, this function frees it before installing the static buffer.

Parameters:
  • r – response

  • body – pointer to read-only / static buffer

  • size – size of body in bytes

  • content_type – MIME type (borrowed); NULL = leave as-is

void axl_http_response_set_streamer(AxlHttpResponse *r, AxlResponseStreamer streamer, void *ctx, AxlResponseCleanup cleanup, size_t total_size, const char *content_type)

Set a streaming response body via producer callback.

Replaces the contiguous-body model for large or unbounded responses. The dispatcher allocates a chunk-sized tx buffer, sends headers, then calls streamer repeatedly to fill the buffer and axl_tcp_send_async’s each filled chunk (chained completions). EOF (returned via *out_size = 0) terminates the response.

Sets Content-Length from total_size when known (the typical file-serve case). Pass (size_t)-1 to signal “unknown length” and emit Transfer-Encoding: chunked instead — each chunk goes on the wire framed as <hex-size>\r\n<data>\r\n, terminated by a 0\r\n\r\n final chunk.

ctx is opaque to the SDK; the dispatcher passes it back unchanged on each streamer invocation. cleanup (NULL-able) fires exactly once when the response ends — successful EOF, streamer error, OR connection reset. Use it to close the file ctx wraps, free buffers, etc.

Mutually exclusive with body / body_static / axl_http_response_set_text / _json / _file. Setting a streamer overrides any prior body assignment (and frees a previously-set non-static body).

static int file_streamer(void *ctx, void *buf, size_t cap, size_t *out)
{
    AxlFile *f = (AxlFile *)ctx;
    return axl_fread(f, buf, cap, out);
}
static void file_close(void *ctx) { axl_fclose((AxlFile *)ctx); }

AxlFile *f = axl_fopen("fs0:/big.iso", "r");
uint64_t size = 0;
axl_file_size(f, &size);
axl_http_response_set_streamer(resp, file_streamer, f, file_close,
                               (size_t)size, "application/octet-stream");
Parameters:
  • r – response

  • streamer – producer callback (must be non-NULL)

  • ctx – opaque user data passed back to streamer / cleanup

  • cleanup – finalizer (NULL = streamer self-cleans)

  • total_size – Content-Length, or (size_t)-1 for chunked

  • content_type – MIME type (borrowed; NULL = leave as-is)

void axl_http_response_set_range(AxlHttpResponse *r, const void *data, size_t offset, size_t length, size_t total_size)

Set a byte-range response (HTTP 206) with Content-Range header.

Copies length bytes starting at (uint8_t *)data + offset into a freshly-allocated body, sets status_code = 206, sets content_type = "application/octet-stream", and emits a Content-Range: bytes <offset>-<offset+length-1>/<total_size> header per RFC 9110 §15.3.7. Allocates r->headers if not already present.

Parameters:
  • r – response

  • data – full data buffer

  • offset – byte offset into data

  • length – number of bytes to send

  • total_size – total size of the resource

void axl_http_response_set_content_range(AxlHttpResponse *r, uint64_t start, uint64_t end, uint64_t total)

Set the Content-Range header on a 206 response.

Use when sending a partial-content response via axl_http_response_set_streamer or any other path that doesn’t go through axl_http_response_set_range (which sets the header automatically). Callers must set status_code = 206 separately — this helper only formats and inserts the header.

Allocates r->headers if not already present, using axl_hash_table_new_full with axl_free_impl destructors for BOTH keys and values. If the consumer pre-allocates themselves, it MUST be created with the same destroy-func contract (e.g. via

axl_hash_table_new_full(

axl_str_hash, axl_str_equal, axl_free_impl, axl_free_impl)

). Mixing in a axl_hash_table_new_str()-shaped table would leak both the strdup’d key (str-table double-strdups) and value (str-table doesn’t own values). Other axl-http-server callers (e.g. WebSocket upgrade handler) build their own headers tables — they don’t compose with this helper today, but new consumers should follow the full-destroy-funcs convention.

Format per RFC 9110 §15.3.7: bytes <start>-<end>/<total>, end inclusive (start <= end < total).

Parameters:
  • r – response

  • start – first byte index of the slice

  • end – last byte index of the slice (inclusive)

  • total – total size of the resource

bool axl_http_parse_range(const char *range_header, uint64_t file_size, AxlHttpRange *out)

Parse an HTTP Range request header.

Supports a single “bytes=START-END” range (not multi-range). Handles “bytes=START-”, “bytes=-SUFFIX”, and “bytes=START-END”. Clamps end to file_size - 1. Sets out->valid on success.

Parameters:
  • range_header – Range header value (e.g. “bytes=0-499”)

  • file_size – total file size

  • out – receives the parsed range

Returns:

true if a valid range was parsed, false otherwise.

bool axl_http_accepts(const char *accept_header, const char *media_type)

Check if an HTTP Accept header includes a media type.

Searches the comma-separated Accept header value for media_type (e.g. “application/json”, “text/html”). Matching is case-insensitive and ignores quality parameters. Also matches wildcard types (wildcard accepts everything).

Parameters:
  • accept_header – Accept header value (may be NULL)

  • media_type – media type to check (e.g. “application/json”)

Returns:

true if media_type is acceptable.

int axl_http_server_use_tls(AxlHttpServer *s, const void *cert_der, size_t cert_len, const void *key_der, size_t key_len)

Enable TLS on the server with DER-encoded cert and key.

After this call, all accepted connections use TLS. The cert and key can be generated with axl_tls_generate_self_signed(). Requires AXL_TLS=1 at build time.

Parameters:
  • s – server

  • cert_der – DER-encoded certificate

  • cert_len – certificate length

  • key_der – DER-encoded private key

  • key_len – key length

Returns:

AXL_OK on success, AXL_ERR if TLS not available or cert/key invalid.

int axl_http_server_add_websocket(AxlHttpServer *s, const char *path, AxlWsHandler handler, void *data)

Register a WebSocket endpoint.

Parameters:
  • s – server

  • path – WebSocket endpoint path

  • handler – WebSocket event handler

  • data – opaque caller data

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_ws_broadcast(AxlHttpServer *s, const char *path, const void *data, size_t size)

Broadcast data to all connected WebSocket clients on a path.

Parameters:
  • s – server

  • path – WebSocket endpoint path

  • data – data to broadcast

  • size – data size in bytes

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_use_auth(AxlHttpServer *s, AxlAuthCallback cb, void *data)

Register an authentication handler for the server.

Parameters:
  • s – server

  • cb – authentication callback

  • data – opaque caller data

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_add_route_auth(AxlHttpServer *s, const char *method, const char *path, AxlHttpHandler handler, void *data, uint32_t auth_flags)

Register a route handler with authentication requirements.

Parameters:
  • s – server

  • method – HTTP method or NULL for any

  • path – path pattern

  • handler – handler function

  • data – context passed to handler

  • auth_flags – AXL_ROUTE_* flags

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_use_cache(AxlHttpServer *s, size_t max_entries)

Enable response caching on the server.

Parameters:
  • s – server

  • max_entries – maximum cache entries

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_set_route_ttl(AxlHttpServer *s, const char *path, size_t ttl_ms)

Set cache TTL for a specific route path.

Stores a path ttl_ms mapping; the next cached response whose request path equals path exactly uses this TTL instead of the server-wide default from axl_http_server_use_cache. Prefix routes (e.g. /css/ followed by a wildcard) are not matched — set the TTL on the exact sub-paths you expect, or rely on the server default.

Parameters:
  • s – server

  • path – exact request path

  • ttl_ms – time-to-live in milliseconds, or AXL_CACHE_FOREVER

Returns:

AXL_OK on success, AXL_ERR on failure.

void axl_http_server_cache_invalidate(AxlHttpServer *s, const char *prefix)

Invalidate cached responses whose path starts with prefix.

Walks the cache and removes every entry whose path portion begins with prefix (the leading “METHOD “ token in the internal cache key is skipped). Pass NULL or “” to clear the whole cache.

Parameters:
  • s – server

  • prefix – path prefix to invalidate (NULL or “” for all)

int axl_http_server_add_upload_route(AxlHttpServer *s, const char *method, const char *path, AxlUploadHandler handler, void *data)

Register a streaming upload route.

Parameters:
  • s – server

  • method – HTTP method

  • path – path pattern

  • handler – upload handler

  • data – opaque caller data

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_server_add_webdav(AxlHttpServer *s, const char *prefix, const AxlWebDavOps *ops, void *user_data)

Mount a WebDAV handler at prefix.

Registers verb routes (OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, MOVE) under <prefix>/<wildcard> that drive ops. The ops table is COPIED into the server — caller may free / re-use the struct after this returns. user_data is borrowed and must outlive the server.

Up to 4 WebDAV mounts per server. Cleanup is automatic on axl_http_server_free.

Parameters:
  • prefix – URL prefix, e.g. “/dav”

  • ops – callback table (copied)

  • user_data – opaque, passed back to ops

Returns:

AXL_OK on success, AXL_ERR on bad arguments or if the server already has 4 mounts.

struct AxlHttpRequest

Public Members

const char *method
const char *path
const char *query
AxlHashTable *headers
const void *body
size_t body_size
char client_addr[46]
void *middleware_data
struct AxlHttpResponse

Public Members

size_t status_code
AxlHashTable *headers
void *body

Response body bytes. Ownership: the SDK calls axl_free on this pointer after the response is sent, unless body_static is set. Handlers that assign body directly must therefore pass an axl_malloc’d buffer. Assigning a .rodata / static const literal here is a heap corruption bug — use axl_http_response_set_static for embedded read-only assets, or one of the copy-based helpers (axl_http_response_set_text / _json / _file) which allocate internally.

size_t body_size
const char *content_type

Content-Type header value. Borrowed pointer — the SDK does NOT free this. Static string literals are fine; if a caller allocates dynamically, the caller is responsible for the lifetime (must outlive the response send).

bool body_static

When true, the SDK will NOT free body after the response is sent. Set by axl_http_response_set_static; ignore otherwise. Default false (zero-init) preserves the “axl_malloc’d, SDK frees” contract for every existing caller — no migration required.

int (*streamer)(void *ctx, void *out_buf, size_t out_buf_size, size_t *out_size)

When non-NULL, the dispatcher streams the body by calling streamer repeatedly. See axl_http_response_set_streamer. Set body / body_size / body_static via the setter rather than touching these fields directly.

void *streamer_ctx

Opaque user data passed to streamer on each invocation. Owned by the caller; lifetime managed via streamer_cleanup.

void (*streamer_cleanup)(void *ctx)

Optional finalizer called once the streaming response either completes (EOF, all bytes sent) OR is aborted (streamer error, connection reset before EOF). Receives streamer_ctx so the caller can close files / free buffers. NULL means the streamer self-cleans via its EOF / error transitions.

size_t streamer_total_size

Total response body size in bytes. Used as Content-Length when known. Pass (size_t)-1 to signal unknown length — the dispatcher emits Transfer-Encoding: chunked instead.

struct AxlHttpRange
#include <axl-http-server.h>

Parsed byte range from an HTTP Range request header.

Public Members

uint64_t start

first byte position (inclusive)

uint64_t end

last byte position (inclusive)

uint64_t total

total file size (from file_size parameter)

bool valid

true if parsing succeeded

struct AxlAuthInfo

Public Members

const char *username
size_t role
struct AxlWebDavOps
#include <axl-http-server.h>

Consumer-supplied filesystem callback table.

Every callback receives user (the value passed to axl_http_server_add_webdav) and a path RELATIVE to the registered prefix (e.g. with prefix /dav and request URL /dav/foo/bar.txt, the consumer sees /foo/bar.txt). Root path is / and refers to the WebDAV mount itself — list_dir(“/”) returns the top-level entries (one virtual entry per UEFI volume, say).

Callbacks return AXL_OK on success, AXL_ERR on failure. The SDK maps the failure to an HTTP status: stat / list_dir / read failures → 404; mkdir / write_open / move failures → 409 (parent missing) or 500 (other); remove failure → 404 or 423 if locked (latter not fully wired in v1).

Streaming callbacks (read/write):

  • read_open returns an opaque ctx the SDK threads into read_chunk (drives the response-body streamer) and read_close (idempotent finalize).

  • write_open likewise; write_chunk receives one chunk per dispatcher buffer; write_close(aborted) runs on EOF (aborted=false) OR mid-upload TCP teardown (aborted=true). Same shape as AxlUploadHandler’s clean-EOF/abort contract.

Public Members

int (*list_dir)(void *user, const char *path, AxlFsEntry *out, size_t max, size_t *count)

PROPFIND backing — list children of a directory.

int (*stat)(void *user, const char *path, AxlFsEntry *out)

Stat — for PROPFIND on a single resource.

int (*read_open)(void *user, const char *path, uint64_t offset, void **out_ctx)

Streaming read — drives axl_http_response_set_streamer for GET.

int (*read_chunk)(void *ctx, void *buf, size_t buf_size, size_t *bytes_read)
void (*read_close)(void *ctx)
int (*write_open)(void *user, const char *path, void **out_ctx)

Streaming write — drives the upload-route chunk handler for PUT.

int (*write_chunk)(void *ctx, const void *data, size_t len)
void (*write_close)(void *ctx, bool aborted)
int (*mkdir)(void *user, const char *path)

Lifecycle — MKCOL / DELETE / MOVE / COPY.

int (*remove)(void *user, const char *path)
int (*move)(void *user, const char *src, const char *dst, bool overwrite)
int (*copy)(void *user, const char *src, const char *dst, bool overwrite, int depth)

COPY: replicate src to dst, leaving src in place. depth is 0 (collection itself only, no contents) or -1 (infinity / deep). The SDK rejects Depth: 1 before reaching here per RFC 4918 §9.8.3. Returning AXL_ERR maps to 409. To get RFC-correct 404 (rather than 409) for missing-source, also set stat — the SDK pre-stats src when stat is wired.

const char *(*content_type)(void *user, const char *path)

Content-Type hint for GET responses (optional). Returning NULL or omitting the callback uses application/octet-stream.

int (*digest)(void *user, const char *path, const char *algo, char *out_hex, size_t hex_size)

Optional: produce a content digest for end-to-end integrity verification (RFC 3230). When wired AND the client sends a Want-Digest: <algo>[, ...] request header on GET / HEAD, the SDK iterates the requested algorithms (in client-listed order) and calls this callback for each — the first call that returns AXL_OK wins, and the SDK emits the matching Digest: <algo>=<hex> response header.

algo is the lowercased canonical algorithm name as the client requested it (typically "sha-256"; legacy "sha-1" and "md5" are also forwarded if requested). out_hex is a caller-allocated buffer of hex_size bytes the consumer fills with the lowercase hex digest + trailing NUL. (Consumer always produces hex; the SDK emits hex per the RFC 3230 id-sha-* alias convention. The buffer is sized to fit SHA-512 hex output.) Return AXL_OK on success, AXL_ERR for any failure (unknown algo, unreadable file, OOM); on AXL_ERR the SDK silently moves on to the next algorithm in the Want-Digest list, or omits the header entirely if none succeed. Same omission behavior as a non-wired callback.

Per RFC 3230 §4.3.2, the digest covers the FULL file even for 206 Partial Content responses — mount clients accumulate the value across their first Range read.

void (*before_response)(void *user, AxlHttpRequest *req, AxlHttpResponse *resp)

Optional last-call hook to mutate the response before the SDK hands it to the dispatcher for wire send. Fires AFTER the SDK’s per-verb logic has set status, headers, body / streamer, but BEFORE the dispatcher serializes. Consumer may add or replace headers via resp->headers (lazy-alloc it if NULL); reading req->path / req->method to scope behavior is fine.

Fires for every WebDAV verb the handler dispatched. For PUT, fires once on clean EOF (when the response status is set), NOT per chunk. For HEAD, fires once with the headers-only response.

Use cases: custom property emission (ETag, Cache-Control, resource-versioning headers), audit-trail header injection, rate-limit hints. For RFC 3230 Digest emission specifically, wire the digest callback instead — the SDK already does the Want-Digest parsing.

AxlHttpClient

Typedefs

typedef struct AxlHttpClient AxlHttpClient

axl-http-client.h:

HTTP client with GET, POST, PUT, DELETE, and file download.

Configuration uses string key-value pairs (like librdkafka):

AxlHttpClient *c = axl_http_client_new();
axl_http_client_set(c, "timeout.ms", "30000");
axl_http_client_set(c, "keep.alive", "false");
axl_http_client_set(c, "max.redirects", "0");
axl_http_client_set(c, "header.User-Agent", "MyApp/1.0");

Supported options: “timeout.ms” — per-operation timeout in milliseconds (default: “10000”) “keep.alive” — connection reuse: “true” (default) or “false” “max.redirects” — redirect limit (default: “5”), “0” to disable “tls.verify” — certificate verification: “true” (default) or “false” “header.<Name>” — default header sent with every request

typedef int (*AxlRequestBodyStreamer)(void *ctx, void *out_buf, size_t out_buf_size, size_t *out_size)

Producer callback for streaming PUT/POST request bodies.

Called repeatedly by the client to fill the next outgoing chunk. The implementation reads from its backing source (file, buffer, generated content) into out_buf and reports the byte count via out_size. A return of AXL_OK with *out_size == 0 signals end-of-body — the client emits the final empty chunk (chunked transfer) or stops sending (Content-Length transfer).

Return:

AXL_OK to continue (including the EOF case); AXL_ERR to abort the request. The client tears the connection down on AXL_ERR; the cleanup callback (if any) still fires.

Functions

AxlHttpClient *axl_http_client_new(void)

Create a new HTTP client with default options.

Returns:

client instance, or NULL on failure.

void axl_http_client_free(AxlHttpClient *c)

Free an HTTP client and close any open connection.

Parameters:
  • c – client to free (NULL-safe)

int axl_http_client_set(AxlHttpClient *c, const char *key, const char *value)

Set a client option.

All values are strings, parsed internally. See header comment for the list of supported options.

Parameters:
  • c – client

  • key – option name

  • value – option value (string)

Returns:

AXL_OK on success, AXL_ERR on unknown option.

const char *axl_http_client_get(AxlHttpClient *c, const char *key)

Get a client option value.

Returns a pointer to the internally stored string. The pointer remains valid until the option is changed or the client is freed.

Parameters:
  • c – client

  • key – option name

Returns:

option value string, or NULL for unknown options.

int axl_http_get(AxlHttpClient *c, const char *url, AxlHttpClientResponse **out_resp)

HTTP GET request.

Parameters:
  • c – client

  • url – full URL string

  • out_resp – receives response; free with axl_http_client_response_free()

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_post(AxlHttpClient *c, const char *url, const void *body, size_t size, const char *content_type, AxlHttpClientResponse **out_resp)

HTTP POST request.

Parameters:
  • c – client

  • url – full URL string

  • body – request body

  • size – body size in bytes

  • content_type – MIME type (e.g. “application/json”)

  • out_resp – receives response

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_put(AxlHttpClient *c, const char *url, const void *body, size_t size, const char *content_type, AxlHttpClientResponse **out_resp)

HTTP PUT request.

Parameters:
  • c – client

  • url – full URL string

  • body – request body

  • size – body size in bytes

  • content_type – MIME type

  • out_resp – receives response

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_delete(AxlHttpClient *c, const char *url, AxlHttpClientResponse **out_resp)

HTTP DELETE request.

Parameters:
  • c – client

  • url – full URL string

  • out_resp – receives response

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_request(AxlHttpClient *c, const char *method, const char *url, const void *body, size_t body_size, const char *content_type, AxlHashTable *extra_headers, AxlHttpClientResponse **out_resp)

Generic HTTP request with optional per-request headers.

Parameters:
  • c – client

  • method – HTTP method (“GET”, “POST”, “PUT”, “DELETE”, etc.)

  • url – full URL string

  • body – request body, or NULL

  • body_size – body size in bytes

  • content_type – MIME type, or NULL

  • extra_headers – optional hash table of additional headers (NULL for none)

  • out_resp – receives response

Returns:

AXL_OK on success, AXL_ERR on failure.

int axl_http_request_streaming(AxlHttpClient *c, const char *method, const char *url, AxlRequestBodyStreamer streamer, void *ctx, void (*cleanup_fn)(void *ctx), size_t total_size, const char *content_type, AxlHashTable *extra_headers, AxlHttpClientResponse **out_resp)

Issue an HTTP request with a streaming request body.

Mirrors axl_http_request but builds the body via streamer rather than a contiguous buffer. Use for multi-chunk uploads where the body isn’t materialized in RAM (UEFI Shell cp to a mounted volume, large-file PUT, generated content, etc.).

When total_size is known, the client emits a Content-Length header and sends the body as raw bytes; if the streamer signals EOF before total_size bytes are produced the request fails with AXL_ERR. Pass (size_t)-1 to use Transfer-Encoding: chunked instead — useful when the producer doesn’t know the total length up front.

cleanup_fn (if non-NULL) fires once after the request completes — success, error, OR streamer abort — so consumers can release the producer state at a single site instead of threading cleanup through every error return.

Streaming requests do NOT retry on stale connections or follow redirects: the producer callback can only be consumed once. Callers who need either behavior must re-invoke this function with a fresh streamer state.

Parameters:
  • method – “PUT”, “POST”, etc.

  • streamer – producer callback

  • ctx – opaque, passed to streamer + cleanup

  • cleanup_fn – optional finalizer (NULL = none)

  • total_size – body length in bytes; (size_t)-1 = chunked

Returns:

AXL_OK on success, AXL_ERR on failure (connection reset, streamer returned AXL_ERR, Content-Length mismatch, etc.).

int axl_http_request_stream_file(AxlHttpClient *c, const char *method, const char *url, const char *path, const char *content_type, AxlHashTable *extra_headers, AxlHttpClientResponse **out_resp)

Issue an HTTP request whose body is the contents of a local file, streamed via AxlStream.

Convenience wrapper over axl_http_request_streaming for the common “upload this file” case. Opens path read-only via axl_fopen, sizes the body via axl_file_info, and streams the bytes to the wire without materializing the whole file in RAM. The stream is closed when the request completes.

Use this for cp / upload semantics where the producer is just “the bytes on disk.” For producer/consumer patterns where the source isn’t a file (ring buffers, generated content), use axl_http_request_streaming with a custom producer callback.

Parameters:
  • path – local file path (UTF-8)

Returns:

AXL_OK on success, AXL_ERR on failure (file unreadable, producer error, connection reset, etc.).

void axl_http_client_response_free(AxlHttpClientResponse *resp)

Free a client response.

Parameters:
  • resp – response to free (NULL-safe)

int axl_http_download(AxlHttpClient *c, const char *url, const char *local_path)

Download a URL to a local file.

Parameters:
  • c – client

  • url – full URL string

  • local_path – filesystem path to write (UTF-8)

Returns:

AXL_OK on success, AXL_ERR on failure.

struct AxlHttpClientResponse

Public Members

size_t status_code
AxlHashTable *headers
void *body
size_t body_size