diff --git a/include/zephyr/net/coap.h b/include/zephyr/net/coap.h index 11d2c3711f2..09df1e98f8a 100644 --- a/include/zephyr/net/coap.h +++ b/include/zephyr/net/coap.h @@ -64,6 +64,7 @@ enum coap_option_num { COAP_OPTION_PROXY_SCHEME = 39, /**< Proxy-Scheme */ COAP_OPTION_SIZE1 = 60, /**< Size1 */ COAP_OPTION_ECHO = 252, /**< Echo (RFC 9175) */ + COAP_OPTION_NO_RESPONSE = 258, /**< No-Response (RFC 7967) */ COAP_OPTION_REQUEST_TAG = 292 /**< Request-Tag (RFC 9175) */ }; @@ -222,6 +223,22 @@ enum coap_content_format { COAP_CONTENT_FORMAT_APP_CBOR = 60 /**< application/cbor */ }; +/** + * @brief Set of No-Response option values for CoAP. + * + * To be used when encoding or decoding a No-Response option defined + * in RFC 7967. + */ +enum coap_no_response { + COAP_NO_RESPONSE_SUPPRESS_2_XX = 0x02, + COAP_NO_RESPONSE_SUPPRESS_4_XX = 0x08, + COAP_NO_RESPONSE_SUPPRESS_5_XX = 0x10, + + COAP_NO_RESPONSE_SUPPRESS_ALL = COAP_NO_RESPONSE_SUPPRESS_2_XX | + COAP_NO_RESPONSE_SUPPRESS_4_XX | + COAP_NO_RESPONSE_SUPPRESS_5_XX, +}; + /** @cond INTERNAL_HIDDEN */ /* block option helper */ @@ -338,8 +355,15 @@ typedef int (*coap_reply_t)(const struct coap_packet *response, * @brief CoAP transmission parameters. */ struct coap_transmission_parameters { - /** Initial ACK timeout. Value is used as a base value to retry pending CoAP packets. */ + /** Initial ACK timeout. Value is used as a base value to retry pending CoAP packets. */ uint32_t ack_timeout; +#if defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) || defined(__DOXYGEN__) + /** + * Set CoAP ack random factor. A value of 150 means a factor of 1.5. A value of 0 defaults + * to @kconfig{CONFIG_COAP_ACK_RANDOM_PERCENT}. The value must be >= 100. + */ + uint16_t ack_random_percent; +#endif /* defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) */ /** Set CoAP retry backoff factor. A value of 200 means a factor of 2.0. */ uint16_t coap_backoff_percent; /** Maximum number of retransmissions. */ @@ -528,6 +552,21 @@ int coap_packet_init(struct coap_packet *cpkt, uint8_t *data, uint16_t max_len, int coap_ack_init(struct coap_packet *cpkt, const struct coap_packet *req, uint8_t *data, uint16_t max_len, uint8_t code); +/** + * @brief Create a new CoAP Reset message for given request. + * + * This function works like @ref coap_packet_init, filling CoAP header type, + * and CoAP header message id fields. + * + * @param cpkt New packet to be initialized using the storage from @a data. + * @param req CoAP request packet that is being acknowledged + * @param data Data that will contain a CoAP packet information + * @param max_len Maximum allowable length of data + * + * @return 0 in case of success or negative in case of error. + */ +int coap_rst_init(struct coap_packet *cpkt, const struct coap_packet *req, + uint8_t *data, uint16_t max_len); /** * @brief Returns a randomly generated array of 8 bytes, that can be * used as a message's token. diff --git a/include/zephyr/net/coap_client.h b/include/zephyr/net/coap_client.h index a4f35374c93..1d68661da3b 100644 --- a/include/zephyr/net/coap_client.h +++ b/include/zephyr/net/coap_client.h @@ -88,7 +88,7 @@ struct coap_client_option { struct coap_client_internal_request { uint8_t request_token[COAP_TOKEN_MAX_LEN]; uint32_t offset; - uint32_t last_id; + uint16_t last_id; uint8_t request_tkl; bool request_ongoing; atomic_t in_callback; @@ -108,7 +108,6 @@ struct coap_client { int fd; struct sockaddr address; socklen_t socklen; - bool response_ready; struct k_mutex lock; uint8_t send_buf[MAX_COAP_MSG_LEN]; uint8_t recv_buf[MAX_COAP_MSG_LEN]; diff --git a/include/zephyr/net/coap_service.h b/include/zephyr/net/coap_service.h index 718d1fb1cfc..f5bf3f8405b 100644 --- a/include/zephyr/net/coap_service.h +++ b/include/zephyr/net/coap_service.h @@ -59,7 +59,7 @@ struct coap_service { }; #define __z_coap_service_define(_name, _host, _port, _flags, _res_begin, _res_end) \ - static struct coap_service_data coap_service_data_##_name = { \ + static struct coap_service_data _CONCAT(coap_service_data_, _name) = { \ .sock_fd = -1, \ }; \ const STRUCT_SECTION_ITERABLE(coap_service, _name) = { \ @@ -69,7 +69,7 @@ struct coap_service { .flags = _flags, \ .res_begin = (_res_begin), \ .res_end = (_res_end), \ - .data = &coap_service_data_##_name, \ + .data = &_CONCAT(coap_service_data_, _name), \ } /** @endcond */ @@ -111,8 +111,8 @@ struct coap_service { * @param _service Name of the associated service. */ #define COAP_RESOURCE_DEFINE(_name, _service, ...) \ - STRUCT_SECTION_ITERABLE_ALTERNATE(coap_resource_##_service, coap_resource, _name) \ - = __VA_ARGS__ + STRUCT_SECTION_ITERABLE_ALTERNATE(_CONCAT(coap_resource_, _service), coap_resource, \ + _name) = __VA_ARGS__ /** * @brief Define a CoAP service with static resources. @@ -132,11 +132,11 @@ struct coap_service { * @param _flags Configuration flags @see @ref COAP_SERVICE_FLAGS. */ #define COAP_SERVICE_DEFINE(_name, _host, _port, _flags) \ - extern struct coap_resource _CONCAT(_coap_resource_##_name, _list_start)[]; \ - extern struct coap_resource _CONCAT(_coap_resource_##_name, _list_end)[]; \ + extern struct coap_resource _CONCAT(_CONCAT(_coap_resource_, _name), _list_start)[]; \ + extern struct coap_resource _CONCAT(_CONCAT(_coap_resource_, _name), _list_end)[]; \ __z_coap_service_define(_name, _host, _port, _flags, \ - &_CONCAT(_coap_resource_##_name, _list_start)[0], \ - &_CONCAT(_coap_resource_##_name, _list_end)[0]) + &_CONCAT(_CONCAT(_coap_resource_, _name), _list_start)[0], \ + &_CONCAT(_CONCAT(_coap_resource_, _name), _list_end)[0]) /** * @brief Count the number of CoAP services. @@ -177,7 +177,7 @@ struct coap_service { * @param _it Name of iterator (of type @ref coap_resource) */ #define COAP_RESOURCE_FOREACH(_service, _it) \ - STRUCT_SECTION_FOREACH_ALTERNATE(coap_resource_##_service, coap_resource, _it) + STRUCT_SECTION_FOREACH_ALTERNATE(_CONCAT(coap_resource_, _service), coap_resource, _it) /** * @brief Iterate over all static resources associated with @p _service . diff --git a/subsys/net/lib/coap/coap.c b/subsys/net/lib/coap/coap.c index c557f3c424b..70f2610adf0 100644 --- a/subsys/net/lib/coap/coap.c +++ b/subsys/net/lib/coap/coap.c @@ -59,6 +59,9 @@ static uint16_t message_id; static struct coap_transmission_parameters coap_transmission_params = { .max_retransmission = CONFIG_COAP_MAX_RETRANSMIT, .ack_timeout = CONFIG_COAP_INIT_ACK_TIMEOUT_MS, +#if defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) + .ack_random_percent = CONFIG_COAP_ACK_RANDOM_PERCENT, +#endif /* defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) */ .coap_backoff_percent = CONFIG_COAP_BACKOFF_PERCENT }; @@ -229,6 +232,19 @@ int coap_ack_init(struct coap_packet *cpkt, const struct coap_packet *req, token, code, id); } +int coap_rst_init(struct coap_packet *cpkt, const struct coap_packet *req, + uint8_t *data, uint16_t max_len) +{ + uint16_t id; + uint8_t ver; + + ver = coap_header_get_version(req); + id = coap_header_get_id(req); + + return coap_packet_init(cpkt, data, max_len, ver, COAP_TYPE_RESET, 0, + NULL, 0, id); +} + static void option_header_set_delta(uint8_t *opt, uint8_t delta) { *opt = (delta & 0xF) << 4; @@ -1706,8 +1722,9 @@ struct coap_pending *coap_pending_next_to_expire( static uint32_t init_ack_timeout(const struct coap_transmission_parameters *params) { #if defined(CONFIG_COAP_RANDOMIZE_ACK_TIMEOUT) - const uint32_t max_ack = params->ack_timeout * - CONFIG_COAP_ACK_RANDOM_PERCENT / 100; + const uint16_t random_percent = params->ack_random_percent ? params->ack_random_percent + : CONFIG_COAP_ACK_RANDOM_PERCENT; + const uint32_t max_ack = params->ack_timeout * random_percent / 100U; const uint32_t min_ack = params->ack_timeout; /* Randomly generated initial ACK timeout diff --git a/subsys/net/lib/coap/coap_client.c b/subsys/net/lib/coap/coap_client.c index 7c49a90981b..ebf99bade9f 100644 --- a/subsys/net/lib/coap/coap_client.c +++ b/subsys/net/lib/coap/coap_client.c @@ -16,13 +16,23 @@ LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL); #define COAP_VERSION 1 #define COAP_SEPARATE_TIMEOUT 6000 #define COAP_PERIODIC_TIMEOUT 500 +#define COAP_EXCHANGE_LIFETIME_FACTOR 3 #define BLOCK1_OPTION_SIZE 4 #define PAYLOAD_MARKER_SIZE 1 +static K_MUTEX_DEFINE(coap_client_mutex); static struct coap_client *clients[CONFIG_COAP_CLIENT_MAX_INSTANCES]; static int num_clients; static K_SEM_DEFINE(coap_client_recv_sem, 0, 1); -static atomic_t coap_client_recv_active; + +static bool timeout_expired(struct coap_client_internal_request *internal_req); +static void cancel_requests_with(struct coap_client *client, int error); +static int recv_response(struct coap_client *client, struct coap_packet *response, bool *truncated); +static int handle_response(struct coap_client *client, const struct coap_packet *response, + bool response_truncated); +static struct coap_client_internal_request *get_request_with_mid( + struct coap_client *client, const struct coap_packet *resp); + static int send_request(int sock, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) @@ -51,23 +61,16 @@ static int receive(int sock, void *buf, size_t max_len, int flags, return err; } -static void reset_block_contexts(struct coap_client_internal_request *request) -{ - request->recv_blk_ctx.block_size = 0; - request->recv_blk_ctx.total_size = 0; - request->recv_blk_ctx.current = 0; - - request->send_blk_ctx.block_size = 0; - request->send_blk_ctx.total_size = 0; - request->send_blk_ctx.current = 0; -} - static void reset_internal_request(struct coap_client_internal_request *request) { request->offset = 0; request->last_id = 0; request->last_response_id = -1; - reset_block_contexts(request); + request->request_ongoing = false; + request->is_observe = false; + request->pending.timeout = 0; + request->recv_blk_ctx = (struct coap_block_context){ 0 }; + request->send_blk_ctx = (struct coap_block_context){ 0 }; } static int coap_client_schedule_poll(struct coap_client *client, int sock, @@ -78,14 +81,26 @@ static int coap_client_schedule_poll(struct coap_client *client, int sock, memcpy(&internal_req->coap_request, req, sizeof(struct coap_client_request)); internal_req->request_ongoing = true; - if (!coap_client_recv_active) { - k_sem_give(&coap_client_recv_sem); - } - atomic_set(&coap_client_recv_active, 1); + k_sem_give(&coap_client_recv_sem); return 0; } +static bool exchange_lifetime_exceeded(struct coap_client_internal_request *internal_req) +{ + int64_t time_since_t0, exchange_lifetime; + + if (coap_header_get_type(&internal_req->request) == COAP_TYPE_NON_CON) { + return true; + } + + time_since_t0 = k_uptime_get() - internal_req->pending.t0; + exchange_lifetime = + (internal_req->pending.params.ack_timeout * COAP_EXCHANGE_LIFETIME_FACTOR); + + return time_since_t0 > exchange_lifetime; +} + static bool has_ongoing_request(struct coap_client *client) { for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { @@ -97,10 +112,33 @@ static bool has_ongoing_request(struct coap_client *client) return false; } +static bool has_ongoing_exchange(struct coap_client *client) +{ + for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { + if (client->requests[i].request_ongoing == true || + !exchange_lifetime_exceeded(&client->requests[i])) { + return true; + } + } + + return false; +} + +static bool has_timeout_expired(struct coap_client *client) +{ + for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { + if (timeout_expired(&client->requests[i])) { + return true; + } + } + return false; +} + static struct coap_client_internal_request *get_free_request(struct coap_client *client) { for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { - if (client->requests[i].request_ongoing == false) { + if (client->requests[i].request_ongoing == false && + exchange_lifetime_exceeded(&client->requests[i])) { return &client->requests[i]; } } @@ -108,15 +146,15 @@ static struct coap_client_internal_request *get_free_request(struct coap_client return NULL; } -static bool has_ongoing_requests(void) +static bool has_ongoing_exchanges(void) { - bool has_requests = false; - for (int i = 0; i < num_clients; i++) { - has_requests |= has_ongoing_request(clients[i]); + if (has_ongoing_exchange(clients[i])) { + return true; + } } - return has_requests; + return false; } static enum coap_block_size coap_client_default_block_size(void) @@ -385,6 +423,7 @@ int coap_client_req(struct coap_client *client, int sock, const struct sockaddr &client->address, client->socklen); if (ret < 0) { LOG_ERR("Transmission failed: %d", errno); + reset_internal_request(internal_req); } else { /* Do not return the number of bytes sent */ ret = 0; @@ -422,10 +461,13 @@ static int resend_request(struct coap_client *client, { int ret = 0; + /* Copy the pending structure if we need to restore it */ + struct coap_pending tmp = internal_req->pending; + if (internal_req->request_ongoing && internal_req->pending.timeout != 0 && coap_pending_cycle(&internal_req->pending)) { - LOG_ERR("Timeout in poll, retrying send"); + LOG_ERR("Timeout, retrying send"); /* Reset send block context as it was updated in previous init from packet */ if (internal_req->send_blk_ctx.total_size > 0) { @@ -434,117 +476,118 @@ static int resend_request(struct coap_client *client, ret = coap_client_init_request(client, &internal_req->coap_request, internal_req, true); if (ret < 0) { - LOG_ERR("Error re-creating CoAP request"); + LOG_ERR("Error re-creating CoAP request %d", ret); + return ret; + } + + ret = send_request(client->fd, internal_req->request.data, + internal_req->request.offset, 0, &client->address, + client->socklen); + if (ret > 0) { + ret = 0; + } else if (ret == -1 && errno == EAGAIN) { + /* Restore the pending structure, retry later */ + internal_req->pending = tmp; + /* Not a fatal socket error, will trigger a retry */ + ret = 0; } else { - ret = send_request(client->fd, internal_req->request.data, - internal_req->request.offset, 0, &client->address, - client->socklen); - if (ret > 0) { - ret = 0; - } else { - LOG_ERR("Failed to resend request, %d", ret); - } + ret = -errno; + LOG_ERR("Failed to resend request, %d", ret); } } else { - LOG_ERR("Timeout in poll, no more retries left"); + LOG_ERR("Timeout, no more retries left"); ret = -ETIMEDOUT; - report_callback_error(internal_req, ret); - internal_req->request_ongoing = false; } return ret; } -static int coap_client_resend_handler(void) +static void coap_client_resend_handler(struct coap_client *client) { int ret = 0; - for (int i = 0; i < num_clients; i++) { - k_mutex_lock(&clients[i]->lock, K_FOREVER); + k_mutex_lock(&client->lock, K_FOREVER); - for (int j = 0; j < CONFIG_COAP_CLIENT_MAX_REQUESTS; j++) { - if (timeout_expired(&clients[i]->requests[j])) { - ret = resend_request(clients[i], &clients[i]->requests[j]); + for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { + if (timeout_expired(&client->requests[i])) { + ret = resend_request(client, &client->requests[i]); + if (ret < 0) { + report_callback_error(&client->requests[i], ret); + reset_internal_request(&client->requests[i]); } } - - k_mutex_unlock(&clients[i]->lock); } - return ret; + k_mutex_unlock(&client->lock); } static int handle_poll(void) { int ret = 0; - while (1) { - struct zsock_pollfd fds[CONFIG_COAP_CLIENT_MAX_INSTANCES] = {0}; - int nfds = 0; + struct zsock_pollfd fds[CONFIG_COAP_CLIENT_MAX_INSTANCES] = {0}; + int nfds = 0; - /* Use periodic timeouts */ - for (int i = 0; i < num_clients; i++) { - fds[i].fd = clients[i]->fd; - fds[i].events = ZSOCK_POLLIN; - fds[i].revents = 0; - nfds++; - } + /* Use periodic timeouts */ + for (int i = 0; i < num_clients; i++) { + fds[i].fd = clients[i]->fd; + fds[i].events = (has_ongoing_exchange(clients[i]) ? ZSOCK_POLLIN : 0) | + (has_timeout_expired(clients[i]) ? ZSOCK_POLLOUT : 0); + fds[i].revents = 0; + nfds++; + } - ret = zsock_poll(fds, nfds, COAP_PERIODIC_TIMEOUT); + ret = zsock_poll(fds, nfds, COAP_PERIODIC_TIMEOUT); - if (ret < 0) { - LOG_ERR("Error in poll:%d", errno); - errno = 0; - return ret; - } else if (ret == 0) { - /* Resend all the expired pending messages */ - ret = coap_client_resend_handler(); + if (ret < 0) { + ret = -errno; + LOG_ERR("Error in poll:%d", ret); + return ret; + } else if (ret == 0) { + return 0; + } - if (ret < 0) { - LOG_ERR("Error resending request: %d", ret); - } + for (int i = 0; i < nfds; i++) { + if (fds[i].revents & ZSOCK_POLLOUT) { + coap_client_resend_handler(clients[i]); + } + if (fds[i].revents & ZSOCK_POLLIN) { + struct coap_packet response; + bool response_truncated = false; - if (!has_ongoing_requests()) { - return ret; + ret = recv_response(clients[i], &response, &response_truncated); + if (ret < 0) { + if (ret == -EAGAIN) { + continue; + } + LOG_ERR("Error receiving response"); + cancel_requests_with(clients[i], -EIO); + continue; } - } else { - for (int i = 0; i < nfds; i++) { - if (fds[i].revents & ZSOCK_POLLERR) { - LOG_ERR("Error in poll for socket %d", fds[i].fd); - } - if (fds[i].revents & ZSOCK_POLLHUP) { - LOG_ERR("Error in poll: POLLHUP for socket %d", fds[i].fd); - } - if (fds[i].revents & ZSOCK_POLLNVAL) { - LOG_ERR("Error in poll: POLLNVAL - fd %d not open", - fds[i].fd); - } - if (fds[i].revents & ZSOCK_POLLIN) { - clients[i]->response_ready = true; - } + k_mutex_lock(&clients[i]->lock, K_FOREVER); + ret = handle_response(clients[i], &response, response_truncated); + if (ret < 0) { + LOG_ERR("Error handling response"); } - return 0; + k_mutex_unlock(&clients[i]->lock); + } + if (fds[i].revents & ZSOCK_POLLERR) { + LOG_ERR("Error in poll for socket %d", fds[i].fd); + cancel_requests_with(clients[i], -EIO); + } + if (fds[i].revents & ZSOCK_POLLHUP) { + LOG_ERR("Error in poll: POLLHUP for socket %d", fds[i].fd); + cancel_requests_with(clients[i], -EIO); + } + if (fds[i].revents & ZSOCK_POLLNVAL) { + LOG_ERR("Error in poll: POLLNVAL - fd %d not open", fds[i].fd); + cancel_requests_with(clients[i], -EIO); } } - return ret; -} - -static bool token_compare(struct coap_client_internal_request *internal_req, - const struct coap_packet *resp) -{ - uint8_t response_token[COAP_TOKEN_MAX_LEN]; - uint8_t response_tkl; - - response_tkl = coap_header_get_token(resp, response_token); - - if (internal_req->request_tkl != response_tkl) { - return false; - } - - return memcmp(&internal_req->request_token, &response_token, response_tkl) == 0; + return 0; } static int recv_response(struct coap_client *client, struct coap_packet *response, bool *truncated) @@ -563,14 +606,11 @@ static int recv_response(struct coap_client *client, struct coap_packet *respons flags, &client->address, &client->socklen); if (total_len < 0) { - LOG_ERR("Error reading response: %d", errno); - if (errno == EOPNOTSUPP) { - return -errno; - } - return -EINVAL; + ret = -errno; + return ret; } else if (total_len == 0) { - LOG_ERR("Zero length recv"); - return -EINVAL; + /* Ignore, UDP can be zero length, but it is not CoAP anymore */ + return 0; } available_len = MIN(total_len, sizeof(client->recv_buf)); @@ -581,7 +621,6 @@ static int recv_response(struct coap_client *client, struct coap_packet *respons ret = coap_packet_parse(response, client->recv_buf, available_len, NULL, 0); if (ret < 0) { LOG_ERR("Invalid data received"); - return ret; } return ret; @@ -608,6 +647,26 @@ static int send_ack(struct coap_client *client, const struct coap_packet *req, return 0; } +static int send_rst(struct coap_client *client, const struct coap_packet *req) +{ + int ret; + struct coap_packet rst; + + ret = coap_rst_init(&rst, req, client->send_buf, MAX_COAP_MSG_LEN); + if (ret < 0) { + LOG_ERR("Failed to initialize CoAP RST-message"); + return ret; + } + + ret = send_request(client->fd, rst.data, rst.offset, 0, &client->address, client->socklen); + if (ret < 0) { + LOG_ERR("Error sending a CoAP RST-message"); + return ret; + } + + return 0; +} + static struct coap_client_internal_request *get_request_with_token( struct coap_client *client, const struct coap_packet *resp) { @@ -618,7 +677,8 @@ static struct coap_client_internal_request *get_request_with_token( response_tkl = coap_header_get_token(resp, response_token); for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { - if (client->requests[i].request_ongoing) { + if (client->requests[i].request_ongoing || + !exchange_lifetime_exceeded(&client->requests[i])) { if (client->requests[i].request_tkl != response_tkl) { continue; } @@ -632,6 +692,23 @@ static struct coap_client_internal_request *get_request_with_token( return NULL; } +static struct coap_client_internal_request *get_request_with_mid( + struct coap_client *client, const struct coap_packet *resp) +{ + uint16_t mid = coap_header_get_id(resp); + + for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { + if (client->requests[i].request_ongoing) { + if (client->requests[i].last_id == mid) { + return &client->requests[i]; + } + } + } + + return NULL; +} + + static bool find_echo_option(const struct coap_packet *response, struct coap_option *option) { return coap_find_options(response, COAP_OPTION_ECHO, option, 1); @@ -641,7 +718,6 @@ static int handle_response(struct coap_client *client, const struct coap_packet bool response_truncated) { int ret = 0; - int response_type; int block_option; int block_num; bool blockwise_transfer = false; @@ -654,36 +730,44 @@ static int handle_response(struct coap_client *client, const struct coap_packet * NCON request results only as a separate CON or NCON message as there is no ACK * With RESET, just drop gloves and call the callback. */ - response_type = coap_header_get_type(response); - - internal_req = get_request_with_token(client, response); - /* Reset and Ack need to match the message ID with request */ - if ((response_type == COAP_TYPE_ACK || response_type == COAP_TYPE_RESET) && - internal_req == NULL) { - LOG_ERR("Unexpected ACK or Reset"); - return -EFAULT; - } else if (response_type == COAP_TYPE_RESET) { - coap_pending_clear(&internal_req->pending); - } /* CON, NON_CON and piggybacked ACK need to match the token with original request */ uint16_t payload_len; + uint8_t response_type = coap_header_get_type(response); uint8_t response_code = coap_header_get_code(response); uint16_t response_id = coap_header_get_id(response); const uint8_t *payload = coap_packet_get_payload(response, &payload_len); + if (response_type == COAP_TYPE_RESET) { + internal_req = get_request_with_mid(client, response); + if (!internal_req) { + LOG_WRN("No matching request for RESET"); + return 0; + } + report_callback_error(internal_req, -ECONNRESET); + reset_internal_request(internal_req); + return 0; + } + /* Separate response coming */ if (payload_len == 0 && response_type == COAP_TYPE_ACK && response_code == COAP_CODE_EMPTY) { + internal_req = get_request_with_mid(client, response); + if (!internal_req) { + LOG_WRN("No matching request for ACK"); + return 0; + } internal_req->pending.t0 = k_uptime_get(); internal_req->pending.timeout = internal_req->pending.t0 + COAP_SEPARATE_TIMEOUT; internal_req->pending.retries = 0; return 1; } - if (internal_req == NULL || !token_compare(internal_req, response)) { - LOG_WRN("Not matching tokens"); - return 1; + internal_req = get_request_with_token(client, response); + if (!internal_req) { + LOG_WRN("No matching request for response"); + (void) send_rst(client, response); /* Ignore errors, unrelated to our queries */ + return 0; } /* MID-based deduplication */ @@ -846,14 +930,13 @@ static int handle_response(struct coap_client *client, const struct coap_packet } } fail: - client->response_ready = false; if (ret < 0 || !internal_req->is_observe) { - internal_req->request_ongoing = false; + reset_internal_request(internal_req); } return ret; } -void coap_client_cancel_requests(struct coap_client *client) +static void cancel_requests_with(struct coap_client *client, int error) { k_mutex_lock(&client->lock, K_FOREVER); @@ -865,25 +948,27 @@ void coap_client_cancel_requests(struct coap_client *client) * do not reenter it. In that case, the user knows their * request was cancelled anyway. */ - report_callback_error(&client->requests[i], -ECANCELED); - client->requests[i].request_ongoing = false; - client->requests[i].is_observe = false; + report_callback_error(&client->requests[i], error); + reset_internal_request(&client->requests[i]); } } - atomic_clear(&coap_client_recv_active); k_mutex_unlock(&client->lock); +} + +void coap_client_cancel_requests(struct coap_client *client) +{ + cancel_requests_with(client, -ECANCELED); /* Wait until after zsock_poll() can time out and return. */ k_sleep(K_MSEC(COAP_PERIODIC_TIMEOUT)); } -static void coap_client_recv(void *coap_cl, void *a, void *b) +void coap_client_recv(void *coap_cl, void *a, void *b) { int ret; k_sem_take(&coap_client_recv_sem, K_FOREVER); while (true) { - atomic_set(&coap_client_recv_active, 1); ret = handle_poll(); if (ret < 0) { /* Error in polling */ @@ -891,41 +976,11 @@ static void coap_client_recv(void *coap_cl, void *a, void *b) goto idle; } - for (int i = 0; i < num_clients; i++) { - if (clients[i]->response_ready) { - struct coap_packet response; - bool response_truncated = false; - - k_mutex_lock(&clients[i]->lock, K_FOREVER); - - ret = recv_response(clients[i], &response, &response_truncated); - if (ret < 0) { - LOG_ERR("Error receiving response"); - clients[i]->response_ready = false; - k_mutex_unlock(&clients[i]->lock); - if (ret == -EOPNOTSUPP) { - LOG_ERR("Socket misconfigured."); - goto idle; - } - continue; - } - - ret = handle_response(clients[i], &response, response_truncated); - if (ret < 0) { - LOG_ERR("Error handling response"); - } - - clients[i]->response_ready = false; - k_mutex_unlock(&clients[i]->lock); - } - } - /* There are more messages coming */ - if (has_ongoing_requests()) { + if (has_ongoing_exchanges()) { continue; } else { idle: - atomic_set(&coap_client_recv_active, 0); k_sem_take(&coap_client_recv_sem, K_FOREVER); } } @@ -937,7 +992,9 @@ int coap_client_init(struct coap_client *client, const char *info) return -EINVAL; } + k_mutex_lock(&coap_client_mutex, K_FOREVER); if (num_clients >= CONFIG_COAP_CLIENT_MAX_INSTANCES) { + k_mutex_unlock(&coap_client_mutex); return -ENOSPC; } @@ -946,6 +1003,7 @@ int coap_client_init(struct coap_client *client, const char *info) clients[num_clients] = client; num_clients++; + k_mutex_unlock(&coap_client_mutex); return 0; } diff --git a/tests/net/lib/coap/src/main.c b/tests/net/lib/coap/src/main.c index 18012cc7873..10c7cef1d2c 100644 --- a/tests/net/lib/coap/src/main.c +++ b/tests/net/lib/coap/src/main.c @@ -1750,12 +1750,15 @@ ZTEST(coap, test_transmission_parameters) params = coap_get_transmission_parameters(); zassert_equal(params.ack_timeout, CONFIG_COAP_INIT_ACK_TIMEOUT_MS, "Wrong ACK timeout"); + zassert_equal(params.ack_random_percent, CONFIG_COAP_ACK_RANDOM_PERCENT, + "Wrong ACK random percent"); zassert_equal(params.coap_backoff_percent, CONFIG_COAP_BACKOFF_PERCENT, "Wrong backoff percent"); zassert_equal(params.max_retransmission, CONFIG_COAP_MAX_RETRANSMIT, "Wrong max retransmission value"); params.ack_timeout = 1000; + params.ack_random_percent = 110; params.coap_backoff_percent = 150; params.max_retransmission = 2; @@ -1772,6 +1775,7 @@ ZTEST(coap, test_transmission_parameters) zassert_not_null(pending, "No free pending"); params.ack_timeout = 3000; + params.ack_random_percent = 130; params.coap_backoff_percent = 250; params.max_retransmission = 3; @@ -1780,6 +1784,7 @@ ZTEST(coap, test_transmission_parameters) zassert_equal(r, 0, "Could not initialize packet"); zassert_equal(pending->params.ack_timeout, 3000, "Wrong ACK timeout"); + zassert_equal(pending->params.ack_random_percent, 130, "Wrong ACK random percent"); zassert_equal(pending->params.coap_backoff_percent, 250, "Wrong backoff percent"); zassert_equal(pending->params.max_retransmission, 3, "Wrong max retransmission value"); @@ -1788,6 +1793,7 @@ ZTEST(coap, test_transmission_parameters) zassert_equal(r, 0, "Could not initialize packet"); zassert_equal(pending->params.ack_timeout, 1000, "Wrong ACK timeout"); + zassert_equal(pending->params.ack_random_percent, 110, "Wrong ACK random percent"); zassert_equal(pending->params.coap_backoff_percent, 150, "Wrong backoff percent"); zassert_equal(pending->params.max_retransmission, 2, "Wrong max retransmission value"); } diff --git a/tests/net/lib/coap_client/CMakeLists.txt b/tests/net/lib/coap_client/CMakeLists.txt index d99bb3145f9..d7f0c0cce5e 100644 --- a/tests/net/lib/coap_client/CMakeLists.txt +++ b/tests/net/lib/coap_client/CMakeLists.txt @@ -26,8 +26,9 @@ add_compile_definitions(CONFIG_COAP_CLIENT_MESSAGE_HEADER_SIZE=48) add_compile_definitions(CONFIG_COAP_CLIENT_STACK_SIZE=1024) add_compile_definitions(CONFIG_COAP_CLIENT_THREAD_PRIORITY=10) add_compile_definitions(CONFIG_COAP_LOG_LEVEL=4) -add_compile_definitions(CONFIG_COAP_INIT_ACK_TIMEOUT_MS=200) +add_compile_definitions(CONFIG_COAP_INIT_ACK_TIMEOUT_MS=10) add_compile_definitions(CONFIG_COAP_CLIENT_MAX_REQUESTS=2) add_compile_definitions(CONFIG_COAP_CLIENT_MAX_INSTANCES=2) add_compile_definitions(CONFIG_COAP_MAX_RETRANSMIT=4) add_compile_definitions(CONFIG_COAP_BACKOFF_PERCENT=200) +add_compile_definitions(CONFIG_COAP_LOG_LEVEL=4) diff --git a/tests/net/lib/coap_client/src/main.c b/tests/net/lib/coap_client/src/main.c index 665aab30892..f06a0ce646c 100644 --- a/tests/net/lib/coap_client/src/main.c +++ b/tests/net/lib/coap_client/src/main.c @@ -16,87 +16,123 @@ LOG_MODULE_REGISTER(coap_client_test); DEFINE_FFF_GLOBALS; #define FFF_FAKES_LIST(FAKE) +#define LONG_ACK_TIMEOUT_MS 200 +#define MORE_THAN_EXCHANGE_LIFETIME_MS 4 * CONFIG_COAP_INIT_ACK_TIMEOUT_MS +#define MORE_THAN_LONG_EXCHANGE_LIFETIME_MS 4 * LONG_ACK_TIMEOUT_MS +#define MORE_THAN_ACK_TIMEOUT_MS \ + (CONFIG_COAP_INIT_ACK_TIMEOUT_MS + CONFIG_COAP_INIT_ACK_TIMEOUT_MS / 2) + +#define VALID_MESSAGE_ID BIT(31) + static int16_t last_response_code; static const char *test_path = "test"; -static uint16_t messages_needing_response[2]; +static uint32_t messages_needing_response[2]; static struct coap_client client; static char *short_payload = "testing"; static char *long_payload = LOREM_IPSUM_SHORT; +static uint16_t get_next_pending_message_id(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(messages_needing_response); i++) { + if (messages_needing_response[i] & VALID_MESSAGE_ID) { + messages_needing_response[i] &= ~VALID_MESSAGE_ID; + return messages_needing_response[i]; + } + } + + return UINT16_MAX; +} + +static void set_next_pending_message_id(uint16_t id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(messages_needing_response); i++) { + if (!(messages_needing_response[i] & VALID_MESSAGE_ID)) { + messages_needing_response[i] = id; + messages_needing_response[i] |= VALID_MESSAGE_ID; + return; + } + } +} + static ssize_t z_impl_zsock_recvfrom_custom_fake(int sock, void *buf, size_t max_len, int flags, - struct sockaddr *src_addr, socklen_t *addrlen) + struct sockaddr *src_addr, socklen_t *addrlen) { uint16_t last_message_id = 0; LOG_INF("Recvfrom"); - uint8_t ack_data[] = { - 0x68, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; + uint8_t ack_data[] = {0x68, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } + last_message_id = get_next_pending_message_id(); - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; + ack_data[2] = (uint8_t)(last_message_id >> 8); + ack_data[3] = (uint8_t)last_message_id; memcpy(buf, ack_data, sizeof(ack_data)); + clear_socket_events(ZSOCK_POLLIN); + return sizeof(ack_data); } -static ssize_t z_impl_zsock_sendto_custom_fake(int sock, void *buf, size_t len, - int flags, const struct sockaddr *dest_addr, - socklen_t addrlen) +static ssize_t z_impl_zsock_sendto_custom_fake(int sock, void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) { uint16_t last_message_id = 0; + uint8_t type; - last_message_id |= ((uint8_t *) buf)[2] << 8; - last_message_id |= ((uint8_t *) buf)[3]; + last_message_id |= ((uint8_t *)buf)[2] << 8; + last_message_id |= ((uint8_t *)buf)[3]; - if (messages_needing_response[0] == 0) { - messages_needing_response[0] = last_message_id; - } else { - messages_needing_response[1] = last_message_id; + type = (((uint8_t *)buf)[0] & 0x30) >> 4; + + set_next_pending_message_id(last_message_id); + LOG_INF("Latest message ID: %d", last_message_id); + + if (type == 0) { + set_socket_events(ZSOCK_POLLIN); } - last_response_code = ((uint8_t *) buf)[1]; + return 1; +} + +static ssize_t z_impl_zsock_sendto_custom_fake_no_reply(int sock, void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, + socklen_t addrlen) +{ + uint16_t last_message_id = 0; + last_message_id |= ((uint8_t *)buf)[2] << 8; + last_message_id |= ((uint8_t *)buf)[3]; + + set_next_pending_message_id(last_message_id); LOG_INF("Latest message ID: %d", last_message_id); return 1; } -static ssize_t z_impl_zsock_sendto_custom_fake_echo(int sock, void *buf, size_t len, - int flags, const struct sockaddr *dest_addr, - socklen_t addrlen) +static ssize_t z_impl_zsock_sendto_custom_fake_echo(int sock, void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, + socklen_t addrlen) { uint16_t last_message_id = 0; struct coap_packet response = {0}; struct coap_option option = {0}; - last_message_id |= ((uint8_t *) buf)[2] << 8; - last_message_id |= ((uint8_t *) buf)[3]; - - if (messages_needing_response[0] == 0) { - messages_needing_response[0] = last_message_id; - } else { - messages_needing_response[1] = last_message_id; - } - - last_response_code = ((uint8_t *) buf)[1]; + last_message_id |= ((uint8_t *)buf)[2] << 8; + last_message_id |= ((uint8_t *)buf)[3]; + set_next_pending_message_id(last_message_id); LOG_INF("Latest message ID: %d", last_message_id); int ret = coap_packet_parse(&response, buf, len, NULL, 0); - if (ret < 0) { LOG_ERR("Invalid data received"); } @@ -108,32 +144,27 @@ static ssize_t z_impl_zsock_sendto_custom_fake_echo(int sock, void *buf, size_t z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake; + set_socket_events(ZSOCK_POLLIN); + return 1; } static ssize_t z_impl_zsock_sendto_custom_fake_echo_next_req(int sock, void *buf, size_t len, - int flags, const struct sockaddr *dest_addr, - socklen_t addrlen) + int flags, + const struct sockaddr *dest_addr, + socklen_t addrlen) { uint16_t last_message_id = 0; struct coap_packet response = {0}; struct coap_option option = {0}; - last_message_id |= ((uint8_t *) buf)[2] << 8; - last_message_id |= ((uint8_t *) buf)[3]; - - if (messages_needing_response[0] == 0) { - messages_needing_response[0] = last_message_id; - } else { - messages_needing_response[1] = last_message_id; - } - - last_response_code = ((uint8_t *) buf)[1]; + last_message_id |= ((uint8_t *)buf)[2] << 8; + last_message_id |= ((uint8_t *)buf)[3]; + set_next_pending_message_id(last_message_id); LOG_INF("Latest message ID: %d", last_message_id); int ret = coap_packet_parse(&response, buf, len, NULL, 0); - if (ret < 0) { LOG_ERR("Invalid data received"); } @@ -153,6 +184,8 @@ static ssize_t z_impl_zsock_sendto_custom_fake_echo_next_req(int sock, void *buf z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake; + set_socket_events(ZSOCK_POLLIN); + return 1; } @@ -162,50 +195,17 @@ static ssize_t z_impl_zsock_recvfrom_custom_fake_response(int sock, void *buf, s { uint16_t last_message_id = 0; - static uint8_t ack_data[] = { - 0x48, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; + static uint8_t ack_data[] = {0x48, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } + last_message_id = get_next_pending_message_id(); - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; + ack_data[2] = (uint8_t)(last_message_id >> 8); + ack_data[3] = (uint8_t)last_message_id; memcpy(buf, ack_data, sizeof(ack_data)); - return sizeof(ack_data); -} - -static ssize_t z_impl_zsock_recvfrom_custom_fake_delayed_response(int sock, void *buf, - size_t max_len, int flags, - struct sockaddr *src_addr, - socklen_t *addrlen) -{ - uint16_t last_message_id = 0; - - static uint8_t ack_data[] = { - 0x68, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } - - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; - - memcpy(buf, ack_data, sizeof(ack_data)); - k_sleep(K_MSEC(10)); + clear_socket_events(ZSOCK_POLLIN); return sizeof(ack_data); } @@ -216,20 +216,13 @@ static ssize_t z_impl_zsock_recvfrom_custom_fake_empty_ack(int sock, void *buf, { uint16_t last_message_id = 0; - static uint8_t ack_data[] = { - 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; + static uint8_t ack_data[] = {0x68, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } + last_message_id = get_next_pending_message_id(); - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; + ack_data[2] = (uint8_t)(last_message_id >> 8); + ack_data[3] = (uint8_t)last_message_id; memcpy(buf, ack_data, sizeof(ack_data)); @@ -244,23 +237,18 @@ static ssize_t z_impl_zsock_recvfrom_custom_fake_unmatching(int sock, void *buf, { uint16_t last_message_id = 0; - static uint8_t ack_data[] = { - 0x68, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 - }; + static uint8_t ack_data[] = {0x68, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } + last_message_id = get_next_pending_message_id(); - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; + ack_data[2] = (uint8_t)(last_message_id >> 8); + ack_data[3] = (uint8_t)last_message_id; memcpy(buf, ack_data, sizeof(ack_data)); + clear_socket_events(ZSOCK_POLLIN); + return sizeof(ack_data); } @@ -275,22 +263,18 @@ static ssize_t z_impl_zsock_recvfrom_custom_fake_echo(int sock, void *buf, size_ 0x00, 0x00, 0x00, 0x00, 0xda, 0xef, 'e', 'c', 'h', 'o', '_', 'v', 'a', 'l', 'u', 'e'}; - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } + last_message_id = get_next_pending_message_id(); - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; + ack_data[2] = (uint8_t)(last_message_id >> 8); + ack_data[3] = (uint8_t)last_message_id; memcpy(buf, ack_data, sizeof(ack_data)); z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_response; z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_echo; + clear_socket_events(ZSOCK_POLLIN); + return sizeof(ack_data); } @@ -305,22 +289,18 @@ static ssize_t z_impl_zsock_recvfrom_custom_fake_echo_next_req(int sock, void *b 0x00, 0x00, 0x00, 0x00, 0xda, 0xef, 'e', 'c', 'h', 'o', '_', 'v', 'a', 'l', 'u', 'e'}; - if (messages_needing_response[0] != 0) { - last_message_id = messages_needing_response[0]; - messages_needing_response[0] = 0; - } else { - last_message_id = messages_needing_response[1]; - messages_needing_response[1] = 0; - } + last_message_id = get_next_pending_message_id(); - ack_data[2] = (uint8_t) (last_message_id >> 8); - ack_data[3] = (uint8_t) last_message_id; + ack_data[2] = (uint8_t)(last_message_id >> 8); + ack_data[3] = (uint8_t)last_message_id; memcpy(buf, ack_data, sizeof(ack_data)); z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_response; z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_echo_next_req; + clear_socket_events(ZSOCK_POLLIN); + return sizeof(ack_data); } @@ -333,6 +313,8 @@ static void *suite_setup(void) static void test_setup(void *data) { + int i; + /* Register resets */ DO_FOREACH_FAKE(RESET_FAKE); /* reset common FFF internal structures */ @@ -340,8 +322,13 @@ static void test_setup(void *data) z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake; z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake; - messages_needing_response[0] = 0; - messages_needing_response[1] = 0; + clear_socket_events(ZSOCK_POLLIN | ZSOCK_POLLOUT | ZSOCK_POLLERR); + + for (i = 0; i < ARRAY_SIZE(messages_needing_response); i++) { + messages_needing_response[i] = 0; + } + + last_response_code = 0; } void coap_callback(int16_t code, size_t offset, const uint8_t *payload, size_t len, bool last_block, @@ -349,6 +336,9 @@ void coap_callback(int16_t code, size_t offset, const uint8_t *payload, size_t l { LOG_INF("CoAP response callback, %d", code); last_response_code = code; + if (user_data) { + k_sem_give((struct k_sem *) user_data); + } } ZTEST_SUITE(coap_client, NULL, suite_setup, test_setup, NULL, NULL); @@ -375,11 +365,10 @@ ZTEST(coap_client, test_get_request) LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - set_socket_events(ZSOCK_POLLIN); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); + LOG_INF("Test done"); } ZTEST(coap_client, test_resend_request) @@ -399,19 +388,20 @@ ZTEST(coap_client, test_resend_request) client_request.payload = short_payload; client_request.len = strlen(short_payload); - z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_delayed_response; + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_no_reply; k_sleep(K_MSEC(1)); LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - k_sleep(K_MSEC(300)); - set_socket_events(ZSOCK_POLLIN); + k_sleep(K_MSEC(MORE_THAN_ACK_TIMEOUT_MS)); + set_socket_events(ZSOCK_POLLIN | ZSOCK_POLLOUT); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); zassert_equal(z_impl_zsock_sendto_fake.call_count, 2); + LOG_INF("Test done"); } ZTEST(coap_client, test_echo_option) @@ -438,11 +428,10 @@ ZTEST(coap_client, test_echo_option) LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - set_socket_events(ZSOCK_POLLIN); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); + LOG_INF("Test done"); } ZTEST(coap_client, test_echo_option_next_req) @@ -469,10 +458,8 @@ ZTEST(coap_client, test_echo_option_next_req) LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - set_socket_events(ZSOCK_POLLIN); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); char *payload = "echo testing"; @@ -484,10 +471,8 @@ ZTEST(coap_client, test_echo_option_next_req) LOG_INF("Send next request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - set_socket_events(ZSOCK_POLLIN); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); } @@ -538,10 +523,8 @@ ZTEST(coap_client, test_send_large_data) LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - set_socket_events(ZSOCK_POLLIN); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); } @@ -559,7 +542,7 @@ ZTEST(coap_client, test_no_response) .len = 0 }; struct coap_transmission_parameters params = { - .ack_timeout = 200, + .ack_timeout = LONG_ACK_TIMEOUT_MS, .coap_backoff_percent = 200, .max_retransmission = 0 }; @@ -567,15 +550,17 @@ ZTEST(coap_client, test_no_response) client_request.payload = short_payload; client_request.len = strlen(short_payload); + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_no_reply; + set_socket_events(ZSOCK_POLLOUT); + k_sleep(K_MSEC(1)); LOG_INF("Send request"); - clear_socket_events(); ret = coap_client_req(&client, 0, &address, &client_request, ¶ms); zassert_true(ret >= 0, "Sending request failed, %d", ret); - k_sleep(K_MSEC(300)); + k_sleep(K_MSEC(MORE_THAN_LONG_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, -ETIMEDOUT, "Unexpected response"); } @@ -603,47 +588,57 @@ ZTEST(coap_client, test_separate_response) LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - set_socket_events(ZSOCK_POLLIN); - - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); } ZTEST(coap_client, test_multiple_requests) { int ret = 0; + int retry = MORE_THAN_EXCHANGE_LIFETIME_MS; + struct k_sem sem1, sem2; + struct sockaddr address = {0}; - struct coap_client_request client_request = { + struct coap_client_request req1 = { .method = COAP_METHOD_GET, .confirmable = true, .path = test_path, .fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN, .cb = coap_callback, - .payload = NULL, - .len = 0 + .payload = short_payload, + .len = strlen(short_payload), + .user_data = &sem1 }; + struct coap_client_request req2 = req1; - client_request.payload = short_payload; - client_request.len = strlen(short_payload); + req2.user_data = &sem2; + + k_sem_init(&sem1, 0, 1); + k_sem_init(&sem2, 0, 1); + + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_no_reply; k_sleep(K_MSEC(1)); - set_socket_events(ZSOCK_POLLIN); LOG_INF("Send request"); - ret = coap_client_req(&client, 0, &address, &client_request, NULL); + ret = coap_client_req(&client, 0, &address, &req1, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - ret = coap_client_req(&client, 0, &address, &client_request, NULL); + ret = coap_client_req(&client, 0, &address, &req2, NULL); zassert_true(ret >= 0, "Sending request failed, %d", ret); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + set_socket_events(ZSOCK_POLLIN); + while (last_response_code == 0 && retry > 0) { + retry--; + k_sleep(K_MSEC(1)); + } zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); - k_sleep(K_MSEC(5)); - k_sleep(K_MSEC(100)); + last_response_code = 0; + set_socket_events(ZSOCK_POLLIN); + zassert_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_ok(k_sem_take(&sem2, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); } @@ -661,7 +656,7 @@ ZTEST(coap_client, test_unmatching_tokens) .len = 0 }; struct coap_transmission_parameters params = { - .ack_timeout = 200, + .ack_timeout = LONG_ACK_TIMEOUT_MS, .coap_backoff_percent = 200, .max_retransmission = 0 }; @@ -670,15 +665,128 @@ ZTEST(coap_client, test_unmatching_tokens) client_request.len = strlen(short_payload); z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_unmatching; + set_socket_events(ZSOCK_POLLIN | ZSOCK_POLLOUT); + + k_sleep(K_MSEC(1)); LOG_INF("Send request"); ret = coap_client_req(&client, 0, &address, &client_request, ¶ms); zassert_true(ret >= 0, "Sending request failed, %d", ret); + + k_sleep(K_MSEC(MORE_THAN_LONG_EXCHANGE_LIFETIME_MS)); + zassert_equal(last_response_code, -ETIMEDOUT, "Unexpected response"); +} + +ZTEST(coap_client, test_multiple_clients) +{ + int ret; + int retry = MORE_THAN_EXCHANGE_LIFETIME_MS; + static struct coap_client client2 = { + .fd = 2, + }; + struct k_sem sem1, sem2; + struct sockaddr address = {0}; + struct coap_client_request req1 = { + .method = COAP_METHOD_GET, + .confirmable = true, + .path = test_path, + .fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN, + .cb = coap_callback, + .payload = short_payload, + .len = strlen(short_payload), + .user_data = &sem1 + }; + struct coap_client_request req2 = req1; + + req2.user_data = &sem2; + req2.payload = long_payload; + req2.len = strlen(long_payload); + + zassert_ok(k_sem_init(&sem1, 0, 1)); + zassert_ok(k_sem_init(&sem2, 0, 1)); + + zassert_ok(coap_client_init(&client2, NULL)); + + k_sleep(K_MSEC(1)); + + LOG_INF("Sending requests"); + ret = coap_client_req(&client, 1, &address, &req1, NULL); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + + ret = coap_client_req(&client2, 2, &address, &req2, NULL); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + + while (last_response_code == 0 && retry > 0) { + retry--; + k_sleep(K_MSEC(1)); + } set_socket_events(ZSOCK_POLLIN); + k_sleep(K_SECONDS(1)); + + /* ensure we got both responses */ + zassert_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_ok(k_sem_take(&sem2, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); +} + + +ZTEST(coap_client, test_poll_err) +{ + int ret = 0; + struct sockaddr address = {0}; + struct coap_client_request client_request = { + .method = COAP_METHOD_GET, + .confirmable = true, + .path = test_path, + .fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN, + .cb = coap_callback, + .payload = short_payload, + .len = strlen(short_payload), + }; + + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_no_reply; + set_socket_events(ZSOCK_POLLERR); + k_sleep(K_MSEC(1)); + + LOG_INF("Send request"); + ret = coap_client_req(&client, 0, &address, &client_request, NULL); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + + k_sleep(K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS)); + zassert_equal(last_response_code, -EIO, "Unexpected response"); +} + +ZTEST(coap_client, test_poll_err_after_response) +{ + int ret = 0; + struct k_sem sem1; + struct sockaddr address = {0}; + struct coap_client_request client_request = { + .method = COAP_METHOD_GET, + .confirmable = true, + .path = test_path, + .fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN, + .cb = coap_callback, + .payload = short_payload, + .len = strlen(short_payload), + .user_data = &sem1 + }; + + zassert_ok(k_sem_init(&sem1, 0, 1)); + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake_no_reply; + set_socket_events(ZSOCK_POLLIN); + k_sleep(K_MSEC(1)); - clear_socket_events(); - k_sleep(K_MSEC(500)); - zassert_equal(last_response_code, -ETIMEDOUT, "Unexpected response"); + + LOG_INF("Send request"); + ret = coap_client_req(&client, 0, &address, &client_request, NULL); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + + zassert_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); + + set_socket_events(ZSOCK_POLLERR); + zassert_not_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); } diff --git a/tests/net/lib/coap_client/src/stubs.c b/tests/net/lib/coap_client/src/stubs.c index 13effd8f91b..63307e27fb7 100644 --- a/tests/net/lib/coap_client/src/stubs.c +++ b/tests/net/lib/coap_client/src/stubs.c @@ -13,7 +13,7 @@ DEFINE_FAKE_VALUE_FUNC(uint32_t, z_impl_sys_rand32_get); DEFINE_FAKE_VOID_FUNC(z_impl_sys_rand_get, void *, size_t); DEFINE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_recvfrom, int, void *, size_t, int, struct sockaddr *, socklen_t *); -DEFINE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_sendto, int, void*, size_t, int, +DEFINE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_sendto, int, void *, size_t, int, const struct sockaddr *, socklen_t); struct zsock_pollfd { @@ -29,9 +29,9 @@ void set_socket_events(short events) my_events |= events; } -void clear_socket_events(void) +void clear_socket_events(short events) { - my_events = 0; + my_events &= ~events; } int z_impl_zsock_socket(int family, int type, int proto) @@ -41,12 +41,14 @@ int z_impl_zsock_socket(int family, int type, int proto) int z_impl_zsock_poll(struct zsock_pollfd *fds, int nfds, int poll_timeout) { - LOG_INF("Polling, events %d", my_events); - k_sleep(K_MSEC(10)); - fds->revents = my_events; - if (my_events) { - return 1; - } else { - return 0; + int events = 0; + k_sleep(K_MSEC(1)); + for (int i = 0; i < nfds; i++) { + fds[i].revents = my_events & (fds[i].events | ZSOCK_POLLERR | ZSOCK_POLLHUP); + if (fds[i].revents) { + events++; + } } + + return events; } diff --git a/tests/net/lib/coap_client/src/stubs.h b/tests/net/lib/coap_client/src/stubs.h index eb340d914eb..9a9f929ce76 100644 --- a/tests/net/lib/coap_client/src/stubs.h +++ b/tests/net/lib/coap_client/src/stubs.h @@ -15,11 +15,30 @@ #include -#define ZSOCK_POLLIN 1 + +/* Copy from zephyr/include/zephyr/net/socket.h */ +/** + * @name Options for poll() + * @{ + */ +/* ZSOCK_POLL* values are compatible with Linux */ +/** zsock_poll: Poll for readability */ +#define ZSOCK_POLLIN 1 +/** zsock_poll: Poll for exceptional condition */ +#define ZSOCK_POLLPRI 2 +/** zsock_poll: Poll for writability */ #define ZSOCK_POLLOUT 4 +/** zsock_poll: Poll results in error condition (output value only) */ +#define ZSOCK_POLLERR 8 +/** zsock_poll: Poll detected closed connection (output value only) */ +#define ZSOCK_POLLHUP 0x10 +/** zsock_poll: Invalid socket (output value only) */ +#define ZSOCK_POLLNVAL 0x20 +/** @} */ + void set_socket_events(short events); -void clear_socket_events(void); +void clear_socket_events(short events); DECLARE_FAKE_VALUE_FUNC(uint32_t, z_impl_sys_rand32_get); DECLARE_FAKE_VOID_FUNC(z_impl_sys_rand_get, void *, size_t);