diff --git a/include/zephyr/linker/common-rom/common-rom-net.ld b/include/zephyr/linker/common-rom/common-rom-net.ld index e535f5f1fd0..71c1c1e089f 100644 --- a/include/zephyr/linker/common-rom/common-rom-net.ld +++ b/include/zephyr/linker/common-rom/common-rom-net.ld @@ -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 diff --git a/include/zephyr/net/coap_service.h b/include/zephyr/net/coap_service.h new file mode 100644 index 00000000000..3e3201308b9 --- /dev/null +++ b/include/zephyr/net/coap_service.h @@ -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 +#include + +#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_ */ diff --git a/subsys/net/lib/coap/CMakeLists.txt b/subsys/net/lib/coap/CMakeLists.txt index 39ef8196fbb..b2e40f9bcfb 100644 --- a/subsys/net/lib/coap/CMakeLists.txt +++ b/subsys/net/lib/coap/CMakeLists.txt @@ -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 +) diff --git a/subsys/net/lib/coap/Kconfig b/subsys/net/lib/coap/Kconfig index 00ee157a116..3125c4ce612 100644 --- a/subsys/net/lib/coap/Kconfig +++ b/subsys/net/lib/coap/Kconfig @@ -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 diff --git a/subsys/net/lib/coap/coap_server.c b/subsys/net/lib/coap/coap_server.c new file mode 100644 index 00000000000..fbcbe1721b2 --- /dev/null +++ b/subsys/net/lib/coap/coap_server.c @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2023 Basalte bv + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL); + +#include + +#include +#include +#include +#include +#include +#ifdef CONFIG_ARCH_POSIX +#include +#else +#include +#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);