AxlNet — Networking
TCP sockets, UDP sockets, socket abstraction layer, URL parsing, HTTP
server, HTTP client, TLS, and network utilities (IPv4 address helpers,
interface enumeration; diagnostics — ICMP ping + axl_net_ping_ex
(traceroute / path-MTU), axl_sntp_query (SNTP time), axl_net_arp_list
(neighbor cache), axl_net_get_link_stats).
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, requiresAXL_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_AUTOpicks the first usable one.local_ip— IPv4 tobind(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.port—uint16_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 ofbring_upinternally. Event-driven viaEFI_IP4_CONFIG2_PROTOCOL.RegisterDataNotify— sub-millisecond wakeup after DHCP completes (firmware that doesn’t support the notify falls back to a 1 s tick). On firmware that lacks IP4Config2 entirely (some OEM laptops), it falls back toDhcp4ServiceBindingthen PXE Base Code DHCP, caching the lease soaxl_net_get_ip_address/axl_net_get_dhcp_leasestill report it;axl_net_last_config_method()says which path won. (Those fallbacks are real-hardware-only — OVMF always has IP4Config2.)axl_net_set_static_ip(nic, ip, netmask, gateway)— raw IP4Config2 setter; static path ofbring_upcalls it afterdrivers_up.
Static config / DNS / hostname (the ifconfig policy layer)
For an on-box network-setup UI (the “Configure” tab), the IP4Config2 policy layer is descriptor-driven like everything else — no hand-authoring the form:
AxlNetStaticOpts+axl_config_descs_net_static(out, cap, base_off)— the policy option group (mode / ip / netmask / gateway / dns / dns2 / hostname), the sibling ofaxl_config_descs_net. Fields areconst char *(so AxlConfig’s pointer-based string auto-apply populates them; an inlinechar[]would silently fail). A UI embeds the struct, emits the descriptors, andaxl_config_newpoints each field at the form value.axl_net_init_static(cfg, nic, timeout)— one-call apply: dispatches oncfg->mode("static"→ set IP/mask/gw + DNS + hostname + settle;"dhcp"→ DHCP + best-effort DNS/hostname). An unrecognized mode is rejected, not silently DHCP’d.axl_net_set_dns(nic, dns, dns2)— the missing resolver setter besideaxl_net_resolve(a query). IP4Config2 makes the DNS list read-only under the DHCP policy, so this is a static-mode operation.axl_net_set_hostname(name)/axl_net_get_hostname(buf, size)— the box’s hostname, persisted to a dedicated AXL non-volatile variable. UEFI has no firmware-advertised hostname, so this is a stored value (set + display + read by an AXL-aware consumer); it does not, by itself, make the firmware DHCP client send the name.axl_net_wait_ip_settled(nic, expect_ipv4, timeout_ms)— IP4Config2 applies asynchronously; this polls until the address has taken (expect_ipv4non-NULL = wait for that address, so a stale prior address doesn’t satisfy it) so a read-back is valid.
tools/netinfo config dogfoods the bring-up path
(--mode static --ip … --dns … --hostname …).
NIC inventory + driver selection
For a local “pick the NIC / get an unknown box online / diagnose”
tool, four accessors layer on top of axl_net_list_interfaces:
axl_net_get_driver_info(mac, &info)— the bound driver name + binding layer (NII3.1/NII/SNP) and a stable bus location (the NIC’s device-path topology, e.g.PciRoot(0x0)/Pci(0x3,0x0), with the MAC/IP network tail trimmed) for the NIC carryingmac. The bus location is a reboot-stable selector that distinguishes two identical NICs, unlike the fragile enumeration index. Kept offaxl_net_list_interfaces(which is polled during link bring-up) because the resolution is heavier — a UI fills the driver/bus columns per row.axl_net_list_available_drivers(out, &count)— the NIC-driver.efi/.efidrvfiles staged ondrivers/<arch>/across mounted volumes, so a UI can offer “try X / Y / Z”.axl_net_try_driver(path_or_name, &result)— load + connect one driver and report{ snp_handles_added, link_up, bound_nic_macs[] }, unloading it again on failure so the next candidate starts clean. Encapsulates the field hazards: iPXE’s watchdog is disarmed (and iPXE must be tried last — itsLoadImagehook breaks later loads), andMediaPresentis treated as advisory.axl_net_connect_stack()— theConnectController-on-SNP step (for firmware that doesn’t auto-connect), exposed so a “my NIC isn’t showing up” action works without a full re-init.
tools/netinfo dogfoods all four (list -v driver/bus columns,
list-bundle, try <driver>).
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 bool — true 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.
AxlTcpandAxlUdpare the primitives underneath – thin wrappers over UEFI’sTCP4_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. Seesdk/examples/tcp-echo-server.cfor the low-level counterpart to the socket-basedecho-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.
To gate a mount behind the server auth callback, use
axl_http_server_add_webdav_auth(s, prefix, &ops, user_data, auth_flags)
(or pass auth_flags to axl_http_server_serve_fs). The flags
(AXL_ROUTE_AUTH / AXL_ROUTE_ADMIN) apply to every verb route —
including the streaming PUT, which is enforced before any body byte.
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 |
|---|---|---|
|
|
body chunk arrived; process it and return AXL_OK |
|
|
clean EOF — set |
|
|
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.
To auth-gate an upload route, register it with
axl_http_server_add_upload_route_auth(server, method, path, handler, data, auth_flags). Uploads bypass the normal dispatch auth check, so
this variant enforces the route’s auth_flags directly — the server
auth callback runs before the first body byte (401 on failure, 403 for
an admin route presented a lesser role).
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.
Async HTTP client
axl_http_get_async / axl_http_post_async are the loop-integrated peers
of axl_http_get / axl_http_post. The whole request — DNS resolve, TCP
connect, TLS handshake, send, receive, redirects — runs as events on a
caller-supplied AxlLoop with no nested loop, so it is safe to issue
from inside a loop callback or a resident driver-pump tick at raised TPL
(axl_loop_attach_driver) — where the sync calls would nest an ephemeral
loop and warn.
static void on_done(AxlHttpClientResponse *resp, AxlStatus st, void *user) {
if (st == AXL_OK) { // non-2xx is still AXL_OK (inspect status_code)
axl_printf("HTTP %zu\n", resp->status_code);
axl_http_client_response_free(resp); // the callback OWNS resp
}
}
// Returns AXL_OK => on_done WILL fire later; AXL_BUSY if a request is already
// in flight on this client (one in flight per client — use separate clients
// for concurrency); any other error => on_done does NOT fire.
axl_http_post_async(c, loop, "https://host/webhook",
body, body_len, "application/json",
/*cancel=*/NULL, on_done, user);
The body is borrowed until the callback fires (not copied). Pass
cb == NULL for fire-and-forget (the response is freed internally). https
requires axl_tls_init() once at startup. The sync axl_http_get/post
remain available and unchanged.
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=AximCodeValidity: current year to +10 years
SubjectAltName:
DNS:localhost,IP:127.0.0.1, plus any provided IP addressesOutput: DER-encoded certificate and private key (caller frees)
Entropy
mbedTLS needs random numbers for key generation and TLS handshakes. AXL provides entropy via:
EFI_RNG_PROTOCOL (hardware RNG) — preferred, used when available
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 --insecureor 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
-
AXL_NET_DRIVERS_NO_LINK
drivers loaded, but no SNP came up
-
AXL_NET_TRY_MAX_MACS
Max NIC MACs reported in one AxlNetTryResult.
Typedefs
-
typedef void (*AxlNetResolveDoneFn)(const AxlIPv4Address *addr, AxlStatus st, void *user)
AxlNetResolveDoneFn:
Completion callback for axl_net_resolve_async.
addris the resolved address on success — borrowed, valid only for the duration of the call, so copy it if needed — or NULL on failure.stis AXL_OK, an error, a timeout, or AXL_CANCELLED.
Enums
-
enum AxlPingReply
Classification of an ICMP probe reply (see
axl_net_ping_ex).Values:
-
enumerator AXL_PING_NO_REPLY
no response within the timeout
-
enumerator AXL_PING_ECHO_REPLY
the target answered — destination reached
-
enumerator AXL_PING_TIME_EXCEEDED
TTL expired at a hop (a traceroute hop)
-
enumerator AXL_PING_UNREACHABLE
destination/host/protocol/port unreachable
-
enumerator AXL_PING_FRAG_NEEDED
“fragmentation needed but DF set” (path-MTU)
-
enumerator AXL_PING_NO_REPLY
-
enum AxlNetConfigMethod
How
axl_net_init/axl_net_auto_initlast configured a NIC.The standard path is the firmware’s
EFI_IP4_CONFIG2_PROTOCOLpolicy layer. Some OEM firmware (e.g. HP business laptops) ships a full network stack — SimpleNetwork, MNP/ARP/IP4/TCP4/UDP4 andDhcp4ServiceBinding— but no IP4Config2. On such firmware the bring-up transparently falls back down a ladder; this enum records which rung succeeded so a consumer can surface it (e.g. “no IP4Config2 — configured via DHCP4-SB”) instead of a mystery.Values:
-
enumerator AXL_NET_CONFIG_NONE
not configured (no bring-up has succeeded)
-
enumerator AXL_NET_CONFIG_IP4CONFIG2
via EFI_IP4_CONFIG2_PROTOCOL (the standard path)
-
enumerator AXL_NET_CONFIG_DHCP4_SB
via EFI_DHCP4_SERVICE_BINDING (no IP4Config2)
-
enumerator AXL_NET_CONFIG_PXE_BC
via EFI_PXE_BASE_CODE_PROTOCOL.Dhcp (last resort)
-
enumerator AXL_NET_CONFIG_NONE
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_ping_ex(AxlIPv4Address *target, size_t timeout_ms, uint8_t ttl, bool dont_fragment, size_t payload_len, AxlPingResult *out)
Send one ICMP echo probe with explicit TTL / Don’t-Fragment control.
The building block for the two classic ICMP diagnostics:
traceroute: increment
ttlfrom 1 until the reply is AXL_PING_ECHO_REPLY (target reached), readingout->responderat each hop’s AXL_PING_TIME_EXCEEDED;path-MTU discovery: set
dont_fragmentwith a largepayload_lenand readout->next_mtufrom an AXL_PING_FRAG_NEEDED reply.
Note
AXL_PING_TIME_EXCEEDED / AXL_PING_FRAG_NEEDED require a real multi-hop / MTU-bottleneck path. QEMU’s SLIRP is a single-hop NAT, not a router, so those reply types only appear on real hardware; the AXL_PING_ECHO_REPLY path is exercised under QEMU.
- Parameters:
target – target IPv4 address
timeout_ms – timeout in milliseconds
ttl – IP TTL (1 = first hop; 0 selects the default 64)
dont_fragment – set the IP Don’t-Fragment bit
payload_len – ICMP payload bytes (0 selects 56; capped at 1472)
out – [out] probe result
- Returns:
AXL_OK if the probe completed — inspect
out->reply; a timeout is AXL_OK without->reply== AXL_PING_NO_REPLY. AXL_ERR on NULL args or if no IP4 stack is available.
-
int axl_sntp_query(const char *server, uint16_t port, size_t timeout_ms, AxlSntpResult *out)
Query an SNTP/NTP server for the current time (RFC 4330).
Sends a client SNTP request over UDP and parses the server’s transmit timestamp into Unix seconds. The clock
offset_ms(server minus the local UEFI RTC) is best-effort — it’s 0 when the firmware has no usable real-time clock.servermay be a hostname (resolved via DNS) or a dotted-decimal IPv4 address.- Parameters:
server – NTP server hostname or dotted-decimal IPv4
port – UDP port (0 selects the standard 123)
timeout_ms – response timeout in milliseconds
out – [out] query result
- Returns:
AXL_OK if the server answered (
out->reachable== true with a validout->unix_secs); AXL_ERR on NULL args, an unresolvableserver, no network, or a timeout (out->reachable== false).
-
int axl_net_arp_list(size_t nic, AxlArpEntry *out, size_t cap, size_t *count)
Read the ARP (IPv4 neighbor) cache for a network interface.
Lists the resolved IPv4<->MAC entries the firmware’s ARP layer holds for the
nic'thARP-capable interface — the UEFI equivalent ofarp -a/ the neighbor table. Only Ethernet (6-byte MAC) / IPv4 (4-byte) entries are reported.Note
The cache only holds neighbors the firmware has actually resolved (e.g. the gateway after DHCP); a quiet link can legitimately return count == 0.
- Parameters:
nic – ARP-capable interface index (0 = first)
out – [out] caller array (NULL to just count)
cap – capacity of
outin entriescount – [out] total entries found
- Returns:
AXL_OK on success —
countis the total entry count (which may exceedcap, signalling truncation;outmay be NULL to just count). AXL_ERR on NULLcount, no ARP-capable interface atnic, or a firmware error.
-
int axl_net_get_link_stats(size_t nic, AxlNetLinkStats *out)
Read physical-link stats for the
nic'thSimpleNetwork interface.Reports
link_upfrom the firmware’sEFI_SIMPLE_NETWORK_PROTOCOLmedia state — the reliably-available field.Note
UEFI exposes no portable link-speed/duplex/auto-neg surface: SimpleNetwork has no speed field, and where a driver reports it at all it’s via vendor
EFI_ADAPTER_INFORMATION_PROTOCOLinfo types this reader does not decode. Sospeed_bpsis typically 0,duplex0 (unknown),autonegfalse — including under QEMU. Treat them as best-effort andlink_upas authoritative.- Parameters:
nic – SimpleNetwork interface index (0 = first)
out – [out] link stats
- Returns:
AXL_OK on success (
outfilled); AXL_ERR on NULLoutor no SimpleNetwork interface atnic.
-
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.
-
int axl_net_resolve_async(const char *hostname, AxlLoop *loop, AxlCancellable *cancel, AxlNetResolveDoneFn cb, void *user)
Resolve a hostname to an IPv4 address asynchronously on
loop.The async peer of axl_net_resolve. The DNS4 query runs as an event source on
loop(no nested loop), so it is safe to call from inside a loop callback or a resident driver-pump at raised TPL — where the sync axl_net_resolve would nest an ephemeral loop and warn.cbfires exactly once: on success, failure, AXL_CANCELLED, or the internal timeout (a fixed 5 s, matching the sync resolver; there is no client to inherit one from). A dotted-decimal IP literal still completes viacbon a later tick (the callback is always deferred, never invoked re-entrantly from within this call). Each call is self-contained (its own DNS4 child); the callback owns nothing to free.- Parameters:
hostname – hostname or IPv4 literal
loop – loop to drive the query on
cancel – optional cancel token (NULL = uncancellable)
cb – completion callback (required)
user – opaque context for
cb
- Returns:
AXL_OK if the query was initiated (
cbWILL fire), or an error if it could not start (in which casecbdoes NOT fire).
-
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:
Calls axl_net_ensure_drivers() to locate and load NIC drivers from the standard driver search path.
Connects all SNP handles to trigger protocol stack creation.
Selects a NIC (by
nic_index, or first available if SIZE_MAX).Waits up to
dhcp_timeout_secfor 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.efiplus 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_takeover_if_no_snp(void)
Take over the NIC with staged drivers — only if the firmware provides NO SimpleNetwork stack of its own.
For OEM firmware that exposes its NIC only through proprietary drivers and publishes ZERO SimpleNetwork handles. This disconnects the firmware’s controllers and connects AXL’s staged drivers so a standard SNP/MNP/IP4/TCP4 stack comes up, then reconnects the stack.
Condition-gated by design: if any SimpleNetwork handle is already present — before, or after a plain
axl_net_ensure_driversattempt — this is a NO-OP and returns AXL_OK. An over-eager takeover destroys a working firmware stack (observed: forcing iPXE + disconnect on a box that already had SNP killed networking), so the guard is the point: takeover runs only when there is provably nothing to lose.- Returns:
AXL_OK if a SimpleNetwork handle is present after the call (whether it was already there — the no-op case — or the takeover brought one up); AXL_ERR if no SNP handle exists even after the takeover attempt.
-
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 totimeout_secfor a lease).static_ipv4!= NULL → static. Calls axl_net_drivers_up (load drivers + link wait, no DHCP timeout), then axl_net_set_static_ip withnetmask(defaulting to255.255.255.0if NULL) andgateway(NULL = no gateway). Sleeps 500 ms after to let IP4Config2 apply the change — the firmware applies the policy + address asynchronously and a subsequentGetDatacan still report the prior state without the settle.
In either case, on success
addr_outis populated via axl_net_get_ip_address (skipped ifaddr_outis 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_outpopulated 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
gatewayto 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_net_set_dns(size_t nic_index, const uint8_t dns[4], const uint8_t *dns2)
Set the DNS resolver(s) on a NIC.
Programs the IP4Config2 DNS-server list (Ip4Config2DataTypeDnsServer) — the missing setter beside axl_net_resolve (which only queries whatever resolver is configured). Pass a secondary in
dns2, or NULL for a single resolver. Works on both the static and DHCP paths (a DHCP box can override the leased resolver).nic_indexis a concrete index into the interface list (it does NOT accept AXL_NET_NIC_AUTO).- Parameters:
nic_index – concrete NIC index (from axl_net_list_interfaces)
dns – primary DNS server IPv4
dns2 – secondary DNS server IPv4 (NULL = none)
- Returns:
AXL_OK on success; AXL_ERR on NULL
dns, no IP4Config2 on the NIC, or a SetData failure.
-
int axl_net_set_hostname(const char *name)
Set the box’s hostname (persisted).
What this does and does not do. UEFI has no standard, firmware- advertised hostname (IP4Config2 carries IP / gateway / DNS but not a name, and there is no portable DHCP-option-12 hook through the IP4Config2 DHCP policy). So this persists
nameto a dedicated AXL non-volatile variable: a single source of truth an on-box UI sets + displays, configuration tools read, and an AXL-aware DHCP/identity consumer can advertise. It does NOT, by itself, cause the firmware’s DHCP client to send the name. Read it back with axl_net_get_hostname.- Parameters:
name – hostname (1..63 chars)
- Returns:
AXL_OK on success; AXL_ERR on NULL/empty
name, a too-long name, or a non-volatile-store write failure.
-
int axl_net_get_hostname(char *buf, size_t size)
Read the persisted hostname set by axl_net_set_hostname.
Writes the stored hostname (NUL-terminated, truncated to
size) intobuf. “Unset” is a normal, expected state (a fresh box), not an error: it returns AXL_OK withbuf== “”. Since axl_net_set_hostname rejects an empty name, “” unambiguously means “no hostname configured”. AXL_ERR is reserved for genuine faults (NULLbuf, zerosize).- Parameters:
buf – [out] hostname text (”” if none set)
size – capacity of
bufin bytes (64 is sufficient)
- Returns:
AXL_OK (a hostname, or “” when none is set); AXL_ERR on NULL
bufor zerosize.
-
int axl_net_wait_ip_settled(size_t nic_index, const uint8_t *expect_ipv4, size_t timeout_ms)
Wait until an IP4Config2 address change has settled.
IP4Config2 applies a policy / manual-address change asynchronously, so a read-back (axl_net_get_ip_address) immediately after axl_net_set_static_ip can still report the prior state. This polls the NIC’s InterfaceInfo until the address has taken or the timeout elapses — the diagnostic-correct replacement for a blind sleep, so a UI knows exactly when its read-back is valid.
expect_ipv4selects the settle signal:non-NULL — wait until StationAddress equals these 4 octets. This is the strong check after a static set: a stale prior non-zero address does NOT satisfy it, closing the “read back the old IP” race. (Used by axl_net_init_static, which knows the address it just set.)
NULL — wait until StationAddress is merely non-zero (any address taken), e.g. after kicking DHCP when the specific lease isn’t known up front.
nic_indexis a concrete index (it does NOT accept AXL_NET_NIC_AUTO). A settle is sub-second, so thetimeout_ms== 0 default is a deliberately short 1 s (not the 10 s DHCP-wait default).- Parameters:
nic_index – concrete NIC index (from axl_net_list_interfaces)
expect_ipv4 – 4 octets to wait for, or NULL = any non-zero
timeout_ms – max wait in ms (0 = 1 s default)
- Returns:
AXL_OK once the address is observed; AXL_ERR on a bad NIC index / no IP4Config2, or if it has not settled within
timeout_ms.
-
int axl_net_get_dhcp_lease(size_t nic_index, AxlDhcpLease *out)
Read a NIC’s active DHCP-leased configuration.
Reports the live IP4Config2 configuration for a NIC whose policy is DHCP: the leased address / mask / default gateway (from the interface’s route table) and the DHCP-provided resolver(s). A purely local, synchronous read (no network round-trip). A NIC on a static policy, or one that has not yet leased an address, is not a DHCP lease and returns AXL_ERR.
Warning
nic_indexindexes the IP4Config2 handle buffer, which is NOT the same index space asaxl_net_list_interfaces/axl_net_get_link_stats(those index the SimpleNetwork handle buffer, and IP4Config2 lives on a child handle on some OEM firmware). An out-of-range index is clamped to the first handle. On a multi-NIC box, passing a list-index here can therefore return a different NIC’s lease. Useaxl_net_get_dhcp_lease_by_macto look a lease up unambiguously by the NIC’s MAC (the stable key fromAxlNetInterface.mac). It does NOT accept AXL_NET_NIC_AUTO.- Parameters:
nic_index – IP4Config2-handle index (see
Warning
; not the SNP/list index)
out – [out] leased configuration
- Returns:
AXL_OK with
outfilled; AXL_ERR on NULLout, a bad NIC index, no IP4Config2 on the NIC, a non-DHCP policy, or no leased address.
-
int axl_net_get_dhcp_lease_by_mac(const uint8_t mac[6], AxlDhcpLease *out)
Read a NIC’s active DHCP-leased configuration, keyed by MAC.
The robust counterpart to
axl_net_get_dhcp_leasefor multi-NIC hosts: resolves the IP4Config2 instance by matching its SimpleNetwork MAC tomac(the same MAC correlationaxl_net_list_interfacesuses to fill its IPv4 columns), so the result is correct regardless of IP4Config2-vs-SNP handle ordering. The reported fields and the DHCP-policy / leased-address preconditions are identical toaxl_net_get_dhcp_lease.macis the stable key paired withAxlNetInterface.mac:iterateaxl_net_list_interfaces, then call this with a row’smac. Correlation is by exact 6-byte match and assumes MACs are unique; if two NICs share a MAC the first match in enumeration order wins.outis fully zeroed before any field is set, so on AXL_ERR every field reads 0.- Parameters:
mac – NIC MAC (from AxlNetInterface.mac)
out – [out] leased configuration
- Returns:
AXL_OK with
outfilled; AXL_ERR on NULLmacorout, no IP4Config2 NIC carrying that MAC, a non-DHCP policy, or no leased address.
-
AxlNetConfigMethod axl_net_last_config_method(void)
The mechanism that configured the NIC on the last bring-up.
Reflects the most recent
axl_net_init/axl_net_auto_init/axl_net_bring_upcall in this process.AXL_NET_CONFIG_NONEbefore any successful bring-up (or after one that failed). Process-global, not per-NIC — matches the single-NIC focus of the bring-up helpers.- Returns:
the active
AxlNetConfigMethod.
-
int axl_net_resolve_ptr(const AxlIPv4Address *ip, char *out, size_t cap)
Reverse-DNS (PTR) lookup: an IPv4 address to a hostname.
The reverse of axl_net_resolve — queries the in-addr.arpa PTR record for
ipvia DNS4 and writes the NUL-terminated name (truncated tocap) intoout. “No PTR record for this address” is a normal negative result (most addresses have none), reported as AXL_ERR — not a distinct status from a transport fault, matching axl_net_resolve’s forward direction.- Parameters:
ip – address to look up
out – [out] hostname buffer (NUL-terminated)
cap – capacity of
outin bytes
- Returns:
AXL_OK with
outfilled; AXL_ERR on NULLip/out, zerocap, no DNS4 resolver configured, or no PTR record forip.
-
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
sizebytes (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
sizebytes (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
aequalsbbyte-for-byte.
-
bool axl_ipv4_in_subnet(const uint8_t dest[4], const uint8_t station[4], const uint8_t mask[4])
True if
destis in the same subnet asstationgivenmask. 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.
-
int axl_net_get_driver_info(const uint8_t mac[6], AxlNetDriverInfo *out)
Resolve which driver is bound to a NIC and where it sits on the bus.
Correlates by MAC: enumerates SNP handles, finds the one whose controller MAC equals
mac(matching by MAC rather than handle index because some OEM firmware binds the addressing protocols to a child handle), then resolves the bound driver image name + binding layer and the trimmed device-path bus location intoout.The bound driver is found by walking past the SNP wrapper to the NII installer (the layer that actually claims the NIC) when present, and falling back to the SNP-installer agent otherwise — so the answer is the driver that owns the hardware, not the generic SnpDxe shim on top.
MAC is the stable key paired with AxlNetInterface.mac: iterate the interfaces from axl_net_list_interfaces(), then call this per row to fill the driver/bus columns.
outis fully zeroed before any field is set, so on AXL_ERR every field reads “” / 0.Correlation is by exact 6-byte match and assumes MACs are unique. If two NICs share a MAC — including the all-zero MAC some adapters report before link/SET_ADDRESS — this returns the first match in enumeration order; a UI needing a guaranteed 1:1 mapping should anchor on
bus_location, not MAC alone.- Parameters:
mac – NIC MAC (from AxlNetInterface.mac)
out – [out] resolved driver identity + bus location
- Returns:
AXL_OK if a NIC with
macwas found (fields are populated;driver/layerare “” if no driver is bound); AXL_ERR ifmacoroutis NULL, or no SNP handle carries that MAC.
-
int axl_net_list_available_drivers(AxlNetDriverFile *out, size_t *count)
List the NIC-driver files available to try on the driver search path.
Scans
drivers/<arch>/on every mounted FAT volume (the same search territory axl_driver_locate / axl_net_ensure_drivers use) for.efi/.efidrvfiles and reports each as an AxlNetDriverFile, so a UI can offer “try driver X / Y / Z”. Entries are de-duplicated by full path. No driver is loaded — this only enumerates files.Unlike axl_net_ensure_drivers (which loads a curated known list), this reports whatever is staged, including drivers AXL doesn’t know by name — a technician can drop a vendor driver into
drivers/<arch>/and have it appear here.Follows the query-count convention of axl_net_list_interfaces: call with
out= NULL to learn the count, then again with a sized buffer.- Parameters:
out – output array (NULL to query count)
count – [in/out] capacity / entries filled
- Returns:
AXL_OK on success (including zero drivers found); AXL_ERR if
countis NULL.
-
int axl_net_try_driver(const char *path_or_name, AxlNetTryResult *out)
Load one specific driver, connect it, and report whether it brought any NIC up — with a clean unload on failure.
The selective-retry primitive a “my NIC needs a different driver” tool is built on: load + start
path_or_name, run axl_net_connect_stack(), and measure the delta in SNP handles. The MACs of the NICs that newly came up are reported inoutso the UI can attribute the result. If nothing bound (snp_handles_added == 0) or load/start failed, the freshly-loaded image is unloaded again (out->unloaded= true) so the next candidate can be tried from a clean slate.path_or_namemay be a full UEFI path or a bare filename; a bare name is resolved through axl_driver_locate()’s search path (so a name from axl_net_list_available_drivers() works directly).iPXE must be tried LAST. When
path_or_nameis recognized as an iPXE driver — by filename heuristic, the same name list axl_net_ensure_drivers uses — this disarms iPXE’s 5-minute boot-services watchdog for you. Detection is best-effort: an iPXE- derived driver under an unrecognized filename (e.g. a relabeled vendor build) gets neither the watchdog disarm nor any ordering protection. Regardless, iPXE’s LoadImage hook breaks subsequent.efiloads in the same session — a hazard no unload can undo — so the caller must order any iPXE attempt after every other candidate. On the failure path the unload is still attempted, but its effect on an already-hooked session is not guaranteed (henceout->unloadedmeans “unload
returned success”, not “hook reverted”).
MediaPresent is advisory. Some firmware misreports link state, so
link_up== false is not a failure: an attempt that adds an SNP handle succeeds (AXL_OK) regardless of reported link. Treatlink_upas a hint, not a gate.Same trust model as axl_driver_load: this executes a
.efioff a mounted FAT volume with full firmware privileges. Don’t pass an attacker-controlledpath_or_name. Call at TPL_APPLICATION (LoadImage/StartImage/ConnectController require it).- Parameters:
path_or_name – driver path, or a bare name resolved on the search path
out – [out] attempt outcome (may be NULL)
- Returns:
AXL_OK if the driver loaded and added at least one SNP handle (
out->snp_handles_added> 0); AXL_ERR otherwise — driver not found, load/start failed, or it bound no NIC.out(if non-NULL) carries the detailed outcome in every case.
-
int axl_net_connect_stack(void)
Connect the driver stack onto every SNP handle.
Runs a global ConnectController (mirroring shell
connect -r) and then a per-SNP-handle reconnect, wiring the MNP / IP4 / TCP4 / UDP4 stack on top of each NIC. This is the step buried inside axl_net_ensure_drivers / axl_net_drivers_up, exposed on its own for two cases:ARM64 firmware that doesn’t auto-connect. Some platforms leave a bound NIC without its protocol stack until ConnectController runs; a “my NIC isn’t showing up” action can call this without a full network re-init.
Internally by axl_net_try_driver(), after loading a candidate.
Idempotent and side-effect-light — safe to call repeatedly. Call at TPL_APPLICATION (ConnectController requires it).
- Returns:
AXL_OK (the underlying connects are best-effort; a handle that declines to bind is not an error).
-
struct AxlPingResult
- #include <axl-net.h>
Result of an
axl_net_ping_exprobe.Public Members
-
AxlPingReply reply
what came back (NO_REPLY on timeout)
-
AxlIPv4Address responder
source IP of the reply (the hop, for traceroute)
-
size_t rtt_ms
round-trip time when a reply arrived (0 otherwise)
-
uint16_t next_mtu
FRAG_NEEDED: next-hop MTU from the ICMP message (0 if absent)
-
AxlPingReply reply
-
struct AxlSntpResult
- #include <axl-net.h>
Result of an
axl_sntp_query.
-
struct AxlArpEntry
- #include <axl-net.h>
One neighbor/ARP cache entry (IPv4 <-> MAC).
-
struct AxlNetLinkStats
- #include <axl-net.h>
Physical-link statistics for a NIC.
-
struct AxlDhcpLease
- #include <axl-net.h>
A NIC’s active DHCP-leased configuration, as held by the firmware’s persistent IP4Config2 layer.
The leased configuration — address, mask, gateway, resolver(s) — that the firmware applied and keeps live across application exits (IP4Config2 is a resident DXE driver, so this survives the tool that read it). A zeroed field means it is not configured (
routerall-zero = no default gateway).dns_countis 0..2 (the first two resolvers).Intentionally NOT here: the DHCP lease lifetimes (lease/T1/T2 seconds), the granting server’s identity, and the domain-name option. IP4Config2 discards those once it applies the lease (it stops its internal DHCP client and reconfigures the IP4 stack statically), so they cannot be read back from a transient tool. Surfacing them requires a resident driver that owns a bound DHCP client for the box’s lifetime — out of scope for this view.
-
struct AxlNetInterface
- #include <axl-net.h>
Network interface descriptor.
Public Members
-
char name[32]
interface name (“eth0”, “eth1”, …)
-
uint8_t mac[6]
MAC address.
-
bool link_up
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)
-
char name[32]
-
struct AxlNetDriverInfo
- #include <axl-net.h>
Bound-driver identity and bus location for one NIC.
Resolved by axl_net_get_driver_info(). Kept separate from AxlNetInterface deliberately: the resolution walks OpenProtocolInformation + the driver image’s device path + the firmware DevicePathToText protocol, which is far heavier than the plain SNP enumeration axl_net_list_interfaces() does — and that listing is polled on a 100 ms tick during link bring-up. A UI fills in the driver/bus columns by calling this once per row, off the hot path.
Public Members
-
char driver[64]
Bound driver’s image name. Four states, distinguishable by value:
the
.efifilename for a disk-loaded driver (e.g. “ipxe-intel.efi”);”<firmware volume>” for a driver dispatched from a firmware volume (e.g. OVMF’s VirtioNetDxe — its image path is an FV file GUID, not a printable name);
”<unknown>” when a driver IS bound but its image name can’t be resolved (the loaded image has no usable file path);
”” only when no driver is bound to the NIC (no BY_DRIVER agent on any of NII3.1 / NII / SNP). In that case
layeris “” too.
-
char layer[16]
Protocol layer the driver bound at — a closed set: “NII3.1” or “NII” when the binding installs EFI_NETWORK_INTERFACE_IDENTIFIER (UEFI driver-model NIC drivers — iPXE, vendor UNDI), “SNP” when the driver publishes Simple Network Protocol directly, or “” when no driver is bound (paired with
driver== “”).
-
char bus_location[160]
Stable hardware location, derived from the NIC’s UEFI device path with the network-addressing tail (MAC/IPv4/IPv6/VLAN/…) trimmed: the PCI/USB topology that anchors the NIC to a physical slot/port — e.g. “PciRoot(0x0)/Pci(0x3,0x0)” (PCI) or “PciRoot(0x0)/Pci(0x1,0x0)/USB(0x1,0x0)” (USB). Stable across reboots (unlike the enumeration index) and distinguishes two otherwise-identical NICs. NUL-terminated and truncated (never overflowed) if a deeply-nested path exceeds the field. “” when the device path is unavailable or the firmware DevicePathToText protocol is absent.
-
char driver[64]
-
struct AxlNetDriverFile
- #include <axl-net.h>
A NIC-driver .efi discovered on the standard search path.
Public Members
-
char name[64]
Driver filename, e.g. “ipxe-intel.efi”. NOT unique on its own — the same filename can be staged on more than one volume. Use
pathas the unambiguous key when calling axl_net_try_driver().
-
char path[256]
Full UEFI path where the file was found (the dedup key — entries are unique by path). Pass this to axl_net_try_driver() to try exactly this file rather than first-match-by-name.
-
uint64_t size
File size in bytes — lets a UI tell a heavyweight iPXE build (~280 KB-1 MB) from a lightweight vendor UNDI shim (~20-200 KB).
-
char name[64]
-
struct AxlNetTryResult
- #include <axl-net.h>
Outcome of an axl_net_try_driver() attempt.
Fully zeroed before the attempt and always populated when
outis non-NULL — including on AXL_ERR, so the caller can tell “driver not
found” from “loaded but bound no NIC”. On an early-out error every field reads false / 0.
Public Members
-
bool found
the driver file was located on the search path
-
bool loaded
LoadImage + StartImage succeeded
-
bool unloaded
The freshly-loaded image was unloaded again (failure rollback). false on success — the driver stays resident. Reflects “unload was
attempted and reported success”, which for the iPXE failure path is NOT a guarantee its LoadImage hook was reverted (see axl_net_try_driver).
-
uint32_t snp_handles_added
Count of NICs that newly produced an SNP handle as a result of this attempt — the honest attribution (the set of SNP handles present after connect that were not present before). This is the true count and may exceed AXL_NET_TRY_MAX_MACS;
bound_nic_macsholds the first AXL_NET_TRY_MAX_MACS of them.
-
bool link_up
at least one newly-bound NIC reports media present
-
size_t bound_nic_count
Number of MACs filled in
bound_nic_macs— min(snp_handles_added, AXL_NET_TRY_MAX_MACS). Equalssnp_handles_addedunless more than AXL_NET_TRY_MAX_MACS NICs bound.
-
uint8_t bound_nic_macs[8][6]
MACs of the NICs that newly produced an SNP handle (first
bound_nic_countentries valid).
-
bool found
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;
AxlNetOptsfactors it into a sub-struct the consumer embeds in its own options type, andaxl_net_init/axl_net_init_from_optsare the matching one-call DHCP bring-up.Pair with the
axl_config_descs_netgroup-injection helper in<axl/axl-config.h>to also pull the matching CLI / config descriptors into a consumer’s own table without copy-paste.Two distinct option groups live here, matching axl-sdk’s connection-vs-policy line:
AxlNetOpts+axl_config_descs_net— per-invocation connection options (which NIC, source/listen IP, port). These bind sockets; they do not touch firmware state.AxlNetStaticOpts+axl_config_descs_net_static— the IP4Config2 policy group (DHCP-vs-static, IP / mask / gateway / DNS / hostname). This is theifconfig-equivalent layer that mutates stateful system config; it is kept a separate struct + descriptor group precisely so the two concepts don’t blur. An on-box network-setup UI wants this group; a one-shot fetch client wants onlyAxlNetOpts.
IPv4 only for v1. An IPv6 / family-tagged variant is a clean future addition.
Maps to
SIZE_MAXwhen passed through to the bring-up call.axl_config_descs_netusesAXL_NET_NIC_AUTO_STR(the stringified value of this sentinel) as the descriptor default, soaxl_config_newauto-applies the sentinel into the embeddednic_indexfield with no extra wiring from the consumer.
-
AXL_NET_NIC_AUTO_STR
Decimal-string form of
AXL_NET_NIC_AUTO, suitable as anAxlConfigDesc.default_valuefor anAXL_CFG_UINTfield.The string is the unsigned decimal representation of
UINT64_MAX— pre-stringified rather than computed at runtime becauseAxlConfigDesc.default_valueis aconst char *that must be a compile-time constant. Surfaced on the public API so consumers synthesizing their own descriptors keep the “auto” semantics consistent withaxl_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
AxlNetOptsfields a consumer wants descriptors for. Pass toaxl_config_descs_net.AXL_NET_OPT_SOURCE_IPandAXL_NET_OPT_LISTEN_IPboth target the same underlyinglocal_ipfield — they differ only in CLI vocabulary (--source-ipfor clients,--listen-ipfor 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
—nic → nic_index
-
enumerator AXL_NET_OPT_SOURCE_IP
—source-ip → local_ip (client)
-
enumerator AXL_NET_OPT_PORT
—port → port
-
enumerator AXL_NET_OPT_LISTEN_IP
—listen-ip → local_ip (server)
-
enumerator AXL_NET_OPT_NIC
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_AUTOtoSIZE_MAXand delegates toaxl_net_bring_upwith a NULL static address (DHCP path). Address read-back is implicit; this helper does not surface the resolved IP — callaxl_net_get_ip_addressseparately if you need it.Static-IP configuration is intentionally out of scope: it mutates
IP4Config2policy, which is the firmware’sifconfiglayer, not a per-tool connection option. Tools that need to install a static IP callaxl_net_set_static_ipdirectly, or defer to UEFI Shellifconfig.- 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
AxlNetOptsbag.Thin thunk over
axl_net_initreadingopts->nic_index. Thelocal_ip/portfields 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
optsis NULL).
-
int axl_net_init_static(const AxlNetStaticOpts *cfg, uint64_t nic_index, size_t timeout_sec)
Apply an
AxlNetStaticOptspolicy bag — the one-call static (or DHCP) bring-up matching the DHCP-onlyaxl_net_init.Dispatches on
cfg->mode(NULL/””/”dhcp” = DHCP; “static” = static; any other value is rejected with AXL_ERR rather than silently defaulting):"static"— loads drivers + waits for link (no DHCP), applies the parsed ip / mask / gateway viaaxl_net_set_static_ip, then the resolver(s) viaaxl_net_set_dnsand the hostname viaaxl_net_set_hostnamewhen set, and waits for the new address to settle (polling for it, so a subsequentaxl_net_get_ip_addressread-back is valid)."dhcp"— runs DHCP viaaxl_net_init, then appliesdns/dns2/hostnameif set (a DHCP box overriding its resolver / naming).
Address read-back is implicit (as in
axl_net_init): callaxl_net_get_ip_addressif you need the resolved IP. NULL/”” fields are skipped;dnsempty skips the resolver set entirely (nodns2without adns). Malformed dotted-quads in a"static"bag fail the call (AXL_ERR) rather than silently configuring a wrong address.nic_indexacceptsAXL_NET_NIC_AUTO(mapped to the first usable NIC).- Parameters:
cfg – policy options bag
nic_index – AXL_NET_NIC_AUTO or 0-based NIC index
timeout_sec – DHCP wait (0 = 10 s default; ignored on static)
- Returns:
AXL_OK on success; AXL_ERR on NULL
cfg, an unrecognizedmode, a parse error, or a driver-load / link / DHCP / IP4Config2 failure.
-
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_index—AXL_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 byaxl_net_list_interfaces. Used at bring-up time to pick which NIC to run DHCP against. Post-bring-up, preferlocal_ipfor 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 rolesbind(2)the same way — the role is implied by whether the consumer subsequently callsconnect()orlisten(). CLI vocabulary is selected by theAXL_NET_OPT_SOURCE_IP/_LISTEN_IPenum bits below.port—uint16_tso it round-trips through the descriptor’sAXL_CFG_UINTparser without truncation.0means “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.
-
struct AxlNetStaticOpts
- #include <axl-net-opts.h>
IP4Config2 policy options — the on-box
ifconfigform.The struct an ifconfig UI binds its Configure form to: pick DHCP-vs-static and, for static, the address / mask / gateway / DNS / hostname. Pair with
axl_config_descs_net_static(in<axl/axl-config.h>) to emit the matching CLI / config descriptors without hand-authoring the form, and apply withaxl_net_init_static.Fields are
const char *(dotted-quad / name strings), NOT inline buffers — matchingAxlNetOpts.local_ipand, crucially, AxlConfig’s string auto-apply, which stores a borrowed pointer into the field (an inlinechar[]would silently fail to populate, or worse). So the normal flow is descriptor-driven: embed this struct, callaxl_config_descs_net_static, andaxl_config_newpoints each field at the interned config value. A consumer filling the struct by hand instead points the fields at its own buffers. Either way the pointed-at strings must outlive theaxl_net_init_staticcall (the AxlConfig’s lifetime, in the descriptor-driven case).axl_net_init_staticparses them; NULL or “” means “unset / leave unchanged” for every field.Field semantics:
mode—"dhcp"(the default; NULL/”” also means DHCP) or"static". The descriptor ships these as itschoicesso a UI can render a two-value picker; note AxlConfig does not itself enforce choices on the set path —axl_net_init_staticis the validator and rejects an unrecognized non-emptymode.ip/netmask/gateway— the static address (used only whenmode == "static").gatewayempty = no gateway.dns/dns2— primary / secondary resolver (applied on both the DHCP and static paths when set — a static box and a DHCP box that wants an override both set these).dnsempty = leave the firmware/DHCP-provided resolver in place (anddns2is then ignored — no secondary without a primary).hostname— the box’s hostname. Empty = leave unchanged. Seeaxl_net_set_hostnamefor what “set” means (a persisted value; UEFI has no firmware-advertised hostname).
Public Members
-
const char *mode
“dhcp” (default; NULL/”” = dhcp) or “static”
-
const char *ip
static IPv4 dotted-quad (mode = static)
-
const char *netmask
subnet mask (mode = static)
-
const char *gateway
default gateway; NULL/”” = none
-
const char *dns
primary DNS server; NULL/”” = leave as-is
-
const char *dns2
secondary DNS server; NULL/”” = none
-
const char *hostname
hostname; NULL/”” = leave unchanged
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_portis 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
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 AxlSocketAddress AxlSocketAddress
-
typedef bool (*AxlSocketCallback)(AxlSocket *sock, AxlStatus status, void *data)
Callback for async socket operations.
sockis the socket for the completed operation (new socket for accept, or the original socket for send/recv/connect).statusis 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,sockmay be NULL (accept) or half-initialized (connect). Always checkstatusbefore usingsock.Return value controls re-arming for ops that support it:
axl_socket_receive_async: true = re-arm with same buffer, false = stopaxl_socket_accept_async: true = keep listening (default), false = stop acceptingaxl_socket_connect_async,axl_socket_send_async: ignored (one-shot ops). Convention:return true;.
Enums
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.
sizeis 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:
truekeeps the listener armed,falsestops 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 intobuf(truncated if larger thansize).The buffer must stay valid across re-arms. Re-arming is controlled by the callback’s return value:
truere-arms with the same buffer,falsestops 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.
sockis 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.statusis 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 = stopaxl_tcp_accept_async: true = keep listening (default), false = stop acceptingaxl_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_ipis 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:skip interfaces whose station IP is 0.0.0.0
prefer an interface whose subnet contains the destination
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_connect_timeout(const char *host, uint16_t port, const AxlIPv4Address *source_ip, size_t connect_timeout_ms, AxlTcp **out_sock)
Connect via TCP4 with an explicit connect-phase timeout.
The timeout-aware form of axl_tcp_connect_via: it bounds the SYN / handshake wait with
connect_timeout_msinstead of the fixed 10 s default the simpler entry points use. A consumer talking to an operator-supplied endpoint (a webhook URL, a REST host) uses this so an unreachable target — a silently-dropped SYN — fails fast instead of stalling the caller’s event loop for ~10 s. axl_tcp_connect and axl_tcp_connect_via are exactly this call withconnect_timeout_ms= 0.connect_timeout_ms== 0 keeps the 10 s default (matching the send/recv convention where 0 means “the default”, not “forever” — a connect with no deadline is rarely what a caller wants). The timeout is an AXL-side deadline: it fires on the loop and cancels the connect, so it bounds the wait regardless of how the underlying transport behaves.- Parameters:
host – IPv4 string or hostname (DNS resolved)
port – remote port number
source_ip – pin to this local station IP, or NULL to auto-pick
connect_timeout_ms – connect deadline in ms (0 = 10 s default)
out_sock – receives the connected socket handle
- Returns:
AXL_OK on success; AXL_ERR on failure, including a timeout (the SYN was not answered within
connect_timeout_ms) and “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_ipis 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-bindingDestroyChild+ 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 afteraxl_loop_runreturned) 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_closeBEFOREaxl_loop_freeon 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.**
sockoutlives this call.** On the async path the AxlTcp struct lives until the firmware signals close-complete. Callers must not touchsockafteraxl_tcp_closereturns; 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 ifcancelwas 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_ipsemantics 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:
truekeeps the listener armed for the next connection,falsestops accepting. Callaxl_tcp_closeon 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_asynccan be called again to resume listening. Close the listener withaxl_tcp_closewhen 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:
truere-arms the recv with the same buffer (typical for streaming servers);falsestops 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
AxlCancellableto the original call and signal it; the cancel callback fires cleanly withAXL_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 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).statusis 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.statusis an AxlStatus value:AXL_OK on a delivered datagram (
data/len/fromdescribe the payload)AXL_ERR on a UEFI-reported recv error (token Status non-zero or RxData NULL);
datais NULL,lenis 0AXL_CANCELLED if the cancellable passed to axl_udp_recv_async was signalled before the next datagram arrived;
dataNULL,len0
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_ipis 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.
addris filled with the dotted-decimal station address (min 16 bytes);out_portreceives 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
bufmust 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
sockis 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+RemotePortset topeer/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_asynccalls accept NULLdest— the configured peer is used. Passing a non-NULLdeststill 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_sendrequires an explicitdest.- 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
groupis 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 forJoinFlag=FALSEwithMulticastAddress = 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 at the protocol level — no response expected. The call still blocks until the local Transmit completes (a few ms for a datagram), so it is synchronous.
Safe to call from an
axl_loop_attach_driverpump callback (atTPL_CALLBACK): the nested completion wait is raised-TPL-safe. It busy-holdsTPL_CALLBACKfor the brief Transmit, so it adds a small latency spike to co-located work on the same pump but does not wedge. For a non-blocking send that returns immediately and reports completion via a callback, useaxl_udp_send_async(the caller must keepdataalive until the callback fires).destmay be NULL only if the socket has been pinned to a peer via axl_udp_connect (the configured peer is used). Otherwisedestis 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
cbfor 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_stoppair: 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 nouser[:pass]@in authority)
-
char *password
userinfo password portion after
:(NULL if no:in userinfo; “” ifuser:@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)
-
char *scheme
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
buffor 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
lenis 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
datauntil 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
headersis NULL.
AxlHttpServer
Defines
-
AXL_WS_CONNECT
-
AXL_WS_TEXT
-
AXL_WS_BINARY
-
AXL_WS_DISCONNECT
-
AXL_ROUTE_NO_AUTH
open: no authentication
Route auth flags, passed to every
*_authregistration function. Any non-zero value requires the server’s auth callback (axl_http_server_use_auth) to succeed — if NO callback is registered, a gated request is rejected 401 unconditionally.
-
AXL_ROUTE_AUTH
any authenticated user (callback succeeds; 401 otherwise)
-
AXL_ROUTE_ADMIN
authenticated AND role >= AXL_ROUTE_ADMIN (403 otherwise)
-
AXL_CACHE_FOREVER
-
AXL_SERVE_FS_READONLY
Serve read-only: PUT / DELETE / MKCOL / MOVE / COPY are rejected with 405 Method Not Allowed; GET / HEAD / PROPFIND / OPTIONS work.
-
AXL_SERVE_FS_NO_DELETE
Reject the DELETE verb (405). Scope is literal: DELETE only. MOVE is still allowed (and a MOVE relocates, removing the source from its original path) — use AXL_SERVE_FS_READONLY for “nothing changes”.
-
AXL_SERVE_FS_NO_OVERWRITE
PUT / MOVE / COPY must not replace an existing resource: an overwrite is refused. Enforced by a destination existence-check in the ops (PUT opens with exclusive-create; MOVE/COPY pre-stat the destination —
axl_file_moveitself replaces unconditionally, so it is never delegated the check). A refusal surfaces as 409 Conflict — the ops layer has no channel to return the RFC-ideal 412 in v1.
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_bufand report the byte count viaout_size. ReturningAXL_OKwith*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_bufin bytes- Param out_size:
[out] bytes written into
out_bufthis call; 0 = EOF- Return:
AXL_OKto continue (including the EOF case with*out_size== 0);AXL_ERRto 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
ctxholds onto. Pass NULL if the streamer manages its own lifecycle.- Param ctx:
the same
ctxpointer 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 struct AxlWsConn AxlWsConn
Opaque per-connection WebSocket handle (one per connected client), distinct from the per-endpoint registration. Passed to an AxlWsConnHandler and the axl_ws_* accessors below.
-
typedef int (*AxlWsConnHandler)(AxlWsConn *conn, size_t event, const void *frame, size_t frame_size, void *data)
Per-connection WebSocket event callback.
The richer sibling of AxlWsHandler: it additionally receives the AxlWsConn the event is for, enabling per-client reply (axl_ws_send), identity (axl_ws_conn_auth / axl_ws_conn_peer), and per-connection session state (axl_ws_conn_set_user_data). Registered via axl_http_server_add_websocket_ex.
eventis AXL_WS_CONNECT (stash session state now), AXL_WS_TEXT / AXL_WS_BINARY (a frame), or AXL_WS_DISCONNECT (tear session state down).connis valid for the duration of the call, and for any later axl_ws_* call while the connection is open — but NOT after this handler returns from AXL_WS_DISCONNECT.datais the per-endpoint opaque from registration (shared by all clients on the path).AXL_WS_CONNECT fires AFTER the 101 handshake response has been sent, so an
axl_ws_sendfrom it (a greeting / banner) is valid. Returning AXL_ERR from AXL_WS_CONNECT rejects the connection — it is closed with no further events (no DISCONNECT). For AXL_WS_TEXT / _BINARY the return is currently ignored; call axl_ws_conn_close to drop a connection mid-stream.- Return:
AXL_OK to keep the connection; AXL_ERR from CONNECT to reject it.
-
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
respfields 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).
respis 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.
-
typedef struct AxlFsRoot AxlFsRoot
A filesystem subtree bound to a WebDAV path-space.
The
user_dataforaxl_fs_webdav_ops(). Maps each relative WebDAV path onto a path withinfs_rootunder the given access flags.Traversal containment (security guarantee): the request path is normalized against the mount root first —
./..components are resolved away and any..that would climb above the mount is rejected (404) — so the mapped path is always insidefs_root. A request like/../../FS0:/secretcannot escape the served subtree.Opaque; create with
axl_fs_root_new, free withaxl_fs_root_free.
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
sis 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 sentinelNULLmethod terminates the list. Stops on the first registration failure and returnsAXL_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 — requiredReplaces the equivalent five separate
axl_http_server_add_routecalls 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.connectionsconfig, opens the TCP listener (pinned tolisten.ipif set, else auto-pick), and registers the async accept onloopso each incoming connection re-arms automatically. The server is fully wired and listening when this returns; the caller drivesloopviaaxl_loop_run(foreground) oraxl_loop_attach_driver(DXE driver mode).axl_http_server_runis 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 inaxl_loop_rununtilaxl_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
Acceptheader advertise interest inmime?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 explicitq=0reject 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
reqwould accept a response of MIME typemime.
-
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 freereq->bodywhile the reader is in use, and callaxl_json_freeonoutwhen 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 (
outpopulated and ready foraxl_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 —
.rodataC 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-sendaxl_free. Passing such a pointer to the axl_http_response_set_text /_jsonfamily would force a copy (waste); assigning it to AxlHttpResponse.body directly causes heap corruption (the dispatch loop would treat the literal as anaxl_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_bytes(AxlHttpResponse *r, const AxlBytes *body, const char *content_type)
Set the response body from an AxlBytes.
Convenience for handlers that already hold their payload as an AxlBytes — a file read via axl_file_get_bytes, a cached blob, a slice. The bytes are COPIED into the response’s owned body (the SDK frees that copy after the response is sent), so the caller may unref body immediately. This is not a zero-copy path: the contiguous-body send copies into the transmit buffer regardless, so for large payloads prefer axl_http_response_set_file / axl_http_response_set_streamer.
content_type is borrowed (typically a string literal); NULL leaves the existing content-type unchanged. An empty body sends an empty payload.
- Parameters:
r – response
body – payload to copy in
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
streamerrepeatedly to fill the buffer andaxl_tcp_send_async’s each filled chunk (chained completions). EOF (returned via*out_size = 0) terminates the response.Sets Content-Length from
total_sizewhen known (the typical file-serve case). Pass(size_t)-1to 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 a0\r\n\r\nfinal chunk.ctxis 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 filectxwraps, 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
lengthbytes starting at(uint8_t *)data + offsetinto a freshly-allocated body, setsstatus_code = 206, setscontent_type = "application/octet-stream", and emits aContent-Range: bytes <offset>-<offset+length-1>/<total_size>header per RFC 9110 §15.3.7. Allocatesr->headersif 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-Rangeheader 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 = 206separately — this helper only formats and inserts the header.Allocates
r->headersif not already present, usingaxl_hash_table_new_fullwithaxl_free_impldestructors for BOTH keys and values. If the consumer pre-allocates themselves, it MUST be created with the same destroy-func contract (e.g. via). Mixing in aaxl_hash_table_new_full(
axl_str_hash, axl_str_equal, axl_free_impl, axl_free_impl)
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_typeis 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.
Each client’s frame is queued on a per-connection outbound FIFO and sent one at a time (the transport is one-send-in-flight, and over TLS frames must serialize or the stream desyncs), so a burst of broadcasts is delivered in order without racing. Under sustained back-pressure (a slow client) the queue is bounded and drops the oldest unsent frames — lossy by design, which suits a live feed (a console mirror, a metrics stream); a consumer that needs every byte must apply its own flow control.
- 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_set_auth_challenge(AxlHttpServer *s, const char *scheme, const char *realm)
Set the WWW-Authenticate challenge emitted on a 401.
When
schemeis set, every 401 the server returns carriesWWW-Authenticate: <scheme> realm="<realm>"(the realm param is omitted whenrealmis NULL), so interactive clients — browsers, macOS Finder, Windows Explorer — prompt for credentials instead of showing a bare 401.schemeis typically"Basic". Passing a NULLschemeclears the challenge (the default: no header, which only works with clients that send credentials preemptively).schememust contain no whitespace; neither argument may contain a quote or CR/LF (rejected to prevent header injection).- Parameters:
s – server
scheme – e.g. “Basic”; NULL clears the challenge
realm – realm label, or NULL to omit the realm
- Returns:
AXL_OK on success; AXL_ERR on a NULL server, an invalid scheme/realm, or allocation 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_add_websocket_ex(AxlHttpServer *s, const char *path, AxlWsConnHandler handler, void *data, uint32_t auth_flags)
Register a WebSocket endpoint with per-connection callbacks + auth.
Like axl_http_server_add_websocket, but the handler is an AxlWsConnHandler (per-client handle) and the upgrade is gated by
auth_flagsexactly like an HTTP route: AXL_ROUTE_NO_AUTH opens it; AXL_ROUTE_AUTH / AXL_ROUTE_ADMIN run the server’s auth callback (axl_http_server_use_auth) against the upgrade request and reject it 401 / 403 — no handshake — on failure, so an authenticated endpoint never completes the upgrade for an unauthorized client. On success the resolved AxlAuthInfo is attached to the connection (axl_ws_conn_auth).A given path is registered with EITHER this or add_websocket, not both.
- Parameters:
s – server
path – WebSocket endpoint path
handler – per-connection event handler
data – per-endpoint opaque
auth_flags – AXL_ROUTE_NO_AUTH / _AUTH / _ADMIN
- Returns:
AXL_OK on success; AXL_ERR on NULL
s/path/handler, the route table being full, or OOM.
-
int axl_ws_send(AxlWsConn *conn, size_t opcode, const void *data, size_t size)
Send a frame to one WebSocket client.
The per-client counterpart of axl_http_server_ws_broadcast: delivers exactly to
conn.opcodeis AXL_WS_TEXT or AXL_WS_BINARY. Safe to call from within the handler — including AXL_WS_CONNECT (a greeting), which fires after the 101 — or from any later code holding a still-openconn(e.g. a terminal pump). NOT valid after that connection’s AXL_WS_DISCONNECT handler has returned.Like axl_http_server_ws_broadcast, frames are queued on the connection’s outbound FIFO and serialized over the one-send-in-flight transport, so back-to-back sends are delivered in order (and never desync TLS); the queue is bounded and drops oldest-unsent under sustained back-pressure.
- Parameters:
conn – target client
opcode – AXL_WS_TEXT or AXL_WS_BINARY
data – frame payload
size – payload size in bytes
- Returns:
AXL_OK on success; AXL_ERR on NULL
conn/data, a badopcode, a closed / non-WebSocket connection, or a send failure.
-
int axl_ws_conn_auth(AxlWsConn *conn, AxlAuthInfo *out)
Read the authenticated identity captured at upgrade.
Fills
outwith the AxlAuthInfo resolved when the connection upgraded on an AXL_ROUTE_AUTH / _ADMIN endpoint. An AXL_ROUTE_NO_AUTH endpoint has no identity: returns AXL_ERR withoutuntouched.- Parameters:
conn – connection
out – [out] identity (username / role)
- Returns:
AXL_OK with
outfilled; AXL_ERR on NULL args or an unauthenticated endpoint.
-
int axl_ws_conn_peer(AxlWsConn *conn, uint8_t out[4])
Get the peer (client) IPv4 address of a WebSocket connection.
Writes the 4 octets of the client’s address into
out(matching the AxlNetInterface.ipv4 byte layout).- Parameters:
conn – connection
out – [out] client IPv4 (4 octets)
- Returns:
AXL_OK with
outfilled; AXL_ERR on NULL args or an address that could not be determined.
-
void axl_ws_conn_set_user_data(AxlWsConn *conn, void *user)
Attach a per-connection user pointer (a session object).
A slot for the consumer’s own per-client state (a terminal session, a subscription set), distinct from the per-endpoint
datashared by all clients on the path. Set it on AXL_WS_CONNECT, use it per frame, free it on AXL_WS_DISCONNECT — AXL never frees it (the consumer owns the lifetime).- Parameters:
conn – connection
user – consumer-owned pointer (AXL does not free it)
-
void *axl_ws_conn_user_data(AxlWsConn *conn)
Read the per-connection user pointer set by axl_ws_conn_set_user_data (NULL if never set).
- Parameters:
conn – connection
-
int axl_ws_conn_close(AxlWsConn *conn)
Close a specific WebSocket connection (server-initiated).
Sends a WebSocket close frame and tears the connection down; the handler’s AXL_WS_DISCONNECT fires as usual. Use for a logout / idle timeout.
connis invalid after the disconnect handler returns.- Parameters:
conn – connection to close
- Returns:
AXL_OK on success; AXL_ERR on a NULL / closed connection.
-
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_msmapping; the next cached response whose request path equalspathexactly uses this TTL instead of the server-wide default fromaxl_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_upload_route_auth(AxlHttpServer *s, const char *method, const char *path, AxlUploadHandler handler, void *data, uint32_t auth_flags)
Register a streaming upload route with authentication requirements.
Like
axl_http_server_add_upload_route, but the route is gated by the server’s authentication callback (axl_http_server_use_auth). The callback runs BEFORE a single body byte reaches the upload handler — a failed check sends 401 (or 403 when anAXL_ROUTE_ADMINroute is presented a lesser role) and the body is never streamed. PassingAXL_ROUTE_NO_AUTHis identical to the non-auth variant.Auth for uploads is necessarily header-based (cookie / Authorization / client address): the body is not materialized, so the auth callback sees a request whose
bodyis NULL.- Parameters:
s – server
method – HTTP method
path – path pattern
handler – upload handler
data – opaque caller data
auth_flags – AXL_ROUTE_* flags
- 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, COPY) under
<prefix>/<wildcard>that driveops. Theopstable is COPIED into the server — caller may free / re-use the struct after this returns.user_datais 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.
-
int axl_http_server_add_webdav_auth(AxlHttpServer *s, const char *prefix, const AxlWebDavOps *ops, void *user_data, uint32_t auth_flags)
Mount a WebDAV handler with authentication requirements.
Like
axl_http_server_add_webdav, butauth_flagsis applied to every verb route the mount registers — including the streaming PUT, which is gated before any body byte is read (seeaxl_http_server_add_upload_route_auth). The server’s authentication callback (axl_http_server_use_auth) therefore gates the whole mount with no per-route glue.AXL_ROUTE_NO_AUTHleaves it open — identical toaxl_http_server_add_webdav.- Parameters:
prefix – URL prefix, e.g. “/dav”
ops – callback table (copied)
user_data – opaque, passed back to ops
auth_flags – AXL_ROUTE_* flags applied to every verb route
- Returns:
AXL_OK on success, AXL_ERR on bad arguments or if the server already has the maximum number of WebDAV mounts.
-
AxlFsRoot *axl_fs_root_new(const char *fs_root, uint32_t flags)
Bind a filesystem subtree to serve via WebDAV.
fs_rootis an axl filesystem path — a volume root like"FS0:"or a subdirectory like"FS0:\\share"(a RAM-disk volume works too). Either separator works ("FS0:/share"too); a trailing separator is accepted and normalized away. The WebDAV root path/maps tofs_rootitself, solist_dir("/")listsfs_root’s entries. The returned root is theuser_datayou pass toaxl_http_server_add_webdavalongsideaxl_fs_webdav_ops(). It is borrowed by the mount, never freed byadd_webdav— you free it once withaxl_fs_root_freeafter every server using it is freed (it may back more than one mount).- Parameters:
fs_root – filesystem base path (e.g. “FS0:” or “FS0:\share”)
flags – AXL_SERVE_FS_* access flags
- Returns:
new root, or NULL on OOM / NULL
fs_root.
-
const AxlWebDavOps *axl_fs_webdav_ops(void)
The generic axl-fs-backed WebDAV callback table.
Each callback maps to an
<axl/axl-fs.h>primitive (list_dir →axl_dir_*, stat →axl_file_info, read →axl_file_view, write →AxlFileWriter, mkdir →axl_dir_mkdir, remove →axl_file_delete/axl_dir_rmdir, move →axl_file_move, copy → stream read+write / recursive). Pass it toaxl_http_server_add_webdavwith anAxlFsRoot*asuser_data. A consumer that wants to wrap or extend the behavior can copy this table and override individual callbacks.The returned table is the full read-write set regardless of any AxlFsRoot flags — the flags are applied by
axl_http_server_serve_fs, which drops the mutating callbacks from its own copy for READONLY / NO_DELETE. A consumer copying this table for the READONLY case should NULL the mutators itself (the handler returns 405 for a NULL op).- Returns:
pointer to a static, immutable ops table (do not free). All state lives in the AxlFsRoot user_data, so the table is re-entrant across mounts.
-
int axl_http_server_serve_fs(AxlHttpServer *s, const char *prefix, const char *fs_root, uint32_t flags, uint32_t auth_flags)
Mount a filesystem subtree as a WebDAV file server. One call.
Equivalent to
axl_fs_root_new(fs_root, flags)+ a copy ofaxl_fs_webdav_ops()(with mutating callbacks dropped per the flags) registered viaaxl_http_server_add_webdavatprefix. The server owns the root and frees it onaxl_http_server_free.The mount is traversal-contained: a request path is normalized and any
..escapingfs_rootis rejected (404), so the server can only ever touch files withinfs_root(see AxlFsRoot).Auth/role gating is built in:
auth_flagsis applied to every verb route, including the streaming PUT, so the server’s authentication callback (axl_http_server_use_auth) gates the whole mount. PassAXL_ROUTE_NO_AUTHfor an open mount,AXL_ROUTE_AUTHto require a logged-in user, orAXL_ROUTE_ADMINto require the admin role.- Parameters:
prefix – URL prefix, e.g. “/dav”
fs_root – filesystem base path (e.g. “FS0:”)
flags – AXL_SERVE_FS_* access flags
auth_flags – AXL_ROUTE_* flags; gates the whole mount
- Returns:
AXL_OK on success, AXL_ERR on bad arguments, OOM, or if the server already has the maximum number of WebDAV mounts.
-
struct AxlHttpRequest
-
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)-1to signal unknown length — the dispatcher emits Transfer-Encoding: chunked instead.
-
size_t status_code
-
struct AxlHttpRange
- #include <axl-http-server.h>
Parsed byte range from an HTTP Range request header.
-
struct AxlAuthInfo
Public Members
-
const char *username
-
size_t role
Caller-defined privilege level returned by the auth callback. A route gated with AXL_ROUTE_ADMIN admits the request only when
role >= AXL_ROUTE_ADMIN(i.e. >= 2) — the route flag doubles as the role threshold. Lower values authenticate but are not admin.
-
const char *username
-
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/davand 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_OKon success,AXL_ERRon 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_openreturns an opaque ctx the SDK threads intoread_chunk(drives the response-body streamer) andread_close(idempotent finalize).write_openlikewise;write_chunkreceives 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
srctodst, leavingsrcin place.depthis 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-statssrcwhen 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 matchingDigest: <algo>=<hex>response header.algois the lowercased canonical algorithm name as the client requested it (typically"sha-256"; legacy"sha-1"and"md5"are also forwarded if requested).out_hexis a caller-allocated buffer ofhex_sizebytes the consumer fills with the lowercase hex digest + trailing NUL. (Consumer always produces hex; the SDK emits hex per the RFC 3230id-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); readingreq->path/req->methodto 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
digestcallback 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” — idle/per-phase timeout in ms, re-armed on progress (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 struct AxlLoop AxlLoop
-
typedef struct AxlCancellable AxlCancellable
-
typedef void (*AxlHttpClientDoneFn)(AxlHttpClientResponse *resp, AxlStatus st, void *user)
AxlHttpClientDoneFn:
Completion callback for the async HTTP requests. On success
stis AXL_OK andrespis the response — the callback TAKES OWNERSHIP and MUST free it with axl_http_client_response_free(). On failure (transport error, timeout, or AXL_CANCELLED)stis the error code andrespis NULL. A non-2xx HTTP status is still AXL_OK with a non-NULLresp(inspect status_code).
-
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_bufand reports the byte count viaout_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_get_async(AxlHttpClient *c, AxlLoop *loop, const char *url, AxlCancellable *cancel, AxlHttpClientDoneFn cb, void *user)
Asynchronous HTTP GET — the async peer of axl_http_get.
Drives the whole request (DNS resolve, connect, TLS handshake, send, receive, redirects) as events on
loopwith NO nested loop, so it is safe from inside a loop callback or a resident driver-pump at raised TPL — where the sync axl_http_get nests an ephemeral loop (and now warns).cbfires exactly once on completion.Uses
c'sconfiguration (timeout, default headers, redirect limit, TLS verification) and connection state (keep-alive), exactly as the sync API. For https, axl_tls_init() must have been called once at startup. The sync axl_http_get is now a thin wrapper over this on a private ephemeral loop.One in-flight request per client. If a request is already running on
c, this returns AXL_BUSY andcbdoes NOT fire — use a separate client for concurrency, or wait for the priorcb. Do NOT axl_http_client_free()cwhile a request is in flight; cancel viacancel(or wait forcb) first.Fire-and-forget: if
cbis NULL the request still runs to completion and the response is freed internally — note this discards ALL post- initiation outcomes, including non-2xx responses and transport errors. Pass a minimalcbif you need to observe failures.- Parameters:
c – client (carries config + connection)
loop – loop to drive the request on
url – full URL string
cancel – optional cancel token (NULL = uncancellable)
cb – completion callback, or NULL (fire-and-forget)
user – opaque context for
cb
- Returns:
AXL_OK if the request was initiated (
cbWILL fire later); AXL_BUSY if a request is already in flight; another error if it could not start.cbfires later IFF this returns AXL_OK.
-
int axl_http_post_async(AxlHttpClient *c, AxlLoop *loop, const char *url, const void *body, size_t size, const char *content_type, AxlCancellable *cancel, AxlHttpClientDoneFn cb, void *user)
Asynchronous HTTP POST — the async peer of axl_http_post.
See axl_http_get_async for the loop / ownership / one-in-flight (AXL_BUSY) / cancel / TLS / fire-and-forget contract.
bodyis borrowed — it must stay valid untilcbfires (it is not copied). Contiguous body only; there is no async streaming-body variant — use the sync axl_http_request_streaming for multi-chunk uploads.- Parameters:
c – client (carries config + connection)
loop – loop to drive the request on
url – full URL string
body – request body (borrowed until
cbfires)size – body size in bytes
content_type – MIME type (e.g. “application/json”), or NULL
cancel – optional cancel token (NULL = uncancellable)
cb – completion callback, or NULL (fire-and-forget)
user – opaque context for
cb
- Returns:
AXL_OK if the request was initiated (
cbWILL fire later); AXL_BUSY if a request is already in flight; another error otherwise.
-
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
streamerrather than a contiguous buffer. Use for multi-chunk uploads where the body isn’t materialized in RAM (UEFI Shellcpto a mounted volume, large-file PUT, generated content, etc.).When
total_sizeis known, the client emits aContent-Lengthheader and sends the body as raw bytes; if the streamer signals EOF beforetotal_sizebytes are produced the request fails with AXL_ERR. Pass(size_t)-1to useTransfer-Encoding: chunkedinstead — 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
pathread-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)
-
AxlBytes *axl_http_client_response_get_bytes(const AxlHttpClientResponse *resp)
Take the response body as an AxlBytes snapshot.
Copies the response body into a reference-counted AxlBytes the caller owns, so it can outlive axl_http_client_response_free and be passed to other subsystems (a parser, a hasher, a cache) as the shared byte-blob currency. The response itself is unchanged.
- Parameters:
resp – response to snapshot
- Returns:
a new AxlBytes (release with axl_bytes_unref), or NULL if
respis NULL, the body is empty, or on allocation failure.
-
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