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 ¶ms);
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, ¶ms);
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