forked from espressif/esp-idf
Merge branch 'bugfix/tls_limit_esp_tls_structure_usage' into 'master'
esp_tls/tcp_transport: Transport to use esp_tcp_connect directly for plain TCP Closes IDF-3005 and IDFGH-5162 See merge request espressif/esp-idf!13418
This commit is contained in:
@@ -267,13 +267,13 @@ static esp_err_t esp_tls_set_socket_non_blocking(int fd, bool non_blocking)
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, const esp_tls_t *tls, const esp_tls_cfg_t *cfg)
|
static inline esp_err_t tcp_connect(const char *host, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_error_handle_t error_handle, int *sockfd)
|
||||||
{
|
{
|
||||||
struct sockaddr_storage address;
|
struct sockaddr_storage address;
|
||||||
int fd;
|
int fd;
|
||||||
esp_err_t ret = esp_tls_hostname_to_fd(host, hostlen, port, &address, &fd);
|
esp_err_t ret = esp_tls_hostname_to_fd(host, hostlen, port, &address, &fd);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno);
|
ESP_INT_EVENT_TRACKER_CAPTURE(error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *s
|
|||||||
int res = select(fd+1, NULL, &fdset, NULL, &tv);
|
int res = select(fd+1, NULL, &fdset, NULL, &tv);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
ESP_LOGE(TAG, "[sock=%d] select() error: %s", fd, strerror(errno));
|
ESP_LOGE(TAG, "[sock=%d] select() error: %s", fd, strerror(errno));
|
||||||
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno);
|
ESP_INT_EVENT_TRACKER_CAPTURE(error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
else if (res == 0) {
|
else if (res == 0) {
|
||||||
@@ -328,7 +328,7 @@ static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *s
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
else if (sockerr) {
|
else if (sockerr) {
|
||||||
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, sockerr);
|
ESP_INT_EVENT_TRACKER_CAPTURE(error_handle, ESP_TLS_ERR_TYPE_SYSTEM, sockerr);
|
||||||
ESP_LOGE(TAG, "[sock=%d] delayed connect error: %s", fd, strerror(sockerr));
|
ESP_LOGE(TAG, "[sock=%d] delayed connect error: %s", fd, strerror(sockerr));
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
@@ -371,7 +371,7 @@ static int esp_tls_low_level_conn(const char *hostname, int hostlen, int port, c
|
|||||||
_esp_tls_net_init(tls);
|
_esp_tls_net_init(tls);
|
||||||
tls->is_tls = true;
|
tls->is_tls = true;
|
||||||
}
|
}
|
||||||
if ((esp_ret = esp_tcp_connect(hostname, hostlen, port, &tls->sockfd, tls, cfg)) != ESP_OK) {
|
if ((esp_ret = tcp_connect(hostname, hostlen, port, cfg, tls->error_handle, &tls->sockfd)) != ESP_OK) {
|
||||||
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_ESP, esp_ret);
|
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_ESP, esp_ret);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -440,6 +440,17 @@ static int esp_tls_low_level_conn(const char *hostname, int hostlen, int port, c
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new plain TCP connection
|
||||||
|
*/
|
||||||
|
esp_err_t esp_tls_plain_tcp_connect(const char *host, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_error_handle_t error_handle, int *sockfd)
|
||||||
|
{
|
||||||
|
if (sockfd == NULL || error_handle == NULL) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return tcp_connect(host, hostlen, port, cfg, error_handle, sockfd);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a new TLS/SSL connection
|
* @brief Create a new TLS/SSL connection
|
||||||
*/
|
*/
|
||||||
|
@@ -171,7 +171,10 @@ typedef struct esp_tls_cfg {
|
|||||||
|
|
||||||
void *ds_data; /*!< Pointer for digital signature peripheral context */
|
void *ds_data; /*!< Pointer for digital signature peripheral context */
|
||||||
bool is_plain_tcp; /*!< Use non-TLS connection: When set to true, the esp-tls uses
|
bool is_plain_tcp; /*!< Use non-TLS connection: When set to true, the esp-tls uses
|
||||||
plain TCP transport rather then TLS/SSL connection */
|
plain TCP transport rather then TLS/SSL connection.
|
||||||
|
Note, that it is possible to connect using a plain tcp transport
|
||||||
|
directly with esp_tls_plain_tcp_connect() API */
|
||||||
|
|
||||||
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
|
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
|
||||||
} esp_tls_cfg_t;
|
} esp_tls_cfg_t;
|
||||||
|
|
||||||
@@ -599,6 +602,21 @@ int esp_tls_server_session_create(esp_tls_cfg_server_t *cfg, int sockfd, esp_tls
|
|||||||
void esp_tls_server_session_delete(esp_tls_t *tls);
|
void esp_tls_server_session_delete(esp_tls_t *tls);
|
||||||
#endif /* ! CONFIG_ESP_TLS_SERVER */
|
#endif /* ! CONFIG_ESP_TLS_SERVER */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a plain TCP connection, returning a valid socket fd on success or an error handle
|
||||||
|
*
|
||||||
|
* @param[in] host Hostname of the host.
|
||||||
|
* @param[in] hostlen Length of hostname.
|
||||||
|
* @param[in] port Port number of the host.
|
||||||
|
* @param[in] cfg ESP-TLS configuration as esp_tls_cfg_t.
|
||||||
|
* @param[out] error_handle ESP-TLS error handle holding potential errors occurred during connection
|
||||||
|
* @param[out] sockfd Socket descriptor if successfully connected on TCP layer
|
||||||
|
* @return ESP_OK on success
|
||||||
|
* ESP_ERR_INVALID_ARG if invalid output parameters
|
||||||
|
* ESP-TLS based error codes on failure
|
||||||
|
*/
|
||||||
|
esp_err_t esp_tls_plain_tcp_connect(const char *host, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_error_handle_t error_handle, int *sockfd);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
41
components/tcp_transport/test/tcp_transport_fixtures.h
Normal file
41
components/tcp_transport/test/tcp_transport_fixtures.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef _TCP_TRANSPORT_FIXTURES_H_
|
||||||
|
#define _TCP_TRANSPORT_FIXTURES_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper test functions for timeout connection tests
|
||||||
|
*
|
||||||
|
* This case simulates connection timeout running tcp connect asynchronously with other socket connection
|
||||||
|
* consuming entire socket listener backlog.
|
||||||
|
* Important: Both tasks must run on the same core, with listener's prio higher to make sure that
|
||||||
|
* 1) first the localhost_listener() creates and connects all sockets until the last one blocks
|
||||||
|
* 2) before the tcp_connect_task() attempts to connect and thus fails with connection timeout
|
||||||
|
*/
|
||||||
|
void tcp_transport_test_connection_timeout(esp_transport_handle_t transport_under_test);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper test function to check socket options configured separately by transports
|
||||||
|
*
|
||||||
|
* This sets up the connection test to start two tasks, but unlike tcp_transport_test_connection_timeout,
|
||||||
|
* here we just let the connection to happen or at least open on TCP layer so we get the internal socket
|
||||||
|
* descriptor. While the connection is in progress or connected, we can check the socket options configured
|
||||||
|
* by the tcp_transport API.
|
||||||
|
*/
|
||||||
|
void tcp_transport_test_socket_options(esp_transport_handle_t transport_under_test, bool async,
|
||||||
|
const struct expected_sock_option *expected_opts, size_t sock_options_len);
|
||||||
|
|
||||||
|
#endif //_TCP_TRANSPORT_FIXTURES_H_
|
@@ -1,454 +0,0 @@
|
|||||||
#include "unity.h"
|
|
||||||
|
|
||||||
#include "esp_transport.h"
|
|
||||||
#include "esp_transport_tcp.h"
|
|
||||||
#include "esp_transport_ssl.h"
|
|
||||||
#include "esp_transport_ws.h"
|
|
||||||
#include "test_utils.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "lwip/err.h"
|
|
||||||
#include "lwip/sockets.h"
|
|
||||||
#include "lwip/sys.h"
|
|
||||||
#include <lwip/netdb.h>
|
|
||||||
#include "freertos/event_groups.h"
|
|
||||||
|
|
||||||
#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;
|
|
||||||
int port;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define TEST_TRANSPORT_BIND_IFNAME() \
|
|
||||||
struct ifreq ifr; \
|
|
||||||
ifr.ifr_name[0] = 'l'; \
|
|
||||||
ifr.ifr_name[1] = 'o'; \
|
|
||||||
ifr.ifr_name[2] = '\0';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Recursively connects with a new socket to loopback interface until the last one blocks.
|
|
||||||
* The last socket is closed upon test teardown, that initiates recursive cleanup (close) for all
|
|
||||||
* active/connected sockets.
|
|
||||||
*/
|
|
||||||
static void connect_once(struct tcp_connect_task_params *params)
|
|
||||||
{
|
|
||||||
struct sockaddr_in dest_addr_ip4 = { .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
|
||||||
.sin_family = AF_INET,
|
|
||||||
.sin_port = htons(params->port) };
|
|
||||||
int connect_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
||||||
if (connect_sock < 0) {
|
|
||||||
params->tcp_listener_failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
params->last_connect_sock = connect_sock;
|
|
||||||
int err = connect(connect_sock, (struct sockaddr *)&dest_addr_ip4, sizeof(dest_addr_ip4));
|
|
||||||
if (err != 0) {
|
|
||||||
// The last connection is expected to fail here, since the both sockets get closed on test cleanup
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
connect_once(params);
|
|
||||||
close(connect_sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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)
|
|
||||||
{
|
|
||||||
const char* TAG = "tcp_transport_test";
|
|
||||||
struct tcp_connect_task_params *params = pvParameters;
|
|
||||||
struct sockaddr_in dest_addr_ip4 = { .sin_addr.s_addr = htonl(INADDR_ANY),
|
|
||||||
.sin_family = AF_INET,
|
|
||||||
.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;
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
int err = bind(params->listen_sock, (struct sockaddr *)&dest_addr_ip4, sizeof(dest_addr_ip4));
|
|
||||||
if (err != 0) {
|
|
||||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
|
||||||
params->tcp_listener_failed = true;
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen with backlog set to a low number
|
|
||||||
err = listen(params->listen_sock, 4);
|
|
||||||
if (err != 0) {
|
|
||||||
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
|
||||||
params->tcp_listener_failed = true;
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
vTaskSuspend(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tcp_connect_task(void *pvParameters)
|
|
||||||
{
|
|
||||||
struct tcp_connect_task_params *params = pvParameters;
|
|
||||||
|
|
||||||
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);
|
|
||||||
vTaskSuspend(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("tcp_transport: init and deinit transport list", "[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");
|
|
||||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_list_destroy(transport_list));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("tcp_transport: using ssl transport separately", "[tcp_transport][leaks=0]")
|
|
||||||
{
|
|
||||||
esp_transport_handle_t h = esp_transport_ssl_init();
|
|
||||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(h));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("tcp_transport: using ws transport separately", "[tcp_transport][leaks=0]")
|
|
||||||
{
|
|
||||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
|
||||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
|
||||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(ws));
|
|
||||||
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(tcp));
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
// Important: Both tasks must run on the same core, with listener's prio higher to make sure that
|
|
||||||
// 1) first the localhost_listener() creates and connects all sockets until the last one blocks
|
|
||||||
// 2) before the tcp_connect_task() attempts to connect and thus fails with connection timeout
|
|
||||||
|
|
||||||
struct tcp_connect_task_params params = { .tcp_connect_done = xEventGroupCreate(),
|
|
||||||
.timeout_ms = 200,
|
|
||||||
.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;
|
|
||||||
|
|
||||||
test_case_uses_tcpip();
|
|
||||||
|
|
||||||
// Create listener and connect it with as many sockets until the last one blocks
|
|
||||||
xTaskCreatePinnedToCore(localhost_listener, "localhost_listener", 4096, (void*)¶ms, 5, &localhost_listener_task_handle, 0);
|
|
||||||
|
|
||||||
// Perform tcp-connect in a separate task to check asynchronously for the timeout
|
|
||||||
xTaskCreatePinnedToCore(tcp_connect_task, "tcp_connect_task", 4096, (void*)¶ms, 4, &tcp_connect_task_handle, 0);
|
|
||||||
|
|
||||||
// Roughly measure tick-time spent while trying to connect
|
|
||||||
TickType_t start = xTaskGetTickCount();
|
|
||||||
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, TCP_CONNECT_DONE & bits); // Connection has finished
|
|
||||||
TEST_ASSERT_EQUAL(-1, params.ret); // Connection failed with -1
|
|
||||||
|
|
||||||
// Test connection attempt took expected timeout value
|
|
||||||
TEST_ASSERT_INT_WITHIN(pdMS_TO_TICKS(params.timeout_ms/5), pdMS_TO_TICKS(params.timeout_ms), end-start);
|
|
||||||
|
|
||||||
// Closing both parties of the last "blocking" connection to unwind localhost_listener() and let other connected sockets closed
|
|
||||||
close(params.listen_sock);
|
|
||||||
close(params.last_connect_sock);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
xEventGroupWaitBits(params.tcp_connect_done, TCP_LISTENER_DONE, true, true, max_wait);
|
|
||||||
TEST_ASSERT_EQUAL(false, params.tcp_listener_failed);
|
|
||||||
vEventGroupDelete(params.tcp_connect_done);
|
|
||||||
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<sock_options_len; ++i) {
|
|
||||||
int value = -1;
|
|
||||||
socklen_t optlen = (socklen_t)sizeof(value);
|
|
||||||
TEST_ASSERT_EQUAL(getsockopt(sock, expected_opts[i].level, expected_opts[i].optname,
|
|
||||||
(void*)&value, &optlen), 0);
|
|
||||||
if (expected_opts[i].opttype == SOCK_OPT_TYPE_BOOL) {
|
|
||||||
TEST_ASSERT_EQUAL((bool)value, (bool) expected_opts[i].optval);
|
|
||||||
} else if (expected_opts[i].opttype == SOCK_OPT_TYPE_INT) {
|
|
||||||
TEST_ASSERT_EQUAL(value, expected_opts[i].optval);
|
|
||||||
} else {
|
|
||||||
TEST_FAIL_MESSAGE("Unsupported socket option type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(sock); // close the tcp_transport's socket so we don't have to wait for connection timeout
|
|
||||||
xEventGroupSetBits(params.tcp_connect_done, TCP_ACCEPTOR_DONE);
|
|
||||||
xEventGroupWaitBits(params.tcp_connect_done, TCP_CONNECT_DONE, true, true, max_wait);
|
|
||||||
// Closing the listener and acceptor sockets
|
|
||||||
close(params.listen_sock);
|
|
||||||
close(params.accepted_sock);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
TEST_ASSERT_EQUAL(false, params.tcp_listener_failed);
|
|
||||||
vEventGroupDelete(params.tcp_connect_done);
|
|
||||||
test_utils_task_delete(localhost_listener_task_handle);
|
|
||||||
test_utils_task_delete(tcp_connect_task_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tcp_transport_keepalive_test(esp_transport_handle_t transport_under_test, esp_transport_keep_alive_t *config)
|
|
||||||
{
|
|
||||||
static struct expected_sock_option expected_opts[4] = {
|
|
||||||
{ .level = SOL_SOCKET, .optname = SO_KEEPALIVE, .optval = 1, .opttype = SOCK_OPT_TYPE_BOOL },
|
|
||||||
{ .level = IPPROTO_TCP },
|
|
||||||
{ .level = IPPROTO_TCP },
|
|
||||||
{ .level = IPPROTO_TCP }
|
|
||||||
};
|
|
||||||
|
|
||||||
expected_opts[1].optname = TCP_KEEPIDLE;
|
|
||||||
expected_opts[1].optval = config->keep_alive_idle;
|
|
||||||
expected_opts[2].optname = TCP_KEEPINTVL;
|
|
||||||
expected_opts[2].optval = config->keep_alive_interval;
|
|
||||||
expected_opts[3].optname = TCP_KEEPCNT;
|
|
||||||
expected_opts[3].optval = config->keep_alive_count;
|
|
||||||
|
|
||||||
socket_operation_test(transport_under_test, expected_opts, sizeof(expected_opts)/sizeof(struct expected_sock_option));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("tcp_transport: Keep alive test", "[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");
|
|
||||||
|
|
||||||
// Perform the test
|
|
||||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
|
||||||
.keep_alive_interval = 5,
|
|
||||||
.keep_alive_idle = 4,
|
|
||||||
.keep_alive_enable = true,
|
|
||||||
.keep_alive_count = 3 };
|
|
||||||
esp_transport_tcp_set_keep_alive(tcp, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Bind device interface to loopback
|
|
||||||
TEST_TRANSPORT_BIND_IFNAME();
|
|
||||||
esp_transport_tcp_set_interface_name(tcp, &ifr);
|
|
||||||
|
|
||||||
tcp_transport_keepalive_test(tcp, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
esp_transport_close(tcp);
|
|
||||||
esp_transport_list_destroy(transport_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("ssl_transport: Keep alive test", "[tcp_transport]")
|
|
||||||
{
|
|
||||||
// Init the transport under test
|
|
||||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
|
||||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
|
||||||
esp_transport_list_add(transport_list, ssl, "ssl");
|
|
||||||
esp_tls_init_global_ca_store();
|
|
||||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
|
||||||
|
|
||||||
// Perform the test
|
|
||||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
|
||||||
.keep_alive_interval = 2,
|
|
||||||
.keep_alive_idle = 3,
|
|
||||||
.keep_alive_enable = true,
|
|
||||||
.keep_alive_count = 4 };
|
|
||||||
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Bind device interface to loopback
|
|
||||||
TEST_TRANSPORT_BIND_IFNAME();
|
|
||||||
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
|
||||||
|
|
||||||
tcp_transport_keepalive_test(ssl, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
esp_transport_close(ssl);
|
|
||||||
esp_transport_list_destroy(transport_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("ws_transport: Keep alive test", "[tcp_transport]")
|
|
||||||
{
|
|
||||||
// Init the transport under test
|
|
||||||
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
|
||||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
|
||||||
esp_transport_list_add(transport_list, ssl, "ssl");
|
|
||||||
esp_tls_init_global_ca_store();
|
|
||||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
|
||||||
esp_transport_handle_t ws = esp_transport_ws_init(ssl);
|
|
||||||
esp_transport_list_add(transport_list, ws, "wss");
|
|
||||||
|
|
||||||
// Perform the test
|
|
||||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
|
||||||
.keep_alive_interval = 1,
|
|
||||||
.keep_alive_idle = 2,
|
|
||||||
.keep_alive_enable = true,
|
|
||||||
.keep_alive_count = 3 };
|
|
||||||
esp_transport_tcp_set_keep_alive(ssl, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Bind device interface to loopback
|
|
||||||
TEST_TRANSPORT_BIND_IFNAME();
|
|
||||||
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
|
||||||
|
|
||||||
tcp_transport_keepalive_test(ws, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
esp_transport_close(ssl);
|
|
||||||
esp_transport_list_destroy(transport_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: This functionality is tested and kept only for compatibility reasons with IDF <= 4.x
|
|
||||||
// It is strongly encouraged to use transport within lists only
|
|
||||||
TEST_CASE("ssl_transport: Check that parameters (keepalive) are set independently on the list", "[tcp_transport]")
|
|
||||||
{
|
|
||||||
// Init the transport under test
|
|
||||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
|
||||||
esp_tls_init_global_ca_store();
|
|
||||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
|
||||||
|
|
||||||
// Perform the test
|
|
||||||
esp_transport_keep_alive_t keep_alive_cfg = {
|
|
||||||
.keep_alive_interval = 2,
|
|
||||||
.keep_alive_idle = 4,
|
|
||||||
.keep_alive_enable = true,
|
|
||||||
.keep_alive_count = 3 };
|
|
||||||
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Bind device interface to loopback
|
|
||||||
TEST_TRANSPORT_BIND_IFNAME();
|
|
||||||
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
|
||||||
|
|
||||||
tcp_transport_keepalive_test(ssl, &keep_alive_cfg);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
esp_transport_close(ssl);
|
|
||||||
esp_transport_destroy(ssl);
|
|
||||||
}
|
|
44
components/tcp_transport/test/test_transport_basic.c
Normal file
44
components/tcp_transport/test/test_transport_basic.c
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include "unity.h"
|
||||||
|
|
||||||
|
#include "esp_transport.h"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
#include "esp_transport_ssl.h"
|
||||||
|
#include "esp_transport_ws.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("tcp_transport: init and deinit transport list", "[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");
|
||||||
|
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_list_destroy(transport_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("tcp_transport: using ssl transport separately", "[tcp_transport][leaks=0]")
|
||||||
|
{
|
||||||
|
esp_transport_handle_t h = esp_transport_ssl_init();
|
||||||
|
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(h));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("tcp_transport: using ws transport separately", "[tcp_transport][leaks=0]")
|
||||||
|
{
|
||||||
|
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||||
|
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||||
|
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(ws));
|
||||||
|
TEST_ASSERT_EQUAL(ESP_OK, esp_transport_destroy(tcp));
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
178
components/tcp_transport/test/test_transport_connect.c
Normal file
178
components/tcp_transport/test/test_transport_connect.c
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#include "unity.h"
|
||||||
|
#include "esp_transport.h"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
#include "esp_transport_ssl.h"
|
||||||
|
#include "esp_transport_ws.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
#include "tcp_transport_fixtures.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define TEST_TRANSPORT_BIND_IFNAME() \
|
||||||
|
struct ifreq ifr; \
|
||||||
|
ifr.ifr_name[0] = 'l'; \
|
||||||
|
ifr.ifr_name[1] = 'o'; \
|
||||||
|
ifr.ifr_name[2] = '\0';
|
||||||
|
|
||||||
|
|
||||||
|
static void tcp_transport_keepalive_test(esp_transport_handle_t transport_under_test, bool async, esp_transport_keep_alive_t *config)
|
||||||
|
{
|
||||||
|
static struct expected_sock_option expected_opts[4] = {
|
||||||
|
{ .level = SOL_SOCKET, .optname = SO_KEEPALIVE, .optval = 1, .opttype = SOCK_OPT_TYPE_BOOL },
|
||||||
|
{ .level = IPPROTO_TCP },
|
||||||
|
{ .level = IPPROTO_TCP },
|
||||||
|
{ .level = IPPROTO_TCP }
|
||||||
|
};
|
||||||
|
|
||||||
|
expected_opts[1].optname = TCP_KEEPIDLE;
|
||||||
|
expected_opts[1].optval = config->keep_alive_idle;
|
||||||
|
expected_opts[2].optname = TCP_KEEPINTVL;
|
||||||
|
expected_opts[2].optval = config->keep_alive_interval;
|
||||||
|
expected_opts[3].optname = TCP_KEEPCNT;
|
||||||
|
expected_opts[3].optval = config->keep_alive_count;
|
||||||
|
|
||||||
|
tcp_transport_test_socket_options(transport_under_test, async, expected_opts,
|
||||||
|
sizeof(expected_opts) / sizeof(struct expected_sock_option));
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
tcp_transport_test_connection_timeout(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");
|
||||||
|
|
||||||
|
tcp_transport_test_connection_timeout(ssl);
|
||||||
|
esp_transport_close(tcp);
|
||||||
|
esp_transport_close(ssl);
|
||||||
|
esp_transport_list_destroy(transport_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("tcp_transport: Keep alive test", "[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");
|
||||||
|
|
||||||
|
// Perform the test
|
||||||
|
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||||
|
.keep_alive_interval = 5,
|
||||||
|
.keep_alive_idle = 4,
|
||||||
|
.keep_alive_enable = true,
|
||||||
|
.keep_alive_count = 3 };
|
||||||
|
esp_transport_tcp_set_keep_alive(tcp, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Bind device interface to loopback
|
||||||
|
TEST_TRANSPORT_BIND_IFNAME();
|
||||||
|
esp_transport_tcp_set_interface_name(tcp, &ifr);
|
||||||
|
|
||||||
|
// Run the test for both sync and async_connect
|
||||||
|
tcp_transport_keepalive_test(tcp, true, &keep_alive_cfg);
|
||||||
|
tcp_transport_keepalive_test(tcp, false, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
esp_transport_close(tcp);
|
||||||
|
esp_transport_list_destroy(transport_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ssl_transport: Keep alive test", "[tcp_transport]")
|
||||||
|
{
|
||||||
|
// Init the transport under test
|
||||||
|
esp_transport_list_handle_t transport_list = esp_transport_list_init();
|
||||||
|
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||||
|
esp_transport_list_add(transport_list, ssl, "ssl");
|
||||||
|
esp_tls_init_global_ca_store();
|
||||||
|
esp_transport_ssl_enable_global_ca_store(ssl);
|
||||||
|
|
||||||
|
// Perform the test
|
||||||
|
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||||
|
.keep_alive_interval = 2,
|
||||||
|
.keep_alive_idle = 3,
|
||||||
|
.keep_alive_enable = true,
|
||||||
|
.keep_alive_count = 4 };
|
||||||
|
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Bind device interface to loopback
|
||||||
|
TEST_TRANSPORT_BIND_IFNAME();
|
||||||
|
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
||||||
|
|
||||||
|
// Run the test for async_connect only
|
||||||
|
// - TLS connection would connect on socket level only, returning tls-handshake in progress
|
||||||
|
tcp_transport_keepalive_test(ssl, true, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
esp_transport_close(ssl);
|
||||||
|
esp_transport_list_destroy(transport_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ws_transport: Keep alive test", "[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 ws = esp_transport_ws_init(tcp);
|
||||||
|
esp_transport_list_add(transport_list, ws, "ws");
|
||||||
|
|
||||||
|
// Perform the test
|
||||||
|
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||||
|
.keep_alive_interval = 11,
|
||||||
|
.keep_alive_idle = 22,
|
||||||
|
.keep_alive_enable = true,
|
||||||
|
.keep_alive_count = 33 };
|
||||||
|
esp_transport_tcp_set_keep_alive(tcp, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Bind device interface to loopback
|
||||||
|
TEST_TRANSPORT_BIND_IFNAME();
|
||||||
|
esp_transport_ssl_set_interface_name(tcp, &ifr);
|
||||||
|
|
||||||
|
// Run the test for sync_connect only (ws doesn't support async)
|
||||||
|
tcp_transport_keepalive_test(ws, false, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
esp_transport_close(tcp);
|
||||||
|
esp_transport_list_destroy(transport_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This functionality is tested and kept only for compatibility reasons with IDF <= 4.x
|
||||||
|
// It is strongly encouraged to use transport within lists only
|
||||||
|
TEST_CASE("ssl_transport: Check that parameters (keepalive) are set independently on the list", "[tcp_transport]")
|
||||||
|
{
|
||||||
|
// Init the transport under test
|
||||||
|
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||||
|
esp_tls_init_global_ca_store();
|
||||||
|
esp_transport_ssl_enable_global_ca_store(ssl);
|
||||||
|
|
||||||
|
// Perform the test
|
||||||
|
esp_transport_keep_alive_t keep_alive_cfg = {
|
||||||
|
.keep_alive_interval = 2,
|
||||||
|
.keep_alive_idle = 4,
|
||||||
|
.keep_alive_enable = true,
|
||||||
|
.keep_alive_count = 3 };
|
||||||
|
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Bind device interface to loopback
|
||||||
|
TEST_TRANSPORT_BIND_IFNAME();
|
||||||
|
esp_transport_ssl_set_interface_name(ssl, &ifr);
|
||||||
|
|
||||||
|
tcp_transport_keepalive_test(ssl, true, &keep_alive_cfg);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
esp_transport_close(ssl);
|
||||||
|
esp_transport_destroy(ssl);
|
||||||
|
}
|
311
components/tcp_transport/test/test_transport_fixtures.c
Normal file
311
components/tcp_transport/test/test_transport_fixtures.c
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
#include "unity.h"
|
||||||
|
|
||||||
|
#include "esp_transport.h"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lwip/err.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "tcp_transport_fixtures.h"
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event flags for synchronization between the listener task, the connection task and the test task
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
TCP_CONNECT_DONE = 1 << 0, /*!< Indicates that the connection task has finished, so the transport_connect() exited */
|
||||||
|
TCP_LISTENER_DONE = 1 << 1, /*!< Indicates that the listener task has finished either with success for failure */
|
||||||
|
TCP_TEST_DONE = 1 << 2, /*!< Indicates that the test case finished, test tear-down() called */
|
||||||
|
TCP_LISTENER_READY = 1 << 3, /*!< Indicates that the listener task is ready to accept connections */
|
||||||
|
TCP_LISTENER_ACCEPTED = 1 << 4, /*!< Indicates that the listener task has accepted a connection (from transport_connect()) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connection test configuration parameters
|
||||||
|
*/
|
||||||
|
struct tcp_connect_test_config {
|
||||||
|
esp_transport_handle_t transport_under_test;
|
||||||
|
bool accept_connection;
|
||||||
|
bool consume_sock_backlog;
|
||||||
|
bool connect_async;
|
||||||
|
int timeout_ms;
|
||||||
|
int port;
|
||||||
|
bool listener_task_prio_higher;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test setup structure containing all the info needed for the connection tests
|
||||||
|
*/
|
||||||
|
struct tcp_connect_test_storage {
|
||||||
|
struct tcp_connect_test_config config;
|
||||||
|
TickType_t max_wait;
|
||||||
|
EventGroupHandle_t tcp_connect_done;
|
||||||
|
int connect_return_value;
|
||||||
|
int listen_sock;
|
||||||
|
int accepted_sock;
|
||||||
|
int last_connect_sock;
|
||||||
|
bool tcp_listener_failed;
|
||||||
|
TaskHandle_t listener_task;
|
||||||
|
TaskHandle_t tcp_connect_task;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct tcp_connect_test_storage *tcp_connect_test_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recursively connects with a new socket to loopback interface until the last one blocks.
|
||||||
|
* The last socket is closed upon test teardown, that initiates recursive cleanup (close) for all
|
||||||
|
* active/connected sockets.
|
||||||
|
*/
|
||||||
|
static void connect_once(struct tcp_connect_test_storage *storage)
|
||||||
|
{
|
||||||
|
struct sockaddr_in dest_addr_ip4 = { .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(storage->config.port) };
|
||||||
|
int connect_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||||
|
if (connect_sock < 0) {
|
||||||
|
storage->tcp_listener_failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
storage->last_connect_sock = connect_sock;
|
||||||
|
int err = connect(connect_sock, (struct sockaddr *)&dest_addr_ip4, sizeof(dest_addr_ip4));
|
||||||
|
if (err != 0) {
|
||||||
|
// The last connection is expected to fail here, since the both sockets get closed on test cleanup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connect_once(storage);
|
||||||
|
close(connect_sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
const char* TAG = "tcp_transport_test";
|
||||||
|
struct tcp_connect_test_storage *storage = pvParameters;
|
||||||
|
struct sockaddr_in dest_addr_ip4 = { .sin_addr.s_addr = htonl(INADDR_ANY),
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(storage->config.port) };
|
||||||
|
// Create listener socket and bind it to ANY address
|
||||||
|
storage->listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(storage->listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
if (storage->listen_sock < 0) {
|
||||||
|
ESP_LOGE(TAG, "Unable to create socket");
|
||||||
|
storage->tcp_listener_failed = true;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
int err = bind(storage->listen_sock, (struct sockaddr *)&dest_addr_ip4, sizeof(dest_addr_ip4));
|
||||||
|
if (err != 0) {
|
||||||
|
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||||
|
storage->tcp_listener_failed = true;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen with backlog set to a low number
|
||||||
|
err = listen(storage->listen_sock, 4);
|
||||||
|
if (err != 0) {
|
||||||
|
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
||||||
|
storage->tcp_listener_failed = true;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener is ready at this point
|
||||||
|
xEventGroupSetBits(storage->tcp_connect_done, TCP_LISTENER_READY);
|
||||||
|
|
||||||
|
if (storage->config.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(storage);
|
||||||
|
} else if (storage->config.accept_connection) {
|
||||||
|
struct sockaddr_storage source_addr;
|
||||||
|
socklen_t addr_len = sizeof(source_addr);
|
||||||
|
storage->accepted_sock = accept(storage->listen_sock, (struct sockaddr *)&source_addr, &addr_len);
|
||||||
|
if (storage->accepted_sock < 0) {
|
||||||
|
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
xEventGroupSetBits(storage->tcp_connect_done, TCP_LISTENER_ACCEPTED); // Mark the socket as accepted
|
||||||
|
// ...and wait for the "acceptor" tests to finish
|
||||||
|
xEventGroupWaitBits(storage->tcp_connect_done, TCP_TEST_DONE, true, true, storage->config.timeout_ms * 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
failed:
|
||||||
|
xEventGroupSetBits(storage->tcp_connect_done, TCP_LISTENER_DONE);
|
||||||
|
vTaskSuspend(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This task simply tries to connect to localhost (server provided by listner's task) using tcp_transport
|
||||||
|
*/
|
||||||
|
static void tcp_connect_task(void *pvParameters)
|
||||||
|
{
|
||||||
|
struct tcp_connect_test_storage *storage = pvParameters;
|
||||||
|
|
||||||
|
int (*connect_fn)(esp_transport_handle_t, const char *, int, int) =
|
||||||
|
storage->config.connect_async ? esp_transport_connect_async : esp_transport_connect;
|
||||||
|
|
||||||
|
storage->connect_return_value = connect_fn(storage->config.transport_under_test, "localhost", storage->config.port, storage->config.timeout_ms);
|
||||||
|
|
||||||
|
if (storage->config.accept_connection) {
|
||||||
|
// If we test the accepted connection, need to wait until the test completes
|
||||||
|
xEventGroupWaitBits(storage->tcp_connect_done, TCP_TEST_DONE, true, true, storage->config.timeout_ms * 10);
|
||||||
|
}
|
||||||
|
xEventGroupSetBits(storage->tcp_connect_done, TCP_CONNECT_DONE);
|
||||||
|
vTaskSuspend(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void close_if_valid(int *s)
|
||||||
|
{
|
||||||
|
if (*s >= 0) {
|
||||||
|
close(*s);
|
||||||
|
*s = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connect test setup function
|
||||||
|
*
|
||||||
|
* Creates the Test storage, configures it accordingly and starts two tasks
|
||||||
|
* * localhost_listener -- to provide a simple server endpoint for the transport layers to connect to
|
||||||
|
* * tcp_connect_task -- to perform the connection
|
||||||
|
*/
|
||||||
|
static tcp_connect_test_t connect_test_setup(struct tcp_connect_test_config *config)
|
||||||
|
{
|
||||||
|
tcp_connect_test_t t = calloc(1, sizeof(struct tcp_connect_test_storage));
|
||||||
|
if (!t) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(&t->config, config, sizeof(struct tcp_connect_test_config));
|
||||||
|
t->tcp_connect_done = xEventGroupCreate();
|
||||||
|
if (!t->tcp_connect_done) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
t->max_wait = pdMS_TO_TICKS(config->timeout_ms * 10);
|
||||||
|
|
||||||
|
t->listen_sock = t->last_connect_sock = t->accepted_sock = -1; // mark all sockets invalid
|
||||||
|
|
||||||
|
test_case_uses_tcpip();
|
||||||
|
|
||||||
|
// Create listener task
|
||||||
|
xTaskCreatePinnedToCore(localhost_listener, "localhost_listener", 4096, t, 5, &t->listener_task, 0);
|
||||||
|
xEventGroupWaitBits(t->tcp_connect_done, TCP_LISTENER_READY, true, true, t->max_wait);
|
||||||
|
|
||||||
|
// Perform tcp-connect in a separate task to check asynchronously for the timeout or to connect (depends on the test config)
|
||||||
|
xTaskCreatePinnedToCore(tcp_connect_task, "tcp_connect_task", 4096, t,
|
||||||
|
config->listener_task_prio_higher? 4 : 6, &t->tcp_connect_task, 0);
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys and cleans out the test environment
|
||||||
|
*/
|
||||||
|
static void connect_test_teardown(tcp_connect_test_t t)
|
||||||
|
{
|
||||||
|
// Mark the test done and wait for the listener to check if finished with no issues
|
||||||
|
xEventGroupSetBits(t->tcp_connect_done, TCP_TEST_DONE);
|
||||||
|
xEventGroupWaitBits(t->tcp_connect_done, TCP_LISTENER_DONE, true, true, t->max_wait);
|
||||||
|
TEST_ASSERT_EQUAL(false, t->tcp_listener_failed);
|
||||||
|
|
||||||
|
// Closing both parties of the last "blocking" connection to unwind localhost_listener() and let other connected sockets closed
|
||||||
|
close_if_valid(&t->listen_sock);
|
||||||
|
close_if_valid(&t->last_connect_sock);
|
||||||
|
close_if_valid(&t->accepted_sock);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
vTaskSuspend(t->tcp_connect_task);
|
||||||
|
vTaskSuspend(t->listener_task);
|
||||||
|
vEventGroupDelete(t->tcp_connect_done);
|
||||||
|
test_utils_task_delete(t->tcp_connect_task);
|
||||||
|
test_utils_task_delete(t->listener_task);
|
||||||
|
free(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility function for testing timeouts for different transports
|
||||||
|
*/
|
||||||
|
void tcp_transport_test_connection_timeout(esp_transport_handle_t transport_under_test)
|
||||||
|
{
|
||||||
|
|
||||||
|
struct tcp_connect_test_config params = {
|
||||||
|
.timeout_ms = 200,
|
||||||
|
.port = 80,
|
||||||
|
.consume_sock_backlog = true,
|
||||||
|
.connect_async = false,
|
||||||
|
.transport_under_test = transport_under_test,
|
||||||
|
.listener_task_prio_higher = true
|
||||||
|
};
|
||||||
|
|
||||||
|
tcp_connect_test_t test = connect_test_setup(¶ms);
|
||||||
|
TEST_ASSERT_NOT_NULL(test);
|
||||||
|
|
||||||
|
// Roughly measure tick-time spent while trying to connect
|
||||||
|
TickType_t start = xTaskGetTickCount();
|
||||||
|
EventBits_t bits = xEventGroupWaitBits(test->tcp_connect_done, TCP_CONNECT_DONE, true, true, test->max_wait);
|
||||||
|
TickType_t end = xTaskGetTickCount();
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TCP_CONNECT_DONE, TCP_CONNECT_DONE & bits); // Connection has finished
|
||||||
|
TEST_ASSERT_EQUAL(-1, test->connect_return_value); // Connection failed with -1
|
||||||
|
|
||||||
|
// Test connection attempt took expected timeout value
|
||||||
|
TEST_ASSERT_INT_WITHIN(pdMS_TO_TICKS(params.timeout_ms/5), pdMS_TO_TICKS(params.timeout_ms), end-start);
|
||||||
|
|
||||||
|
// Close the last bound connection, to recursively unwind the consumed backlog
|
||||||
|
close_if_valid(&test->last_connect_sock);
|
||||||
|
|
||||||
|
connect_test_teardown(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility function for testing timeouts for different transports, options and both sync and async connection
|
||||||
|
*/
|
||||||
|
void tcp_transport_test_socket_options(esp_transport_handle_t transport_under_test, bool async,
|
||||||
|
const struct expected_sock_option *expected_opts, size_t sock_options_len)
|
||||||
|
{
|
||||||
|
struct tcp_connect_test_config params = {
|
||||||
|
.timeout_ms = 200,
|
||||||
|
.port = 80,
|
||||||
|
.accept_connection = true,
|
||||||
|
.consume_sock_backlog = false,
|
||||||
|
.transport_under_test = transport_under_test,
|
||||||
|
.connect_async = async,
|
||||||
|
.listener_task_prio_higher = false
|
||||||
|
};
|
||||||
|
|
||||||
|
tcp_connect_test_t test = connect_test_setup(¶ms);
|
||||||
|
TEST_ASSERT_NOT_NULL(test);
|
||||||
|
|
||||||
|
// Wait till the connection gets accepted to get the client's socket
|
||||||
|
xEventGroupWaitBits(test->tcp_connect_done, TCP_LISTENER_ACCEPTED, true, true, test->max_wait);
|
||||||
|
int sock = esp_transport_get_socket(params.transport_under_test);
|
||||||
|
for (int i=0; i<sock_options_len; ++i) {
|
||||||
|
int value = -1;
|
||||||
|
socklen_t optlen = (socklen_t)sizeof(value);
|
||||||
|
TEST_ASSERT_EQUAL(getsockopt(sock, expected_opts[i].level, expected_opts[i].optname,
|
||||||
|
(void*)&value, &optlen), 0);
|
||||||
|
if (expected_opts[i].opttype == SOCK_OPT_TYPE_BOOL) {
|
||||||
|
TEST_ASSERT_EQUAL((bool)value, (bool) expected_opts[i].optval);
|
||||||
|
} else if (expected_opts[i].opttype == SOCK_OPT_TYPE_INT) {
|
||||||
|
TEST_ASSERT_EQUAL(value, expected_opts[i].optval);
|
||||||
|
} else {
|
||||||
|
TEST_FAIL_MESSAGE("Unsupported socket option type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the tcp_transport's socket so we don't have to wait for connection timeout
|
||||||
|
close(sock);
|
||||||
|
|
||||||
|
connect_test_teardown(test);
|
||||||
|
}
|
@@ -23,6 +23,8 @@
|
|||||||
#include "esp_transport_utils.h"
|
#include "esp_transport_utils.h"
|
||||||
#include "esp_transport_internal.h"
|
#include "esp_transport_internal.h"
|
||||||
|
|
||||||
|
#define INVALID_SOCKET (-1)
|
||||||
|
|
||||||
#define GET_SSL_FROM_TRANSPORT_OR_RETURN(ssl, t) \
|
#define GET_SSL_FROM_TRANSPORT_OR_RETURN(ssl, t) \
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t); \
|
transport_esp_tls_t *ssl = ssl_get_context_data(t); \
|
||||||
if (!ssl) { return; }
|
if (!ssl) { return; }
|
||||||
@@ -42,6 +44,7 @@ typedef struct transport_esp_tls {
|
|||||||
esp_tls_cfg_t cfg;
|
esp_tls_cfg_t cfg;
|
||||||
bool ssl_initialized;
|
bool ssl_initialized;
|
||||||
transport_ssl_conn_state_t conn_state;
|
transport_ssl_conn_state_t conn_state;
|
||||||
|
int sockfd;
|
||||||
} transport_esp_tls_t;
|
} transport_esp_tls_t;
|
||||||
|
|
||||||
static inline struct transport_esp_tls * ssl_get_context_data(esp_transport_handle_t t)
|
static inline struct transport_esp_tls * ssl_get_context_data(esp_transport_handle_t t)
|
||||||
@@ -63,8 +66,6 @@ static inline struct transport_esp_tls * ssl_get_context_data(esp_transport_hand
|
|||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ssl_close(esp_transport_handle_t t);
|
|
||||||
|
|
||||||
static int esp_tls_connect_async(esp_transport_handle_t t, const char *host, int port, int timeout_ms, bool is_plain_tcp)
|
static int esp_tls_connect_async(esp_transport_handle_t t, const char *host, int port, int timeout_ms, bool is_plain_tcp)
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
@@ -78,9 +79,15 @@ static int esp_tls_connect_async(esp_transport_handle_t t, const char *host, int
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
ssl->conn_state = TRANS_SSL_CONNECTING;
|
ssl->conn_state = TRANS_SSL_CONNECTING;
|
||||||
|
ssl->sockfd = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
if (ssl->conn_state == TRANS_SSL_CONNECTING) {
|
if (ssl->conn_state == TRANS_SSL_CONNECTING) {
|
||||||
return esp_tls_conn_new_async(host, strlen(host), port, &ssl->cfg, ssl->tls);
|
int progress = esp_tls_conn_new_async(host, strlen(host), port, &ssl->cfg, ssl->tls);
|
||||||
|
if (progress >= 0) {
|
||||||
|
ssl->sockfd = ssl->tls->sockfd;
|
||||||
|
}
|
||||||
|
return progress;
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -95,12 +102,12 @@ static inline int tcp_connect_async(esp_transport_handle_t t, const char *host,
|
|||||||
return esp_tls_connect_async(t, host, port, timeout_ms, true);
|
return esp_tls_connect_async(t, host, port, timeout_ms, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int esp_tls_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms, bool is_plain_tcp)
|
static int ssl_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
|
|
||||||
ssl->cfg.timeout_ms = timeout_ms;
|
ssl->cfg.timeout_ms = timeout_ms;
|
||||||
ssl->cfg.is_plain_tcp = is_plain_tcp;
|
ssl->cfg.is_plain_tcp = false;
|
||||||
|
|
||||||
ssl->ssl_initialized = true;
|
ssl->ssl_initialized = true;
|
||||||
ssl->tls = esp_tls_init();
|
ssl->tls = esp_tls_init();
|
||||||
@@ -114,22 +121,30 @@ static int esp_tls_connect(esp_transport_handle_t t, const char *host, int port,
|
|||||||
esp_transport_set_errors(t, ssl->tls->error_handle);
|
esp_transport_set_errors(t, ssl->tls->error_handle);
|
||||||
esp_tls_conn_destroy(ssl->tls);
|
esp_tls_conn_destroy(ssl->tls);
|
||||||
ssl->tls = NULL;
|
ssl->tls = NULL;
|
||||||
|
ssl->sockfd = INVALID_SOCKET;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ssl->sockfd = ssl->tls->sockfd;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tcp_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
|
||||||
|
{
|
||||||
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
|
esp_tls_last_error_t *err_handle = esp_transport_get_error_handle(t);
|
||||||
|
|
||||||
|
ssl->cfg.timeout_ms = timeout_ms;
|
||||||
|
esp_err_t err = esp_tls_plain_tcp_connect(host, strlen(host), port, &ssl->cfg, err_handle, &ssl->sockfd);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open a new connection: %d", err);
|
||||||
|
err_handle->last_error = err;
|
||||||
|
ssl->sockfd = INVALID_SOCKET;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int ssl_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
|
static int base_poll_read(esp_transport_handle_t t, int timeout_ms)
|
||||||
{
|
|
||||||
return esp_tls_connect(t, host, port, timeout_ms, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int tcp_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
|
|
||||||
{
|
|
||||||
return esp_tls_connect(t, host, port, timeout_ms, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ssl_poll_read(esp_transport_handle_t t, int timeout_ms)
|
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
@@ -139,26 +154,26 @@ static int ssl_poll_read(esp_transport_handle_t t, int timeout_ms)
|
|||||||
fd_set errset;
|
fd_set errset;
|
||||||
FD_ZERO(&readset);
|
FD_ZERO(&readset);
|
||||||
FD_ZERO(&errset);
|
FD_ZERO(&errset);
|
||||||
FD_SET(ssl->tls->sockfd, &readset);
|
FD_SET(ssl->sockfd, &readset);
|
||||||
FD_SET(ssl->tls->sockfd, &errset);
|
FD_SET(ssl->sockfd, &errset);
|
||||||
|
|
||||||
if ((remain = esp_tls_get_bytes_avail(ssl->tls)) > 0) {
|
if (ssl->tls && (remain = esp_tls_get_bytes_avail(ssl->tls)) > 0) {
|
||||||
ESP_LOGD(TAG, "remain data in cache, need to read again");
|
ESP_LOGD(TAG, "remain data in cache, need to read again");
|
||||||
return remain;
|
return remain;
|
||||||
}
|
}
|
||||||
ret = select(ssl->tls->sockfd + 1, &readset, NULL, &errset, esp_transport_utils_ms_to_timeval(timeout_ms, &timeout));
|
ret = select(ssl->sockfd + 1, &readset, NULL, &errset, esp_transport_utils_ms_to_timeval(timeout_ms, &timeout));
|
||||||
if (ret > 0 && FD_ISSET(ssl->tls->sockfd, &errset)) {
|
if (ret > 0 && FD_ISSET(ssl->sockfd, &errset)) {
|
||||||
int sock_errno = 0;
|
int sock_errno = 0;
|
||||||
uint32_t optlen = sizeof(sock_errno);
|
uint32_t optlen = sizeof(sock_errno);
|
||||||
getsockopt(ssl->tls->sockfd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen);
|
getsockopt(ssl->sockfd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen);
|
||||||
esp_transport_capture_errno(t, sock_errno);
|
esp_transport_capture_errno(t, sock_errno);
|
||||||
ESP_LOGE(TAG, "ssl_poll_read select error %d, errno = %s, fd = %d", sock_errno, strerror(sock_errno), ssl->tls->sockfd);
|
ESP_LOGE(TAG, "poll_read select error %d, errno = %s, fd = %d", sock_errno, strerror(sock_errno), ssl->sockfd);
|
||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ssl_poll_write(esp_transport_handle_t t, int timeout_ms)
|
static int base_poll_write(esp_transport_handle_t t, int timeout_ms)
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
@@ -167,15 +182,15 @@ static int ssl_poll_write(esp_transport_handle_t t, int timeout_ms)
|
|||||||
fd_set errset;
|
fd_set errset;
|
||||||
FD_ZERO(&writeset);
|
FD_ZERO(&writeset);
|
||||||
FD_ZERO(&errset);
|
FD_ZERO(&errset);
|
||||||
FD_SET(ssl->tls->sockfd, &writeset);
|
FD_SET(ssl->sockfd, &writeset);
|
||||||
FD_SET(ssl->tls->sockfd, &errset);
|
FD_SET(ssl->sockfd, &errset);
|
||||||
ret = select(ssl->tls->sockfd + 1, NULL, &writeset, &errset, esp_transport_utils_ms_to_timeval(timeout_ms, &timeout));
|
ret = select(ssl->sockfd + 1, NULL, &writeset, &errset, esp_transport_utils_ms_to_timeval(timeout_ms, &timeout));
|
||||||
if (ret > 0 && FD_ISSET(ssl->tls->sockfd, &errset)) {
|
if (ret > 0 && FD_ISSET(ssl->sockfd, &errset)) {
|
||||||
int sock_errno = 0;
|
int sock_errno = 0;
|
||||||
uint32_t optlen = sizeof(sock_errno);
|
uint32_t optlen = sizeof(sock_errno);
|
||||||
getsockopt(ssl->tls->sockfd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen);
|
getsockopt(ssl->sockfd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen);
|
||||||
esp_transport_capture_errno(t, sock_errno);
|
esp_transport_capture_errno(t, sock_errno);
|
||||||
ESP_LOGE(TAG, "ssl_poll_write select error %d, errno = %s, fd = %d", sock_errno, strerror(sock_errno), ssl->tls->sockfd);
|
ESP_LOGE(TAG, "poll_write select error %d, errno = %s, fd = %d", sock_errno, strerror(sock_errno), ssl->sockfd);
|
||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -183,14 +198,14 @@ static int ssl_poll_write(esp_transport_handle_t t, int timeout_ms)
|
|||||||
|
|
||||||
static int ssl_write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms)
|
static int ssl_write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms)
|
||||||
{
|
{
|
||||||
int poll, ret;
|
int poll;
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
|
|
||||||
if ((poll = esp_transport_poll_write(t, timeout_ms)) <= 0) {
|
if ((poll = esp_transport_poll_write(t, timeout_ms)) <= 0) {
|
||||||
ESP_LOGW(TAG, "Poll timeout or error, errno=%s, fd=%d, timeout_ms=%d", strerror(errno), ssl->tls->sockfd, timeout_ms);
|
ESP_LOGW(TAG, "Poll timeout or error, errno=%s, fd=%d, timeout_ms=%d", strerror(errno), ssl->sockfd, timeout_ms);
|
||||||
return poll;
|
return poll;
|
||||||
}
|
}
|
||||||
ret = esp_tls_conn_write(ssl->tls, (const unsigned char *) buffer, len);
|
int ret = esp_tls_conn_write(ssl->tls, (const unsigned char *) buffer, len);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ESP_LOGE(TAG, "esp_tls_conn_write error, errno=%s", strerror(errno));
|
ESP_LOGE(TAG, "esp_tls_conn_write error, errno=%s", strerror(errno));
|
||||||
esp_transport_set_errors(t, ssl->tls->error_handle);
|
esp_transport_set_errors(t, ssl->tls->error_handle);
|
||||||
@@ -198,15 +213,32 @@ static int ssl_write(esp_transport_handle_t t, const char *buffer, int len, int
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int tcp_write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms)
|
||||||
|
{
|
||||||
|
int poll;
|
||||||
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
|
|
||||||
|
if ((poll = esp_transport_poll_write(t, timeout_ms)) <= 0) {
|
||||||
|
ESP_LOGW(TAG, "Poll timeout or error, errno=%s, fd=%d, timeout_ms=%d", strerror(errno), ssl->sockfd, timeout_ms);
|
||||||
|
return poll;
|
||||||
|
}
|
||||||
|
int ret = send(ssl->sockfd,(const unsigned char *) buffer, len, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
ESP_LOGE(TAG, "tcp_write error, errno=%s", strerror(errno));
|
||||||
|
esp_transport_capture_errno(t, errno);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int ssl_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms)
|
static int ssl_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms)
|
||||||
{
|
{
|
||||||
int poll, ret;
|
int poll;
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
|
|
||||||
if ((poll = esp_transport_poll_read(t, timeout_ms)) <= 0) {
|
if ((poll = esp_transport_poll_read(t, timeout_ms)) <= 0) {
|
||||||
return poll;
|
return poll;
|
||||||
}
|
}
|
||||||
ret = esp_tls_conn_read(ssl->tls, (unsigned char *)buffer, len);
|
int ret = esp_tls_conn_read(ssl->tls, (unsigned char *)buffer, len);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ESP_LOGE(TAG, "esp_tls_conn_read error, errno=%s", strerror(errno));
|
ESP_LOGE(TAG, "esp_tls_conn_read error, errno=%s", strerror(errno));
|
||||||
esp_transport_set_errors(t, ssl->tls->error_handle);
|
esp_transport_set_errors(t, ssl->tls->error_handle);
|
||||||
@@ -221,7 +253,30 @@ static int ssl_read(esp_transport_handle_t t, char *buffer, int len, int timeout
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ssl_close(esp_transport_handle_t t)
|
static int tcp_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms)
|
||||||
|
{
|
||||||
|
int poll;
|
||||||
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
|
|
||||||
|
if ((poll = esp_transport_poll_read(t, timeout_ms)) <= 0) {
|
||||||
|
return poll;
|
||||||
|
}
|
||||||
|
int ret = recv(ssl->sockfd, (unsigned char *)buffer, len, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
ESP_LOGE(TAG, "tcp_read error, errno=%s", strerror(errno));
|
||||||
|
esp_transport_capture_errno(t, errno);
|
||||||
|
}
|
||||||
|
if (ret == 0) {
|
||||||
|
if (poll > 0) {
|
||||||
|
// no error, socket reads 0 while previously detected as readable -> connection has been closed cleanly
|
||||||
|
capture_tcp_transport_error(t, ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN);
|
||||||
|
}
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int base_close(esp_transport_handle_t t)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
@@ -229,11 +284,15 @@ static int ssl_close(esp_transport_handle_t t)
|
|||||||
ret = esp_tls_conn_destroy(ssl->tls);
|
ret = esp_tls_conn_destroy(ssl->tls);
|
||||||
ssl->conn_state = TRANS_SSL_INIT;
|
ssl->conn_state = TRANS_SSL_INIT;
|
||||||
ssl->ssl_initialized = false;
|
ssl->ssl_initialized = false;
|
||||||
|
ssl->sockfd = INVALID_SOCKET;
|
||||||
|
} else if (ssl && ssl->sockfd >= 0) {
|
||||||
|
close(ssl->sockfd);
|
||||||
|
ssl->sockfd = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ssl_destroy(esp_transport_handle_t t)
|
static int base_destroy(esp_transport_handle_t t)
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
@@ -335,13 +394,13 @@ void esp_transport_ssl_crt_bundle_attach(esp_transport_handle_t t, esp_err_t ((*
|
|||||||
ssl->cfg.crt_bundle_attach = crt_bundle_attach;
|
ssl->cfg.crt_bundle_attach = crt_bundle_attach;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ssl_get_socket(esp_transport_handle_t t)
|
static int base_get_socket(esp_transport_handle_t t)
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *ssl = ssl_get_context_data(t);
|
transport_esp_tls_t *ctx = ssl_get_context_data(t);
|
||||||
if (ssl && ssl->tls) {
|
if (ctx) {
|
||||||
return ssl->tls->sockfd;
|
return ctx->sockfd;
|
||||||
}
|
}
|
||||||
return -1;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
void esp_transport_ssl_set_ds_data(esp_transport_handle_t t, void *ds_data)
|
void esp_transport_ssl_set_ds_data(esp_transport_handle_t t, void *ds_data)
|
||||||
@@ -368,16 +427,16 @@ esp_transport_handle_t esp_transport_ssl_init(void)
|
|||||||
if (t == NULL) {
|
if (t == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
esp_transport_set_func(t, ssl_connect, ssl_read, ssl_write, base_close, base_poll_read, base_poll_write, base_destroy);
|
||||||
esp_transport_set_func(t, ssl_connect, ssl_read, ssl_write, ssl_close, ssl_poll_read, ssl_poll_write, ssl_destroy);
|
|
||||||
esp_transport_set_async_connect_func(t, ssl_connect_async);
|
esp_transport_set_async_connect_func(t, ssl_connect_async);
|
||||||
t->_get_socket = ssl_get_socket;
|
t->_get_socket = base_get_socket;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct transport_esp_tls* esp_transport_esp_tls_create(void)
|
struct transport_esp_tls* esp_transport_esp_tls_create(void)
|
||||||
{
|
{
|
||||||
transport_esp_tls_t *transport_esp_tls = calloc(1, sizeof(transport_esp_tls_t));
|
transport_esp_tls_t *transport_esp_tls = calloc(1, sizeof(transport_esp_tls_t));
|
||||||
|
transport_esp_tls->sockfd = INVALID_SOCKET;
|
||||||
return transport_esp_tls;
|
return transport_esp_tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,9 +451,9 @@ esp_transport_handle_t esp_transport_tcp_init(void)
|
|||||||
if (t == NULL) {
|
if (t == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
esp_transport_set_func(t, tcp_connect, ssl_read, ssl_write, ssl_close, ssl_poll_read, ssl_poll_write, ssl_destroy);
|
esp_transport_set_func(t, tcp_connect, tcp_read, tcp_write, base_close, base_poll_read, base_poll_write, base_destroy);
|
||||||
esp_transport_set_async_connect_func(t, tcp_connect_async);
|
esp_transport_set_async_connect_func(t, tcp_connect_async);
|
||||||
t->_get_socket = ssl_get_socket;
|
t->_get_socket = base_get_socket;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -602,7 +602,7 @@ esp_transport_handle_t esp_transport_ws_init(esp_transport_handle_t parent_handl
|
|||||||
});
|
});
|
||||||
|
|
||||||
esp_transport_set_func(t, ws_connect, ws_read, ws_write, ws_close, ws_poll_read, ws_poll_write, ws_destroy);
|
esp_transport_set_func(t, ws_connect, ws_read, ws_write, ws_close, ws_poll_read, ws_poll_write, ws_destroy);
|
||||||
// webocket underlying transfer is the payload transfer handle
|
// websocket underlying transfer is the payload transfer handle
|
||||||
esp_transport_set_parent_transport_func(t, ws_get_payload_transport_handle);
|
esp_transport_set_parent_transport_func(t, ws_get_payload_transport_handle);
|
||||||
|
|
||||||
esp_transport_set_context_data(t, ws);
|
esp_transport_set_context_data(t, ws);
|
||||||
|
Reference in New Issue
Block a user