7.2.1.1. Minimal Socket Implementation
7.2.1.1.1. Introduction
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 is useful for understanding how to create a 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.
7.2.1.1.2. 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:
cmake_minimum_required(VERSION 3.6.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)
set(ANJ_NET_WITH_TCP OFF)
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 will contain the custom network compatibility layer implementation.
7.2.1.1.3. Limitations
To ensure clarity, the implementation includes only essential functionality and has the following limitations:
Supports only the UDP protocol.
Supports only IPv4 addresses. Connecting to IPv6 addresses is not possible.
Does not preserve the local port between multiple connections to the same server.
Does not validate input parameters.
Implements minimal error handling. The return values of several POSIX functions are not checked, including:
fcntl
close
shutdown
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.
7.2.1.1.4. Create socket context
The socket context is represented by a custom structure: net_ctx_posix_impl_t
.
This structure stores:
a file descriptor for the socket (
sockfd
)the current socket state (state)
You can customize this structure to meet the requirements of your specific implementation. The example below shows a basic implementation:
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
Value NET_GENERAL_ERROR
is defined as -3
in the example to prevent
collision with existing error codes used by the network API. Please refer
to include_public/anj/compat/net/anj_net_api.h for a full list of reserved error
codes and description when specific network API functions can return them.
The network API function are allowed to return other error codes then
NET_GENERAL_ERROR
or the error codes reserved in anj_net_api.h. This
might help in the development process but Anjay Lite will treat them in the
same way.
7.2.1.1.5. Connect
To establish a connection, first create a helper function that sets a socket to non-blocking mode. Non-blocking sockets prevent Anjay Lite from being halted while waiting for incoming data.
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:
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;
}
7.2.1.1.5.1. 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 toAF_INET
(IPv4).
hints.ai_socktype
is set toSOCK_DGRAM
(UDP).
Note
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_EAGAIN
to inform Anjay Lite that it needs to be
called again to finish establishing the connection.
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.
7.2.1.1.6. Send
Before implementing the send functionality, create a helper function to check if an error indicates a blocking condition:
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
:
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_EAGAIN : 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:
It sends the data from the buffer to the connected socket.
If an error occurs it checks whether the error indicates a non-blocking situation.
If not all of the data was sent, we return an error, as partial sent is not allowed in the case of UDP.
On success, it reports the number of bytes sent via the
bytes_sent
output parameter.
7.2.1.1.7. Receive
The receive function follows a similar logic to the send function:
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) {
return would_block(errno) ? ANJ_NET_EAGAIN : 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
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.
7.2.1.1.8. Shutdown
The anj_udp_shutdown
function is straightforward but requires updating the socket
context’s state to ANJ_NET_SOCKET_STATE_SHUTDOWN
upon completion.
int anj_udp_shutdown(anj_net_ctx_t *ctx_) {
net_ctx_posix_impl_t *ctx = (net_ctx_posix_impl_t *) ctx_;
shutdown(ctx->sockfd, SHUT_RDWR);
ctx->state = ANJ_NET_SOCKET_STATE_SHUTDOWN;
return ANJ_NET_OK;
}
7.2.1.1.9. Close
The 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.
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.
7.2.1.1.10. 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.
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;
}
7.2.1.1.11. Get Inner MTU
The anj_udp_get_inner_mtu
function returns the assumed maximum transmission
unit for UDP datagrams over IPv4 without the protocol header overhead.
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()
.
7.2.1.1.12. Get State
The anj_udp_get_state
function allows Anjay Lite to retrieve the current connection status of the context.
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;
}