.. Copyright 2023-2026 AVSystem AVSystem Anjay Lite LwM2M SDK All rights reserved. Licensed under AVSystem Anjay Lite LwM2M Client SDK - Non-Commercial License. See the attached LICENSE file for details. Minimal socket implementation ============================= .. contents:: :local: Overview -------- This tutorial demonstrates how to implement a minimal UDP network compatibility layer for Anjay Lite using POSIX sockets. Although Anjay Lite already provides a built-in implementation for POSIX environments, this example helps you understand how to create your own custom network layer. .. note:: Code related to this tutorial can be found under `examples/custom-network/minimal` in the Anjay Lite source directory and is based on `examples/tutorial/BC-MandatoryObjects` example. Update the build configuration ------------------------------ This example uses CMake as a build system and demonstrates how to provide a basic UDP network layer implementation. To disable the default POSIX socket implementation and enable the custom UDP layer, apply the following changes in `CMakeLists.txt`: .. highlight:: cmake .. snippet-source:: examples/custom-network/minimal/CMakeLists.txt :emphasize-lines: 7-8,15 cmake_minimum_required(VERSION 3.16.0) project(anjay_lite_minimal_network_api C) set(CMAKE_C_STANDARD 99) set(ANJ_WITH_SOCKET_POSIX_COMPAT OFF) set(ANJ_NET_WITH_UDP ON) if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(anjay_lite_DIR "../../../cmake") find_package(anjay_lite REQUIRED) endif() add_executable(anjay_lite_minimal_network_api src/main.c src/net.c) The `examples/custom-network/minimal/src/net.c` file contains the custom network compatibility layer implementation. Limitations ----------- For clarity, this example includes only essential functionality and has the following limitations: - Supports only UDP protocol. - Supports only IPv4 (no IPv6 connections). - Does not preserve the local port between multiple connections to the same server. - Does not validate input parameters. - Performs minimal error handling. The return values of several POSIX functions are not checked, including: - ``fcntl`` - ``close`` - Does not support socket configuration, such as selecting the address family. - Uses a fixed inner MTU value, which may not reflect actual network conditions. Despite these limitations, this implementation is sufficient to connect to a LwM2M server using the networking compatibility layer. Create socket context --------------------- The socket context is represented by a custom structure: ``net_ctx_posix_impl_t``. This structure stores: - the socket file descriptor (``sockfd``) - the current socket state (``state``) You can modify this structure to fit your needs. The example below shows a basic implementation: .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c typedef struct net_ctx_posix_impl { sockfd_t sockfd; anj_net_socket_state_t state; } net_ctx_posix_impl_t; int anj_udp_create_ctx(anj_net_ctx_t **ctx_, const anj_net_config_t *config) { (void) config; net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) malloc(sizeof(net_ctx_posix_impl_t)); if (!ctx) { return NET_GENERAL_ERROR; } ctx->sockfd = INVALID_SOCKET; ctx->state = ANJ_NET_SOCKET_STATE_CLOSED; *ctx_ = (anj_net_ctx_t *) ctx; return ANJ_NET_OK; } The ``anj_udp_create_ctx`` function initializes the network context by allocating memory for the ``net_ctx_posix_impl_t`` structure and initialize its values. .. note:: If dynamic memory allocation is not allowed in the project, this function can assign the ``ctx_`` pointer to a static global structure instead, omitting the need to use the ``malloc`` function. .. note:: The only positive values that network API functions may return are error codes with the ``ANJ_NET_E`` prefix defined in the `include_public/anj/compat/net/anj_net_api.h` file (e.g. ``ANJ_NET_EAGAIN``); all other positive values are forbidden - they are reserved for client-side logic for potential new error codes. Please refer to `anj_net_api.h` for a full list of defined error codes and description when specific network API functions can return them. ``NET_GENERAL_ERROR`` is defined as ``-1``, but any negative value may be returned to indicate an error, Anjay Lite will treat them in the same way. Connect ------- First, define a helper function to set sockets to non-blocking mode.d This prevents Anjay Lite from halting while waiting for incoming data. .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c static void set_socket_non_blocking(sockfd_t sockfd) { int flags = fcntl(sockfd, F_GETFL, 0); if (flags >= 0) { fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); } } Now implement the ``anj_udp_connect`` function: .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_connect(anj_net_ctx_t *ctx_, const char *hostname, const char *port_str) { net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_; struct addrinfo *serverinfo = NULL; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; if (getaddrinfo(hostname, port_str, &hints, &serverinfo) || !serverinfo) { if (serverinfo) { freeaddrinfo(serverinfo); } return NET_GENERAL_ERROR; } ctx->sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (ctx->sockfd < 0) { freeaddrinfo(serverinfo); return NET_GENERAL_ERROR; } if (connect(ctx->sockfd, serverinfo->ai_addr, serverinfo->ai_addrlen)) { freeaddrinfo(serverinfo); return NET_GENERAL_ERROR; } set_socket_non_blocking(ctx->sockfd); ctx->state = ANJ_NET_SOCKET_STATE_CONNECTED; freeaddrinfo(serverinfo); return ANJ_NET_OK; } How it works ^^^^^^^^^^^^ The ``anj_udp_connect`` function performs the following steps: **Resolve server address** It uses ``getaddrinfo()`` to convert the provided hostname and port into a list of address structures suitable for an IPv4 UDP connection. - ``hints.ai_family`` is set to ``AF_INET`` (IPv4). - ``hints.ai_socktype`` is set to ``SOCK_DGRAM`` (UDP). .. attention:: The ``getaddrinfo`` function may block, which can halt the execution of Anjay Lite during the call. If non-blocking behavior is required, use an asynchronous variant if available. If the connect operation is pending in a non-blocking scenario, return ``ANJ_NET_EINPROGRESS`` to inform Anjay Lite that it needs to be called again to finish establishing the connection. Note that returning ``ANJ_NET_EINPROGRESS`` again and again will block the library from performing any other operation and falling back with error handling procedure. **Create a socket and connect it to the server** It creates a new IPv4 UDP socket with ``socket(AF_INET, SOCK_DGRAM, 0)``. Then, it connects the socket to the server address obtained earlier. If the connection succeeds, the socket is ready for communication with the target host. **Set socket to non-blocking mode** It ensures the socket is configured as non-blocking to prevent delays during future send and recv operations. **Update socket state** It updates the socket's state to ``ANJ_NET_SOCKET_STATE_CONNECTED``. **Release address information** It frees the memory allocated by ``getaddrinfo()``. .. note:: Always keep the socket ``state`` correctly updated. Anjay Lite relies on the socket state to determine the current connection status. Send ---- Before implementing the send functionality, create a helper function to check if an error indicates a blocking condition: .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c static bool would_block(int errno_val) { switch (errno_val) { #ifdef EAGAIN case EAGAIN: return true; #endif #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) case EWOULDBLOCK: return true; #endif #ifdef EINPROGRESS case EINPROGRESS: return true; #endif #ifdef EBUSY case EBUSY: return true; #endif default: return false; } } The ``would_block`` function checks the ``errno`` value set by the POSIX socket system calls. It returns ``true`` if the error code indicates that the operation would have blocked. Otherwise, it returns ``false``. Now implement ``anj_udp_send``: .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_send(anj_net_ctx_t *ctx_, size_t *bytes_sent, const uint8_t *buf, size_t length) { net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_; errno = 0; ssize_t result = send(ctx->sockfd, buf, length, 0); if (result < 0) { return would_block(errno) ? ANJ_NET_EINPROGRESS : NET_GENERAL_ERROR; } *bytes_sent = (size_t) result; if (*bytes_sent < length) { /* Partial sent not allowed in case of UDP */ return NET_GENERAL_ERROR; } return ANJ_NET_OK; } **How it works** The ``anj_udp_send`` function acts as a simple wrapper around the standard POSIX ``send`` call: - Sends the data from the buffer to the connected socket. - If an error occurs it checks whether the error indicates a non-blocking situation. - Returns an error if only part of the data was sent. - On success, reports the number of bytes sent via the ``bytes_sent`` output parameter. Receive ------- Receiving follows the same pattern as sending: .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_recv(anj_net_ctx_t *ctx_, size_t *bytes_received, uint8_t *buf, size_t length) { net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_; errno = 0; ssize_t result = recv(ctx->sockfd, buf, length, 0); if (result < 0) { // in anj_net api, recv differentiates between EAGAIN and EINPROGRESS if (errno == EAGAIN) { return ANJ_NET_EAGAIN; } return would_block(errno) ? ANJ_NET_EINPROGRESS : NET_GENERAL_ERROR; } *bytes_received = (size_t) result; if (*bytes_received == length) { /** * Buffer entirely filled - data possibly truncated. This will * incorrectly reject packets that have exactly buffer_length * bytes, but we have no means of distinguishing the edge case * without recvmsg. * This does only apply to datagram sockets (in our case: UDP). */ return ANJ_NET_EMSGSIZE; } return ANJ_NET_OK; } .. note:: The anj_upd_recv function differentiates between ``EAGAIN`` and ``EINPROGRESS``. That is the only function in the Anjay Lite network API that does so. ``EAGAIN`` indicates that no data is currently available for reading, ``EINPROGRESS`` indicates any state where the operation would block while receiving data. .. note:: If the buffer is too small to hold the incoming packet, or matches it exactly ``anj_udp_recv`` returns ``ANJ_NET_EMSGSIZE``. This informs Anjay Lite to drop the packet gracefully. Any other error is treated as fatal and triggers a connection reset. Close ----- ``anj_udp_close`` closes the underlying socket and updates the socket ``state`` to ``ANJ_NET_SOCKET_STATE_CLOSED`` indicating that it is no longer active. .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c :emphasize-lines: 7 int anj_udp_close(anj_net_ctx_t *ctx_) { net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_; close(ctx->sockfd); ctx->sockfd = INVALID_SOCKET; ctx->state = ANJ_NET_SOCKET_STATE_CLOSED; return ANJ_NET_OK; } .. note:: The context object itself is not cleared here to preserve data for possible reuse. Context cleanup --------------- The cleanup function releases all resources associated with the socket context. First, it closes the socket if it is still open, then it frees the dynamically allocated memory that stored the socket context's state. .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_cleanup_ctx(anj_net_ctx_t **ctx_) { net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) *ctx_; *ctx_ = NULL; close(ctx->sockfd); free(ctx); return ANJ_NET_OK; } Get inner MTU ------------- ``anj_udp_get_inner_mtu`` returns the assumed maximum transmission unit (MTU) for IPv4 UDP datagrams, excluding protocol headers: .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_get_inner_mtu(anj_net_ctx_t *ctx, int32_t *out_value) { (void) ctx; *out_value = 548; /* 576 (IPv4 MTU) - 28 bytes of headers */ return ANJ_NET_OK; } .. note:: This is a static implementation. In a real-world project, retrieve the actual MTU dynamically using ``getsockopt()``. Get state --------- The ``anj_udp_get_state`` function lets Anjay Lite retrieve the current connection status of the context. .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_get_state(anj_net_ctx_t *ctx_, anj_net_socket_state_t *out_value) { net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_; *(anj_net_socket_state_t *) out_value = ctx->state; return ANJ_NET_OK; } Queue Mode RX off ----------------- ``anj_udp_queue_mode_rx_off`` function hints the transport that the client will not need to receive application data until it initiates the next outgoing exchange. Behavior really depends on the targeted hardware platform. In this minimal POSIX implementation, this functionality is not applicable and simply returns ``ANJ_NET_OK``. .. highlight:: c .. snippet-source:: examples/custom-network/minimal/src/net.c int anj_udp_queue_mode_rx_off(anj_net_ctx_t *ctx_) { (void) ctx_; return ANJ_NET_OK; } Summary ------- This example provides a minimal custom UDP socket layer for Anjay Lite. It covers connection management, send/receive operations, and cleanup. While not suitable for production use, it serves as a clear reference for integrating Anjay Lite with a platform-specific network stack.