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:
parent
5227f24815
commit
ae6e0106e7
@ -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
|
||||
|
||||
292
include/zephyr/net/coap_service.h
Normal file
292
include/zephyr/net/coap_service.h
Normal 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_ */
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
762
subsys/net/lib/coap/coap_server.c
Normal file
762
subsys/net/lib/coap/coap_server.c
Normal 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);
|
||||
Loading…
Reference in New Issue
Block a user