net: lib: coap: Add an asynchronous coap client
The coap client takes requests and provides responses
asynchronously to callback given in a request.
Currently supports only 1 request at a time.
Signed-off-by: Jarno Lämsä <jarno.lamsa@nordicsemi.no>
diff --git a/include/zephyr/net/coap_client.h b/include/zephyr/net/coap_client.h
new file mode 100644
index 0000000..dc5d008
--- /dev/null
+++ b/include/zephyr/net/coap_client.h
@@ -0,0 +1,139 @@
+/** @file
+ * @brief CoAP client API
+ *
+ * An API for applications to do CoAP requests
+ */
+
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef ZEPHYR_INCLUDE_NET_COAP_CLIENT_H_
+#define ZEPHYR_INCLUDE_NET_COAP_CLIENT_H_
+
+/**
+ * @brief CoAP client API
+ * @defgroup coap_client CoAP client API
+ * @ingroup networking
+ * @{
+ */
+
+#include <zephyr/net/coap.h>
+
+
+#define MAX_COAP_MSG_LEN (CONFIG_COAP_CLIENT_MESSAGE_HEADER_SIZE + \
+ CONFIG_COAP_CLIENT_MESSAGE_SIZE)
+
+/**
+ * @typedef coap_client_response_cb_t
+ * @brief Callback for CoAP request.
+ *
+ * This callback is called for responses to CoAP client requests.
+ * It is used to indicate errors, response codes from server or to deliver payload.
+ * Blockwise transfers cause this callback to be called sequentially with increasing payload offset
+ * and only partial content in buffer pointed by payload parameter.
+ *
+ * @param result_code Result code of the response. Negative if there was a failure in send.
+ * @ref coap_response_code for positive.
+ * @param offset Payload offset from the beginning of a blockwise transfer.
+ * @param payload Buffer containing the payload from the response. NULL for empty payload.
+ * @param len Size of the payload.
+ * @param last_block Indicates the last block of the response.
+ * @param user_data User provided context.
+ */
+typedef void (*coap_client_response_cb_t)(int16_t result_code,
+ size_t offset, const uint8_t *payload, size_t len,
+ bool last_block, void *user_data);
+
+/**
+ * @brief Representation of a CoAP client request.
+ */
+struct coap_client_request {
+ enum coap_method method; /**< Method of the request */
+ bool confirmable; /**< CoAP Confirmable/Non-confirmable message */
+ const char *path; /**< Path of the requested resource */
+ enum coap_content_format fmt; /**< Content format to be used */
+ uint8_t *payload; /**< User allocated buffer for send request */
+ size_t len; /**< Length of the payload */
+ coap_client_response_cb_t cb; /**< Callback when response received */
+ struct coap_client_option *options; /**< Extra options to be added to request */
+ uint8_t num_options; /**< Number of extra options */
+ void *user_data; /**< User provided context */
+};
+
+/**
+ * @brief Representation of extra options for the CoAP client request
+ */
+struct coap_client_option {
+ uint16_t code;
+#if defined(CONFIG_COAP_EXTENDED_OPTIONS_LEN)
+ uint16_t len;
+ uint8_t value[CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE];
+#else
+ uint8_t len;
+ uint8_t value[12];
+#endif
+};
+
+/** @cond INTERNAL_HIDDEN */
+struct coap_client {
+ int fd;
+ struct sockaddr address;
+ socklen_t socklen;
+ uint8_t send_buf[MAX_COAP_MSG_LEN];
+ uint8_t recv_buf[MAX_COAP_MSG_LEN];
+ uint8_t request_token[COAP_TOKEN_MAX_LEN];
+ int request_tkl;
+ int offset;
+ int retry_count;
+ struct coap_block_context recv_blk_ctx;
+ struct coap_block_context send_blk_ctx;
+ struct coap_pending pending;
+ struct coap_client_request *coap_request;
+ struct coap_packet request;
+ k_tid_t tid;
+ struct k_thread thread;
+ struct k_sem coap_client_recv_sem;
+ atomic_t coap_client_recv_active;
+
+ K_THREAD_STACK_MEMBER(coap_thread_stack, CONFIG_COAP_CLIENT_STACK_SIZE);
+};
+/** @endcond */
+
+/**
+ * @brief Initialize the CoAP client.
+ *
+ * @param[in] client Client instance.
+ * @param[in] info Name for the receiving thread of the client. Setting this NULL will result as
+ * default name of "coap_client".
+ *
+ * @return int Zero on success, otherwise a negative error code.
+ */
+int coap_client_init(struct coap_client *client, const char *info);
+
+/**
+ * @brief Send CoAP request
+ *
+ * Operation is handled asynchronously using a background thread.
+ * If the socket isn't connected to a destination address, user must provide a destination address,
+ * otherwise the address should be set as NULL.
+ * Once the callback is called with last block set as true, socket can be closed or
+ * used for another query.
+ *
+ * @param client Client instance.
+ * @param sock Open socket file descriptor.
+ * @param addr the destination address of the request.
+ * @param req CoAP request structure
+ * @param retries How many times to retry or -1 to use default.
+ * @return zero when operation started successfully or negative error code otherwise.
+ */
+
+int coap_client_req(struct coap_client *client, int sock, const struct sockaddr *addr,
+ struct coap_client_request *req, int retries);
+
+/**
+ * @}
+ */
+
+#endif /* ZEPHYR_INCLUDE_NET_COAP_CLIENT_H_ */
diff --git a/subsys/net/lib/coap/CMakeLists.txt b/subsys/net/lib/coap/CMakeLists.txt
index d97d0e3..39ef819 100644
--- a/subsys/net/lib/coap/CMakeLists.txt
+++ b/subsys/net/lib/coap/CMakeLists.txt
@@ -6,3 +6,7 @@
coap.c
coap_link_format.c
)
+
+zephyr_sources_ifdef(CONFIG_COAP_CLIENT
+ coap_client.c
+)
diff --git a/subsys/net/lib/coap/Kconfig b/subsys/net/lib/coap/Kconfig
index 06f7ea2..12a7c0c 100644
--- a/subsys/net/lib/coap/Kconfig
+++ b/subsys/net/lib/coap/Kconfig
@@ -94,6 +94,47 @@
help
This option enables keeping application-specific user data
+config COAP_CLIENT
+ bool "CoAP client support [EXPERIMENTAL]"
+ select EXPERIMENTAL
+ help
+ This option enables the API for CoAP-client for sending CoAP requests
+
+if COAP_CLIENT
+
+config COAP_CLIENT_THREAD_PRIORITY
+ int "Coap client thread priority"
+ default NUM_PREEMPT_PRIORITIES
+ help
+ Priority of receive thread of the CoAP client.
+
+config COAP_CLIENT_BLOCK_SIZE
+ int "LWM2M CoAP block-wise transfer size"
+ default 256
+ range 64 1024
+ help
+ CoAP block size used by CoAP client when performing block-wise
+ transfers. Possible values: 64, 128, 256, 512 and 1024.
+
+config COAP_CLIENT_MESSAGE_SIZE
+ int "Message payload size"
+ default COAP_CLIENT_BLOCK_SIZE
+ help
+ CoAP client message payload size. Can't be smaller than COAP_CLIENT_BLOCK_SIZE.
+
+config COAP_CLIENT_MESSAGE_HEADER_SIZE
+ int "Room for CoAP header data"
+ default 48
+ range 24 128
+ help
+ Extra room allocated to handle CoAP header data
+
+config COAP_CLIENT_STACK_SIZE
+ int "Stack size of the CoAP client thread"
+ default 1024
+
+endif # COAP_CLIENT
+
module = COAP
module-dep = NET_LOG
module-str = Log level for CoAP
diff --git a/subsys/net/lib/coap/coap_client.c b/subsys/net/lib/coap/coap_client.c
new file mode 100644
index 0000000..ce20cd3
--- /dev/null
+++ b/subsys/net/lib/coap/coap_client.c
@@ -0,0 +1,742 @@
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * 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/net/coap.h>
+#include <zephyr/net/coap_client.h>
+
+#define COAP_VERSION 1
+#define COAP_PATH_ELEM_DELIM '/'
+#define COAP_PATH_ELEM_QUERY '?'
+#define COAP_PATH_ELEM_AMP '&'
+#define COAP_SEPARATE_TIMEOUT 6000
+#define DEFAULT_RETRY_AMOUNT 5
+#define BLOCK1_OPTION_SIZE 4
+#define PAYLOAD_MARKER_SIZE 1
+
+static int coap_client_schedule_poll(struct coap_client *client, int sock,
+ struct coap_client_request *req)
+{
+ client->fd = sock;
+ client->coap_request = req;
+
+ k_sem_give(&client->coap_client_recv_sem);
+ atomic_set(&client->coap_client_recv_active, 1);
+
+ return 0;
+}
+
+static int send_request(int sock, const void *buf, size_t len, int flags,
+ const struct sockaddr *dest_addr, socklen_t addrlen)
+{
+ if (addrlen == 0) {
+ return sendto(sock, buf, len, flags, NULL, 0);
+ } else {
+ return sendto(sock, buf, len, flags, dest_addr, addrlen);
+ }
+}
+
+static int receive(int sock, void *buf, size_t max_len, int flags,
+ struct sockaddr *src_addr, socklen_t *addrlen)
+{
+ if (*addrlen == 0) {
+ return recvfrom(sock, buf, max_len, flags, NULL, NULL);
+ } else {
+ return recvfrom(sock, buf, max_len, flags, src_addr, addrlen);
+ }
+}
+
+static void reset_block_contexts(struct coap_client *client)
+{
+ client->recv_blk_ctx.block_size = 0;
+ client->recv_blk_ctx.total_size = 0;
+ client->recv_blk_ctx.current = 0;
+
+ client->send_blk_ctx.block_size = 0;
+ client->send_blk_ctx.total_size = 0;
+ client->send_blk_ctx.current = 0;
+}
+
+static int coap_client_init_path_options(struct coap_packet *pckt, const char *path)
+{
+ int ret = 0;
+ int path_start, path_end;
+ int path_length;
+ bool contains_query = false;
+ int i;
+
+ path_start = 0;
+ path_end = 0;
+ path_length = strlen(path);
+ for (i = 0; i < path_length; i++) {
+ path_end = i;
+ if (path[i] == COAP_PATH_ELEM_DELIM) {
+ /* Guard for preceding delimiters */
+ if (path_start < path_end) {
+ ret = coap_packet_append_option(pckt, COAP_OPTION_URI_PATH,
+ path + path_start,
+ path_end - path_start);
+ if (ret < 0) {
+ LOG_ERR("Failed to append path to CoAP message");
+ goto out;
+ }
+ }
+ /* Check if there is a new path after delimiter,
+ * if not, point to the end of string to not add
+ * new option after this
+ */
+ if (path_length > i + 1) {
+ path_start = i + 1;
+ } else {
+ path_start = path_length;
+ }
+ } else if (path[i] == COAP_PATH_ELEM_QUERY) {
+ /* Guard for preceding delimiters */
+ if (path_start < path_end) {
+ ret = coap_packet_append_option(pckt, COAP_OPTION_URI_PATH,
+ path + path_start,
+ path_end - path_start);
+ if (ret < 0) {
+ LOG_ERR("Failed to append path to CoAP message");
+ goto out;
+ }
+ }
+ /* Rest of the path is query */
+ contains_query = true;
+ if (path_length > i + 1) {
+ path_start = i + 1;
+ } else {
+ path_start = path_length;
+ }
+ break;
+ }
+ }
+
+ if (contains_query) {
+ for (i = path_start; i < path_length; i++) {
+ path_end = i;
+ if (path[i] == COAP_PATH_ELEM_AMP || path[i] == COAP_PATH_ELEM_QUERY) {
+ /* Guard for preceding delimiters */
+ if (path_start < path_end) {
+ ret = coap_packet_append_option(pckt, COAP_OPTION_URI_QUERY,
+ path + path_start,
+ path_end - path_start);
+ if (ret < 0) {
+ LOG_ERR("Failed to append path to CoAP message");
+ goto out;
+ }
+ }
+ /* Check if there is a new query option after delimiter,
+ * if not, point to the end of string to not add
+ * new option after this
+ */
+ if (path_length > i + 1) {
+ path_start = i + 1;
+ } else {
+ path_start = path_length;
+ }
+ }
+ }
+ }
+
+ if (path_start < path_end) {
+ if (contains_query) {
+ ret = coap_packet_append_option(pckt, COAP_OPTION_URI_QUERY,
+ path + path_start,
+ path_end - path_start + 1);
+ } else {
+ ret = coap_packet_append_option(pckt, COAP_OPTION_URI_PATH,
+ path + path_start,
+ path_end - path_start + 1);
+ }
+ if (ret < 0) {
+ LOG_ERR("Failed to append path to CoAP message");
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static enum coap_block_size coap_client_default_block_size(void)
+{
+ switch (CONFIG_COAP_CLIENT_BLOCK_SIZE) {
+ case 16:
+ return COAP_BLOCK_16;
+ case 32:
+ return COAP_BLOCK_32;
+ case 64:
+ return COAP_BLOCK_64;
+ case 128:
+ return COAP_BLOCK_128;
+ case 256:
+ return COAP_BLOCK_256;
+ case 512:
+ return COAP_BLOCK_512;
+ case 1024:
+ return COAP_BLOCK_1024;
+ }
+
+ return COAP_BLOCK_256;
+}
+
+static int coap_client_init_request(struct coap_client *client,
+ struct coap_client_request *req)
+{
+ int ret = 0;
+ int i;
+
+ memset(client->send_buf, 0, sizeof(client->send_buf));
+ ret = coap_packet_init(&client->request, client->send_buf, MAX_COAP_MSG_LEN, 1,
+ req->confirmable ? COAP_TYPE_CON : COAP_TYPE_NON_CON,
+ COAP_TOKEN_MAX_LEN, coap_next_token(), req->method,
+ coap_next_id());
+
+ if (ret < 0) {
+ LOG_ERR("Failed to init CoAP message %d", ret);
+ goto out;
+ }
+
+ ret = coap_client_init_path_options(&client->request, req->path);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to parse path to options %d", ret);
+ goto out;
+ }
+
+ ret = coap_append_option_int(&client->request, COAP_OPTION_CONTENT_FORMAT, req->fmt);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to append content format option");
+ goto out;
+ }
+
+ /* Blockwise receive ongoing, request next block. */
+ if (client->recv_blk_ctx.current > 0) {
+ ret = coap_append_block2_option(&client->request, &client->recv_blk_ctx);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to append block 2 option");
+ goto out;
+ }
+ }
+
+ /* Add extra options if any */
+ for (i = 0; i < req->num_options; i++) {
+ ret = coap_packet_append_option(&client->request, req->options[i].code,
+ req->options[i].value, req->options[i].len);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to append %d option", req->options[i].code);
+ goto out;
+ }
+ }
+
+ if (req->payload) {
+ uint16_t payload_len;
+ uint16_t offset;
+
+ /* Blockwise send ongoing, add block1 */
+ if (client->send_blk_ctx.total_size > 0 ||
+ (req->len > CONFIG_COAP_CLIENT_MESSAGE_SIZE)) {
+
+ if (client->send_blk_ctx.total_size == 0) {
+ coap_block_transfer_init(&client->send_blk_ctx,
+ coap_client_default_block_size(),
+ req->len);
+ }
+ ret = coap_append_block1_option(&client->request, &client->send_blk_ctx);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to append block1 option");
+ goto out;
+ }
+ }
+
+ ret = coap_packet_append_payload_marker(&client->request);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to append payload marker to CoAP message");
+ goto out;
+ }
+
+ if (client->send_blk_ctx.total_size > 0) {
+ uint16_t block_in_bytes =
+ coap_block_size_to_bytes(client->send_blk_ctx.block_size);
+
+ payload_len = client->send_blk_ctx.total_size -
+ client->send_blk_ctx.current;
+ if (payload_len > block_in_bytes) {
+ payload_len = block_in_bytes;
+ }
+ offset = client->send_blk_ctx.current;
+ } else {
+ payload_len = req->len;
+ offset = 0;
+ }
+
+ ret = coap_packet_append_payload(&client->request, req->payload + offset,
+ payload_len);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to append payload to CoAP message");
+ goto out;
+ }
+
+ if (client->send_blk_ctx.total_size > 0) {
+ coap_next_block(&client->request, &client->send_blk_ctx);
+ }
+ }
+ client->request_tkl = coap_header_get_token(&client->request, client->request_token);
+out:
+ return ret;
+}
+
+
+int coap_client_req(struct coap_client *client, int sock, const struct sockaddr *addr,
+ struct coap_client_request *req, int retries)
+{
+ int ret;
+
+ if (client->coap_client_recv_active) {
+ return -EAGAIN;
+ }
+
+ if (sock < 0 || req == NULL || req->path == NULL) {
+ return -EINVAL;
+ }
+
+ if (addr != NULL) {
+ memcpy(&client->address, addr, sizeof(*addr));
+ client->socklen = sizeof(client->address);
+ } else {
+ memset(&client->address, 0, sizeof(client->address));
+ client->socklen = 0;
+ }
+
+ if (retries == -1) {
+ client->retry_count = DEFAULT_RETRY_AMOUNT;
+ } else {
+ client->retry_count = retries;
+ }
+
+ ret = coap_client_init_request(client, req);
+ if (ret < 0) {
+ LOG_ERR("Failed to initialize coap request");
+ return ret;
+ }
+
+ ret = coap_client_schedule_poll(client, sock, req);
+ if (ret < 0) {
+ LOG_ERR("Failed to schedule polling");
+ goto out;
+ }
+
+ ret = coap_pending_init(&client->pending, &client->request, &client->address,
+ client->retry_count);
+
+ if (ret < 0) {
+ LOG_ERR("Failed to initialize pending struct");
+ goto out;
+ }
+
+ coap_pending_cycle(&client->pending);
+
+ ret = send_request(sock, client->request.data, client->request.offset, 0, &client->address,
+ client->socklen);
+
+ if (ret < 0) {
+ LOG_ERR("Transmission failed: %d", errno);
+ } else {
+ /* Do not return the number of bytes sent */
+ ret = 0;
+ }
+out:
+ return ret;
+}
+
+static int handle_poll(struct coap_client *client)
+{
+ int ret = 0;
+
+ while (1) {
+ struct pollfd fds;
+
+ fds.fd = client->fd;
+ fds.events = POLLIN;
+ fds.revents = 0;
+ /* rfc7252#section-5.2.2, use separate timeout value for a separate response */
+ if (client->pending.timeout != 0) {
+ ret = poll(&fds, 1, client->pending.timeout);
+ } else {
+ ret = poll(&fds, 1, COAP_SEPARATE_TIMEOUT);
+ }
+
+ if (ret < 0) {
+ LOG_ERR("Error in poll:%d", errno);
+ errno = 0;
+ return ret;
+ } else if (ret == 0) {
+ if (client->pending.timeout != 0 && coap_pending_cycle(&client->pending)) {
+ LOG_ERR("Timeout in poll, retrying send");
+ send_request(client->fd, client->request.data,
+ client->request.offset, 0, &client->address,
+ client->socklen);
+ } else {
+ /* No more retries left, don't retry */
+ LOG_ERR("Timeout in poll, no more retries");
+ ret = -EFAULT;
+ break;
+ }
+ } else {
+ if (fds.revents & POLLERR) {
+ LOG_ERR("Error in poll");
+ ret = -EIO;
+ break;
+ }
+
+ if (fds.revents & POLLHUP) {
+ LOG_ERR("Error in poll: POLLHUP");
+ ret = -ECONNRESET;
+ break;
+ }
+
+ if (fds.revents & POLLNVAL) {
+ LOG_ERR("Error in poll: POLLNVAL - fd not open");
+ ret = -EINVAL;
+ break;
+ }
+
+ if (!(fds.revents & POLLIN)) {
+ LOG_ERR("Unknown poll error");
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static bool token_compare(struct coap_client *client, const struct coap_packet *resp)
+{
+ uint8_t response_token[COAP_TOKEN_MAX_LEN];
+ uint8_t response_tkl;
+
+ response_tkl = coap_header_get_token(resp, response_token);
+
+ if (client->request_tkl != response_tkl) {
+ return false;
+ }
+
+ return memcmp(&client->request_token, &response_token, response_tkl) == 0;
+}
+
+static int recv_response(struct coap_client *client, struct coap_packet *response)
+{
+ int len;
+ int ret;
+
+ memset(client->recv_buf, 0, sizeof(client->recv_buf));
+ len = receive(client->fd, client->recv_buf, sizeof(client->recv_buf), MSG_DONTWAIT,
+ &client->address, &client->socklen);
+
+ if (len < 0) {
+ LOG_ERR("Error reading response: %d", errno);
+ return -EINVAL;
+ } else if (len == 0) {
+ LOG_ERR("Zero length recv");
+ return -EINVAL;
+ }
+
+ LOG_DBG("Received %d bytes", len);
+
+ ret = coap_packet_parse(response, client->recv_buf, len, NULL, 0);
+ if (ret < 0) {
+ LOG_ERR("Invalid data received");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void report_callback_error(struct coap_client *client, int error_code)
+{
+ if (client->coap_request->cb) {
+ client->coap_request->cb(error_code, 0, NULL, 0, true,
+ client->coap_request->user_data);
+ }
+}
+
+static int send_ack(struct coap_client *client, const struct coap_packet *req,
+ uint8_t response_code)
+{
+ int ret;
+
+ ret = coap_ack_init(&client->request, req, client->send_buf, MAX_COAP_MSG_LEN,
+ response_code);
+ if (ret < 0) {
+ LOG_ERR("Failed to initialize CoAP ACK-message");
+ return ret;
+ }
+
+ ret = send_request(client->fd, client->request.data, client->request.offset, 0,
+ &client->address, client->socklen);
+ if (ret < 0) {
+ LOG_ERR("Error sending a CoAP ACK-message");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int send_reset(struct coap_client *client, const struct coap_packet *req,
+ uint8_t response_code)
+{
+ int ret;
+ uint16_t id;
+ uint8_t token[COAP_TOKEN_MAX_LEN];
+ uint8_t tkl;
+
+ id = coap_header_get_id(req);
+ tkl = response_code ? coap_header_get_token(req, token) : 0;
+ ret = coap_packet_init(&client->request, client->send_buf, MAX_COAP_MSG_LEN, COAP_VERSION,
+ COAP_TYPE_RESET, tkl, token, response_code, id);
+
+ if (ret < 0) {
+ LOG_ERR("Error creating CoAP reset message");
+ return ret;
+ }
+
+ ret = send_request(client->fd, client->request.data, client->request.offset, 0,
+ &client->address, client->socklen);
+ if (ret < 0) {
+ LOG_ERR("Error sending CoAP reset message");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int handle_response(struct coap_client *client, const struct coap_packet *response)
+{
+ int ret = 0;
+ int response_type;
+ int block_option;
+ int block_num;
+ bool blockwise_transfer = false;
+ bool last_block = false;
+
+ /* Handle different types, ACK might be separate or piggybacked
+ * CON and NCON contains a separate response, CON needs an empty response
+ * CON request results as ACK and possibly separate CON or NCON response
+ * NCON request results only as a separate CON or NCON message as there is no ACK
+ * With RESET, just drop gloves and call the callback.
+ */
+ response_type = coap_header_get_type(response);
+
+ /* Reset and Ack need to match the message ID with request */
+ if ((response_type == COAP_TYPE_ACK || response_type == COAP_TYPE_RESET) &&
+ coap_header_get_id(response) != client->pending.id) {
+ LOG_ERR("Unexpected ACK or Reset");
+ return -EFAULT;
+ } else if (response_type == COAP_TYPE_RESET) {
+ coap_pending_clear(&client->pending);
+ }
+
+ /* CON, NON_CON and piggybacked ACK need to match the token with original request */
+ uint16_t payload_len;
+ uint8_t response_code = coap_header_get_code(response);
+ const uint8_t *payload = coap_packet_get_payload(response, &payload_len);
+
+ /* Separate response */
+ if (payload_len == 0 && response_type == COAP_TYPE_ACK &&
+ response_code == COAP_CODE_EMPTY) {
+ /* Clear the pending, poll uses now the separate timeout for the response. */
+ coap_pending_clear(&client->pending);
+ return 1;
+ }
+
+ /* Check for tokens */
+ if (!token_compare(client, response)) {
+ LOG_ERR("Not matching tokens, respond with reset");
+ ret = send_reset(client, response, COAP_RESPONSE_CODE_NOT_FOUND);
+ return 1;
+ }
+
+ /* Send ack for CON */
+ if (response_type == COAP_TYPE_CON) {
+ /* CON response is always a separate response, respond with empty ACK. */
+ ret = send_ack(client, response, COAP_CODE_EMPTY);
+ if (ret < 0) {
+ goto fail;
+ }
+ }
+
+ if (client->pending.timeout != 0) {
+ coap_pending_clear(&client->pending);
+ }
+
+ /* Check if block2 exists */
+ block_option = coap_get_option_int(response, COAP_OPTION_BLOCK2);
+ if (block_option > 0) {
+ blockwise_transfer = true;
+ last_block = !GET_MORE(block_option);
+ block_num = GET_BLOCK_NUM(block_option);
+
+ if (block_num == 0) {
+ coap_block_transfer_init(&client->recv_blk_ctx,
+ coap_client_default_block_size(),
+ 0);
+ client->offset = 0;
+ }
+
+ ret = coap_update_from_block(response, &client->recv_blk_ctx);
+ if (ret < 0) {
+ LOG_ERR("Error updating block context");
+ }
+ coap_next_block(response, &client->recv_blk_ctx);
+ } else {
+ client->offset = 0;
+ last_block = true;
+ }
+
+ /* Check if this was a response to last blockwise send */
+ if (client->send_blk_ctx.total_size > 0) {
+ blockwise_transfer = true;
+ if (client->send_blk_ctx.total_size == client->send_blk_ctx.current) {
+ last_block = true;
+ } else {
+ last_block = false;
+ }
+ }
+
+ /* Call user callback */
+ if (client->coap_request->cb) {
+ client->coap_request->cb(response_code, client->offset, payload, payload_len,
+ last_block, client->coap_request->user_data);
+
+ /* Update the offset for next callback in a blockwise transfer */
+ if (blockwise_transfer) {
+ client->offset += payload_len;
+ }
+ }
+
+ /* If this wasn't last block, send the next request */
+ if (blockwise_transfer && !last_block) {
+ ret = coap_client_init_request(client, client->coap_request);
+
+ if (ret < 0) {
+ LOG_ERR("Error creating a CoAP request");
+ goto fail;
+ }
+
+ if (client->pending.timeout != 0) {
+ LOG_ERR("Previous pending hasn't arrived");
+ goto fail;
+ }
+
+ ret = coap_pending_init(&client->pending, &client->request, &client->address,
+ client->retry_count);
+ if (ret < 0) {
+ LOG_ERR("Error creating pending");
+ goto fail;
+ }
+ coap_pending_cycle(&client->pending);
+
+ ret = send_request(client->fd, client->request.data, client->request.offset, 0,
+ &client->address, client->socklen);
+ if (ret < 0) {
+ LOG_ERR("Error sending a CoAP request");
+ goto fail;
+ } else {
+ return 1;
+ }
+ }
+fail:
+ return ret;
+}
+
+void coap_client_recv(void *coap_cl, void *a, void *b)
+{
+ int ret;
+ struct coap_client *const client = coap_cl;
+
+ reset_block_contexts(client);
+ k_sem_take(&client->coap_client_recv_sem, K_FOREVER);
+ while (true) {
+ struct coap_packet response;
+
+ atomic_set(&client->coap_client_recv_active, 1);
+ ret = handle_poll(client);
+ if (ret < 0) {
+ /* Error in polling, clear pending. */
+ LOG_ERR("Error in poll");
+ coap_pending_clear(&client->pending);
+ report_callback_error(client, ret);
+ goto idle;
+ }
+
+ ret = recv_response(client, &response);
+ if (ret < 0) {
+ LOG_ERR("Error receiving response");
+ report_callback_error(client, ret);
+ goto idle;
+ }
+
+ ret = handle_response(client, &response);
+ if (ret < 0) {
+ LOG_ERR("Error handling respnse");
+ report_callback_error(client, ret);
+ goto idle;
+ }
+
+ /* There are more messages coming for the original request */
+ if (ret > 0) {
+ continue;
+ } else {
+idle:
+ reset_block_contexts(client);
+ atomic_set(&client->coap_client_recv_active, 0);
+ k_sem_take(&client->coap_client_recv_sem, K_FOREVER);
+ }
+ }
+}
+
+int coap_client_init(struct coap_client *client, const char *info)
+{
+ if (client == NULL) {
+ return -EINVAL;
+ }
+
+ client->fd = -1;
+ k_sem_init(&client->coap_client_recv_sem, 0, 1);
+
+ client->tid =
+ k_thread_create(&client->thread, client->coap_thread_stack,
+ K_THREAD_STACK_SIZEOF(client->coap_thread_stack),
+ coap_client_recv, client, NULL, NULL,
+ CONFIG_COAP_CLIENT_THREAD_PRIORITY, 0, K_NO_WAIT);
+
+ if (IS_ENABLED(CONFIG_THREAD_NAME)) {
+ if (info != NULL) {
+ k_thread_name_set(client->tid, info);
+ } else {
+ k_thread_name_set(client->tid, "coap_client");
+ }
+ }
+
+ return 0;
+}