net: lib: coap: Add service support

Add CoAP services and server as a subsystem implementation.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
This commit is contained in:
Pieter De Gendt 2023-09-21 17:25:22 +02:00 committed by Carles Cufí
parent 5227f24815
commit ae6e0106e7
5 changed files with 1155 additions and 0 deletions

View File

@ -17,3 +17,7 @@
#if defined(CONFIG_HTTP_SERVER)
ITERABLE_SECTION_ROM(http_service_desc, 4)
#endif
#if defined(CONFIG_COAP_SERVER)
ITERABLE_SECTION_ROM(coap_service, 4)
#endif

View File

@ -0,0 +1,292 @@
/*
* Copyright (c) 2023 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file
* @brief CoAP Service API
*
* An API for applications to respond to CoAP requests
*/
#ifndef ZEPHYR_INCLUDE_NET_COAP_SERVICE_H_
#define ZEPHYR_INCLUDE_NET_COAP_SERVICE_H_
#include <zephyr/net/coap.h>
#include <zephyr/sys/iterable_sections.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief CoAP Service API
* @defgroup coap_service CoAP service API
* @ingroup networking
* @{
*/
/**
* @name CoAP Service configuration flags
* @anchor COAP_SERVICE_FLAGS
* @{
*/
/** Start the service on boot. */
#define COAP_SERVICE_AUTOSTART BIT(0)
/** @} */
/** @cond INTERNAL_HIDDEN */
struct coap_service_data {
int sock_fd;
struct coap_observer observers[CONFIG_COAP_SERVICE_OBSERVERS];
struct coap_pending pending[CONFIG_COAP_SERVICE_PENDING_MESSAGES];
};
struct coap_service {
const char *name;
const char *host;
uint16_t *port;
uint8_t flags;
struct coap_resource *res_begin;
struct coap_resource *res_end;
struct coap_service_data *data;
};
#define __z_coap_service_define(_name, _host, _port, _flags, _res_begin, _res_end) \
static struct coap_service_data coap_service_data_##_name; \
const STRUCT_SECTION_ITERABLE(coap_service, _name) = { \
.name = STRINGIFY(_name), \
.host = _host, \
.port = (uint16_t *)(_port), \
.flags = _flags, \
.res_begin = (_res_begin), \
.res_end = (_res_end), \
.data = &coap_service_data_##_name, \
}
/** @endcond */
/**
* @brief Define a static CoAP resource owned by the service named @p _service .
*
* @note The handlers registered with the resource can return a CoAP response code to reply with
* an acknowledge without any payload, nothing is sent if the return value is 0 or negative.
* As seen in the example.
*
* @code{.c}
* static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
*
* static int led_put(struct coap_resource *resource, struct coap_packet *request,
* struct sockaddr *addr, socklen_t addr_len)
* {
* const uint8_t *payload;
* uint16_t payload_len;
*
* payload = coap_packet_get_payload(request, &payload_len);
* if (payload_len != 1) {
* return COAP_RESPONSE_CODE_BAD_REQUEST;
* }
*
* if (gpio_pin_set_dt(&led, payload[0]) < 0) {
* return COAP_RESPONSE_CODE_INTERNAL_ERROR;
* }
*
* return COAP_RESPONSE_CODE_CHANGED;
* }
*
* COAP_RESOURCE_DEFINE(my_resource, my_service, {
* .put = led_put,
* });
* @endcode
*
* @param _name Name of the resource.
* @param _service Name of the associated service.
*/
#define COAP_RESOURCE_DEFINE(_name, _service, ...) \
STRUCT_SECTION_ITERABLE_ALTERNATE(coap_resource_##_service, coap_resource, _name) \
= __VA_ARGS__
/**
* @brief Define a CoAP service with static resources.
*
* @note The @p _host parameter can be `NULL`. If not, it is used to specify an IP address either in
* IPv4 or IPv6 format a fully-qualified hostname or a virtual host, otherwise the any address is
* used.
*
* @note The @p _port parameter must be non-`NULL`. It points to a location that specifies the port
* number to use for the service. If the specified port number is zero, then an ephemeral port
* number will be used and the actual port number assigned will be written back to memory. For
* ephemeral port numbers, the memory pointed to by @p _port must be writeable.
*
* @param _name Name of the service.
* @param _host IP address or hostname associated with the service.
* @param[inout] _port Pointer to port associated with the service.
* @param _flags Configuration flags @see @ref COAP_SERVICE_FLAGS.
*/
#define COAP_SERVICE_DEFINE(_name, _host, _port, _flags) \
extern struct coap_resource _CONCAT(_coap_resource_##_name, _list_start)[]; \
extern struct coap_resource _CONCAT(_coap_resource_##_name, _list_end)[]; \
__z_coap_service_define(_name, _host, _port, _flags, \
&_CONCAT(_coap_resource_##_name, _list_start)[0], \
&_CONCAT(_coap_resource_##_name, _list_end)[0])
/**
* @brief Count the number of CoAP services.
*
* @param[out] _dst Pointer to location where result is written.
*/
#define COAP_SERVICE_COUNT(_dst) STRUCT_SECTION_COUNT(coap_service, _dst)
/**
* @brief Count CoAP service static resources.
*
* @param _service Pointer to a service.
*/
#define COAP_SERVICE_RESOURCE_COUNT(_service) ((_service)->res_end - (_service)->res_begin)
/**
* @brief Check if service has the specified resource.
*
* @param _service Pointer to a service.
* @param _resource Pointer to a resource.
*/
#define COAP_SERVICE_HAS_RESOURCE(_service, _resource) \
((_service)->res_begin <= _resource && _resource < (_service)->res_end)
/**
* @brief Iterate over all CoAP services.
*
* @param _it Name of iterator (of type @ref coap_service)
*/
#define COAP_SERVICE_FOREACH(_it) STRUCT_SECTION_FOREACH(coap_service, _it)
/**
* @brief Iterate over static CoAP resources associated with a given @p _service.
*
* @note This macro requires that @p _service is defined with @ref COAP_SERVICE_DEFINE.
*
* @param _service Name of CoAP service
* @param _it Name of iterator (of type @ref coap_resource)
*/
#define COAP_RESOURCE_FOREACH(_service, _it) \
STRUCT_SECTION_FOREACH_ALTERNATE(coap_resource_##_service, coap_resource, _it)
/**
* @brief Iterate over all static resources associated with @p _service .
*
* @note This macro is suitable for a @p _service defined with @ref COAP_SERVICE_DEFINE.
*
* @param _service Pointer to COAP service
* @param _it Name of iterator (of type @ref coap_resource)
*/
#define COAP_SERVICE_FOREACH_RESOURCE(_service, _it) \
for (struct coap_resource *_it = (_service)->res_begin; ({ \
__ASSERT(_it <= (_service)->res_end, "unexpected list end location"); \
_it < (_service)->res_end; \
}); _it++)
/**
* @brief Start the provided @p service .
*
* @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
*
* @param service Pointer to CoAP service
* @retval 0 in case of success.
* @retval -EALREADY in case of an already running service.
* @retval -ENOMEM in case the server has no available context.
*/
int coap_service_start(const struct coap_service *service);
/**
* @brief Stop the provided @p service .
*
* @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
*
* @param service Pointer to CoAP service
* @retval 0 in case of success.
* @retval -EALREADY in case the service isn't running.
*/
int coap_service_stop(const struct coap_service *service);
/**
* @brief Send a CoAP message from the provided @p service .
*
* @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
*
* @param service Pointer to CoAP service
* @param cpkt CoAP Packet to send
* @param addr Peer address
* @param addr_len Peer address length
* @return 0 in case of success or negative in case of error.
*/
int coap_service_send(const struct coap_service *service, const struct coap_packet *cpkt,
const struct sockaddr *addr, socklen_t addr_len);
/**
* @brief Send a CoAP message from the provided @p resource .
*
* @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
*
* @param resource Pointer to CoAP resource
* @param cpkt CoAP Packet to send
* @param addr Peer address
* @param addr_len Peer address length
* @return 0 in case of success or negative in case of error.
*/
int coap_resource_send(const struct coap_resource *resource, const struct coap_packet *cpkt,
const struct sockaddr *addr, socklen_t addr_len);
/**
* @brief Parse a CoAP observe request for the provided @p resource .
*
* @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
*
* If the observe option value is equal to 0, an observer will be added, if the value is equal
* to 1, an existing observer will be removed.
*
* @param resource Pointer to CoAP resource
* @param request CoAP request to parse
* @param addr Peer address
* @return the observe option value in case of success or negative in case of error.
*/
int coap_resource_parse_observe(struct coap_resource *resource, const struct coap_packet *request,
const struct sockaddr *addr);
/**
* @brief Lookup an observer by address and remove it from the @p resource .
*
* @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
*
* @param resource Pointer to CoAP resource
* @param addr Peer address
* @return 0 in case of success or negative in case of error.
*/
int coap_resource_remove_observer_by_addr(struct coap_resource *resource,
const struct sockaddr *addr);
/**
* @brief Lookup an observer by token and remove it from the @p resource .
*
* @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
*
* @param resource Pointer to CoAP resource
* @param token Pointer to the token
* @param token_len Length of valid bytes in the token
* @return 0 in case of success or negative in case of error.
*/
int coap_resource_remove_observer_by_token(struct coap_resource *resource,
const uint8_t *token, uint8_t token_len);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_NET_COAP_SERVICE_H_ */

View File

@ -10,3 +10,7 @@ zephyr_sources_ifdef(CONFIG_COAP
zephyr_sources_ifdef(CONFIG_COAP_CLIENT
coap_client.c
)
zephyr_sources_ifdef(CONFIG_COAP_SERVER
coap_server.c
)

View File

@ -153,6 +153,99 @@ config COAP_CLIENT_MAX_REQUESTS
endif # COAP_CLIENT
config COAP_SERVER
bool "CoAP server support [EXPERIMENTAL]"
select EXPERIMENTAL
select NET_SOCKETS
select NET_SOCKETPAIR
help
This option enables the API for CoAP-services to register resources.
if COAP_SERVER
config COAP_SERVER_STACK_SIZE
int "CoAP server thread stack size"
default 4096
help
CoAP server thread stack size for processing RX/TX events.
config COAP_SERVER_BLOCK_SIZE
int "CoAP server block-wise transfer size"
default 256
range 64 1024
help
CoAP block size used by CoAP server resources when performing block-wise
transfers. Possible values: 64, 128, 256, 512 and 1024.
config COAP_SERVER_MESSAGE_SIZE
int "CoAP server message payload size"
default COAP_SERVER_BLOCK_SIZE
help
CoAP server message payload size. Can't be smaller than COAP_SERVER_BLOCK_SIZE.
config COAP_SERVER_MESSAGE_OPTIONS
int "CoAP server message options"
default 16
help
CoAP server message maximum number of options to parse.
config COAP_SERVER_WELL_KNOWN_CORE
bool "CoAP server support ./well-known/core service"
default y
help
Enable responding to the ./well-known/core service resource.
config COAP_SERVICE_PENDING_MESSAGES
int "CoAP service pending messages"
default 10
help
Maximum number of pending CoAP messages to retransmit per active service.
config COAP_SERVICE_PENDING_RETRANSMITS
int "CoAP retransmit count"
default 2
help
Maximum number of retries to send a pending message.
config COAP_SERVICE_OBSERVERS
int "CoAP service observers"
default 3
help
Maximum number of CoAP observers per active service.
choice COAP_SERVER_PENDING_ALLOCATOR
prompt "Pending data allocator"
default COAP_SERVER_PENDING_ALLOCATOR_STATIC
config COAP_SERVER_PENDING_ALLOCATOR_NONE
bool "No pending packets"
help
Never allocate data for pending requests, this disables retransmits for confirmable
packets.
config COAP_SERVER_PENDING_ALLOCATOR_STATIC
bool "Static reserved memory"
help
Static memory will be reserved for pending messages. The total size is equal to
COAP_SERVER_PENDING_ALLOCATOR_STATIC_BLOCKS * COAP_SERVER_MESSAGE_SIZE.
config COAP_SERVER_PENDING_ALLOCATOR_SYSTEM_HEAP
bool "System heap allocator"
depends on HEAP_MEM_POOL_SIZE > 0
help
Use k_malloc/k_free for pending data.
endchoice
config COAP_SERVER_PENDING_ALLOCATOR_STATIC_BLOCKS
int "Number of pending data blocks"
default COAP_SERVICE_PENDING_MESSAGES
depends on COAP_SERVER_PENDING_ALLOCATOR_STATIC
help
The number of data blocks to reserve for pending messages to retransmit.
endif
module = COAP
module-dep = NET_LOG
module-str = Log level for CoAP

View File

@ -0,0 +1,762 @@
/*
* Copyright (c) 2023 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL);
#include <zephyr/net/socket.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/net/coap.h>
#include <zephyr/net/coap_link_format.h>
#include <zephyr/net/coap_service.h>
#ifdef CONFIG_ARCH_POSIX
#include <fcntl.h>
#else
#include <zephyr/posix/fcntl.h>
#endif
#if defined(CONFIG_NET_TC_THREAD_COOPERATIVE)
/* Lowest priority cooperative thread */
#define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1)
#else
#define THREAD_PRIORITY K_PRIO_PREEMPT(CONFIG_NUM_PREEMPT_PRIORITIES - 1)
#endif
#define ADDRLEN(sock) \
(((struct sockaddr *)sock)->sa_family == AF_INET ? \
sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))
/* Shortened defines */
#define MAX_OPTIONS CONFIG_COAP_SERVER_MESSAGE_OPTIONS
#define MAX_PENDINGS CONFIG_COAP_SERVICE_PENDING_MESSAGES
#define MAX_OBSERVERS CONFIG_COAP_SERVICE_OBSERVERS
#define MAX_POLL_FD CONFIG_NET_SOCKETS_POLL_MAX
BUILD_ASSERT(CONFIG_NET_SOCKETS_POLL_MAX > 0, "CONFIG_NET_SOCKETS_POLL_MAX can't be 0");
static K_MUTEX_DEFINE(lock);
static int control_socks[2];
#if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC)
K_MEM_SLAB_DEFINE_STATIC(pending_data, CONFIG_COAP_SERVER_MESSAGE_SIZE,
CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC_BLOCKS, 4);
#endif
static inline void *coap_server_alloc(size_t len)
{
#if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC)
void *ptr;
int ret;
if (len > CONFIG_COAP_SERVER_MESSAGE_SIZE) {
return NULL;
}
ret = k_mem_slab_alloc(&pending_data, &ptr, K_NO_WAIT);
if (ret < 0) {
return NULL;
}
return ptr;
#elif defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_SYSTEM_HEAP)
return k_malloc(len);
#else
ARG_UNUSED(len);
return NULL;
#endif
}
static inline void coap_server_free(void *ptr)
{
#if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC)
k_mem_slab_free(&pending_data, ptr);
#elif defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_SYSTEM_HEAP)
k_free(ptr);
#else
ARG_UNUSED(ptr);
#endif
}
static int coap_service_remove_observer(const struct coap_service *service,
struct coap_resource *resource,
const struct sockaddr *addr,
const uint8_t *token, uint8_t tkl)
{
struct coap_observer *obs;
/* Prefer token to find the observer */
if (tkl > 0 && token != NULL) {
obs = coap_find_observer_by_token(service->data->observers, MAX_OBSERVERS, token,
tkl);
} else if (addr != NULL) {
obs = coap_find_observer_by_addr(service->data->observers, MAX_OBSERVERS, addr);
} else {
/* Either a token or an address is required */
return -EINVAL;
}
if (obs == NULL) {
return 0;
}
if (resource == NULL) {
COAP_SERVICE_FOREACH_RESOURCE(service, it) {
if (coap_remove_observer(it, obs)) {
memset(obs, 0, sizeof(*obs));
return 1;
}
}
} else if (coap_remove_observer(resource, obs)) {
memset(obs, 0, sizeof(*obs));
return 1;
}
return 0;
}
static int coap_server_process(int sock_fd)
{
uint8_t buf[CONFIG_COAP_SERVER_MESSAGE_SIZE];
struct sockaddr client_addr;
socklen_t client_addr_len = sizeof(client_addr);
struct coap_service *service = NULL;
struct coap_packet request;
struct coap_pending *pending;
struct coap_option options[MAX_OPTIONS] = { 0 };
uint8_t opt_num = MAX_OPTIONS;
uint8_t type;
ssize_t received;
int ret;
received = zsock_recvfrom(sock_fd, buf, sizeof(buf), ZSOCK_MSG_DONTWAIT, &client_addr,
&client_addr_len);
__ASSERT_NO_MSG(received <= sizeof(buf));
if (received < 0) {
if (errno == EWOULDBLOCK) {
return 0;
}
LOG_ERR("Failed to process client request (%d)", -errno);
return -errno;
}
ret = coap_packet_parse(&request, buf, received, options, opt_num);
if (ret < 0) {
LOG_ERR("Failed To parse coap message (%d)", ret);
return ret;
}
(void)k_mutex_lock(&lock, K_FOREVER);
/* Find the active service */
COAP_SERVICE_FOREACH(svc) {
if (svc->data->sock_fd == sock_fd) {
service = svc;
break;
}
}
if (service == NULL) {
ret = -ENOENT;
goto unlock;
}
type = coap_header_get_type(&request);
pending = coap_pending_received(&request, service->data->pending, MAX_PENDINGS);
if (pending) {
uint8_t token[COAP_TOKEN_MAX_LEN];
uint8_t tkl;
switch (type) {
case COAP_TYPE_RESET:
tkl = coap_header_get_token(&request, token);
coap_service_remove_observer(service, NULL, &client_addr, token, tkl);
__fallthrough;
case COAP_TYPE_ACK:
coap_server_free(pending->data);
coap_pending_clear(pending);
break;
default:
LOG_WRN("Unexpected pending type %d", type);
ret = -EINVAL;
goto unlock;
}
goto unlock;
} else if (type == COAP_TYPE_ACK || type == COAP_TYPE_RESET) {
LOG_WRN("Unexpected type %d without pending packet", type);
ret = -EINVAL;
goto unlock;
}
if (IS_ENABLED(CONFIG_COAP_SERVER_WELL_KNOWN_CORE) &&
coap_header_get_code(&request) == COAP_METHOD_GET &&
coap_uri_path_match(COAP_WELL_KNOWN_CORE_PATH, options, opt_num)) {
uint8_t well_known_buf[CONFIG_COAP_SERVER_MESSAGE_SIZE];
struct coap_packet response;
ret = coap_well_known_core_get_len(service->res_begin,
COAP_SERVICE_RESOURCE_COUNT(service),
&request, &response,
well_known_buf, sizeof(well_known_buf));
if (ret < 0) {
LOG_ERR("Failed to build well known core for %s (%d)", service->name, ret);
goto unlock;
}
ret = coap_service_send(service, &response, &client_addr, client_addr_len);
} else {
ret = coap_handle_request_len(&request, service->res_begin,
COAP_SERVICE_RESOURCE_COUNT(service),
options, opt_num, &client_addr, client_addr_len);
/* Shortcut for replying a code without a body */
if (ret > 0 && type == COAP_TYPE_CON) {
/* Minimal sized ack buffer */
uint8_t ack_buf[COAP_TOKEN_MAX_LEN + 4U];
struct coap_packet ack;
ret = coap_ack_init(&ack, &request, ack_buf, sizeof(ack_buf), (uint8_t)ret);
if (ret < 0) {
LOG_ERR("Failed to init ACK (%d)", ret);
goto unlock;
}
ret = coap_service_send(service, &ack, &client_addr, client_addr_len);
}
}
unlock:
(void)k_mutex_unlock(&lock);
return ret;
}
static void coap_server_retransmit(void)
{
struct coap_pending *pending;
int64_t remaining;
int64_t now = k_uptime_get();
int ret;
(void)k_mutex_lock(&lock, K_FOREVER);
COAP_SERVICE_FOREACH(service) {
if (service->data->sock_fd < 0) {
continue;
}
pending = coap_pending_next_to_expire(service->data->pending, MAX_PENDINGS);
if (pending == NULL) {
/* No work to be done */
continue;
}
/* Check if the pending request has expired */
remaining = pending->t0 + pending->timeout - now;
if (remaining > 0) {
continue;
}
if (coap_pending_cycle(pending)) {
ret = zsock_sendto(service->data->sock_fd, pending->data, pending->len, 0,
&pending->addr, ADDRLEN(&pending->addr));
if (ret < 0) {
LOG_ERR("Failed to send pending retransmission for %s (%d)",
service->name, ret);
}
__ASSERT_NO_MSG(ret == pending->len);
} else {
LOG_WRN("Packet retransmission failed for %s", service->name);
coap_service_remove_observer(service, NULL, &pending->addr, NULL, 0U);
coap_server_free(pending->data);
coap_pending_clear(pending);
}
}
(void)k_mutex_unlock(&lock);
}
static int coap_server_poll_timeout(void)
{
struct coap_pending *pending;
int64_t result = INT64_MAX;
int64_t remaining;
int64_t now = k_uptime_get();
COAP_SERVICE_FOREACH(svc) {
if (svc->data->sock_fd < -1) {
continue;
}
pending = coap_pending_next_to_expire(svc->data->pending, MAX_PENDINGS);
if (pending == NULL) {
continue;
}
remaining = pending->t0 + pending->timeout - now;
if (result > remaining) {
result = remaining;
}
}
if (result == INT64_MAX) {
return -1;
}
return MAX(result, 0);
}
static void coap_server_update_services(void)
{
zsock_send(control_socks[1], &(char){0}, 1, 0);
}
static inline bool coap_service_in_section(const struct coap_service *service)
{
STRUCT_SECTION_START_EXTERN(coap_service);
STRUCT_SECTION_END_EXTERN(coap_service);
return STRUCT_SECTION_START(coap_service) <= service &&
STRUCT_SECTION_END(coap_service) > service;
}
int coap_service_start(const struct coap_service *service)
{
int ret;
uint8_t af;
socklen_t len;
struct sockaddr_storage addr_storage;
union {
struct sockaddr *addr;
struct sockaddr_in *addr4;
struct sockaddr_in6 *addr6;
} addr_ptrs = {
.addr = (struct sockaddr *)&addr_storage,
};
if (!coap_service_in_section(service)) {
__ASSERT_NO_MSG(false);
return -EINVAL;
}
k_mutex_lock(&lock, K_FOREVER);
if (service->data->sock_fd >= 0) {
ret = -EALREADY;
goto end;
}
/* set the default address (in6addr_any / INADDR_ANY are all 0) */
addr_storage = (struct sockaddr_storage){0};
if (IS_ENABLED(CONFIG_NET_IPV6) && service->host != NULL &&
zsock_inet_pton(AF_INET6, service->host, &addr_ptrs.addr6->sin6_addr) == 1) {
/* if a literal IPv6 address is provided as the host, use IPv6 */
af = AF_INET6;
len = sizeof(struct sockaddr_in6);
addr_ptrs.addr6->sin6_family = AF_INET6;
addr_ptrs.addr6->sin6_port = htons(*service->port);
} else if (IS_ENABLED(CONFIG_NET_IPV4) && service->host != NULL &&
zsock_inet_pton(AF_INET, service->host, &addr_ptrs.addr4->sin_addr) == 1) {
/* if a literal IPv4 address is provided as the host, use IPv4 */
af = AF_INET;
len = sizeof(struct sockaddr_in);
addr_ptrs.addr4->sin_family = AF_INET;
addr_ptrs.addr4->sin_port = htons(*service->port);
} else if (IS_ENABLED(CONFIG_NET_IPV6)) {
/* prefer IPv6 if both IPv6 and IPv4 are supported */
af = AF_INET6;
len = sizeof(struct sockaddr_in6);
addr_ptrs.addr6->sin6_family = AF_INET6;
addr_ptrs.addr6->sin6_port = htons(*service->port);
} else if (IS_ENABLED(CONFIG_NET_IPV4)) {
af = AF_INET;
len = sizeof(struct sockaddr_in);
addr_ptrs.addr4->sin_family = AF_INET;
addr_ptrs.addr4->sin_port = htons(*service->port);
} else {
ret = -ENOTSUP;
goto end;
}
service->data->sock_fd = zsock_socket(af, SOCK_DGRAM, IPPROTO_UDP);
if (service->data->sock_fd < 0) {
ret = -errno;
goto end;
}
ret = zsock_fcntl(service->data->sock_fd, F_SETFL, O_NONBLOCK);
if (ret < 0) {
ret = -errno;
goto close;
}
ret = zsock_bind(service->data->sock_fd, addr_ptrs.addr, len);
if (ret < 0) {
ret = -errno;
goto close;
}
if (*service->port == 0) {
/* ephemeral port - read back the port number */
len = sizeof(addr_storage);
ret = zsock_getsockname(service->data->sock_fd, addr_ptrs.addr, &len);
if (ret < 0) {
goto close;
}
if (af == AF_INET6) {
*service->port = addr_ptrs.addr6->sin6_port;
} else {
*service->port = addr_ptrs.addr4->sin_port;
}
}
end:
k_mutex_unlock(&lock);
coap_server_update_services();
return ret;
close:
(void)zsock_close(service->data->sock_fd);
service->data->sock_fd = -1;
k_mutex_unlock(&lock);
return ret;
}
int coap_service_stop(const struct coap_service *service)
{
int ret;
if (!coap_service_in_section(service)) {
__ASSERT_NO_MSG(false);
return -EINVAL;
}
k_mutex_lock(&lock, K_FOREVER);
if (service->data->sock_fd < 0) {
ret = -EALREADY;
goto end;
}
/* Closing a socket will trigger a poll event */
ret = zsock_close(service->data->sock_fd);
service->data->sock_fd = -1;
end:
k_mutex_unlock(&lock);
return ret;
}
int coap_service_send(const struct coap_service *service, const struct coap_packet *cpkt,
const struct sockaddr *addr, socklen_t addr_len)
{
int ret;
if (!coap_service_in_section(service)) {
__ASSERT_NO_MSG(false);
return -EINVAL;
}
(void)k_mutex_lock(&lock, K_FOREVER);
if (service->data->sock_fd < 0) {
(void)k_mutex_unlock(&lock);
return -EBADF;
}
/*
* Check if we should start with retransmits, if creating a pending message fails we still
* try to send.
*/
if (coap_header_get_type(cpkt) == COAP_TYPE_CON) {
struct coap_pending *pending = coap_pending_next_unused(service->data->pending,
MAX_PENDINGS);
if (pending == NULL) {
LOG_WRN("No pending message available for %s", service->name);
goto send;
}
ret = coap_pending_init(pending, cpkt, addr,
CONFIG_COAP_SERVICE_PENDING_RETRANSMITS);
if (ret < 0) {
LOG_WRN("Failed to init pending message for %s (%d)", service->name, ret);
goto send;
}
/* Replace tracked data with our allocated copy */
pending->data = coap_server_alloc(pending->len);
if (pending->data == NULL) {
LOG_WRN("Failed to allocate pending message data for %s", service->name);
coap_pending_clear(pending);
goto send;
}
memcpy(pending->data, cpkt->data, pending->len);
coap_pending_cycle(pending);
/* Trigger event in receive loop to schedule retransmit */
coap_server_update_services();
}
send:
(void)k_mutex_unlock(&lock);
ret = zsock_sendto(service->data->sock_fd, cpkt->data, cpkt->offset, 0, addr, addr_len);
if (ret < 0) {
LOG_ERR("Failed to send CoAP message (%d)", ret);
return ret;
}
__ASSERT_NO_MSG(ret == cpkt->offset);
return 0;
}
int coap_resource_send(const struct coap_resource *resource, const struct coap_packet *cpkt,
const struct sockaddr *addr, socklen_t addr_len)
{
/* Find owning service */
COAP_SERVICE_FOREACH(svc) {
if (COAP_SERVICE_HAS_RESOURCE(svc, resource)) {
return coap_service_send(svc, cpkt, addr, addr_len);
}
}
return -ENOENT;
}
int coap_resource_parse_observe(struct coap_resource *resource, const struct coap_packet *request,
const struct sockaddr *addr)
{
const struct coap_service *service = NULL;
int ret;
if (!coap_packet_is_request(request)) {
return -EINVAL;
}
ret = coap_get_option_int(request, COAP_OPTION_OBSERVE);
if (ret < 0) {
return ret;
}
/* Find owning service */
COAP_SERVICE_FOREACH(svc) {
if (COAP_SERVICE_HAS_RESOURCE(svc, resource)) {
service = svc;
break;
}
}
if (service == NULL) {
return -ENOENT;
}
(void)k_mutex_lock(&lock, K_FOREVER);
if (ret == 0) {
struct coap_observer *observer;
observer = coap_observer_next_unused(service->data->observers, MAX_OBSERVERS);
if (observer == NULL) {
ret = -ENOMEM;
goto unlock;
}
coap_observer_init(observer, request, addr);
coap_register_observer(resource, observer);
} else if (ret == 1) {
uint8_t token[COAP_TOKEN_MAX_LEN];
uint8_t tkl;
tkl = coap_header_get_token(request, token);
ret = coap_service_remove_observer(service, resource, addr, token, tkl);
if (ret < 0) {
LOG_WRN("Failed to remove observer (%d)", ret);
}
}
unlock:
(void)k_mutex_unlock(&lock);
return ret;
}
static int coap_resource_remove_observer(struct coap_resource *resource,
const struct sockaddr *addr,
const uint8_t *token, uint8_t token_len)
{
const struct coap_service *service = NULL;
int ret;
/* Find owning service */
COAP_SERVICE_FOREACH(svc) {
if (COAP_SERVICE_HAS_RESOURCE(svc, resource)) {
service = svc;
break;
}
}
if (service == NULL) {
return -ENOENT;
}
(void)k_mutex_lock(&lock, K_FOREVER);
ret = coap_service_remove_observer(service, resource, addr, token, token_len);
(void)k_mutex_unlock(&lock);
if (ret == 1) {
/* An observer was found and removed */
return 0;
} else if (ret == 0) {
/* No matching observer found */
return -ENOENT;
}
/* An error occurred */
return ret;
}
int coap_resource_remove_observer_by_addr(struct coap_resource *resource,
const struct sockaddr *addr)
{
return coap_resource_remove_observer(resource, addr, NULL, 0);
}
int coap_resource_remove_observer_by_token(struct coap_resource *resource,
const uint8_t *token, uint8_t token_len)
{
return coap_resource_remove_observer(resource, NULL, token, token_len);
}
static void coap_server_thread(void *p1, void *p2, void *p3)
{
struct zsock_pollfd sock_fds[MAX_POLL_FD];
int sock_nfds;
int ret;
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
/* Create a socket pair to wake zsock_poll */
ret = zsock_socketpair(AF_UNIX, SOCK_STREAM, 0, control_socks);
if (ret < 0) {
LOG_ERR("Failed to create socket pair (%d)", ret);
return;
}
for (int i = 0; i < 2; ++i) {
ret = zsock_fcntl(control_socks[i], F_SETFL, O_NONBLOCK);
if (ret < 0) {
zsock_close(control_socks[0]);
zsock_close(control_socks[1]);
LOG_ERR("Failed to set socket pair [%d] non-blocking (%d)", i, ret);
return;
}
}
COAP_SERVICE_FOREACH(svc) {
/* Init all file descriptors to -1 */
svc->data->sock_fd = -1;
if (svc->flags & COAP_SERVICE_AUTOSTART) {
ret = coap_service_start(svc);
if (ret < 0) {
LOG_ERR("Failed to autostart service %s (%d)", svc->name, ret);
}
}
}
while (true) {
sock_nfds = 0;
COAP_SERVICE_FOREACH(svc) {
if (svc->data->sock_fd < 0) {
continue;
}
if (sock_nfds >= MAX_POLL_FD) {
LOG_ERR("Maximum active CoAP services reached (%d), "
"increase CONFIG_NET_SOCKETS_POLL_MAX to support more.",
MAX_POLL_FD);
break;
}
sock_fds[sock_nfds].fd = svc->data->sock_fd;
sock_fds[sock_nfds].events = ZSOCK_POLLIN;
sock_fds[sock_nfds].revents = 0;
sock_nfds++;
}
/* Add socket pair FD to allow wake up */
if (sock_nfds < MAX_POLL_FD) {
sock_fds[sock_nfds].fd = control_socks[0];
sock_fds[sock_nfds].events = ZSOCK_POLLIN;
sock_fds[sock_nfds].revents = 0;
sock_nfds++;
}
__ASSERT_NO_MSG(sock_nfds > 0);
ret = zsock_poll(sock_fds, sock_nfds, coap_server_poll_timeout());
if (ret < 0) {
LOG_ERR("Poll error (%d)", -errno);
k_msleep(10);
}
for (int i = 0; i < sock_nfds; ++i) {
/* Check the wake up event */
if (sock_fds[i].fd == control_socks[0] &&
sock_fds[i].revents & ZSOCK_POLLIN) {
char tmp;
zsock_recv(sock_fds[i].fd, &tmp, 1, 0);
continue;
}
/* Check if socket can receive/was closed first */
if (sock_fds[i].revents & ZSOCK_POLLIN) {
coap_server_process(sock_fds[i].fd);
continue;
}
if (sock_fds[i].revents & ZSOCK_POLLERR) {
LOG_ERR("Poll error on %d", sock_fds[i].fd);
}
if (sock_fds[i].revents & ZSOCK_POLLHUP) {
LOG_ERR("Poll hup on %d", sock_fds[i].fd);
}
if (sock_fds[i].revents & ZSOCK_POLLNVAL) {
LOG_ERR("Poll invalid on %d", sock_fds[i].fd);
}
}
/* Process retransmits */
coap_server_retransmit();
}
}
K_THREAD_DEFINE(coap_server_id, CONFIG_COAP_SERVER_STACK_SIZE,
coap_server_thread, NULL, NULL, NULL,
THREAD_PRIORITY, 0, 0);