chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(wss_server)
|
||||
@@ -0,0 +1,64 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# HTTPS Websocket server
|
||||
|
||||
This example creates an HTTPS server with SSL/TLS support using ESP-TLS and employs a simple Websocket request handler. It demonstrates handling multiple clients from the server including:
|
||||
* PING-PONG mechanism
|
||||
* Sending asynchronous messages to all clients
|
||||
|
||||
For more information, refer to the [esp_https_server component documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_https_server.html).
|
||||
|
||||
|
||||
### Websocket support in `http_server`
|
||||
|
||||
Please refer to the documentation of [Websocket server](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/esp_http_server.html#websocket-server) feature in the documentation,
|
||||
or to the description of using websocket handlers in httpd in the [simple ws echo](../../http_server/ws_echo_server/README.md#how-to-use-example) example.
|
||||
|
||||
## How to use example
|
||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32/ESP32-S2/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for power supply and programming
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Certificates
|
||||
|
||||
You will need to approve a security exception in your browser. This is because of a self signed
|
||||
certificate; this will be always the case, unless you preload the CA root into your browser/system
|
||||
as trusted.
|
||||
|
||||
You can generate a new certificate using the OpenSSL command line tool:
|
||||
|
||||
```
|
||||
openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -x509 -days 3650 -out servercert.pem -subj "/CN=ESP32 HTTPS server example" -addext "keyUsage=critical,digitalSignature,keyCertSign"
|
||||
```
|
||||
|
||||
Expiry time and metadata fields can be adjusted in the invocation.
|
||||
|
||||
Please see the openssl man pages (man openssl-req) for more details.
|
||||
|
||||
It is **strongly recommended** to not reuse the example certificate in your application;
|
||||
it is included only for demonstration.
|
||||
@@ -0,0 +1,14 @@
|
||||
set(requires esp_https_server nvs_flash esp_timer esp_netif)
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
list(APPEND requires esp_stubs protocol_examples_common)
|
||||
else()
|
||||
list(APPEND requires esp_wifi esp_eth)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "wss_server_example.c" "keep_alive.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES ${requires}
|
||||
EMBED_TXTFILES "certs/servercert.pem"
|
||||
"certs/prvtkey.pem")
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH
|
||||
JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw
|
||||
h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT
|
||||
aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al
|
||||
3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg
|
||||
0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB
|
||||
vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui
|
||||
f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9
|
||||
Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y
|
||||
JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX
|
||||
49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc
|
||||
+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6
|
||||
pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D
|
||||
0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG
|
||||
YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV
|
||||
MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL
|
||||
CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin
|
||||
7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1
|
||||
noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8
|
||||
4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g
|
||||
Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/
|
||||
nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3
|
||||
q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2
|
||||
lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB
|
||||
jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr
|
||||
v/t+MeGJP/0Zw8v/X2CFll96
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDOzCCAiOgAwIBAgIUGtx0JiogvT3DlTnZ3+tAT7Tr5JEwDQYJKoZIhvcNAQEL
|
||||
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMjUw
|
||||
NDAyMDcwNzE1WhcNMzUwMzMxMDcwNzE1WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ
|
||||
UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T
|
||||
sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k
|
||||
qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd
|
||||
GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4
|
||||
sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb
|
||||
jAn4PCuR2akdF4G8WLUeDWECAwEAAaNjMGEwHQYDVR0OBBYEFMnmdJKOEepXrHI/
|
||||
ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMA0GCSqGSIb3DQEBCwUAA4IBAQAT
|
||||
lpY3s1IAV369xl7cri72ErqFRBveKvJCaq/1l0FSH1w/u3SxABQw9SH29Lg0hbAa
|
||||
jotcCMo4XZpmKSsQn0Zs4ZgKh1zk90JZVtssWuU3y8ftq9t4JjskRHTF19yp7CCC
|
||||
zKIHPlCyYjCgw3tWhrsWa95dF3ebVrPkuUfd6CCxe8OB4EC74svI+uuxA7Ud9Jtx
|
||||
Dno5yFu7NKpRLSwFqJnxbU0bZp434v2vcexkbvZP0Z2AW+C2L+x90xcP5rW2vL+a
|
||||
S0bj0quNH43cGKbBFzE9cLy04xsGlOqzYSQcFbGeT9LyQBLFZtp6QlC97ZsQ78fG
|
||||
A2PRPwcF0QCvQrT42b8y
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
||||
esp_stubs:
|
||||
path: ${IDF_PATH}/examples/protocols/linux_stubs/esp_stubs
|
||||
rules:
|
||||
- if: "target in [linux]"
|
||||
@@ -0,0 +1,239 @@
|
||||
/* Keep Alive engine for wss server example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <esp_log.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "keep_alive.h"
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_system.h>
|
||||
#include "esp_timer.h"
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
typedef enum {
|
||||
NO_CLIENT = 0,
|
||||
CLIENT_FD_ADD,
|
||||
CLIENT_FD_REMOVE,
|
||||
CLIENT_UPDATE,
|
||||
CLIENT_ACTIVE,
|
||||
STOP_TASK,
|
||||
} client_fd_action_type_t;
|
||||
|
||||
typedef struct {
|
||||
client_fd_action_type_t type;
|
||||
int fd;
|
||||
uint64_t last_seen;
|
||||
} client_fd_action_t;
|
||||
|
||||
typedef struct wss_keep_alive_storage {
|
||||
size_t max_clients;
|
||||
wss_check_client_alive_cb_t check_client_alive_cb;
|
||||
wss_check_client_alive_cb_t client_not_alive_cb;
|
||||
size_t keep_alive_period_ms;
|
||||
size_t not_alive_after_ms;
|
||||
void * user_ctx;
|
||||
QueueHandle_t q;
|
||||
client_fd_action_t clients[];
|
||||
} wss_keep_alive_storage_t;
|
||||
|
||||
typedef struct wss_keep_alive_storage* wss_keep_alive_t;
|
||||
|
||||
static const char *TAG = "wss_keep_alive";
|
||||
|
||||
static uint64_t _tick_get_ms(void)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
#else
|
||||
return esp_timer_get_time() / 1000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Goes over active clients to find out how long we could sleep before checking who's alive
|
||||
static uint64_t get_max_delay(wss_keep_alive_t h)
|
||||
{
|
||||
int64_t check_after_ms = 30000; // max delay, no need to check anyone
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == CLIENT_ACTIVE) {
|
||||
uint64_t check_this_client_at = h->clients[i].last_seen + h->keep_alive_period_ms;
|
||||
if (check_this_client_at < check_after_ms + _tick_get_ms()) {
|
||||
check_after_ms = check_this_client_at - _tick_get_ms();
|
||||
if (check_after_ms < 0) {
|
||||
check_after_ms = 1000; // min delay, some client(s) not responding already
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return check_after_ms;
|
||||
}
|
||||
|
||||
|
||||
static bool update_client(wss_keep_alive_t h, int sockfd, uint64_t timestamp)
|
||||
{
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == CLIENT_ACTIVE && h->clients[i].fd == sockfd) {
|
||||
h->clients[i].last_seen = timestamp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool remove_client(wss_keep_alive_t h, int sockfd)
|
||||
{
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == CLIENT_ACTIVE && h->clients[i].fd == sockfd) {
|
||||
h->clients[i].type = NO_CLIENT;
|
||||
h->clients[i].fd = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool add_new_client(wss_keep_alive_t h,int sockfd)
|
||||
{
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == NO_CLIENT) {
|
||||
h->clients[i].type = CLIENT_ACTIVE;
|
||||
h->clients[i].fd = sockfd;
|
||||
h->clients[i].last_seen = _tick_get_ms();
|
||||
return true; // success
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void keep_alive_task(void* arg)
|
||||
{
|
||||
wss_keep_alive_storage_t *keep_alive_storage = arg;
|
||||
bool run_task = true;
|
||||
client_fd_action_t client_action;
|
||||
while (run_task) {
|
||||
if (xQueueReceive(keep_alive_storage->q, (void *) &client_action,
|
||||
get_max_delay(keep_alive_storage) / portTICK_PERIOD_MS) == pdTRUE) {
|
||||
switch (client_action.type) {
|
||||
case CLIENT_FD_ADD:
|
||||
if (!add_new_client(keep_alive_storage, client_action.fd)) {
|
||||
ESP_LOGE(TAG, "Cannot add new client");
|
||||
}
|
||||
break;
|
||||
case CLIENT_FD_REMOVE:
|
||||
if (!remove_client(keep_alive_storage, client_action.fd)) {
|
||||
ESP_LOGE(TAG, "Cannot remove client fd:%d", client_action.fd);
|
||||
}
|
||||
break;
|
||||
case CLIENT_UPDATE:
|
||||
if (!update_client(keep_alive_storage, client_action.fd, client_action.last_seen)) {
|
||||
ESP_LOGE(TAG, "Cannot find client fd:%d", client_action.fd);
|
||||
}
|
||||
break;
|
||||
case STOP_TASK:
|
||||
run_task = false;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unexpected client action");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// timeout: check if PING message needed
|
||||
for (int i=0; i<keep_alive_storage->max_clients; ++i) {
|
||||
if (keep_alive_storage->clients[i].type == CLIENT_ACTIVE) {
|
||||
if (keep_alive_storage->clients[i].last_seen + keep_alive_storage->keep_alive_period_ms <= _tick_get_ms()) {
|
||||
ESP_LOGD(TAG, "Haven't seen the client (fd=%d) for a while", keep_alive_storage->clients[i].fd);
|
||||
if (keep_alive_storage->clients[i].last_seen + keep_alive_storage->not_alive_after_ms <= _tick_get_ms()) {
|
||||
ESP_LOGE(TAG, "Client (fd=%d) not alive!", keep_alive_storage->clients[i].fd);
|
||||
keep_alive_storage->client_not_alive_cb(keep_alive_storage, keep_alive_storage->clients[i].fd);
|
||||
} else {
|
||||
keep_alive_storage->check_client_alive_cb(keep_alive_storage, keep_alive_storage->clients[i].fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vQueueDelete(keep_alive_storage->q);
|
||||
free(keep_alive_storage);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
wss_keep_alive_t wss_keep_alive_start(wss_keep_alive_config_t *config)
|
||||
{
|
||||
size_t queue_size = config->max_clients/2;
|
||||
size_t client_list_size = config->max_clients + queue_size;
|
||||
wss_keep_alive_t keep_alive_storage = calloc(1,
|
||||
sizeof(wss_keep_alive_storage_t) + client_list_size* sizeof(client_fd_action_t));
|
||||
if (keep_alive_storage == NULL) {
|
||||
return false;
|
||||
}
|
||||
keep_alive_storage->check_client_alive_cb = config->check_client_alive_cb;
|
||||
keep_alive_storage->client_not_alive_cb = config->client_not_alive_cb;
|
||||
keep_alive_storage->max_clients = config->max_clients;
|
||||
keep_alive_storage->not_alive_after_ms = config->not_alive_after_ms;
|
||||
keep_alive_storage->keep_alive_period_ms = config->keep_alive_period_ms;
|
||||
keep_alive_storage->user_ctx = config->user_ctx;
|
||||
keep_alive_storage->q = xQueueCreate(queue_size, sizeof(client_fd_action_t));
|
||||
if (xTaskCreate(keep_alive_task, "keep_alive_task", config->task_stack_size,
|
||||
keep_alive_storage, config->task_prio, NULL) != pdTRUE) {
|
||||
wss_keep_alive_stop(keep_alive_storage);
|
||||
return false;
|
||||
}
|
||||
return keep_alive_storage;
|
||||
}
|
||||
|
||||
void wss_keep_alive_stop(wss_keep_alive_t h)
|
||||
{
|
||||
client_fd_action_t stop = { .type = STOP_TASK };
|
||||
xQueueSendToBack(h->q, &stop, 0);
|
||||
// internal structs will be de-allocated in the task
|
||||
}
|
||||
|
||||
esp_err_t wss_keep_alive_add_client(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_FD_ADD};
|
||||
if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t wss_keep_alive_remove_client(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_FD_REMOVE};
|
||||
if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t wss_keep_alive_client_is_active(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_UPDATE,
|
||||
.last_seen = _tick_get_ms()};
|
||||
if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
|
||||
}
|
||||
|
||||
void wss_keep_alive_set_user_ctx(wss_keep_alive_t h, void *ctx)
|
||||
{
|
||||
h->user_ctx = ctx;
|
||||
}
|
||||
|
||||
void* wss_keep_alive_get_user_ctx(wss_keep_alive_t h)
|
||||
{
|
||||
return h->user_ctx;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/* Keep Alive engine for wss server example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define KEEP_ALIVE_CONFIG_DEFAULT() \
|
||||
{ \
|
||||
.max_clients = 10, \
|
||||
.task_stack_size = 2048, \
|
||||
.task_prio = tskIDLE_PRIORITY+1, \
|
||||
.keep_alive_period_ms = 5000, \
|
||||
.not_alive_after_ms = 10000, \
|
||||
}
|
||||
|
||||
struct wss_keep_alive_storage;
|
||||
typedef struct wss_keep_alive_storage* wss_keep_alive_t;
|
||||
typedef bool (*wss_check_client_alive_cb_t)(wss_keep_alive_t h, int fd);
|
||||
typedef bool (*wss_client_not_alive_cb_t)(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Confiuration struct
|
||||
*/
|
||||
typedef struct {
|
||||
size_t max_clients; /*!< max number of clients */
|
||||
size_t task_stack_size; /*!< stack size of the created task */
|
||||
size_t task_prio; /*!< priority of the created task */
|
||||
size_t keep_alive_period_ms; /*!< check every client after this time */
|
||||
size_t not_alive_after_ms; /*!< consider client not alive after this time */
|
||||
wss_check_client_alive_cb_t check_client_alive_cb; /*!< callback function to check if client is alive */
|
||||
wss_client_not_alive_cb_t client_not_alive_cb; /*!< callback function to notify that the client is not alive */
|
||||
void *user_ctx; /*!< user context available in the keep-alive handle */
|
||||
} wss_keep_alive_config_t;
|
||||
|
||||
/**
|
||||
* @brief Adds a new client to internal set of clients to keep an eye on
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param fd socket file descriptor for this client
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t wss_keep_alive_add_client(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Removes this client from the set
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param fd socket file descriptor for this client
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t wss_keep_alive_remove_client(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Notify that this client is alive
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param fd socket file descriptor for this client
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
|
||||
esp_err_t wss_keep_alive_client_is_active(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Starts keep-alive engine
|
||||
*
|
||||
* @param config keep-alive configuration
|
||||
* @return keep alive handle
|
||||
*/
|
||||
wss_keep_alive_t wss_keep_alive_start(wss_keep_alive_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Stops keep-alive engine
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
*/
|
||||
void wss_keep_alive_stop(wss_keep_alive_t h);
|
||||
|
||||
/**
|
||||
* @brief Sets user defined context
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param ctx user context
|
||||
*/
|
||||
void wss_keep_alive_set_user_ctx(wss_keep_alive_t h, void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Gets user defined context
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @return ctx user context
|
||||
*/
|
||||
void* wss_keep_alive_get_user_ctx(wss_keep_alive_t h);
|
||||
@@ -0,0 +1,345 @@
|
||||
/* Simple HTTP + SSL + WS Server Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_system.h>
|
||||
#include "esp_eth.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "lwip/sockets.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_https_server.h>
|
||||
#include "esp_key_config.h"
|
||||
#include "keep_alive.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if !CONFIG_HTTPD_WS_SUPPORT
|
||||
#error This example cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
||||
#endif
|
||||
|
||||
struct async_resp_arg {
|
||||
httpd_handle_t hd;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static const char *TAG = "wss_echo_server";
|
||||
static const size_t max_clients = 4;
|
||||
|
||||
static esp_err_t ws_handler(httpd_req_t *req)
|
||||
{
|
||||
if (req->method == HTTP_GET) {
|
||||
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
|
||||
return ESP_OK;
|
||||
}
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
uint8_t *buf = NULL;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
|
||||
// First receive the full ws message
|
||||
/* Set max_len = 0 to get the frame len */
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
|
||||
if (ws_pkt.len) {
|
||||
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
|
||||
buf = calloc(1, ws_pkt.len + 1);
|
||||
if (buf == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to calloc memory for buf");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ws_pkt.payload = buf;
|
||||
/* Set max_len = ws_pkt.len to get the frame payload */
|
||||
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
// If it was a PONG, update the keep-alive
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_PONG) {
|
||||
ESP_LOGD(TAG, "Received PONG message");
|
||||
free(buf);
|
||||
return wss_keep_alive_client_is_active(httpd_get_global_user_ctx(req->handle),
|
||||
httpd_req_to_sockfd(req));
|
||||
|
||||
// If it was a TEXT message, just echo it back
|
||||
} else if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_PING || ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
|
||||
ESP_LOGI(TAG, "Received packet with message: %s", ws_pkt.payload);
|
||||
} else if (ws_pkt.type == HTTPD_WS_TYPE_PING) {
|
||||
// Response PONG packet to peer
|
||||
ESP_LOGI(TAG, "Got a WS PING frame, Replying PONG");
|
||||
ws_pkt.type = HTTPD_WS_TYPE_PONG;
|
||||
} else if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
|
||||
// Response CLOSE packet with no payload to peer
|
||||
ws_pkt.len = 0;
|
||||
ws_pkt.payload = NULL;
|
||||
}
|
||||
ret = httpd_ws_send_frame(req, &ws_pkt);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
|
||||
}
|
||||
ESP_LOGI(TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", req->handle,
|
||||
httpd_req_to_sockfd(req), httpd_ws_get_fd_info(req->handle, httpd_req_to_sockfd(req)));
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
free(buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t wss_open_fd(httpd_handle_t hd, int sockfd)
|
||||
{
|
||||
ESP_LOGI(TAG, "New client connected %d", sockfd);
|
||||
wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
|
||||
return wss_keep_alive_add_client(h, sockfd);
|
||||
}
|
||||
|
||||
void wss_close_fd(httpd_handle_t hd, int sockfd)
|
||||
{
|
||||
ESP_LOGI(TAG, "Client disconnected %d", sockfd);
|
||||
wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
|
||||
wss_keep_alive_remove_client(h, sockfd);
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
static const httpd_uri_t ws = {
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_handler,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true,
|
||||
.handle_ws_control_frames = true
|
||||
};
|
||||
|
||||
|
||||
static void send_hello(void *arg)
|
||||
{
|
||||
static const char * data = "Hello client";
|
||||
struct async_resp_arg *resp_arg = arg;
|
||||
httpd_handle_t hd = resp_arg->hd;
|
||||
int fd = resp_arg->fd;
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = (uint8_t*)data;
|
||||
ws_pkt.len = strlen(data);
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
|
||||
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
|
||||
free(resp_arg);
|
||||
}
|
||||
|
||||
static void send_ping(void *arg)
|
||||
{
|
||||
struct async_resp_arg *resp_arg = arg;
|
||||
httpd_handle_t hd = resp_arg->hd;
|
||||
int fd = resp_arg->fd;
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = NULL;
|
||||
ws_pkt.len = 0;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_PING;
|
||||
|
||||
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
|
||||
free(resp_arg);
|
||||
}
|
||||
|
||||
bool client_not_alive_cb(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
ESP_LOGE(TAG, "Client not alive, closing fd %d", fd);
|
||||
httpd_sess_trigger_close(wss_keep_alive_get_user_ctx(h), fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_client_alive_cb(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
ESP_LOGD(TAG, "Checking if client (fd=%d) is alive", fd);
|
||||
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||
assert(resp_arg != NULL);
|
||||
resp_arg->hd = wss_keep_alive_get_user_ctx(h);
|
||||
resp_arg->fd = fd;
|
||||
|
||||
if (httpd_queue_work(resp_arg->hd, send_ping, resp_arg) == ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static httpd_handle_t start_wss_echo_server(void)
|
||||
{
|
||||
// Prepare keep-alive engine
|
||||
wss_keep_alive_config_t keep_alive_config = KEEP_ALIVE_CONFIG_DEFAULT();
|
||||
keep_alive_config.max_clients = max_clients;
|
||||
keep_alive_config.client_not_alive_cb = client_not_alive_cb;
|
||||
keep_alive_config.check_client_alive_cb = check_client_alive_cb;
|
||||
wss_keep_alive_t keep_alive = wss_keep_alive_start(&keep_alive_config);
|
||||
|
||||
// Start the httpd server
|
||||
httpd_handle_t server = NULL;
|
||||
ESP_LOGI(TAG, "Starting server");
|
||||
|
||||
httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
/* Use non-privileged port on Linux since port 443 requires root */
|
||||
conf.port_secure = 8443;
|
||||
#endif
|
||||
conf.httpd.max_open_sockets = max_clients;
|
||||
conf.httpd.global_user_ctx = keep_alive;
|
||||
conf.httpd.open_fn = wss_open_fd;
|
||||
conf.httpd.close_fn = wss_close_fd;
|
||||
|
||||
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
|
||||
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
|
||||
conf.servercert = servercert_start;
|
||||
conf.servercert_len = servercert_end - servercert_start;
|
||||
|
||||
extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
|
||||
extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
|
||||
static esp_key_config_t server_key = {
|
||||
.source = ESP_KEY_SOURCE_BUFFER,
|
||||
.buffer = {
|
||||
.data = prvtkey_pem_start,
|
||||
}
|
||||
};
|
||||
server_key.buffer.len = prvtkey_pem_end - prvtkey_pem_start;
|
||||
conf.server_key = &server_key;
|
||||
|
||||
esp_err_t ret = httpd_ssl_start(&server, &conf);
|
||||
if (ESP_OK != ret) {
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set URI handlers
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
wss_keep_alive_set_user_ctx(keep_alive, server);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
static esp_err_t stop_wss_echo_server(httpd_handle_t server)
|
||||
{
|
||||
// Stop keep alive thread
|
||||
wss_keep_alive_stop(httpd_get_global_user_ctx(server));
|
||||
// Stop the httpd server
|
||||
return httpd_ssl_stop(server);
|
||||
}
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
if (stop_wss_echo_server(*server) == ESP_OK) {
|
||||
*server = NULL;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to stop https server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
*server = start_wss_echo_server();
|
||||
}
|
||||
}
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
// Get all clients and send async message
|
||||
static void wss_server_send_messages(httpd_handle_t* server)
|
||||
{
|
||||
bool send_messages = true;
|
||||
|
||||
// Send async message to all connected clients that use websocket protocol every 10 seconds
|
||||
while (send_messages) {
|
||||
vTaskDelay(10000 / portTICK_PERIOD_MS);
|
||||
|
||||
if (!*server) { // httpd might not have been created by now
|
||||
continue;
|
||||
}
|
||||
size_t clients = max_clients;
|
||||
int client_fds[max_clients];
|
||||
if (httpd_get_client_list(*server, &clients, client_fds) == ESP_OK) {
|
||||
for (size_t i=0; i < clients; ++i) {
|
||||
int sock = client_fds[i];
|
||||
if (httpd_ws_get_fd_info(*server, sock) == HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
ESP_LOGI(TAG, "Active client (fd=%d) -> sending async message", sock);
|
||||
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||
assert(resp_arg != NULL);
|
||||
resp_arg->hd = *server;
|
||||
resp_arg->fd = sock;
|
||||
if (httpd_queue_work(resp_arg->hd, send_hello, resp_arg) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_queue_work failed!");
|
||||
send_messages = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "httpd_get_client_list failed!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* Register event handlers to start server when Wi-Fi or Ethernet is connected,
|
||||
* and stop server when disconnection happens.
|
||||
*/
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
/* On Linux, start the server directly since there are no WiFi/Ethernet events */
|
||||
server = start_wss_echo_server();
|
||||
#endif // CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
/* This function demonstrates periodic sending Websocket messages
|
||||
* to all connected clients to this server
|
||||
*/
|
||||
wss_server_send_messages(&server);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import websocket
|
||||
from common_test_methods import get_env_config_variable
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
OPCODE_TEXT = 0x1
|
||||
OPCODE_BIN = 0x2
|
||||
OPCODE_PING = 0x9
|
||||
OPCODE_PONG = 0xA
|
||||
CORRECT_ASYNC_DATA = 'Hello client'
|
||||
|
||||
|
||||
class WsClient:
|
||||
def __init__(self, ip, port, ca_file): # type: (str, int, str) -> None
|
||||
self.port = port
|
||||
self.ip = ip
|
||||
sslopt = {'ca_certs': ca_file, 'check_hostname': False}
|
||||
self.ws = websocket.WebSocket(sslopt=sslopt)
|
||||
# Set timeout to 10 seconds to avoid connection failure at the time of handshake
|
||||
self.ws.settimeout(10)
|
||||
|
||||
def __enter__(self): # type: ignore
|
||||
self.ws.connect(f'wss://{self.ip}:{self.port}/ws')
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): # type: ignore
|
||||
self.ws.close()
|
||||
|
||||
def read(self) -> Any:
|
||||
return self.ws.recv_data(control_frame=True)
|
||||
|
||||
def write(self, data: str, opcode: int = OPCODE_TEXT) -> Any:
|
||||
if opcode == OPCODE_PING:
|
||||
return self.ws.ping(data)
|
||||
if opcode == OPCODE_PONG:
|
||||
return self.ws.pong(data)
|
||||
return self.ws.send(data)
|
||||
|
||||
|
||||
class wss_client_thread(threading.Thread):
|
||||
def __init__(self, ip, port, ca_file): # type: (str, int, str) -> None
|
||||
threading.Thread.__init__(self)
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.ca_file = ca_file
|
||||
self.start_time = time.time()
|
||||
self.exc = None
|
||||
self.data = 'Espressif'
|
||||
self.async_response = False
|
||||
|
||||
def run(self) -> None:
|
||||
with WsClient(self.ip, self.port, self.ca_file) as ws:
|
||||
while True:
|
||||
try:
|
||||
opcode, data = ws.read()
|
||||
data = data.decode('UTF-8')
|
||||
|
||||
if opcode == OPCODE_PING:
|
||||
ws.write(data=self.data, opcode=OPCODE_PONG)
|
||||
if opcode == OPCODE_TEXT:
|
||||
if data == CORRECT_ASYNC_DATA:
|
||||
self.async_response = True
|
||||
logging.info(f'Thread {self.name} obtained correct async message')
|
||||
# Keep sending pong to update the keepalive in the server
|
||||
if (time.time() - self.start_time) > 20:
|
||||
break
|
||||
except Exception as e:
|
||||
logging.info('Failed to connect to the client and read async data')
|
||||
self.exc = e # type: ignore
|
||||
if self.async_response is not True:
|
||||
self.exc = RuntimeError('Failed to obtain correct async data') # type: ignore
|
||||
|
||||
def join(self, timeout: float | None = 0) -> None:
|
||||
threading.Thread.join(self)
|
||||
if self.exc:
|
||||
raise self.exc
|
||||
|
||||
|
||||
def test_multiple_client_keep_alive_and_async_response(ip, port, ca_file): # type: (str, int, str) -> None
|
||||
threads = []
|
||||
for _ in range(3):
|
||||
try:
|
||||
thread = wss_client_thread(ip, port, ca_file)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
except OSError:
|
||||
logging.info('Error: unable to start thread')
|
||||
# keep delay of 5 seconds between two connections to avoid handshake timeout
|
||||
time.sleep(5)
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
|
||||
@pytest.mark.wifi_router
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_protocol_https_wss_server(dut: Dut) -> None:
|
||||
# Get binary file
|
||||
binary_file = os.path.join(dut.app.binary_path, 'wss_server.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info(f'https_wss_server_bin_size : {bin_size // 1024}KB')
|
||||
|
||||
logging.info('Starting wss_server test app')
|
||||
|
||||
logging.info('Waiting to connect with AP')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
|
||||
dut.expect('Please input ssid password:')
|
||||
env_name = 'wifi_router'
|
||||
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
|
||||
ap_password = get_env_config_variable(env_name, 'ap_password')
|
||||
dut.write(f'{ap_ssid} {ap_password}')
|
||||
# Parse IP address of STA
|
||||
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
|
||||
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode()
|
||||
|
||||
logging.info(f'Got IP : {got_ip}')
|
||||
logging.info(f'Got Port : {got_port}')
|
||||
|
||||
ca_file = os.path.join(os.path.dirname(__file__), 'main', 'certs', 'servercert.pem')
|
||||
# Start ws server test
|
||||
with WsClient(got_ip, int(got_port), ca_file) as ws:
|
||||
# Check for echo
|
||||
DATA = 'Espressif'
|
||||
dut.expect('performing session handshake')
|
||||
client_fd = int(dut.expect(r'New client connected (\d+)', timeout=30)[1].decode())
|
||||
ws.write(data=DATA, opcode=OPCODE_TEXT)
|
||||
dut.expect(rf'Received packet with message: {DATA}')
|
||||
opcode, data = ws.read()
|
||||
data = data.decode('UTF-8')
|
||||
if data != DATA:
|
||||
raise RuntimeError('Failed to receive the correct echo response.')
|
||||
logging.info('Correct echo response obtained from the wss server')
|
||||
|
||||
# Test for PING
|
||||
logging.info('Testing for send PING')
|
||||
ws.write(data=DATA, opcode=OPCODE_PING)
|
||||
dut.expect('Got a WS PING frame, Replying PONG')
|
||||
opcode, data = ws.read()
|
||||
data = data.decode('UTF-8')
|
||||
if data != DATA or opcode != OPCODE_PONG:
|
||||
raise RuntimeError('Failed to receive the PONG response')
|
||||
logging.info('Passed the test for PING')
|
||||
|
||||
# Test for keepalive
|
||||
logging.info('Testing for keep alive (approx time = 20s)')
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
opcode, data = ws.read()
|
||||
if opcode == OPCODE_PING:
|
||||
ws.write(data='Espressif', opcode=OPCODE_PONG)
|
||||
logging.info('Received PING, replying PONG (to update the keepalive)')
|
||||
# Keep sending pong to update the keepalive in the server
|
||||
if (time.time() - start_time) > 20:
|
||||
break
|
||||
except Exception:
|
||||
logging.info('Failed the test for keep alive,\nthe client got abruptly disconnected')
|
||||
raise
|
||||
|
||||
# keepalive timeout is 10 seconds so do not respond for (10 + 1) seconds
|
||||
logging.info(
|
||||
'Testing if client is disconnected if it does not respond for 10s '
|
||||
'i.e. keep_alive timeout (approx time = 11s)'
|
||||
)
|
||||
try:
|
||||
dut.expect(f'Client not alive, closing fd {client_fd}', timeout=20)
|
||||
dut.expect(f'Client disconnected {client_fd}')
|
||||
except Exception:
|
||||
logging.info('ENV_ERROR:Failed the test for keep alive,\nthe connection was not closed after timeout')
|
||||
|
||||
time.sleep(11)
|
||||
logging.info('Passed the test for keep alive')
|
||||
|
||||
# Test keep alive and async response for multiple simultaneous client connections
|
||||
logging.info('Testing for multiple simultaneous client connections (approx time = 30s)')
|
||||
test_multiple_client_keep_alive_and_async_response(got_ip, int(got_port), ca_file)
|
||||
logging.info('Passed the test for multiple simultaneous client connections')
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
Reference in New Issue
Block a user