From b3f3bce23eaa854f0eb03e2c92e1c342b9a4ef4a Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Wed, 13 Nov 2024 13:24:28 +0200 Subject: [PATCH] net: lib: coap_client: Add API to cancel specific request Add a new API to cancel just one, or mathing requests, instead of cancelling all ongoing requests. Signed-off-by: Seppo Takalo --- include/zephyr/net/coap_client.h | 15 ++++ subsys/net/lib/coap/coap_client.c | 35 +++++++++ tests/net/lib/coap_client/src/main.c | 108 +++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) diff --git a/include/zephyr/net/coap_client.h b/include/zephyr/net/coap_client.h index 1d68661da3b..bbeebb2d26c 100644 --- a/include/zephyr/net/coap_client.h +++ b/include/zephyr/net/coap_client.h @@ -158,6 +158,21 @@ int coap_client_req(struct coap_client *client, int sock, const struct sockaddr */ void coap_client_cancel_requests(struct coap_client *client); +/** + * @brief Cancel matching requests. + * + * This function cancels all CoAP client request that matches the given request. + * The request is matched based on the method, path, callback and user_data, if provided. + * Any field set to NULL is considered a wildcard. + * + * (struct coap_client_request){0} cancels all requests. + * (struct coap_client_request){.method = COAP_METHOD_GET} cancels all GET requests. + * + * @param client Pointer to the CoAP client instance. + * @param req Pointer to the CoAP client request to be canceled. + */ +void coap_client_cancel_request(struct coap_client *client, struct coap_client_request *req); + /** * @brief Initialise a Block2 option to be added to a request * diff --git a/subsys/net/lib/coap/coap_client.c b/subsys/net/lib/coap/coap_client.c index 4c4e13d8f0c..bda13112175 100644 --- a/subsys/net/lib/coap/coap_client.c +++ b/subsys/net/lib/coap/coap_client.c @@ -1016,6 +1016,41 @@ void coap_client_cancel_requests(struct coap_client *client) k_sleep(K_MSEC(COAP_PERIODIC_TIMEOUT)); } +static bool requests_match(struct coap_client_request *a, struct coap_client_request *b) +{ + /* enum coap_method does not have value for zero, so differentiate valid values */ + if (a->method && b->method && a->method != b->method) { + return false; + } + if (a->path && b->path && strcmp(a->path, b->path) != 0) { + return false; + } + if (a->cb && b->cb && a->cb != b->cb) { + return false; + } + if (a->user_data && b->user_data && a->user_data != b->user_data) { + return false; + } + /* It is intentional that (struct coap_client_request){0} matches all */ + return true; +} + +void coap_client_cancel_request(struct coap_client *client, struct coap_client_request *req) +{ + k_mutex_lock(&client->lock, K_FOREVER); + + for (int i = 0; i < CONFIG_COAP_CLIENT_MAX_REQUESTS; i++) { + if (client->requests[i].request_ongoing && + requests_match(&client->requests[i].coap_request, req)) { + LOG_DBG("Cancelling request %d", i); + report_callback_error(&client->requests[i], -ECANCELED); + release_internal_request(&client->requests[i]); + } + } + + k_mutex_unlock(&client->lock); +} + void coap_client_recv(void *coap_cl, void *a, void *b) { int ret; diff --git a/tests/net/lib/coap_client/src/main.c b/tests/net/lib/coap_client/src/main.c index c44fd30fbdc..2f3fa28ebff 100644 --- a/tests/net/lib/coap_client/src/main.c +++ b/tests/net/lib/coap_client/src/main.c @@ -1142,3 +1142,111 @@ ZTEST(coap_client, test_request_rst) zassert_ok(k_sem_take(&sem, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); zassert_equal(last_response_code, -ECONNRESET, ""); } + +ZTEST(coap_client, test_cancel) +{ + 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; + + 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)); + + zassert_ok(coap_client_req(&client, 0, &address, &req1, NULL)); + zassert_ok(coap_client_req(&client, 0, &address, &req2, NULL)); + + k_sleep(K_SECONDS(1)); + + coap_client_cancel_request(&client, &req1); + zassert_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_not_ok(k_sem_take(&sem2, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_equal(last_response_code, -ECANCELED, ""); + + set_socket_events(client.fd, ZSOCK_POLLIN); /* First response is the cancelled one */ + zassert_not_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + set_socket_events(client.fd, ZSOCK_POLLIN); + zassert_ok(k_sem_take(&sem2, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, ""); +} + +ZTEST(coap_client, test_cancel_match) +{ + 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.path = "another"; + + 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)); + + zassert_ok(coap_client_req(&client, 0, &address, &req1, NULL)); + zassert_ok(coap_client_req(&client, 0, &address, &req2, NULL)); + + k_sleep(K_SECONDS(1)); + + /* match only one */ + coap_client_cancel_request(&client, &(struct coap_client_request) { + .path = test_path + }); + zassert_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_not_ok(k_sem_take(&sem2, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_equal(last_response_code, -ECANCELED, ""); + + zassert_ok(coap_client_req(&client, 0, &address, &req1, NULL)); + + /* should not match */ + coap_client_cancel_request(&client, &(struct coap_client_request) { + .path = test_path, + .user_data = &sem2, + }); + zassert_not_ok(k_sem_take(&sem1, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + zassert_not_ok(k_sem_take(&sem2, K_MSEC(MORE_THAN_EXCHANGE_LIFETIME_MS))); + + /* match both (all GET queries) */ + coap_client_cancel_request(&client, &(struct coap_client_request) { + .method = COAP_METHOD_GET, + }); + 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_ok(coap_client_req(&client, 0, &address, &req1, NULL)); + zassert_ok(coap_client_req(&client, 0, &address, &req2, NULL)); + + /* match both (wildcard)*/ + coap_client_cancel_request(&client, &(struct coap_client_request) {0}); + 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))); + +}