Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add memory_type Support for Flexible Memory Allocation in WebSocket Client to permit other memory type caps to be used (IDFGH-13127) #586

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
16 changes: 16 additions & 0 deletions components/esp_websocket_client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## [1.x.x](https://github.com/espressif/esp-protocols/commits/websocket-v1.x.x)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to modify, in fact you should not, the Changelog.
As stated in the contribution guide, you only need to follow the conventional commits, and we take care of the release notes when releasing a new version of the component.


### Features

- WebSocket can use Extende memory region to Buffer using CAPS area ([0d0630ed76](https://github.com/espressif/esp-protocols/commit/0d0630ed76))
- WebSocket Task can be created with CAPS area ([0d0630ed76](https://github.com/espressif/esp-protocols/commit/0d0630ed76))
- Websocket Task can be pinned to a core or run without affinity ([0d0630ed76](https://github.com/espressif/esp-protocols/commit/0d0630ed76))

### Bug Fixes

TLDR

### Updated

TLDR

## [1.2.3](https://github.com/espressif/esp-protocols/commits/websocket-v1.2.3)

### Features
Expand Down
151 changes: 129 additions & 22 deletions components/esp_websocket_client/esp_websocket_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include "esp_system.h"
#include <errno.h>
#include <arpa/inet.h>
#include "esp_heap_caps.h"


static const char *TAG = "websocket_client";

Expand All @@ -39,6 +41,20 @@ static const char *TAG = "websocket_client";
#define WEBSOCKET_KEEP_ALIVE_INTERVAL (5)
#define WEBSOCKET_KEEP_ALIVE_COUNT (3)

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
#define VALID_MEMORY_FLAGS (MALLOC_CAP_EXEC | MALLOC_CAP_32BIT | MALLOC_CAP_8BIT | MALLOC_CAP_DMA | \
MALLOC_CAP_PID2 | MALLOC_CAP_PID3 | MALLOC_CAP_PID4 | MALLOC_CAP_PID5 | \
MALLOC_CAP_PID6 | MALLOC_CAP_PID7 | MALLOC_CAP_SPIRAM | MALLOC_CAP_INTERNAL | \
MALLOC_CAP_DEFAULT | MALLOC_CAP_IRAM_8BIT | MALLOC_CAP_RETENTION | \
MALLOC_CAP_RTCRAM | MALLOC_CAP_TCM | MALLOC_CAP_INVALID)
#else
#define VALID_MEMORY_FLAGS (MALLOC_CAP_EXEC | MALLOC_CAP_32BIT | MALLOC_CAP_8BIT | MALLOC_CAP_DMA | \
MALLOC_CAP_PID2 | MALLOC_CAP_PID3 | MALLOC_CAP_PID4 | MALLOC_CAP_PID5 | \
MALLOC_CAP_PID6 | MALLOC_CAP_PID7 | MALLOC_CAP_SPIRAM | MALLOC_CAP_INTERNAL | \
MALLOC_CAP_DEFAULT | MALLOC_CAP_IRAM_8BIT | MALLOC_CAP_RETENTION | \
MALLOC_CAP_RTCRAM | MALLOC_CAP_INVALID)
#endif

#define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \
action; \
Expand Down Expand Up @@ -137,6 +153,8 @@ struct esp_websocket_client {
int payload_offset;
esp_transport_keep_alive_t keep_alive_cfg;
struct ifreq *if_name;
int memory_type;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against making the selection in runtime but, in other components, we make this choice in compile time through Kconfig.
Do you have a use case to have it set in runtime?

int core;
};

static uint64_t _tick_get_ms(void)
Expand All @@ -152,14 +170,14 @@ static esp_err_t esp_websocket_new_buf(esp_websocket_client_handle_t client, boo
free(client->tx_buffer);
}

client->tx_buffer = calloc(1, client->buffer_size);
client->tx_buffer = heap_caps_calloc(1, client->buffer_size,client->memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, return ESP_ERR_NO_MEM);
} else {
if (client->rx_buffer) {
free(client->rx_buffer);
}

client->rx_buffer = calloc(1, client->buffer_size);
client->rx_buffer = heap_caps_calloc(1, client->buffer_size,client->memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, return ESP_ERR_NO_MEM);
}
#endif
Expand Down Expand Up @@ -249,7 +267,7 @@ static esp_err_t esp_websocket_client_error(esp_websocket_client_handle_t client
if (client->errormsg_buffer) {
free(client->errormsg_buffer);
}
client->errormsg_buffer = malloc(needed_size);
client->errormsg_buffer = heap_caps_malloc(needed_size,client->memory_type);
if (client->errormsg_buffer == NULL) {
client->errormsg_size = 0;
ESP_LOGE(TAG, "Failed to allocate...");
Expand All @@ -268,7 +286,7 @@ static esp_err_t esp_websocket_client_error(esp_websocket_client_handle_t client
return ESP_OK;
}

static char *http_auth_basic(const char *username, const char *password)
static char *http_auth_basic(const char *username, const char *password, int memory_type)
{
int out;
char *user_info = NULL;
Expand All @@ -285,7 +303,7 @@ static char *http_auth_basic(const char *username, const char *password)
}

esp_crypto_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info));
digest = calloc(1, strlen(WS_HTTP_BASIC_AUTH) + n + 1);
digest = heap_caps_calloc(1, strlen(WS_HTTP_BASIC_AUTH) + n + 1,memory_type);
if (digest) {
strcpy(digest, WS_HTTP_BASIC_AUTH);
esp_crypto_base64_encode((unsigned char *)digest + 6, n, (size_t *)&out, (const unsigned char *)user_info, strlen(user_info));
Expand Down Expand Up @@ -332,7 +350,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c

if (cfg->username && cfg->password) {
free(cfg->auth);
cfg->auth = http_auth_basic(cfg->username, cfg->password);
cfg->auth = http_auth_basic(cfg->username, cfg->password, client->memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->auth, return ESP_ERR_NO_MEM);
}

Expand Down Expand Up @@ -619,9 +637,35 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand

esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
{
esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client));
int memory_type = 0;
if ((config->memory_type & ~VALID_MEMORY_FLAGS) != 0) {
ESP_LOGE("TAG", "Invalid memory_type flags set.\n");
return NULL;
} else if (config->memory_type == 0) {
memory_type = MALLOC_CAP_DEFAULT; //MALLOC_CAP_DEFAULT
ESP_LOGD("TAG","memory_type is not set, using MALLOC_CAP_DEFAULT memory [%d] versus external [%d].\n",memory_type,MALLOC_CAP_SPIRAM);
} else {
memory_type = config->memory_type;
ESP_LOGD("TAG","memory_type is set to %d \n", config->memory_type);
}

esp_websocket_client_handle_t client = heap_caps_calloc(1, sizeof(struct esp_websocket_client),memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL);

if (config->core){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer it to be introduced in a separated PR. Since we'll have to take into consideration portability among different IDF versions given the proposal of usage xTaskCreatePinnedToCoreWithCaps. Could you please move this to a second PR?

if (config->core >2){
ESP_LOGW("TAG", "Invalid core affinity flag set, using default tskNO_AFFINITY.\n");
client->core = tskNO_AFFINITY;
}
else{
client->core = config->core;
}
}
else {
ESP_LOGW("TAG", "Invalid core affinity flag set, using default tskNO_AFFINITY.\n");
client->core = tskNO_AFFINITY;
}

esp_event_loop_args_t event_args = {
.queue_size = WEBSOCKET_EVENT_QUEUE_SIZE,
.task_name = NULL // no task will be created
Expand All @@ -633,6 +677,8 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
return NULL;
}

client->memory_type = memory_type;

if (config->keep_alive_enable == true) {
client->keep_alive_cfg.keep_alive_enable = true;
client->keep_alive_cfg.keep_alive_idle = (config->keep_alive_idle == 0) ? WEBSOCKET_KEEP_ALIVE_IDLE : config->keep_alive_idle;
Expand All @@ -641,15 +687,15 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
}

if (config->if_name) {
client->if_name = calloc(1, sizeof(struct ifreq) + 1);
client->if_name = heap_caps_calloc(1, sizeof(struct ifreq) + 1,memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->if_name, goto _websocket_init_fail);
memcpy(client->if_name, config->if_name, sizeof(struct ifreq));
}

client->lock = xSemaphoreCreateRecursiveMutex();
ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail);

client->config = calloc(1, sizeof(websocket_config_storage_t));
client->config = heap_caps_calloc(1, sizeof(websocket_config_storage_t),memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);

if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
Expand All @@ -661,7 +707,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
}

if (!config->disable_auto_reconnect && config->reconnect_timeout_ms <= 0) {
client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS;
client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS;
ESP_LOGW(TAG, "`reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_RECONNECT_TIMEOUT_MS);
} else {
client->wait_timeout_ms = config->reconnect_timeout_ms;
Expand Down Expand Up @@ -708,11 +754,11 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
client->errormsg_buffer = NULL;
client->errormsg_size = 0;
#ifndef CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER
client->rx_buffer = malloc(buffer_size);
client->rx_buffer = heap_caps_malloc(buffer_size,client->memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, {
goto _websocket_init_fail;
});
client->tx_buffer = malloc(buffer_size);
client->tx_buffer = heap_caps_malloc(buffer_size,client->memory_type);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, {
goto _websocket_init_fail;
});
Expand Down Expand Up @@ -842,7 +888,7 @@ esp_err_t esp_websocket_client_append_header(esp_websocket_client_handle_t clien

// If no previous headers exist
if (cfg->headers == NULL) {
cfg->headers = (char *)malloc(len);
cfg->headers = (char *)heap_caps_malloc(len,client->memory_type);
if (cfg->headers == NULL) {
ESP_LOGE(TAG, "Failed to allocate...");
return ESP_ERR_NO_MEM;
Expand All @@ -856,7 +902,7 @@ esp_err_t esp_websocket_client_append_header(esp_websocket_client_handle_t clien
size_t new_len = current_len + len;

// Allocate memory for new headers
char *new_headers = (char *)malloc(new_len);
char *new_headers = (char *)heap_caps_malloc(new_len,client->memory_type);
if (new_headers == NULL) {
ESP_LOGE(TAG, "Failed to allocate...");
return ESP_ERR_NO_MEM;
Expand Down Expand Up @@ -1088,7 +1134,8 @@ static void esp_websocket_client_task(void *pv)
if (client->selected_for_destroying == true) {
destroy_and_free_resources(client);
}
vTaskDelete(NULL);
vTaskDeleteWithCaps(client->task_handle);

}

esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
Expand All @@ -1105,12 +1152,72 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
return ESP_FAIL;
}

if (xTaskCreate(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) {
ESP_LOGE(TAG, "Error create websocket task");
return ESP_FAIL;
}
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
if (!client->core) {
// No core affinity
if (!client->memory_type) {
// Default memory
if (xTaskCreatePinnedToCoreWithCaps(esp_websocket_client_task,
client->config->task_name ? client->config->task_name : "websocket_task",
client->config->task_stack,
client,
client->config->task_prio,
&client->task_handle,
tskNO_AFFINITY,
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) != pdTRUE) {
ESP_LOGE(TAG, "Failed to create websocket task without core affinity using default memory");
return ESP_FAIL;
}
ESP_LOGD(TAG, "Websocket task created without core affinity using default memory");
} else {
// Specific memory type without core affinity
if (xTaskCreatePinnedToCoreWithCaps(esp_websocket_client_task,
client->config->task_name ? client->config->task_name : "websocket_task",
client->config->task_stack,
client,
client->config->task_prio,
&client->task_handle,
tskNO_AFFINITY,
client->memory_type) != pdTRUE) {
ESP_LOGE(TAG, "Failed to create websocket task without core affinity using specified memory type");
return ESP_FAIL;
}
ESP_LOGD(TAG, "Websocket task created without core affinity using specified memory type");
}
} else {
// With core affinity
if (!client->memory_type) {
// Default memory with core affinity
if (xTaskCreatePinnedToCoreWithCaps(esp_websocket_client_task,
client->config->task_name ? client->config->task_name : "websocket_task",
client->config->task_stack,
client,
client->config->task_prio,
&client->task_handle,
client->core,
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) != pdTRUE) {
ESP_LOGE(TAG, "Failed to create websocket task with core affinity using default memory (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)");
return ESP_FAIL;
}
ESP_LOGD(TAG, "Websocket task created with core affinity using default memory (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)");
} else {
// Specific memory type with core affinity
if (xTaskCreatePinnedToCoreWithCaps(esp_websocket_client_task,
client->config->task_name ? client->config->task_name : "websocket_task",
client->config->task_stack,
client,
client->config->task_prio,
&client->task_handle,
client->core,
client->memory_type) != pdTRUE) {
ESP_LOGE(TAG, "Failed to create websocket task with core affinity using specified memory type");
return ESP_FAIL;
}
ESP_LOGD(TAG, "Websocket task created with core affinity using specified memory type (%d)", client->memory_type);
}
}


xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
return ESP_OK;
}

Expand Down Expand Up @@ -1143,7 +1250,7 @@ static int esp_websocket_client_send_close(esp_websocket_client_handle_t client,
uint8_t *close_status_data = NULL;
// RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2)
if (total_len >= 2) {
close_status_data = calloc(1, total_len);
close_status_data = heap_caps_calloc(1, total_len,client->memory_type);;
ESP_WS_CLIENT_MEM_CHECK(TAG, close_status_data, return -1);
// RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status
uint16_t *code_network_order = (uint16_t *) close_status_data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ typedef struct {
int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */
size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
int memory_type; /*!< The name of memory region type to be used in the heap_caps_malloc / heap_caps_calloc */
esp_transport_handle_t ext_transport; /*!< External WebSocket tcp_transport handle to the client; or if null, the client will create its own transport handle. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be some kind of leftover.

} esp_websocket_client_config_t;

/**
Expand Down
36 changes: 36 additions & 0 deletions docs/esp_websocket_client/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Features
* Supports WebSocket over TCP, TLS with mbedtls
* Easy to setup with URI
* Multiple instances (Multiple clients in one application)
* Use Buffer and memory as Internal, External or any other CAPS memory allowed.
* Allow Task to use Internal, External or any other CAPS memory allowed.
* Allow Task to be created with or without CPU Core affinity.


Configuration
-------------
Expand Down Expand Up @@ -51,6 +55,38 @@ overridden. Sample:
};
//WebSocket client will connect to websocket.org using port 4567

Memory Usage configurations for TASK and BUFFERs:

.. code:: c

const esp_websocket_client_config_t ws_cfg = {
.uri = "ws://echo.websocket.org:123",
.port = 4567,
.memory_type = MALLOC_CAP_SPIRAM;
};

.. note:: WebSocket client will connect to websocket.org using port 4567
.. note:: Will use the external SPIRAM memory to allocate memory
.. note:: if .memory_type is not defined it will use default caps MALLOC_CAP_DEFAULT
.. note:: If .memory_type is defined it also will be used in the TASK creation




Task Affinity configurations:

.. code:: c

const esp_websocket_client_config_t ws_cfg = {
.uri = "ws://echo.websocket.org:123",
.port = 4567,
.memory_type = MALLOC_CAP_SPIRAM;
.core = 1;
};

.. note:: The client is indifferent to the core if the option is not used. The memory_type is not necessary to definy the core affinity.


TLS
^^^

Expand Down