diff --git a/include/zephyr/net/coap.h b/include/zephyr/net/coap.h index 77a6966794f..325ced2ebbc 100644 --- a/include/zephyr/net/coap.h +++ b/include/zephyr/net/coap.h @@ -61,6 +61,7 @@ enum coap_option_num { COAP_OPTION_PROXY_URI = 35, /**< Proxy-Uri */ COAP_OPTION_PROXY_SCHEME = 39, /**< Proxy-Scheme */ COAP_OPTION_SIZE1 = 60, /**< Size1 */ + COAP_OPTION_ECHO = 252, /**< Echo (RFC 9175) */ COAP_OPTION_REQUEST_TAG = 292 /**< Request-Tag (RFC 9175) */ }; diff --git a/include/zephyr/net/coap_client.h b/include/zephyr/net/coap_client.h index f836f339063..c3de1abee27 100644 --- a/include/zephyr/net/coap_client.h +++ b/include/zephyr/net/coap_client.h @@ -20,6 +20,7 @@ */ #include +#include #define MAX_COAP_MSG_LEN (CONFIG_COAP_CLIENT_MESSAGE_HEADER_SIZE + \ @@ -101,6 +102,8 @@ struct coap_client { uint8_t send_buf[MAX_COAP_MSG_LEN]; uint8_t recv_buf[MAX_COAP_MSG_LEN]; struct coap_client_internal_request requests[CONFIG_COAP_CLIENT_MAX_REQUESTS]; + struct coap_option echo_option; + bool send_echo; }; /** @endcond */ diff --git a/subsys/net/lib/coap/coap_client.c b/subsys/net/lib/coap/coap_client.c index 95a0f0d0ac4..a947b15ae8e 100644 --- a/subsys/net/lib/coap/coap_client.c +++ b/subsys/net/lib/coap/coap_client.c @@ -332,6 +332,17 @@ int coap_client_req(struct coap_client *client, int sock, const struct sockaddr goto out; } + if (client->send_echo) { + ret = coap_packet_append_option(&internal_req->request, COAP_OPTION_ECHO, + client->echo_option.value, client->echo_option.len); + if (ret < 0) { + LOG_ERR("Failed to append echo option"); + k_mutex_unlock(&client->send_mutex); + goto out; + } + client->send_echo = false; + } + ret = coap_client_schedule_poll(client, sock, req, internal_req); if (ret < 0) { LOG_ERR("Failed to schedule polling"); @@ -604,6 +615,11 @@ struct coap_client_internal_request *get_request_with_token(struct coap_client * 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); +} + static int handle_response(struct coap_client *client, const struct coap_packet *response) { int ret = 0; @@ -651,6 +667,60 @@ static int handle_response(struct coap_client *client, const struct coap_packet return 1; } + /* Received echo option */ + if (find_echo_option(response, &client->echo_option)) { + /* Resend request with echo option */ + if (response_code == COAP_RESPONSE_CODE_UNAUTHORIZED) { + k_mutex_lock(&client->send_mutex, K_FOREVER); + + ret = coap_client_init_request(client, &internal_req->coap_request, + internal_req, false); + + if (ret < 0) { + LOG_ERR("Error creating a CoAP request"); + k_mutex_unlock(&client->send_mutex); + goto fail; + } + + ret = coap_packet_append_option(&internal_req->request, COAP_OPTION_ECHO, + client->echo_option.value, + client->echo_option.len); + if (ret < 0) { + LOG_ERR("Failed to append echo option"); + k_mutex_unlock(&client->send_mutex); + goto fail; + } + + if (coap_header_get_type(&internal_req->request) == COAP_TYPE_CON) { + ret = coap_pending_init(&internal_req->pending, + &internal_req->request, &client->address, + internal_req->retry_count); + if (ret < 0) { + LOG_ERR("Error creating pending"); + k_mutex_unlock(&client->send_mutex); + goto fail; + } + + coap_pending_cycle(&internal_req->pending); + } + + ret = send_request(client->fd, internal_req->request.data, + internal_req->request.offset, 0, &client->address, + client->socklen); + k_mutex_unlock(&client->send_mutex); + + if (ret < 0) { + LOG_ERR("Error sending a CoAP request"); + goto fail; + } else { + return 1; + } + } else { + /* Send echo in next request */ + client->send_echo = true; + } + } + /* Send ack for CON */ if (response_type == COAP_TYPE_CON) { /* CON response is always a separate response, respond with empty ACK. */ diff --git a/tests/net/lib/coap_client/src/main.c b/tests/net/lib/coap_client/src/main.c index bb9d9fc38c5..1a6fd2d88bc 100644 --- a/tests/net/lib/coap_client/src/main.c +++ b/tests/net/lib/coap_client/src/main.c @@ -75,6 +75,89 @@ static ssize_t z_impl_zsock_sendto_custom_fake(int sock, void *buf, size_t len, last_response_code = ((uint8_t *) buf)[1]; 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) +{ + 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]; + + 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"); + } + + ret = coap_find_options(&response, COAP_OPTION_ECHO, &option, 1); + + zassert_equal(ret, 1, "Coap echo option not found, %d", ret); + zassert_mem_equal(option.value, "echo_value", option.len, "Incorrect echo data"); + + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake; + + 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) +{ + 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]; + + 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"); + } + + ret = coap_header_get_code(&response); + zassert_equal(ret, COAP_METHOD_POST, "Incorrect method, %d", ret); + + uint16_t payload_len; + + const uint8_t *payload = coap_packet_get_payload(&response, &payload_len); + + zassert_mem_equal(payload, "echo testing", payload_len, "Incorrect payload"); + + ret = coap_find_options(&response, COAP_OPTION_ECHO, &option, 1); + zassert_equal(ret, 1, "Coap echo option not found, %d", ret); + zassert_mem_equal(option.value, "echo_value", option.len, "Incorrect echo data"); + + z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake; + return 1; } @@ -158,6 +241,66 @@ static ssize_t z_impl_zsock_recvfrom_custom_fake_unmatching(int sock, void *buf, return sizeof(ack_data); } +static ssize_t z_impl_zsock_recvfrom_custom_fake_echo(int sock, void *buf, size_t max_len, + int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + uint16_t last_message_id = 0; + + LOG_INF("Recvfrom"); + uint8_t ack_data[] = {0x68, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 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; + } + + 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; + + return sizeof(ack_data); +} + +static ssize_t z_impl_zsock_recvfrom_custom_fake_echo_next_req(int sock, void *buf, size_t max_len, + int flags, 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, 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; + } + + 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; + + return sizeof(ack_data); +} + static void *suite_setup(void) { coap_client_init(&client, NULL); @@ -214,6 +357,83 @@ ZTEST(coap_client, test_get_request) zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); } +ZTEST(coap_client, test_echo_option) +{ + 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 = NULL, + .len = 0 + }; + + client_request.payload = short_payload; + client_request.len = strlen(short_payload); + + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_echo; + + k_sleep(K_MSEC(1)); + + LOG_INF("Send request"); + ret = coap_client_req(&client, 0, &address, &client_request, -1); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + set_socket_events(ZSOCK_POLLIN); + + k_sleep(K_MSEC(5)); + k_sleep(K_MSEC(1000)); + zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); +} + +ZTEST(coap_client, test_echo_option_next_req) +{ + 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 = NULL, + .len = 0 + }; + + client_request.payload = short_payload; + client_request.len = strlen(short_payload); + + z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_echo_next_req; + + k_sleep(K_MSEC(1)); + + LOG_INF("Send request"); + ret = coap_client_req(&client, 0, &address, &client_request, -1); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + set_socket_events(ZSOCK_POLLIN); + + k_sleep(K_MSEC(5)); + k_sleep(K_MSEC(1000)); + zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); + + char *payload = "echo testing"; + + client_request.method = COAP_METHOD_POST; + client_request.payload = payload; + client_request.len = strlen(payload); + + LOG_INF("Send next request"); + ret = coap_client_req(&client, 0, &address, &client_request, -1); + zassert_true(ret >= 0, "Sending request failed, %d", ret); + set_socket_events(ZSOCK_POLLIN); + + k_sleep(K_MSEC(5)); + k_sleep(K_MSEC(1000)); + zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response"); +} + ZTEST(coap_client, test_get_no_path) { int ret = 0;