diff --git a/components/tcp_transport/test/test_transport.c b/components/tcp_transport/test/test_transport.c index 8a52577b7c..dc82672302 100644 --- a/components/tcp_transport/test/test_transport.c +++ b/components/tcp_transport/test/test_transport.c @@ -14,6 +14,9 @@ #define TCP_CONNECT_DONE (1) #define TCP_LISTENER_DONE (2) +#define TCP_ACCEPTOR_DONE (4) +#define TCP_LISTENER_ACCEPTED (8) +#define TCP_LISTENER_READY (16) struct tcp_connect_task_params { int timeout_ms; @@ -21,8 +24,12 @@ struct tcp_connect_task_params { EventGroupHandle_t tcp_connect_done; int ret; int listen_sock; + int accepted_sock; int last_connect_sock; bool tcp_listener_failed; + esp_transport_handle_t transport_under_test; + bool accept_connection; + bool consume_sock_backlog; }; /** @@ -51,8 +58,12 @@ static void connect_once(struct tcp_connect_task_params *params) } /** - * @brief creates a listener (without and acceptor) and connect to as many times as possible - * to prepare an endpoint which would make the client block but not complete TCP handshake + * @brief creates a listener (and an acceptor if configured) + * + * if consume_sock_backlog set: connect as many times as possible to prepare an endpoint which + * would make the client block but not complete TCP handshake + * + * if accept_connection set: waiting normally for connection creating an acceptor to mimic tcp-transport endpoint */ static void localhost_listener(void *pvParameters) { @@ -63,6 +74,9 @@ static void localhost_listener(void *pvParameters) .sin_port = htons(params->port) }; // Create listener socket and bind it to ANY address params->listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + int opt = 1; + setsockopt(params->listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + if (params->listen_sock < 0) { ESP_LOGE(TAG, "Unable to create socket"); params->tcp_listener_failed = true; @@ -83,11 +97,27 @@ static void localhost_listener(void *pvParameters) goto failed; } - // Ideally we would set backlog to 0, but since this is an implementation specific recommendation parameter, - // we recursively create sockets and try to connect to this listener in order to consume the backlog. After - // the backlog is consumed, the last connection blocks (waiting for accept), but at that point we are sure - // that any other connection would also block - connect_once(params); + // Listener is ready at this point + xEventGroupSetBits(params->tcp_connect_done, TCP_LISTENER_READY); + + if (params->consume_sock_backlog) { + // Ideally we would set backlog to 0, but since this is an implementation specific recommendation parameter, + // we recursively create sockets and try to connect to this listener in order to consume the backlog. After + // the backlog is consumed, the last connection blocks (waiting for accept), but at that point we are sure + // that any other connection would also block + connect_once(params); + } else if (params->accept_connection) { + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + params->accepted_sock = accept(params->listen_sock, (struct sockaddr *)&source_addr, &addr_len); + if (params->accepted_sock < 0) { + ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); + goto failed; + } + xEventGroupSetBits(params->tcp_connect_done, TCP_LISTENER_ACCEPTED); // Mark the socket as accepted + // ...and wait for the "acceptor" tests to finish + xEventGroupWaitBits(params->tcp_connect_done, TCP_ACCEPTOR_DONE, true, true, params->timeout_ms * 10); + } failed: xEventGroupSetBits(params->tcp_connect_done, TCP_LISTENER_DONE); @@ -98,14 +128,12 @@ static void tcp_connect_task(void *pvParameters) { struct tcp_connect_task_params *params = pvParameters; - esp_transport_list_handle_t transport_list = esp_transport_list_init(); - esp_transport_handle_t tcp = esp_transport_tcp_init(); - esp_transport_list_add(transport_list, tcp, "tcp"); - - params->ret = esp_transport_connect(tcp, "localhost", params->port, params->timeout_ms); + params->ret = esp_transport_connect(params->transport_under_test, "localhost", params->port, params->timeout_ms); + if (params->accept_connection) { + // If we test the accepted connection, need to wait until the test completes + xEventGroupWaitBits(params->tcp_connect_done, TCP_ACCEPTOR_DONE, true, true, params->timeout_ms * 10); + } xEventGroupSetBits(params->tcp_connect_done, TCP_CONNECT_DONE); - esp_transport_close(tcp); - esp_transport_list_destroy(transport_list); vTaskSuspend(NULL); } @@ -132,7 +160,7 @@ TEST_CASE("tcp_transport: using ws transport separately", "[tcp_transport][leaks TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(tcp)); } -TEST_CASE("tcp_transport: connect timeout", "[tcp_transport]") +static void transport_connection_timeout_test(esp_transport_handle_t transport_under_test) { // This case simulates connection timeout running tcp connect asynchronously with other socket connection // consuming entire socket listener backlog. @@ -142,7 +170,9 @@ TEST_CASE("tcp_transport: connect timeout", "[tcp_transport]") struct tcp_connect_task_params params = { .tcp_connect_done = xEventGroupCreate(), .timeout_ms = 200, - .port = 80 }; + .port = 80, + .consume_sock_backlog = true, + .transport_under_test = transport_under_test }; TickType_t max_wait = pdMS_TO_TICKS(params.timeout_ms * 10); TaskHandle_t localhost_listener_task_handle = NULL; TaskHandle_t tcp_connect_task_handle = NULL; @@ -160,7 +190,7 @@ TEST_CASE("tcp_transport: connect timeout", "[tcp_transport]") EventBits_t bits = xEventGroupWaitBits(params.tcp_connect_done, TCP_CONNECT_DONE, true, true, max_wait); TickType_t end = xTaskGetTickCount(); - TEST_ASSERT_EQUAL(TCP_CONNECT_DONE, bits); // Connection has finished + TEST_ASSERT_EQUAL(TCP_CONNECT_DONE, TCP_CONNECT_DONE&bits); // Connection has finished TEST_ASSERT_EQUAL(-1, params.ret); // Connection failed with -1 // Test connection attempt took expected timeout value @@ -177,3 +207,184 @@ TEST_CASE("tcp_transport: connect timeout", "[tcp_transport]") test_utils_task_delete(localhost_listener_task_handle); test_utils_task_delete(tcp_connect_task_handle); } + +TEST_CASE("tcp_transport: connect timeout", "[tcp_transport]") +{ + // Init the transport under test + esp_transport_list_handle_t transport_list = esp_transport_list_init(); + esp_transport_handle_t tcp = esp_transport_tcp_init(); + esp_transport_list_add(transport_list, tcp, "tcp"); + + transport_connection_timeout_test(tcp); + esp_transport_close(tcp); + esp_transport_list_destroy(transport_list); +} + +TEST_CASE("ssl_transport: connect timeout", "[tcp_transport]") +{ + // Init the transport under test + esp_transport_list_handle_t transport_list = esp_transport_list_init(); + esp_transport_handle_t tcp = esp_transport_tcp_init(); + esp_transport_list_add(transport_list, tcp, "tcp"); + esp_transport_handle_t ssl = esp_transport_ssl_init(); + esp_transport_list_add(transport_list, ssl, "ssl"); + + transport_connection_timeout_test(ssl); + esp_transport_close(tcp); + esp_transport_close(ssl); + esp_transport_list_destroy(transport_list); +} + +TEST_CASE("transport: init and deinit multiple transport items", "[tcp_transport][leaks=0]") +{ + esp_transport_list_handle_t transport_list = esp_transport_list_init(); + esp_transport_handle_t tcp = esp_transport_tcp_init(); + esp_transport_list_add(transport_list, tcp, "tcp"); + esp_transport_handle_t ssl = esp_transport_ssl_init(); + esp_transport_list_add(transport_list, ssl, "ssl"); + esp_transport_handle_t ws = esp_transport_ws_init(tcp); + esp_transport_list_add(transport_list, ws, "ws"); + esp_transport_handle_t wss = esp_transport_ws_init(ssl); + esp_transport_list_add(transport_list, wss, "wss"); + TEST_ASSERT_EQUAL(ESP_OK, esp_transport_list_destroy(transport_list)); +} + +// This is a private API of the tcp transport, but needed for socket operation tests +int esp_transport_get_socket(esp_transport_handle_t t); + +// Structures and types for passing socket options +enum expected_sock_option_types { + SOCK_OPT_TYPE_BOOL, + SOCK_OPT_TYPE_INT, +}; + +struct expected_sock_option { + int level; + int optname; + int optval; + enum expected_sock_option_types opttype; +}; + +static void socket_operation_test(esp_transport_handle_t transport_under_test, + const struct expected_sock_option expected_opts[], size_t sock_options_len) +{ + struct tcp_connect_task_params params = { .tcp_connect_done = xEventGroupCreate(), + .timeout_ms = 200, + .port = 80, + .accept_connection = true, + .transport_under_test = transport_under_test }; + TickType_t max_wait = pdMS_TO_TICKS(params.timeout_ms * 10); + TaskHandle_t localhost_listener_task_handle = NULL; + TaskHandle_t tcp_connect_task_handle = NULL; + + test_case_uses_tcpip(); + + // Create a listener and wait for it to be ready + xTaskCreatePinnedToCore(localhost_listener, "localhost_listener", 4096, (void*)¶ms, 5, &localhost_listener_task_handle, 0); + xEventGroupWaitBits(params.tcp_connect_done, TCP_LISTENER_READY, true, true, max_wait); + // Perform tcp-connect in a separate task + xTaskCreatePinnedToCore(tcp_connect_task, "tcp_connect_task", 4096, (void*)¶ms, 6, &tcp_connect_task_handle, 0); + + // Wait till the connection gets accepted to get the client's socket + xEventGroupWaitBits(params.tcp_connect_done, TCP_LISTENER_ACCEPTED, true, true, max_wait); + int sock = esp_transport_get_socket(params.transport_under_test); + for (int i=0; i