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 if (response_type != COAP_TYPE_ACK) {
822 /* Ignore errors, unrelated to our queries */
823 (void)send_rst(client, response);
824 }
825 return 0;
826 }
827
828 /* Received echo option */
829 if (find_echo_option(response, &client->echo_option)) {
830 /* Resend request with echo option */
831 if (response_code == COAP_RESPONSE_CODE_UNAUTHORIZED) {
832 ret = coap_client_init_request(client, &internal_req->coap_request,
833 internal_req, false);
834
835 if (ret < 0) {
836 LOG_ERR("Error creating a CoAP request");
837 goto fail;
838 }
839
840 ret = coap_packet_append_option(&internal_req->request, COAP_OPTION_ECHO,
841 client->echo_option.value,
842 client->echo_option.len);
843 if (ret < 0) {
844 LOG_ERR("Failed to append echo option");
845 goto fail;
846 }
847
848 if (coap_header_get_type(&internal_req->request) == COAP_TYPE_CON) {
849 struct coap_transmission_parameters params =
850 internal_req->pending.params;
851 ret = coap_pending_init(&internal_req->pending,
852 &internal_req->request, &client->address,
853 ¶ms);
854 if (ret < 0) {
855 LOG_ERR("Error creating pending");
856 goto fail;
857 }
858
859 coap_pending_cycle(&internal_req->pending);
860 }
861
862 ret = send_request(client->fd, internal_req->request.data,
863 internal_req->request.offset, 0, &client->address,
864 client->socklen);
865 if (ret < 0) {
866 LOG_ERR("Error sending a CoAP request");
867 goto fail;
868 } else {
869 return 1;
870 }
871 } else {
872 /* Send echo in next request */
873 client->send_echo = true;
874 }
875 }
876
877 /* Send ack for CON */
878 if (response_type == COAP_TYPE_CON) {
879 /* CON response is always a separate response, respond with empty ACK. */
880 ret = send_ack(client, response, COAP_CODE_EMPTY);
881 if (ret < 0) {
882 goto fail;
883 }
884 }
885
886 /* MID-based deduplication */
887 if (response_id == internal_req->last_response_id) {
888 LOG_WRN("Duplicate MID, dropping");
889 return 0;
890 }
891
892 internal_req->last_response_id = response_id;
893
894 if (!internal_req->request_ongoing) {
895 if (internal_req->is_observe) {
896 (void) send_rst(client, response);
897 return 0;
898 }
899 LOG_DBG("Drop request, already handled");
900 return 0;
901 }
902
903 if (internal_req->pending.timeout != 0) {
904 coap_pending_clear(&internal_req->pending);
905 }
906
907 /* Check if block2 exists */
908 block_option = coap_get_option_int(response, COAP_OPTION_BLOCK2);
909 if (block_option > 0 || response_truncated) {
910 blockwise_transfer = true;
911 last_block = response_truncated ? false : !GET_MORE(block_option);
912 block_num = (block_option > 0) ? GET_BLOCK_NUM(block_option) : 0;
913
914 if (block_num == 0) {
915 coap_block_transfer_init(&internal_req->recv_blk_ctx,
916 coap_client_default_block_size(),
917 0);
918 internal_req->offset = 0;
919 }
920
921 ret = coap_update_from_block(response, &internal_req->recv_blk_ctx);
922 if (ret < 0) {
923 LOG_ERR("Error updating block context");
924 }
925 coap_next_block(response, &internal_req->recv_blk_ctx);
926 } else {
927 internal_req->offset = 0;
928 last_block = true;
929 }
930
931 /* Check if this was a response to last blockwise send */
932 if (internal_req->send_blk_ctx.total_size > 0) {
933 blockwise_transfer = true;
934 internal_req->offset = internal_req->send_blk_ctx.current;
935 if (internal_req->send_blk_ctx.total_size == internal_req->send_blk_ctx.current) {
936 last_block = true;
937 } else {
938 last_block = false;
939 }
940 }
941
942 /* Until the last block of a transfer, limit data size sent to the application to the block
943 * size, to avoid data above block size being repeated when the next block is received.
944 */
945 if (blockwise_transfer && !last_block) {
946 payload_len = MIN(payload_len, CONFIG_COAP_CLIENT_BLOCK_SIZE);
947 }
948
949 /* Call user callback */
950 if (internal_req->coap_request.cb) {
951 if (!atomic_set(&internal_req->in_callback, 1)) {
952 internal_req->coap_request.cb(response_code, internal_req->offset, payload,
953 payload_len, last_block,
954 internal_req->coap_request.user_data);
955 atomic_clear(&internal_req->in_callback);
956 }
957 if (!internal_req->request_ongoing) {
958 /* User callback must have called coap_client_cancel_requests(). */
959 goto fail;
960 }
961 /* Update the offset for next callback in a blockwise transfer */
962 if (blockwise_transfer) {
963 internal_req->offset += payload_len;
964 }
965 }
966
967 /* If this wasn't last block, send the next request */
968 if (blockwise_transfer && !last_block) {
969 ret = coap_client_init_request(client, &internal_req->coap_request, internal_req,
970 false);
971
972 if (ret < 0) {
973 LOG_ERR("Error creating a CoAP request");
974 goto fail;
975 }
976
977 struct coap_transmission_parameters params = internal_req->pending.params;
978 ret = coap_pending_init(&internal_req->pending, &internal_req->request,
979 &client->address, ¶ms);
980 if (ret < 0) {
981 LOG_ERR("Error creating pending");
982 goto fail;
983 }
984 coap_pending_cycle(&internal_req->pending);
985
986 ret = send_request(client->fd, internal_req->request.data,
987 internal_req->request.offset, 0, &client->address,
988 client->socklen);
989 if (ret < 0) {
990 LOG_ERR("Error sending a CoAP request");
991 goto fail;
992 } else {
993 return 1;
994 }
995 }
996 fail:
997 if (ret < 0) {
998 report_callback_error(internal_req, ret);
999 }
1000 if (!internal_req->is_observe) {
1001 if (response_type == COAP_TYPE_ACK) {
1002 /* This is piggybacked ACK,
1003 * no need to wait for lifetime to expire, all data is already transferred
1004 * and acknowledged
1005 */
1006 reset_internal_request(internal_req);
1007 } else {
1008 release_internal_request(internal_req);
1009 }
1010 }
1011 return ret;
1012 }
1013
cancel_requests_with(struct coap_client * client,int error)1014 static void cancel_requests_with(struct coap_client *client, int error)
1015 {
1016 k_mutex_lock(&client->lock, K_FOREVER);
1017
1018 for (int i = 0; i < ARRAY_SIZE(client->requests); i++) {
1019 if (client->requests[i].request_ongoing == true) {
1020 LOG_DBG("Cancelling request %d", i);
1021 /* Report the request was cancelled. This will be skipped if
1022 * this function was called from the user's callback so we
1023 * do not reenter it. In that case, the user knows their
1024 * request was cancelled anyway.
1025 */
1026 report_callback_error(&client->requests[i], error);
1027 }
1028
1029 /* Clear all requests, even completed ones, so that our
1030 * handle_poll() does not poll() anymore for this socket.
1031 */
1032 reset_internal_request(&client->requests[i]);
1033 }
1034 k_mutex_unlock(&client->lock);
1035
1036 }
1037
coap_client_cancel_requests(struct coap_client * client)1038 void coap_client_cancel_requests(struct coap_client *client)
1039 {
1040 cancel_requests_with(client, -ECANCELED);
1041 /* Wait until after zsock_poll() can time out and return. */
1042 k_sleep(K_MSEC(COAP_PERIODIC_TIMEOUT));
1043 }
1044
requests_match(struct coap_client_request * a,struct coap_client_request * b)1045 static bool requests_match(struct coap_client_request *a, struct coap_client_request *b)
1046 {
1047 /* enum coap_method does not have value for zero, so differentiate valid values */
1048 if (a->method && b->method && a->method != b->method) {
1049 return false;
1050 }
1051 if (a->path && b->path && strcmp(a->path, b->path) != 0) {
1052 return false;
1053 }
1054 if (a->cb && b->cb && a->cb != b->cb) {
1055 return false;
1056 }
1057 if (a->user_data && b->user_data && a->user_data != b->user_data) {
1058 return false;
1059 }
1060 /* It is intentional that (struct coap_client_request){0} matches all */
1061 return true;
1062 }
1063
coap_client_cancel_request(struct coap_client * client,struct coap_client_request * req)1064 void coap_client_cancel_request(struct coap_client *client, struct coap_client_request *req)
1065 {
1066 k_mutex_lock(&client->lock, K_FOREVER);
1067
1068 for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) {
1069 if (client->requests[i].request_ongoing &&
1070 requests_match(&client->requests[i].coap_request, req)) {
1071 LOG_DBG("Cancelling request %d", i);
1072 report_callback_error(&client->requests[i], -ECANCELED);
1073 release_internal_request(&client->requests[i]);
1074 }
1075 }
1076
1077 k_mutex_unlock(&client->lock);
1078 }
1079
coap_client_recv(void * coap_cl,void * a,void * b)1080 void coap_client_recv(void *coap_cl, void *a, void *b)
1081 {
1082 int ret;
1083
1084 k_sem_take(&coap_client_recv_sem, K_FOREVER);
1085 while (true) {
1086 ret = handle_poll();
1087 if (ret < 0) {
1088 /* Error in polling */
1089 LOG_ERR("Error in poll");
1090 goto idle;
1091 }
1092
1093 /* There are more messages coming */
1094 if (has_ongoing_exchanges()) {
1095 continue;
1096 } else {
1097 idle:
1098 k_sem_take(&coap_client_recv_sem, K_FOREVER);
1099 }
1100 }
1101 }
1102
coap_client_init(struct coap_client * client,const char * info)1103 int coap_client_init(struct coap_client *client, const char *info)
1104 {
1105 if (client == NULL) {
1106 return -EINVAL;
1107 }
1108
1109 k_mutex_lock(&coap_client_mutex, K_FOREVER);
1110 if (num_clients >= CONFIG_COAP_CLIENT_MAX_INSTANCES) {
1111 k_mutex_unlock(&coap_client_mutex);
1112 return -ENOSPC;
1113 }
1114
1115 k_mutex_init(&client->lock);
1116
1117 clients[num_clients] = client;
1118 num_clients++;
1119
1120 k_mutex_unlock(&coap_client_mutex);
1121 return 0;
1122 }
1123
coap_client_option_initial_block2(void)1124 struct coap_client_option coap_client_option_initial_block2(void)
1125 {
1126 struct coap_client_option block2 = {
1127 .code = COAP_OPTION_BLOCK2,
1128 .len = 1,
1129 .value[0] = coap_bytes_to_block_size(CONFIG_COAP_CLIENT_BLOCK_SIZE),
1130 };
1131
1132 return block2;
1133 }
1134
coap_client_has_ongoing_exchange(struct coap_client * client)1135 bool coap_client_has_ongoing_exchange(struct coap_client *client)
1136 {
1137 if (client == NULL) {
1138 LOG_ERR("Invalid (NULL) Client");
1139 return false;
1140 }
1141
1142 return has_ongoing_exchange(client);
1143 }
1144
1145 #define COAP_CLIENT_THREAD_PRIORITY CLAMP(CONFIG_COAP_CLIENT_THREAD_PRIORITY, \
1146 K_HIGHEST_APPLICATION_THREAD_PRIO, \
1147 K_LOWEST_APPLICATION_THREAD_PRIO)
1148
1149 K_THREAD_DEFINE(coap_client_recv_thread, CONFIG_COAP_CLIENT_STACK_SIZE,
1150 coap_client_recv, NULL, NULL, NULL,
1151 COAP_CLIENT_THREAD_PRIORITY, 0, 0);
1152