1 /*
2  * Copyright (c) 2023 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <string.h>
8 #include <zephyr/logging/log.h>
9 LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL);
10 
11 #include <zephyr/net/socket.h>
12 
13 #include <zephyr/net/coap.h>
14 #include <zephyr/net/coap_client.h>
15 
16 #define COAP_VERSION 1
17 #define COAP_SEPARATE_TIMEOUT 6000
18 #define COAP_PERIODIC_TIMEOUT 500
19 #define COAP_EXCHANGE_LIFETIME_FACTOR 3
20 #define BLOCK1_OPTION_SIZE 4
21 #define PAYLOAD_MARKER_SIZE 1
22 
23 static K_MUTEX_DEFINE(coap_client_mutex);
24 static struct coap_client *clients[CONFIG_COAP_CLIENT_MAX_INSTANCES];
25 static int num_clients;
26 static K_SEM_DEFINE(coap_client_recv_sem, 0, 1);
27 
28 static bool timeout_expired(struct coap_client_internal_request *internal_req);
29 static void cancel_requests_with(struct coap_client *client, int error);
30 static int recv_response(struct coap_client *client, struct coap_packet *response, bool *truncated);
31 static int handle_response(struct coap_client *client, const struct coap_packet *response,
32 			   bool response_truncated);
33 static struct coap_client_internal_request *get_request_with_mid(struct coap_client *client,
34 								 uint16_t mid);
35 
send_request(int sock,const void * buf,size_t len,int flags,const struct sockaddr * dest_addr,socklen_t addrlen)36 static int send_request(int sock, const void *buf, size_t len, int flags,
37 			const struct sockaddr *dest_addr, socklen_t addrlen)
38 {
39 	int ret;
40 
41 	LOG_HEXDUMP_DBG(buf, len, "Send CoAP Request:");
42 	if (addrlen == 0) {
43 		ret = zsock_sendto(sock, buf, len, flags, NULL, 0);
44 	} else {
45 		ret = zsock_sendto(sock, buf, len, flags, dest_addr, addrlen);
46 	}
47 
48 	return ret >= 0 ? ret : -errno;
49 }
50 
receive(int sock,void * buf,size_t max_len,int flags,struct sockaddr * src_addr,socklen_t * addrlen)51 static int receive(int sock, void *buf, size_t max_len, int flags,
52 		   struct sockaddr *src_addr, socklen_t *addrlen)
53 {
54 	ssize_t err;
55 
56 	if (*addrlen == 0) {
57 		err = zsock_recvfrom(sock, buf, max_len, flags, NULL, NULL);
58 	} else {
59 		err = zsock_recvfrom(sock, buf, max_len, flags, src_addr, addrlen);
60 	}
61 	if (err > 0) {
62 		LOG_HEXDUMP_DBG(buf, err, "Receive CoAP Response:");
63 	}
64 	return err >= 0 ? err : -errno;
65 }
66 
67 /** Reset all fields to zero.
68  * Use when a new request is filled in.
69  */
reset_internal_request(struct coap_client_internal_request * request)70 static void reset_internal_request(struct coap_client_internal_request *request)
71 {
72 	*request = (struct coap_client_internal_request){
73 		.last_response_id = -1,
74 	};
75 }
76 
77 /** Release a request structure.
78  * Use when a request is no longer needed, but we might still receive
79  * responses for it, which must be handled.
80  */
release_internal_request(struct coap_client_internal_request * request)81 static void release_internal_request(struct coap_client_internal_request *request)
82 {
83 	request->request_ongoing = false;
84 	request->pending.timeout = 0;
85 }
86 
coap_client_schedule_poll(struct coap_client * client,int sock,struct coap_client_request * req,struct coap_client_internal_request * internal_req)87 static int coap_client_schedule_poll(struct coap_client *client, int sock,
88 				     struct coap_client_request *req,
89 				     struct coap_client_internal_request *internal_req)
90 {
91 	client->fd = sock;
92 	memcpy(&internal_req->coap_request, req, sizeof(struct coap_client_request));
93 	internal_req->request_ongoing = true;
94 
95 	k_sem_give(&coap_client_recv_sem);
96 
97 	return 0;
98 }
99 
exchange_lifetime_exceeded(struct coap_client_internal_request * internal_req)100 static bool exchange_lifetime_exceeded(struct coap_client_internal_request *internal_req)
101 {
102 	int64_t time_since_t0, exchange_lifetime;
103 
104 	if (coap_header_get_type(&internal_req->request) == COAP_TYPE_NON_CON) {
105 		return true;
106 	}
107 
108 	if (internal_req->pending.t0 == 0) {
109 		return true;
110 	}
111 
112 	time_since_t0 = k_uptime_get() - internal_req->pending.t0;
113 	exchange_lifetime =
114 		(internal_req->pending.params.ack_timeout * COAP_EXCHANGE_LIFETIME_FACTOR);
115 
116 	return time_since_t0 > exchange_lifetime;
117 }
118 
has_ongoing_request(struct coap_client * client)119 static bool has_ongoing_request(struct coap_client *client)
120 {
121 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
122 		if (client->requests[i].request_ongoing == true) {
123 			return true;
124 		}
125 	}
126 
127 	return false;
128 }
129 
has_ongoing_exchange(struct coap_client * client)130 static bool has_ongoing_exchange(struct coap_client *client)
131 {
132 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
133 		if (client->requests[i].request_ongoing == true ||
134 		    !exchange_lifetime_exceeded(&client->requests[i])) {
135 			return true;
136 		}
137 	}
138 
139 	return false;
140 }
141 
has_timeout_expired(struct coap_client * client)142 static bool has_timeout_expired(struct coap_client *client)
143 {
144 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
145 		if (timeout_expired(&client->requests[i])) {
146 			return true;
147 		}
148 	}
149 	return false;
150 }
151 
get_free_request(struct coap_client * client)152 static struct coap_client_internal_request *get_free_request(struct coap_client *client)
153 {
154 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
155 		if (client->requests[i].request_ongoing == false &&
156 		    exchange_lifetime_exceeded(&client->requests[i])) {
157 			return &client->requests[i];
158 		}
159 	}
160 
161 	return NULL;
162 }
163 
has_ongoing_exchanges(void)164 static bool has_ongoing_exchanges(void)
165 {
166 	for (int i = 0; i < num_clients; i++) {
167 		if (has_ongoing_exchange(clients[i])) {
168 			return true;
169 		}
170 	}
171 
172 	return false;
173 }
174 
coap_client_default_block_size(void)175 static enum coap_block_size coap_client_default_block_size(void)
176 {
177 	switch (CONFIG_COAP_CLIENT_BLOCK_SIZE) {
178 	case 16:
179 		return COAP_BLOCK_16;
180 	case 32:
181 		return COAP_BLOCK_32;
182 	case 64:
183 		return COAP_BLOCK_64;
184 	case 128:
185 		return COAP_BLOCK_128;
186 	case 256:
187 		return COAP_BLOCK_256;
188 	case 512:
189 		return COAP_BLOCK_512;
190 	case 1024:
191 		return COAP_BLOCK_1024;
192 	}
193 
194 	return COAP_BLOCK_256;
195 }
196 
coap_client_init_request(struct coap_client * client,struct coap_client_request * req,struct coap_client_internal_request * internal_req,bool reconstruct)197 static int coap_client_init_request(struct coap_client *client,
198 				    struct coap_client_request *req,
199 				    struct coap_client_internal_request *internal_req,
200 				    bool reconstruct)
201 {
202 	int ret = 0;
203 	int i;
204 	bool block2 = false;
205 
206 	memset(client->send_buf, 0, sizeof(client->send_buf));
207 
208 	if (!reconstruct) {
209 		uint8_t *token = coap_next_token();
210 
211 		internal_req->last_id = coap_next_id();
212 		internal_req->request_tkl = COAP_TOKEN_MAX_LEN & 0xf;
213 		memcpy(internal_req->request_token, token, internal_req->request_tkl);
214 	}
215 
216 	ret = coap_packet_init(&internal_req->request, client->send_buf, MAX_COAP_MSG_LEN,
217 			       1, req->confirmable ? COAP_TYPE_CON : COAP_TYPE_NON_CON,
218 			       COAP_TOKEN_MAX_LEN, internal_req->request_token, req->method,
219 			       internal_req->last_id);
220 
221 	if (ret < 0) {
222 		LOG_ERR("Failed to init CoAP message %d", ret);
223 		goto out;
224 	}
225 
226 	ret = coap_packet_set_path(&internal_req->request, req->path);
227 
228 	if (ret < 0) {
229 		LOG_ERR("Failed to parse path to options %d", ret);
230 		goto out;
231 	}
232 
233 	/* Add content format option only if there is a payload */
234 	if (req->payload) {
235 		ret = coap_append_option_int(&internal_req->request,
236 					     COAP_OPTION_CONTENT_FORMAT, req->fmt);
237 
238 		if (ret < 0) {
239 			LOG_ERR("Failed to append content format option");
240 			goto out;
241 		}
242 	}
243 
244 	/* Blockwise receive ongoing, request next block. */
245 	if (internal_req->recv_blk_ctx.current > 0) {
246 		block2 = true;
247 		ret = coap_append_block2_option(&internal_req->request,
248 						&internal_req->recv_blk_ctx);
249 
250 		if (ret < 0) {
251 			LOG_ERR("Failed to append block 2 option");
252 			goto out;
253 		}
254 	}
255 
256 	/* Add extra options if any */
257 	for (i = 0; i < req->num_options; i++) {
258 		if (COAP_OPTION_BLOCK2 == req->options[i].code && block2) {
259 			/* After the first request, ignore any block2 option added by the
260 			 * application, since NUM (and possibly SZX) must be updated based on the
261 			 * server response.
262 			 */
263 			continue;
264 		}
265 
266 		ret = coap_packet_append_option(&internal_req->request, req->options[i].code,
267 						req->options[i].value, req->options[i].len);
268 
269 		if (ret < 0) {
270 			LOG_ERR("Failed to append %d option", req->options[i].code);
271 			goto out;
272 		}
273 	}
274 
275 	if (req->payload) {
276 		uint16_t payload_len;
277 		uint16_t offset;
278 
279 		/* Blockwise send ongoing, add block1 */
280 		if (internal_req->send_blk_ctx.total_size > 0 ||
281 		   (req->len > CONFIG_COAP_CLIENT_MESSAGE_SIZE)) {
282 
283 			if (internal_req->send_blk_ctx.total_size == 0) {
284 				coap_block_transfer_init(&internal_req->send_blk_ctx,
285 							 coap_client_default_block_size(),
286 							 req->len);
287 				/* Generate request tag */
288 				uint8_t *tag = coap_next_token();
289 
290 				memcpy(internal_req->request_tag, tag, COAP_TOKEN_MAX_LEN);
291 			}
292 			ret = coap_append_block1_option(&internal_req->request,
293 							&internal_req->send_blk_ctx);
294 
295 			if (ret < 0) {
296 				LOG_ERR("Failed to append block1 option");
297 				goto out;
298 			}
299 
300 			ret = coap_packet_append_option(&internal_req->request,
301 				COAP_OPTION_REQUEST_TAG, internal_req->request_tag,
302 				COAP_TOKEN_MAX_LEN);
303 
304 			if (ret < 0) {
305 				LOG_ERR("Failed to append request tag option");
306 				goto out;
307 			}
308 		}
309 
310 		ret = coap_packet_append_payload_marker(&internal_req->request);
311 
312 		if (ret < 0) {
313 			LOG_ERR("Failed to append payload marker to CoAP message");
314 			goto out;
315 		}
316 
317 		if (internal_req->send_blk_ctx.total_size > 0) {
318 			uint16_t block_in_bytes =
319 				coap_block_size_to_bytes(internal_req->send_blk_ctx.block_size);
320 
321 			payload_len = internal_req->send_blk_ctx.total_size -
322 				      internal_req->send_blk_ctx.current;
323 			if (payload_len > block_in_bytes) {
324 				payload_len = block_in_bytes;
325 			}
326 			offset = internal_req->send_blk_ctx.current;
327 		} else {
328 			payload_len = req->len;
329 			offset = 0;
330 		}
331 
332 		ret = coap_packet_append_payload(&internal_req->request, req->payload + offset,
333 						 payload_len);
334 
335 		if (ret < 0) {
336 			LOG_ERR("Failed to append payload to CoAP message");
337 			goto out;
338 		}
339 
340 		if (internal_req->send_blk_ctx.total_size > 0) {
341 			coap_next_block(&internal_req->request, &internal_req->send_blk_ctx);
342 		}
343 	}
344 out:
345 	return ret;
346 }
347 
coap_client_req(struct coap_client * client,int sock,const struct sockaddr * addr,struct coap_client_request * req,struct coap_transmission_parameters * params)348 int coap_client_req(struct coap_client *client, int sock, const struct sockaddr *addr,
349 		    struct coap_client_request *req, struct coap_transmission_parameters *params)
350 {
351 	int ret;
352 	struct coap_client_internal_request *internal_req;
353 
354 	if (client == NULL || sock < 0 || req == NULL || req->path == NULL) {
355 		return -EINVAL;
356 	}
357 
358 	k_mutex_lock(&client->lock, K_FOREVER);
359 
360 	internal_req = get_free_request(client);
361 
362 	if (internal_req == NULL) {
363 		LOG_DBG("No more free requests");
364 		ret = -EAGAIN;
365 		goto out;
366 	}
367 
368 	/* Don't allow changing to a different socket if there is already request ongoing. */
369 	if (client->fd != sock && has_ongoing_request(client)) {
370 		ret = -EALREADY;
371 		goto release;
372 	}
373 
374 	/* Don't allow changing to a different address if there is already request ongoing. */
375 	if (addr != NULL) {
376 		if (memcmp(&client->address, addr, sizeof(*addr)) != 0) {
377 			if (has_ongoing_request(client)) {
378 				LOG_WRN("Can't change to a different socket, request ongoing.");
379 				ret = -EALREADY;
380 				goto release;
381 			}
382 
383 			memcpy(&client->address, addr, sizeof(*addr));
384 			client->socklen = sizeof(client->address);
385 		}
386 	} else {
387 		if (client->socklen != 0) {
388 			if (has_ongoing_request(client)) {
389 				LOG_WRN("Can't change to a different socket, request ongoing.");
390 				ret = -EALREADY;
391 				goto release;
392 			}
393 
394 			memset(&client->address, 0, sizeof(client->address));
395 			client->socklen = 0;
396 		}
397 	}
398 
399 	reset_internal_request(internal_req);
400 
401 	ret = coap_client_init_request(client, req, internal_req, false);
402 	if (ret < 0) {
403 		LOG_ERR("Failed to initialize coap request");
404 		goto release;
405 	}
406 
407 	if (client->send_echo) {
408 		ret = coap_packet_append_option(&internal_req->request, COAP_OPTION_ECHO,
409 						client->echo_option.value, client->echo_option.len);
410 		if (ret < 0) {
411 			LOG_ERR("Failed to append echo option");
412 			goto release;
413 		}
414 		client->send_echo = false;
415 	}
416 
417 	ret = coap_client_schedule_poll(client, sock, req, internal_req);
418 	if (ret < 0) {
419 		LOG_ERR("Failed to schedule polling");
420 		goto release;
421 	}
422 
423 	ret = coap_pending_init(&internal_req->pending, &internal_req->request,
424 				&client->address, params);
425 
426 	if (ret < 0) {
427 		LOG_ERR("Failed to initialize pending struct");
428 		goto release;
429 	}
430 
431 	/* Non-Confirmable messages are not retried, but we still track the lifetime as
432 	 * replies are acceptable.
433 	 */
434 	if (coap_header_get_type(&internal_req->request) == COAP_TYPE_NON_CON) {
435 		internal_req->pending.retries = 0;
436 	}
437 	coap_pending_cycle(&internal_req->pending);
438 	internal_req->is_observe = coap_request_is_observe(&internal_req->request);
439 	LOG_DBG("Request is_observe %d", internal_req->is_observe);
440 
441 	ret = send_request(sock, internal_req->request.data, internal_req->request.offset, 0,
442 			  &client->address, client->socklen);
443 	if (ret < 0) {
444 		ret = -errno;
445 	}
446 
447 release:
448 	if (ret < 0) {
449 		LOG_ERR("Failed to send request: %d", ret);
450 		reset_internal_request(internal_req);
451 	} else {
452 		/* Do not return the number of bytes sent */
453 		ret = 0;
454 	}
455 out:
456 	k_mutex_unlock(&client->lock);
457 	return ret;
458 }
459 
report_callback_error(struct coap_client_internal_request * internal_req,int error_code)460 static void report_callback_error(struct coap_client_internal_request *internal_req, int error_code)
461 {
462 	if (internal_req->coap_request.cb) {
463 		if (!atomic_set(&internal_req->in_callback, 1)) {
464 			internal_req->coap_request.cb(error_code, 0, NULL, 0, true,
465 						      internal_req->coap_request.user_data);
466 			atomic_clear(&internal_req->in_callback);
467 		} else {
468 			LOG_DBG("Cannot call the callback; already in it.");
469 		}
470 	}
471 }
472 
timeout_expired(struct coap_client_internal_request * internal_req)473 static bool timeout_expired(struct coap_client_internal_request *internal_req)
474 {
475 	if (internal_req->pending.timeout == 0) {
476 		return false;
477 	}
478 
479 	return (internal_req->request_ongoing &&
480 		internal_req->pending.timeout <= (k_uptime_get() - internal_req->pending.t0));
481 }
482 
resend_request(struct coap_client * client,struct coap_client_internal_request * internal_req)483 static int resend_request(struct coap_client *client,
484 			  struct coap_client_internal_request *internal_req)
485 {
486 	int ret = 0;
487 
488 	/* Copy the pending structure if we need to restore it */
489 	struct coap_pending tmp = internal_req->pending;
490 
491 	if (internal_req->request_ongoing &&
492 	    internal_req->pending.timeout != 0 &&
493 	    coap_pending_cycle(&internal_req->pending)) {
494 		LOG_ERR("Timeout, retrying send");
495 
496 		/* Reset send block context as it was updated in previous init from packet */
497 		if (internal_req->send_blk_ctx.total_size > 0) {
498 			internal_req->send_blk_ctx.current = internal_req->offset;
499 		}
500 		ret = coap_client_init_request(client, &internal_req->coap_request,
501 					       internal_req, true);
502 		if (ret < 0) {
503 			LOG_ERR("Error re-creating CoAP request %d", ret);
504 			return ret;
505 		}
506 
507 		ret = send_request(client->fd, internal_req->request.data,
508 					internal_req->request.offset, 0, &client->address,
509 					client->socklen);
510 		if (ret > 0) {
511 			ret = 0;
512 		} else if (ret == -EAGAIN) {
513 			/* Restore the pending structure, retry later */
514 			internal_req->pending = tmp;
515 			/* Not a fatal socket error, will trigger a retry */
516 			ret = 0;
517 		} else {
518 			LOG_ERR("Failed to resend request, %d", ret);
519 		}
520 	} else {
521 		LOG_ERR("Timeout, no more retries left");
522 		ret = -ETIMEDOUT;
523 	}
524 
525 	return ret;
526 }
527 
coap_client_resend_handler(struct coap_client * client)528 static void coap_client_resend_handler(struct coap_client *client)
529 {
530 	int ret = 0;
531 
532 	k_mutex_lock(&client->lock, K_FOREVER);
533 
534 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
535 		if (timeout_expired(&client->requests[i])) {
536 			if (!client->requests[i].coap_request.confirmable) {
537 				release_internal_request(&client->requests[i]);
538 				continue;
539 			}
540 
541 			ret = resend_request(client, &client->requests[i]);
542 			if (ret < 0) {
543 				report_callback_error(&client->requests[i], ret);
544 				release_internal_request(&client->requests[i]);
545 			}
546 		}
547 	}
548 
549 	k_mutex_unlock(&client->lock);
550 }
551 
get_client(int sock)552 static struct coap_client *get_client(int sock)
553 {
554 	for (int i = 0; i < num_clients; i++) {
555 		if (clients[i]->fd == sock) {
556 			return clients[i];
557 		}
558 	}
559 
560 	return NULL;
561 }
562 
handle_poll(void)563 static int handle_poll(void)
564 {
565 	int ret = 0;
566 
567 	struct zsock_pollfd fds[CONFIG_COAP_CLIENT_MAX_INSTANCES] = {0};
568 	int nfds = 0;
569 
570 	/* Use periodic timeouts */
571 	for (int i = 0; i < num_clients; i++) {
572 		short events = (has_ongoing_exchange(clients[i]) ? ZSOCK_POLLIN : 0) |
573 			       (has_timeout_expired(clients[i]) ? ZSOCK_POLLOUT : 0);
574 
575 		if (events == 0) {
576 			/* Skip this socket */
577 			continue;
578 		}
579 		fds[nfds].fd = clients[i]->fd;
580 		fds[nfds].events = events;
581 		fds[nfds].revents = 0;
582 		nfds++;
583 	}
584 
585 	ret = zsock_poll(fds, nfds, COAP_PERIODIC_TIMEOUT);
586 
587 	if (ret < 0) {
588 		ret = -errno;
589 		LOG_ERR("Error in poll:%d", ret);
590 		return ret;
591 	} else if (ret == 0) {
592 		return 0;
593 	}
594 
595 	for (int i = 0; i < nfds; i++) {
596 		struct coap_client *client = get_client(fds[i].fd);
597 
598 		if (!client) {
599 			LOG_ERR("No client found for socket %d", fds[i].fd);
600 			continue;
601 		}
602 
603 		if (fds[i].revents & ZSOCK_POLLOUT) {
604 			coap_client_resend_handler(client);
605 		}
606 		if (fds[i].revents & ZSOCK_POLLIN) {
607 			struct coap_packet response;
608 			bool response_truncated = false;
609 
610 			ret = recv_response(client, &response, &response_truncated);
611 			if (ret < 0) {
612 				if (ret == -EAGAIN) {
613 					continue;
614 				}
615 				LOG_ERR("Error receiving response");
616 				cancel_requests_with(client, -EIO);
617 				continue;
618 			}
619 
620 			k_mutex_lock(&client->lock, K_FOREVER);
621 			ret = handle_response(client, &response, response_truncated);
622 			if (ret < 0) {
623 				LOG_ERR("Error handling response");
624 			}
625 
626 			k_mutex_unlock(&client->lock);
627 		}
628 		if (fds[i].revents & ZSOCK_POLLERR) {
629 			LOG_ERR("Error in poll for socket %d", fds[i].fd);
630 			cancel_requests_with(client, -EIO);
631 		}
632 		if (fds[i].revents & ZSOCK_POLLHUP) {
633 			LOG_ERR("Error in poll: POLLHUP for socket %d", fds[i].fd);
634 			cancel_requests_with(client, -EIO);
635 		}
636 		if (fds[i].revents & ZSOCK_POLLNVAL) {
637 			LOG_ERR("Error in poll: POLLNVAL - fd %d not open", fds[i].fd);
638 			cancel_requests_with(client, -EIO);
639 		}
640 	}
641 
642 	return 0;
643 }
644 
recv_response(struct coap_client * client,struct coap_packet * response,bool * truncated)645 static int recv_response(struct coap_client *client, struct coap_packet *response, bool *truncated)
646 {
647 	int total_len;
648 	int available_len;
649 	int ret;
650 	int flags = ZSOCK_MSG_DONTWAIT;
651 
652 	if (IS_ENABLED(CONFIG_COAP_CLIENT_TRUNCATE_MSGS)) {
653 		flags |= ZSOCK_MSG_TRUNC;
654 	}
655 
656 	memset(client->recv_buf, 0, sizeof(client->recv_buf));
657 	total_len = receive(client->fd, client->recv_buf, sizeof(client->recv_buf), flags,
658 			    &client->address, &client->socklen);
659 
660 	if (total_len < 0) {
661 		ret = -errno;
662 		return ret;
663 	} else if (total_len == 0) {
664 		/* Ignore, UDP can be zero length, but it is not CoAP anymore */
665 		return 0;
666 	}
667 
668 	available_len = MIN(total_len, sizeof(client->recv_buf));
669 	*truncated = available_len < total_len;
670 
671 	LOG_DBG("Received %d bytes", available_len);
672 
673 	ret = coap_packet_parse(response, client->recv_buf, available_len, NULL, 0);
674 	if (ret < 0) {
675 		LOG_ERR("Invalid data received");
676 	}
677 
678 	return ret;
679 }
680 
send_ack(struct coap_client * client,const struct coap_packet * req,uint8_t response_code)681 static int send_ack(struct coap_client *client, const struct coap_packet *req,
682 		    uint8_t response_code)
683 {
684 	int ret;
685 	struct coap_packet ack;
686 
687 	ret = coap_ack_init(&ack, req, client->send_buf, MAX_COAP_MSG_LEN, response_code);
688 	if (ret < 0) {
689 		LOG_ERR("Failed to initialize CoAP ACK-message");
690 		return ret;
691 	}
692 
693 	ret = send_request(client->fd, ack.data, ack.offset, 0, &client->address, client->socklen);
694 	if (ret < 0) {
695 		LOG_ERR("Error sending a CoAP ACK-message");
696 		return ret;
697 	}
698 
699 	return 0;
700 }
701 
send_rst(struct coap_client * client,const struct coap_packet * req)702 static int send_rst(struct coap_client *client, const struct coap_packet *req)
703 {
704 	int ret;
705 	struct coap_packet rst;
706 
707 	ret = coap_rst_init(&rst, req, client->send_buf, MAX_COAP_MSG_LEN);
708 	if (ret < 0) {
709 		LOG_ERR("Failed to initialize CoAP RST-message");
710 		return ret;
711 	}
712 
713 	ret = send_request(client->fd, rst.data, rst.offset, 0, &client->address, client->socklen);
714 	if (ret < 0) {
715 		LOG_ERR("Error sending a CoAP RST-message");
716 		return ret;
717 	}
718 
719 	return 0;
720 }
721 
get_request_with_token(struct coap_client * client,const struct coap_packet * resp)722 static struct coap_client_internal_request *get_request_with_token(
723 	struct coap_client *client, const struct coap_packet *resp)
724 {
725 
726 	uint8_t response_token[COAP_TOKEN_MAX_LEN];
727 	uint8_t response_tkl;
728 
729 	response_tkl = coap_header_get_token(resp, response_token);
730 
731 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
732 		if (client->requests[i].request_ongoing ||
733 		    !exchange_lifetime_exceeded(&client->requests[i])) {
734 			if (client->requests[i].request_tkl == 0) {
735 				continue;
736 			}
737 			if (client->requests[i].request_tkl != response_tkl) {
738 				continue;
739 			}
740 			if (memcmp(&client->requests[i].request_token, &response_token,
741 			    response_tkl) == 0) {
742 				return &client->requests[i];
743 			}
744 		}
745 	}
746 
747 	return NULL;
748 }
749 
get_request_with_mid(struct coap_client * client,uint16_t mid)750 static struct coap_client_internal_request *get_request_with_mid(struct coap_client *client,
751 								 uint16_t mid)
752 {
753 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
754 		if (client->requests[i].request_ongoing) {
755 			if (client->requests[i].last_id == (int)mid) {
756 				return &client->requests[i];
757 			}
758 		}
759 	}
760 
761 	return NULL;
762 }
763 
find_echo_option(const struct coap_packet * response,struct coap_option * option)764 static bool find_echo_option(const struct coap_packet *response, struct coap_option *option)
765 {
766 	return coap_find_options(response, COAP_OPTION_ECHO, option, 1);
767 }
768 
handle_response(struct coap_client * client,const struct coap_packet * response,bool response_truncated)769 static int handle_response(struct coap_client *client, const struct coap_packet *response,
770 			   bool response_truncated)
771 {
772 	int ret = 0;
773 	int block_option;
774 	int block_num;
775 	bool blockwise_transfer = false;
776 	bool last_block = false;
777 	struct coap_client_internal_request *internal_req;
778 
779 	/* Handle different types, ACK might be separate or piggybacked
780 	 * CON and NCON contains a separate response, CON needs an empty response
781 	 * CON request results as ACK and possibly separate CON or NCON response
782 	 * NCON request results only as a separate CON or NCON message as there is no ACK
783 	 * With RESET, just drop gloves and call the callback.
784 	 */
785 
786 	/* CON, NON_CON and piggybacked ACK need to match the token with original request */
787 	uint16_t payload_len;
788 	uint8_t response_type = coap_header_get_type(response);
789 	uint8_t response_code = coap_header_get_code(response);
790 	uint16_t response_id = coap_header_get_id(response);
791 	const uint8_t *payload = coap_packet_get_payload(response, &payload_len);
792 
793 	if (response_type == COAP_TYPE_RESET) {
794 		internal_req = get_request_with_mid(client, response_id);
795 		if (!internal_req) {
796 			LOG_WRN("No matching request for RESET");
797 			return 0;
798 		}
799 		report_callback_error(internal_req, -ECONNRESET);
800 		release_internal_request(internal_req);
801 		return 0;
802 	}
803 
804 	/* Separate response coming */
805 	if (payload_len == 0 && response_type == COAP_TYPE_ACK &&
806 	    response_code == COAP_CODE_EMPTY) {
807 		internal_req = get_request_with_mid(client, response_id);
808 		if (!internal_req) {
809 			LOG_WRN("No matching request for ACK");
810 			return 0;
811 		}
812 		internal_req->pending.t0 = k_uptime_get();
813 		internal_req->pending.timeout = COAP_SEPARATE_TIMEOUT;
814 		internal_req->pending.retries = 0;
815 		return 1;
816 	}
817 
818 	internal_req = get_request_with_token(client, response);
819 	if (!internal_req) {
820 		LOG_WRN("No matching request for response");
821 		(void) send_rst(client, response); /* Ignore errors, unrelated to our queries */
822 		return 0;
823 	}
824 
825 	/* Received echo option */
826 	if (find_echo_option(response, &client->echo_option)) {
827 		 /* Resend request with echo option */
828 		if (response_code == COAP_RESPONSE_CODE_UNAUTHORIZED) {
829 			ret = coap_client_init_request(client, &internal_req->coap_request,
830 						       internal_req, false);
831 
832 			if (ret < 0) {
833 				LOG_ERR("Error creating a CoAP request");
834 				goto fail;
835 			}
836 
837 			ret = coap_packet_append_option(&internal_req->request, COAP_OPTION_ECHO,
838 							client->echo_option.value,
839 							client->echo_option.len);
840 			if (ret < 0) {
841 				LOG_ERR("Failed to append echo option");
842 				goto fail;
843 			}
844 
845 			if (coap_header_get_type(&internal_req->request) == COAP_TYPE_CON) {
846 				struct coap_transmission_parameters params =
847 					internal_req->pending.params;
848 				ret = coap_pending_init(&internal_req->pending,
849 							&internal_req->request, &client->address,
850 							&params);
851 				if (ret < 0) {
852 					LOG_ERR("Error creating pending");
853 					goto fail;
854 				}
855 
856 				coap_pending_cycle(&internal_req->pending);
857 			}
858 
859 			ret = send_request(client->fd, internal_req->request.data,
860 					   internal_req->request.offset, 0, &client->address,
861 					   client->socklen);
862 			if (ret < 0) {
863 				LOG_ERR("Error sending a CoAP request");
864 				goto fail;
865 			} else {
866 				return 1;
867 			}
868 		} else {
869 			/* Send echo in next request */
870 			client->send_echo = true;
871 		}
872 	}
873 
874 	/* Send ack for CON */
875 	if (response_type == COAP_TYPE_CON) {
876 		/* CON response is always a separate response, respond with empty ACK. */
877 		ret = send_ack(client, response, COAP_CODE_EMPTY);
878 		if (ret < 0) {
879 			goto fail;
880 		}
881 	}
882 
883 	/* MID-based deduplication */
884 	if (response_id == internal_req->last_response_id) {
885 		LOG_WRN("Duplicate MID, dropping");
886 		return 0;
887 	}
888 
889 	internal_req->last_response_id = response_id;
890 
891 	if (!internal_req->request_ongoing) {
892 		if (internal_req->is_observe) {
893 			(void) send_rst(client, response);
894 			return 0;
895 		}
896 		LOG_DBG("Drop request, already handled");
897 		return 0;
898 	}
899 
900 	if (internal_req->pending.timeout != 0) {
901 		coap_pending_clear(&internal_req->pending);
902 	}
903 
904 	/* Check if block2 exists */
905 	block_option = coap_get_option_int(response, COAP_OPTION_BLOCK2);
906 	if (block_option > 0 || response_truncated) {
907 		blockwise_transfer = true;
908 		last_block = response_truncated ? false : !GET_MORE(block_option);
909 		block_num = (block_option > 0) ? GET_BLOCK_NUM(block_option) : 0;
910 
911 		if (block_num == 0) {
912 			coap_block_transfer_init(&internal_req->recv_blk_ctx,
913 						 coap_client_default_block_size(),
914 						 0);
915 			internal_req->offset = 0;
916 		}
917 
918 		ret = coap_update_from_block(response, &internal_req->recv_blk_ctx);
919 		if (ret < 0) {
920 			LOG_ERR("Error updating block context");
921 		}
922 		coap_next_block(response, &internal_req->recv_blk_ctx);
923 	} else {
924 		internal_req->offset = 0;
925 		last_block = true;
926 	}
927 
928 	/* Check if this was a response to last blockwise send */
929 	if (internal_req->send_blk_ctx.total_size > 0) {
930 		blockwise_transfer = true;
931 		internal_req->offset = internal_req->send_blk_ctx.current;
932 		if (internal_req->send_blk_ctx.total_size == internal_req->send_blk_ctx.current) {
933 			last_block = true;
934 		} else {
935 			last_block = false;
936 		}
937 	}
938 
939 	/* Until the last block of a transfer, limit data size sent to the application to the block
940 	 * size, to avoid data above block size being repeated when the next block is received.
941 	 */
942 	if (blockwise_transfer && !last_block) {
943 		payload_len = MIN(payload_len, CONFIG_COAP_CLIENT_BLOCK_SIZE);
944 	}
945 
946 	/* Call user callback */
947 	if (internal_req->coap_request.cb) {
948 		if (!atomic_set(&internal_req->in_callback, 1)) {
949 			internal_req->coap_request.cb(response_code, internal_req->offset, payload,
950 						      payload_len, last_block,
951 						      internal_req->coap_request.user_data);
952 			atomic_clear(&internal_req->in_callback);
953 		}
954 		if (!internal_req->request_ongoing) {
955 			/* User callback must have called coap_client_cancel_requests(). */
956 			goto fail;
957 		}
958 		/* Update the offset for next callback in a blockwise transfer */
959 		if (blockwise_transfer) {
960 			internal_req->offset += payload_len;
961 		}
962 	}
963 
964 	/* If this wasn't last block, send the next request */
965 	if (blockwise_transfer && !last_block) {
966 		ret = coap_client_init_request(client, &internal_req->coap_request, internal_req,
967 					       false);
968 
969 		if (ret < 0) {
970 			LOG_ERR("Error creating a CoAP request");
971 			goto fail;
972 		}
973 
974 		struct coap_transmission_parameters params = internal_req->pending.params;
975 		ret = coap_pending_init(&internal_req->pending, &internal_req->request,
976 					&client->address, &params);
977 		if (ret < 0) {
978 			LOG_ERR("Error creating pending");
979 			goto fail;
980 		}
981 		coap_pending_cycle(&internal_req->pending);
982 
983 		ret = send_request(client->fd, internal_req->request.data,
984 				   internal_req->request.offset, 0, &client->address,
985 				   client->socklen);
986 		if (ret < 0) {
987 			LOG_ERR("Error sending a CoAP request");
988 			goto fail;
989 		} else {
990 			return 1;
991 		}
992 	}
993 fail:
994 	if (ret < 0) {
995 		report_callback_error(internal_req, ret);
996 	}
997 	if (!internal_req->is_observe) {
998 		release_internal_request(internal_req);
999 	}
1000 	return ret;
1001 }
1002 
cancel_requests_with(struct coap_client * client,int error)1003 static void cancel_requests_with(struct coap_client *client, int error)
1004 {
1005 	k_mutex_lock(&client->lock, K_FOREVER);
1006 
1007 	for (int i = 0; i < ARRAY_SIZE(client->requests); i++) {
1008 		if (client->requests[i].request_ongoing == true) {
1009 			LOG_DBG("Cancelling request %d", i);
1010 			/* Report the request was cancelled. This will be skipped if
1011 			 * this function was called from the user's callback so we
1012 			 * do not reenter it. In that case, the user knows their
1013 			 * request was cancelled anyway.
1014 			 */
1015 			report_callback_error(&client->requests[i], error);
1016 			release_internal_request(&client->requests[i]);
1017 		}
1018 		/* If our socket has failed, clear all requests, even completed ones,
1019 		 * so that our handle_poll() does not poll() anymore for this socket.
1020 		 */
1021 		if (error == -EIO) {
1022 			reset_internal_request(&client->requests[i]);
1023 		}
1024 	}
1025 	k_mutex_unlock(&client->lock);
1026 
1027 }
1028 
coap_client_cancel_requests(struct coap_client * client)1029 void coap_client_cancel_requests(struct coap_client *client)
1030 {
1031 	cancel_requests_with(client, -ECANCELED);
1032 	/* Wait until after zsock_poll() can time out and return. */
1033 	k_sleep(K_MSEC(COAP_PERIODIC_TIMEOUT));
1034 }
1035 
requests_match(struct coap_client_request * a,struct coap_client_request * b)1036 static bool requests_match(struct coap_client_request *a, struct coap_client_request *b)
1037 {
1038 	/* enum coap_method does not have value for zero, so differentiate valid values */
1039 	if (a->method && b->method && a->method != b->method) {
1040 		return false;
1041 	}
1042 	if (a->path && b->path && strcmp(a->path, b->path) != 0) {
1043 		return false;
1044 	}
1045 	if (a->cb && b->cb && a->cb != b->cb) {
1046 		return false;
1047 	}
1048 	if (a->user_data && b->user_data && a->user_data != b->user_data) {
1049 		return false;
1050 	}
1051 	/* It is intentional that (struct coap_client_request){0} matches all */
1052 	return true;
1053 }
1054 
coap_client_cancel_request(struct coap_client * client,struct coap_client_request * req)1055 void coap_client_cancel_request(struct coap_client *client, struct coap_client_request *req)
1056 {
1057 	k_mutex_lock(&client->lock, K_FOREVER);
1058 
1059 	for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
1060 		if (client->requests[i].request_ongoing &&
1061 		    requests_match(&client->requests[i].coap_request, req)) {
1062 			LOG_DBG("Cancelling request %d", i);
1063 			report_callback_error(&client->requests[i], -ECANCELED);
1064 			release_internal_request(&client->requests[i]);
1065 		}
1066 	}
1067 
1068 	k_mutex_unlock(&client->lock);
1069 }
1070 
coap_client_recv(void * coap_cl,void * a,void * b)1071 void coap_client_recv(void *coap_cl, void *a, void *b)
1072 {
1073 	int ret;
1074 
1075 	k_sem_take(&coap_client_recv_sem, K_FOREVER);
1076 	while (true) {
1077 		ret = handle_poll();
1078 		if (ret < 0) {
1079 			/* Error in polling */
1080 			LOG_ERR("Error in poll");
1081 			goto idle;
1082 		}
1083 
1084 		/* There are more messages coming */
1085 		if (has_ongoing_exchanges()) {
1086 			continue;
1087 		} else {
1088 idle:
1089 			k_sem_take(&coap_client_recv_sem, K_FOREVER);
1090 		}
1091 	}
1092 }
1093 
coap_client_init(struct coap_client * client,const char * info)1094 int coap_client_init(struct coap_client *client, const char *info)
1095 {
1096 	if (client == NULL) {
1097 		return -EINVAL;
1098 	}
1099 
1100 	k_mutex_lock(&coap_client_mutex, K_FOREVER);
1101 	if (num_clients >= CONFIG_COAP_CLIENT_MAX_INSTANCES) {
1102 		k_mutex_unlock(&coap_client_mutex);
1103 		return -ENOSPC;
1104 	}
1105 
1106 	k_mutex_init(&client->lock);
1107 
1108 	clients[num_clients] = client;
1109 	num_clients++;
1110 
1111 	k_mutex_unlock(&coap_client_mutex);
1112 	return 0;
1113 }
1114 
1115 
1116 K_THREAD_DEFINE(coap_client_recv_thread, CONFIG_COAP_CLIENT_STACK_SIZE,
1117 		coap_client_recv, NULL, NULL, NULL,
1118 		CONFIG_COAP_CLIENT_THREAD_PRIORITY, 0, 0);
1119