Networking
The oveRTOS networking subsystem provides a portable BSD-like socket API, DNS resolution, TLS encryption, and higher-level protocol clients (HTTP, MQTT) and servers (HTTPD with WebSocket support). The stack runs on all four backends with zero-heap support throughout.
All networking modules require CONFIG_OVE_NET. When it is not set, every function is replaced by a static inline stub that returns OVE_ERR_NOT_SUPPORTED.
Two stacks: blocking vs async
oveRTOS exposes two TCP/IP stacks via separate Kconfig options. Pick one per build — they are mutually exclusive at the transport layer because both want to own the MAC + ARP cache + descriptor ring.
CONFIG_OVE_NET (blocking) |
CONFIG_OVE_ASYNC_NET (async, Rust-only) |
|
|---|---|---|
| TCP/IP stack | lwIP (FreeRTOS) / Zephyr net / NuttX net / POSIX | smoltcp via embassy-net |
| Language | C, C++, Rust, Zig | Rust only |
| API style | Blocking BSD sockets | embassy_net::TcpSocket, async/await |
| TLS | mbedTLS in-tree (net_tls) |
embedded-tls (crates.io) |
| HTTP client | In-tree C (net_http) |
reqwless (crates.io) |
| MQTT client | In-tree C (net_mqtt) |
rust-mqtt (crates.io) |
| HTTPD | In-tree C, REST + WS (net_httpd) |
picoserve (crates.io) |
| SNTP | In-tree C (net_sntp) |
sntpc (crates.io) |
| Boards | All 4 RTOSes on every supported board | qemu-mps2-an500 (FreeRTOS+Zephyr+NuttX), stm32f746g-discovery (FreeRTOS) — hardware-verified |
| RAM | ~30-50 KB lwIP heap | ~25 KB total (3 KB StackResources + smoltcp bufs + DMA descriptors) |
Use blocking when: you're writing synchronous code, need all five protocol layers production-ready, are using C / C++ / Zig, or need cross-RTOS portability on hardware where embassy-net isn't ported yet.
Use async when: you want many concurrent network tasks on one thread, need embedded-hal-async / embedded-io-async interop with sensor drivers, or are already using async elsewhere (embassy_time::Timer::after, AsyncUart, etc.).
The rest of this page documents the blocking stack. For the async stack see the ove::async_net Rust API docs and the async-networking cookbook.
Architecture
graph TD
APP["Application"]
HTTP["HTTP Client<br/><small>ove/net_http.h</small>"]
MQTT["MQTT Client<br/><small>ove/net_mqtt.h</small>"]
HTTPD["HTTP Server<br/><small>ove/net_httpd.h</small>"]
SNTP["SNTP Client<br/><small>ove/net_sntp.h</small>"]
TLS["TLS (mbedTLS)<br/><small>ove/net_tls.h</small>"]
SOCK["Sockets + DNS<br/><small>ove/net.h</small>"]
BACK["Backend<br/><small>lwIP · Zephyr zsock · NuttX · POSIX</small>"]
APP --> HTTP
APP --> MQTT
APP --> HTTPD
APP --> SNTP
APP --> SOCK
HTTP --> TLS
MQTT --> TLS
HTTP --> SOCK
MQTT --> SOCK
HTTPD --> SOCK
SNTP --> SOCK
TLS --> SOCK
SOCK --> BACK
Each layer only depends on the layer directly below it. The socket layer is the only backend-specific code; everything above it is portable C shared across all RTOSes.
Kconfig Options
| Option | Default | Description |
|---|---|---|
CONFIG_OVE_NET |
n | Core socket API, DNS, network interface |
CONFIG_OVE_NET_TLS |
n | TLS sessions via mbedTLS |
CONFIG_OVE_NET_HTTP |
n | HTTP/1.1 client |
CONFIG_OVE_NET_MQTT |
n | MQTT 3.1.1 client |
CONFIG_OVE_NET_HTTPD |
n | Embedded HTTP server |
CONFIG_OVE_NET_HTTPD_WS |
n | WebSocket support for HTTPD |
CONFIG_OVE_NET_SNTP |
n | Simple NTP time sync |
CONFIG_OVE_NET_TLS_HEAP_SIZE |
32768 | Static TLS heap size (zero-heap mode) |
CONFIG_OVE_NET_HTTP_MAX_RESPONSE |
8192 | Max HTTP response buffer |
CONFIG_OVE_NET_HTTPD_MAX_BODY |
1024 | Max POST body for HTTPD |
CONFIG_OVE_NET_MQTT_RX_BUF |
1024 | MQTT receive buffer |
CONFIG_OVE_NET_MQTT_TX_BUF |
512 | MQTT transmit buffer |
CONFIG_OVE_ASYNC_NET |
n | Async stack (embassy-net) — mutually exclusive with CONFIG_OVE_NET |
Backend pool sizing
Backend-specific compile-time pool knobs (lwIP MEM_SIZE, Zephyr NET_MAX_CONN, NuttX TCP_PREALLOC_TCBS, …) are documented under Internals → Backend tuning → Networking pools. App code does not need to set them unless you are sizing for a specific traffic profile.
Static-storage convenience macros
OVE_NETIF_DEFINE_STATIC, OVE_TLS_DEFINE_STATIC,
OVE_HTTP_CLIENT_DEFINE_STATIC, and OVE_MQTT_CLIENT_DEFINE_STATIC
declare a handle plus its storage in BSS, with a constructor that calls
the matching _init() before main(). OVE_SOCKET_DEFINE declares
just the storage (the socket family/type are decided at
ove_socket_open() time).
Networking Error Codes
In addition to the common error codes, networking functions may return:
| Constant | Value | Meaning |
|---|---|---|
OVE_ERR_NET_REFUSED |
-8 | Connection refused by remote host |
OVE_ERR_NET_UNREACHABLE |
-9 | Network or host unreachable |
OVE_ERR_NET_ADDR_IN_USE |
-10 | Local address already bound |
OVE_ERR_NET_RESET |
-11 | Connection reset by peer |
OVE_ERR_NET_DNS_FAIL |
-12 | DNS name resolution failed |
OVE_ERR_NET_CLOSED |
-13 | Connection closed by peer |
Address and Network Interface
Address Type
ove_sockaddr_t holds an IPv4 or IPv6 address with port. Use the helper to construct IPv4 addresses:
ove_sockaddr_t addr;
ove_sockaddr_ipv4(&addr, 192, 168, 1, 100, 8080);
Network Interface
Before any networking, bring up the interface with static IP or DHCP:
ove_netif_t netif;
ove_netif_storage_t storage;
ove_netif_init(&netif, &storage);
ove_netif_config_t cfg = {0};
cfg.use_dhcp = 0;
ove_sockaddr_ipv4(&cfg.static_ip, 172, 1, 1, 2, 0);
ove_sockaddr_ipv4(&cfg.netmask, 255, 255, 255, 0, 0);
ove_sockaddr_ipv4(&cfg.gateway, 172, 1, 1, 1, 0);
ove_sockaddr_ipv4(&cfg.dns, 8, 8, 8, 8, 0);
ove_netif_up(netif, &cfg);
For DHCP, set cfg.use_dhcp = 1 and leave the address fields zeroed.
Network Interface API
| Function | Description |
|---|---|
ove_netif_init |
Initialise from caller-supplied storage |
ove_netif_deinit |
Release resources |
ove_netif_create |
Heap-allocate and initialise (heap mode) |
ove_netif_destroy |
Destroy heap-allocated interface |
ove_netif_up |
Bring interface up with config |
ove_netif_down |
Tear down interface |
ove_netif_get_addr |
Query current IP, gateway, netmask |
Sockets (TCP and UDP)
Socket Lifecycle
sequenceDiagram
participant App
participant Socket
App->>Socket: ove_socket_open (TCP or UDP)
App->>Socket: ove_socket_connect (TCP) or ove_socket_bind (UDP)
loop Data exchange
App->>Socket: ove_socket_send / ove_socket_recv
end
App->>Socket: ove_socket_close
TCP Example
ove_socket_t sock;
ove_socket_storage_t storage;
ove_socket_open(&sock, &storage, OVE_AF_INET, OVE_SOCK_STREAM);
ove_sockaddr_t dest;
ove_dns_resolve("example.com", &dest, OVE_SEC(5));
dest.port = 80;
ove_socket_connect(sock, &dest, OVE_SEC(5));
const char *req = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
size_t sent;
ove_socket_send(sock, req, strlen(req), &sent);
char buf[512];
size_t received;
ove_socket_recv(sock, buf, sizeof(buf), &received, OVE_SEC(5));
ove_socket_close(sock);
UDP Example
ove_socket_t sock;
ove_socket_storage_t storage;
ove_socket_open(&sock, &storage, OVE_AF_INET, OVE_SOCK_DGRAM);
ove_sockaddr_t local = {0};
ove_sockaddr_ipv4(&local, 0, 0, 0, 0, 9999);
ove_socket_bind(sock, &local);
ove_sockaddr_t dest;
ove_sockaddr_ipv4(&dest, 127, 0, 0, 1, 9999);
size_t sent;
ove_socket_sendto(sock, "hello", 5, &sent, &dest);
char buf[64];
size_t received;
ove_sockaddr_t src;
ove_socket_recvfrom(sock, buf, sizeof(buf), &received, &src, 2000);
ove_socket_close(sock);
Socket API
| Function | Description |
|---|---|
ove_socket_open |
Open a socket with caller-supplied storage |
ove_socket_close |
Close a socket |
ove_socket_connect |
Connect to a remote address (TCP) |
ove_socket_bind |
Bind to a local address |
ove_socket_listen |
Mark socket as listening (TCP server) |
ove_socket_accept |
Accept an incoming connection |
ove_socket_send |
Send data on a connected socket |
ove_socket_recv |
Receive data with timeout |
ove_socket_sendto |
Send a datagram to a destination (UDP) |
ove_socket_recvfrom |
Receive a datagram with source address |
ove_socket_create |
Heap-allocate a socket (heap mode) |
ove_socket_destroy |
Destroy a heap-allocated socket |
DNS
ove_sockaddr_t addr;
int rc = ove_dns_resolve("example.com", &addr, OVE_SEC(10));
if (rc == OVE_OK) {
/* addr.addr[0..3] contains the IPv4 address */
addr.port = 80; /* set port for subsequent connect */
}
The hostname must be a null-terminated C string. The timeout is in nanoseconds; use OVE_MS(...) or OVE_SEC(...) for ergonomic values.
TLS
TLS wraps an existing TCP socket with mbedTLS encryption. Requires CONFIG_OVE_NET_TLS.
ove_tls_t tls;
ove_tls_storage_t tls_storage;
ove_tls_init(&tls, &tls_storage);
ove_tls_config_t cfg = {0};
cfg.hostname = "example.com"; /* SNI hostname for verification */
/* A non-NULL ca_cert is required for a verified handshake. To opt out of
* certificate validation (test/dev only) set cfg.allow_insecure = 1 — the
* handshake refuses to complete otherwise. */
ove_tls_handshake(tls, sock, &cfg); /* sock is an already-connected TCP socket */
size_t sent;
ove_tls_send(tls, "GET / HTTP/1.1\r\n...", len, &sent);
char buf[512];
size_t received;
ove_tls_recv(tls, buf, sizeof(buf), &received);
ove_tls_close(tls);
ove_tls_deinit(tls);
For certificate verification, set cfg.ca_cert and cfg.ca_cert_len to a PEM or DER certificate.
HTTP Client
Requires CONFIG_OVE_NET_HTTP. Handles DNS, TCP connection, optional TLS, request formatting, and response parsing internally.
ove_http_client_t client;
ove_http_client_storage_t storage;
ove_http_client_init(&client, &storage);
ove_http_response_t resp;
ove_http_get(client, "http://example.com/", &resp);
/* resp.status = 200, resp.body = "...", resp.body_len = 540 */
ove_http_response_free(&resp);
ove_http_post(client, "http://httpbin.org/post",
"application/json", "{\"key\":\"value\"}", 15, &resp);
ove_http_response_free(&resp);
ove_http_client_deinit(client);
HTTP Methods
| Method | Enum Value |
|---|---|
| GET | OVE_HTTP_GET |
| POST | OVE_HTTP_POST |
| PUT | OVE_HTTP_PUT |
| DELETE | OVE_HTTP_DELETE |
| PATCH | OVE_HTTP_PATCH |
HTTP Client API
| Function | Description |
|---|---|
ove_http_client_init |
Initialise from caller-supplied storage |
ove_http_client_deinit |
Release resources |
ove_http_get |
Perform a GET request |
ove_http_post |
Perform a POST request |
ove_http_request |
Generic request with method, body, content type |
ove_http_request_ex |
Extended request with custom headers |
ove_http_response_free |
Free response body/header buffers |
MQTT Client
Requires CONFIG_OVE_NET_MQTT. Implements MQTT 3.1.1 with QoS 0 and 1.
static void on_message(const char *topic, size_t topic_len,
const void *payload, size_t payload_len,
void *user_data)
{
OVE_LOG_INF("MQTT: [%.*s] %.*s", (int)topic_len, topic,
(int)payload_len, (const char *)payload);
}
ove_mqtt_client_t mqtt;
ove_mqtt_client_storage_t storage;
ove_mqtt_client_init(&mqtt, &storage);
ove_mqtt_config_t cfg = {
.host = "test.mosquitto.org",
.port = 1883,
.client_id = "my-device",
.keep_alive_s = 30,
.on_message = on_message,
/* Optional authentication */
.username = NULL,
.password = NULL,
/* Optional TLS — set use_tls = 1 and supply tls_ca_cert (+ tls_ca_cert_len),
* or set tls_allow_insecure = 1 for test/dev. */
.use_tls = 0,
.user_data = NULL,
};
ove_mqtt_connect(mqtt, &cfg);
ove_mqtt_subscribe(mqtt, "my/topic", OVE_MQTT_QOS0);
ove_mqtt_publish(mqtt, "my/topic", "hello", 5, OVE_MQTT_QOS1);
/* Call periodically to process incoming messages and send keep-alive */
ove_mqtt_loop(mqtt, OVE_MS(500));
ove_mqtt_disconnect(mqtt);
ove_mqtt_client_deinit(mqtt);
MQTT Client API
| Function | Description |
|---|---|
ove_mqtt_connect |
Connect to broker with config |
ove_mqtt_disconnect |
Disconnect from broker |
ove_mqtt_publish |
Publish a message (QoS 0 or 1) |
ove_mqtt_subscribe |
Subscribe to a topic filter |
ove_mqtt_unsubscribe |
Unsubscribe from a topic |
ove_mqtt_loop |
Process packets and send keep-alive |
HTTP Server
Requires CONFIG_OVE_NET_HTTPD. Single-threaded HTTP/1.1 server with path-based routing and a built-in Tasmota-style web dashboard.
ove_httpd_config_t cfg = { .port = 80, .max_body_size = 1024 };
ove_httpd_start(&cfg);
ove_httpd_register_builtin_routes(); /* /api/info, /api/leds, /api/gpio, etc. */
Custom Routes
static int my_handler(ove_httpd_req_t *req, ove_httpd_resp_t *resp)
{
const char *path = ove_httpd_req_path(req);
const char *seg = ove_httpd_req_segment(req, 1); /* e.g. "0" from /api/items/0 */
return ove_httpd_resp_json(resp, 200, "{\"status\":\"ok\"}");
}
ove_httpd_route("GET", "/api/items/*", my_handler);
Built-in Dashboard Routes
| Route | Method | Description |
|---|---|---|
/ |
GET | Web dashboard (HTML/CSS/JS) |
/api/info |
GET | Device info (RTOS, board, uptime, memory) |
/api/leds |
GET/POST | LED state query and control |
/api/gpio |
GET/POST | GPIO pin read/write |
/api/network |
GET | Network interface status |
/api/log |
GET | Recent log messages |
/api/system/memory |
GET | System heap totals (free / used / peak). |
/api/system/threads |
GET | Thread list with state, priority, stack usage, CPU%. |
/api/audio/stats |
GET | Audio graph runtime statistics (requires CONFIG_OVE_AUDIO). |
/api/infer/stats |
GET | Last inference latency and stats (requires CONFIG_OVE_INFER). |
HTTPD API
| Function | Description |
|---|---|
ove_httpd_start |
Start the HTTP server. |
ove_httpd_stop |
Stop the server. |
ove_httpd_set_netif |
Bind the server to a specific network interface for the /api/network route. |
ove_httpd_set_audio_graph |
Register an ove_audio_graph for the /api/audio/stats route (requires CONFIG_OVE_AUDIO). |
ove_httpd_set_model |
Register an ove_model_t for the /api/infer/stats route (requires CONFIG_OVE_INFER). |
ove_httpd_route |
Register a route handler. |
ove_httpd_register_builtin_routes |
Register the built-in dashboard. |
ove_httpd_req_method |
Get request method string. |
ove_httpd_req_path |
Get request path. |
ove_httpd_req_query |
Get query string. |
ove_httpd_req_body |
Get POST body. |
ove_httpd_req_body_len |
Get POST body length. |
ove_httpd_req_segment |
Get path segment by index. |
ove_httpd_resp_json |
Send JSON response. |
ove_httpd_resp_html |
Send HTML response (requires explicit len, not NUL-terminated). |
ove_httpd_resp_send |
Send response with caller-chosen content type. |
ove_httpd_resp_send_gz |
Send a pre-compressed gzip response (used for the bundled dashboard). |
ove_httpd_resp_error |
Send error response. |
ove_httpd_log_append |
Append a line to the in-memory log ring buffer surfaced at /api/log. |
WebSocket Support
When CONFIG_OVE_NET_HTTPD_WS is enabled, the server supports RFC 6455 WebSocket connections. Both handlers return void; the third argument to ove_httpd_ws_route is an ove_httpd_ws_close_handler_t invoked on disconnect (pass NULL if not needed).
static void ws_on_message(ove_httpd_ws_conn_t *conn,
const void *data, size_t len)
{
/* Echo back */
ove_httpd_ws_send(conn, data, len);
}
ove_httpd_ws_route("/ws", ws_on_message, NULL);
ove_httpd_ws_broadcast("/ws", "hello", 5);
SNTP
Requires CONFIG_OVE_NET_SNTP. Performs a single NTP query to get UTC time.
ove_sntp_config_t cfg = { .server = "pool.ntp.org", .timeout_ns = OVE_SEC(5) };
ove_sntp_sync(&cfg);
/* Passing NULL uses the built-in defaults (pool.ntp.org, 5 s timeout). */
uint32_t utc_s;
ove_sntp_get_utc(&utc_s); /* seconds since 1970-01-01 */
int64_t offset_us;
ove_sntp_get_offset_us(&offset_us); /* microseconds offset between local
clock and the last successful sync */
Zero-Heap Networking
When CONFIG_OVE_ZERO_HEAP is enabled, the entire networking stack operates without any dynamic memory allocation:
- lwIP (FreeRTOS): Uses static memory pools for PCBs, packet buffers, and ARP entries
- mbedTLS: Uses
MBEDTLS_MEMORY_BUFFER_ALLOC_Cwith a static buffer (CONFIG_OVE_NET_TLS_HEAP_SIZE) - HTTP client: Response buffer embedded in the client storage struct
- MQTT client: Socket storage embedded in the client struct
- All sockets: Use
_init()/_deinit()with caller-suppliedove_*_storage_t
The _create()/_destroy() convenience macros automatically expand to static storage in zero-heap mode (see Allocation Strategies).
Language Bindings
All four languages provide equivalent networking APIs. Key type mappings:
| Concept | C | C++ | Rust | Zig |
|---|---|---|---|---|
| Address | ove_sockaddr_t |
ove::Address |
ove::net::Address |
ove.Address |
| Net interface | ove_netif_t |
ove::NetIf |
ove::net::NetIf |
ove.NetIf |
| TCP socket | ove_socket_t |
ove::TcpSocket |
ove::net::TcpStream |
ove.TcpStream |
| UDP socket | ove_socket_t |
ove::UdpSocket |
ove::net::UdpSocket |
ove.UdpSocket |
| DNS resolve | ove_dns_resolve() |
ove::dns::resolve() |
ove::net::dns_resolve() |
ove.net.dns.resolve() |
| HTTP client | ove_http_client_t |
ove::http::Client |
ove::net_http::Client |
ove.HttpClient |
| MQTT client | ove_mqtt_client_t |
ove::mqtt::Client |
ove::net_mqtt::Client |
ove.MqttClient |
| TLS session | ove_tls_t |
ove::tls::Session |
ove::net_tls::Session |
ove.TlsSession |
| HTTPD start | ove_httpd_start() |
ove::httpd::start() |
ove::net_httpd::start() |
ove.net_httpd.start() |
| Cleanup | _deinit() / _destroy() |
Destructor | Drop trait |
defer obj.destroy() |
RAII Patterns by Language
- C: Manual
_init()/_deinit()or_create()/_destroy() - C++: Automatic via destructor; move-only types (no copy)
- Rust: Automatic via
Droptrait;Result<T>error handling - Zig: Explicit
destroy()withdefer;Error!Treturn types; comptime zero-heap dispatch
MQTT Callback Patterns
| Language | Pattern |
|---|---|
| C | Function pointer: void (*)(topic, topic_len, payload, payload_len, user_data) |
| C++ | std::function<void(std::string_view topic, std::span<const uint8_t> payload)> |
| Rust | fn(&str, &[u8]) via trampoline (zero unsafe in app code) |
| Zig | comptime fn([]const u8, []const u8) void via trampoline, or connectWithContext() for typed state |