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;
+}