Merge branch 'feature/add_protocol_http_example_tests' into 'master'

Feature/add protocol http example tests

Closes IDF-1154

See merge request espressif/esp-idf!11967
pull/6828/head
Mahavir Jain 2021-03-16 08:01:59 +00:00
commit e7d37aa7db
14 zmienionych plików z 683 dodań i 35 usunięć

Wyświetl plik

@ -0,0 +1,72 @@
#!/usr/bin/env python
#
# Copyright 2021 Espressif Systems (Shanghai) CO LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import http.client
import os
import tiny_test_fw
import ttfw_idf
from tiny_test_fw import Utility
HTTP_OK = 200
TEST_SERVER = 'http2.golang.org'
def is_test_server_available(): # type: () -> bool
# 443 - default https port
conn = http.client.HTTPSConnection(TEST_SERVER, 443, timeout=10)
conn.request('GET', '/')
resp = conn.getresponse()
conn.close()
if resp.status == HTTP_OK:
return True
return False
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
def test_examples_protocol_http2_request(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
"""
steps: |
1. join AP
2. connect to http2.golang.org
3. send http2 request
4. send http2 put response
"""
dut1 = env.get_dut('http2_request', 'examples/protocols/http2_request', dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, 'http2-request.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('http2_request_bin_size', '{}KB'.format(bin_size // 1024))
# start the test
# check if test server is avilable
test_server_available = is_test_server_available()
# Skip the test if the server test server (http2.golang.org) is not available at the moment.
if test_server_available:
dut1.start_app()
# check for connection
dut1.expect('Connection done', timeout=30)
# check for echo response
dut1.expect('[echo-response] HELLO WORLD', timeout=30)
dut1.expect('[echo-response] Frame fully received')
dut1.expect('[echo-response] Stream Closed')
# check for get response
dut1.expect('[get-response] Frame fully received')
else:
Utility.console_log('test server \"{0}\" is not available at the moment.\nSkipping the test with status = success.'.format(TEST_SERVER))
if __name__ == '__main__':
test_examples_protocol_http2_request() # pylint: disable=no-value-for-parameter

Wyświetl plik

@ -0,0 +1,11 @@
CONFIG_ESP32_SPIRAM_SUPPORT=y
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y

Wyświetl plik

@ -0,0 +1,52 @@
#!/usr/bin/env python
#
# Copyright 2021 Espressif Systems (Shanghai) CO LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import tiny_test_fw
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
def test_examples_protocol_http_request(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
"""
steps: |
1. join AP
2. connect to example.com
3. check conneciton success
"""
dut1 = env.get_dut('http_request', 'examples/protocols/http_request', dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, 'http-request.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('http_request_bin_size', '{}KB'.format(bin_size // 1024))
# start test
dut1.start_app()
dut1.expect(re.compile(r'DNS lookup succeeded.'))
# check if connected or not
dut1.expect(' ... connected', timeout=60)
dut1.expect(' ... socket send success')
dut1.expect(' ... set socket receiving timeout success')
# check server response
dut1.expect(re.compile(r'HTTP/1.0 200 OK'))
# read from the socket completed
dut1.expect('... done reading from socket. Last read return=0 errno=128')
dut1.expect(re.compile(r'(\d)...'))
if __name__ == '__main__':
test_examples_protocol_http_request() # pylint: disable=no-value-for-parameter

Wyświetl plik

@ -0,0 +1,9 @@
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y

Wyświetl plik

@ -0,0 +1,147 @@
#!/usr/bin/env python
#
# Copyright 2021 Espressif Systems (Shanghai) CO LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import hashlib
import http.client
import os
import re
import tiny_test_fw
import ttfw_idf
from idf_http_server_test import adder as client
from tiny_test_fw import Utility
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_protocol_http_server_file_serving(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
# Acquire DUT
dut1 = env.get_dut('http file_serving', 'examples/protocols/http_server/file_serving', dut_class=ttfw_idf.ESP32DUT)
# Get binary file
binary_file = os.path.join(dut1.app.binary_path, 'file_server.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('file_server_bin_size', '{}KB'.format(bin_size // 1024))
Utility.console_log('Erasing the flash on the chip')
# erase the flash
dut1.erase_flash()
# Upload binary and start testing
Utility.console_log('Starting http file serving simple test app')
dut1.start_app()
# Parse IP address of STA
Utility.console_log('Waiting to connect with AP')
got_ip = dut1.expect(re.compile(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)'), timeout=30)[0]
# Expected logs
dut1.expect('Initializing SPIFFS', timeout=30)
got_port = dut1.expect(re.compile(r"Starting HTTP Server on port: '(\d+)'"), timeout=30)[0]
Utility.console_log('Got IP : ' + got_ip)
Utility.console_log('Got Port : ' + got_port)
# Run test script
conn = client.start_session(got_ip, got_port)
# upload a file onto the server
upload_data = 'Test data to be sent to the server'
upload_file_name = 'example.txt'
upload_file_hash = hashlib.md5(upload_data.encode('UTF-8'))
upload_file_digest = upload_file_hash.digest()
Utility.console_log('\nTesting the uploading of file on the file server')
client.postreq(conn, '/upload/' + str(upload_file_name), upload_data)
try:
dut1.expect('File reception complete', timeout=10)
except Exception:
Utility.console_log('Failed the test to upload file on the file server')
raise
Utility.console_log('Passed the test to uploaded file on the file server')
# Download the uploaded file from the file server
Utility.console_log("\nTesting for Download of \"existing\" file from the file server")
download_data = client.getreq(conn, '/' + str(upload_file_name))
try:
dut1.expect('File sending complete', timeout=10)
except Exception:
Utility.console_log('Failed the test to download existing file from the file server')
raise
Utility.console_log('Passed the test to downloaded existing file from the file server')
download_file_hash = hashlib.md5(download_data)
download_file_digest = download_file_hash.digest()
if download_file_digest != upload_file_digest:
raise RuntimeError('The md5 hash of the downloaded file does not match with that of the uploaded file')
# Upload existing file on the file server
Utility.console_log("\nTesting the upload of \"already existing\" file on the file server")
client.postreq(conn, '/upload/' + str(upload_file_name), data=None)
try:
dut1.expect('File already exists : /spiffs/' + str(upload_file_name), timeout=10)
except Exception:
Utility.console_log('Failed the test for uploading existing file on the file server')
raise
Utility.console_log('Passed the test for uploading existing file on the file server')
# Previous URI was an invalid URI so the server should have closed the connection.
# Trying to send request to the server
try:
client.getreq(conn, '/')
except http.client.RemoteDisconnected:
# It is correct behavior that the connection was closed by the server
pass
except Exception:
Utility.console_log('Connection was not closed successfully by the server after last invalid URI')
raise
conn = client.start_session(got_ip, got_port)
# Delete the existing file from the file server
Utility.console_log("\nTesting the deletion of \"existing\" file on the file server")
client.postreq(conn, '/delete/' + str(upload_file_name), data=None)
try:
dut1.expect('Deleting file : /' + str(upload_file_name), timeout=10)
except Exception:
Utility.console_log('Failed the test for deletion of existing file on the file server')
raise
Utility.console_log('Passed the test for deletion of existing file on the file server')
conn = client.start_session(got_ip, got_port)
# Try to delete non existing file from the file server
Utility.console_log("\nTesting the deletion of \"non existing\" file on the file server")
client.postreq(conn, '/delete/' + str(upload_file_name), data=None)
try:
dut1.expect('File does not exist : /' + str(upload_file_name), timeout=10)
except Exception:
Utility.console_log('Failed the test for deleting non existing file on the file server')
raise
Utility.console_log('Passed the test for deleting non existing file on the file server')
conn = client.start_session(got_ip, got_port)
# Try to download non existing file from the file server
Utility.console_log("\nTesting for Download of \"non existing\" file from the file server")
download_data = client.getreq(conn, '/' + str(upload_file_name))
try:
dut1.expect('Failed to stat file : /spiffs/' + str(upload_file_name), timeout=10)
except Exception:
Utility.console_log('Failed the test to download non existing file from the file server')
raise
Utility.console_log('Passed the test to downloaded non existing file from the file server')
if __name__ == '__main__':
test_examples_protocol_http_server_file_serving() # pylint: disable=no-value-for-parameter

Wyświetl plik

@ -475,7 +475,7 @@ esp_err_t start_file_server(const char *base_path)
* target URIs which match the wildcard scheme */
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_LOGI(TAG, "Starting HTTP Server");
ESP_LOGI(TAG, "Starting HTTP Server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start file server!");
return ESP_FAIL;

Wyświetl plik

@ -1,3 +1,3 @@
CONFIG_EXAMPLE_MOUNT_SD_CARD=y
CONFIG_EXAMPLE_FORMAT_IF_MOUNT_SDCARD_FAILED=y
CONFIG_EXAMPLE_USE_SDMMC_HOST=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"

Wyświetl plik

@ -0,0 +1,3 @@
CONFIG_EXAMPLE_MOUNT_SD_CARD=y
CONFIG_EXAMPLE_FORMAT_IF_MOUNT_SDCARD_FAILED=y
CONFIG_EXAMPLE_USE_SDMMC_HOST=y

Wyświetl plik

@ -72,6 +72,7 @@ static esp_err_t echo_handler(httpd_req_t *req)
return ESP_OK;
}
httpd_ws_frame_t ws_pkt;
uint8_t *buf = NULL;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
/* Set max_len = 0 to get the frame len */
@ -81,25 +82,27 @@ static esp_err_t echo_handler(httpd_req_t *req)
return ret;
}
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
uint8_t *buf = calloc(1, ws_pkt.len + 1);
if (buf == NULL) {
ESP_LOGE(TAG, "Failed to calloc memory for buf");
return ESP_ERR_NO_MEM;
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;
}
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
}
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);
return ret;
}
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
strcmp((char*)ws_pkt.payload,"Trigger async") == 0) {
free(buf);
buf = NULL;
return trigger_async_send(req->handle, req);
}
@ -108,7 +111,6 @@ static esp_err_t echo_handler(httpd_req_t *req)
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
}
free(buf);
buf = NULL;
return ret;
}

Wyświetl plik

@ -0,0 +1,51 @@
#!/usr/bin/env python
#
# Copyright 2021 Espressif Systems (Shanghai) CO LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import tiny_test_fw
import ttfw_idf
from tiny_test_fw import Utility
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
def test_examples_protocol_https_mbedtls(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
"""
steps: |
1. join AP
2. connect to www.howsmyssl.com:443
3. send http request
"""
dut1 = env.get_dut('https_mbedtls', 'examples/protocols/https_mbedtls', dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, 'https-mbedtls.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('https_mbedtls_bin_size', '{}KB'.format(bin_size // 1024))
# start test
dut1.start_app()
dut1.expect('Connected.', timeout=30)
Utility.console_log('TCP connection established with the server\n performing SSL/TLS handshake')
dut1.expect('Performing the SSL/TLS handshake...')
dut1.expect('Certificate verified.')
Utility.console_log('SSL/TLS handshake successful')
dut1.expect('Writing HTTP request...')
dut1.expect('Reading HTTP response...')
dut1.expect(re.compile(r'Completed (\d) requests'))
if __name__ == '__main__':
test_examples_protocol_https_mbedtls() # pylint: disable=no-value-for-parameter

Wyświetl plik

@ -0,0 +1,11 @@
CONFIG_ESP32_SPIRAM_SUPPORT=y
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y

Wyświetl plik

@ -0,0 +1,98 @@
#!/usr/bin/env python
#
# Copyright 2021 Espressif Systems (Shanghai) CO LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import http.client
import os
import re
import ssl
import tiny_test_fw
import ttfw_idf
from tiny_test_fw import Utility
server_cert_pem = '-----BEGIN CERTIFICATE-----\n'\
'MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\n'\
'BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx\n'\
'MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ\n'\
'UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n'\
'ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T\n'\
'sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k\n'\
'qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd\n'\
'GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4\n'\
'sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb\n'\
'jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/\n'\
'ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud\n'\
'EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3\n'\
'emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY\n'\
'W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx\n'\
'bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN\n'\
'ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl\n'\
'hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=\n'\
'-----END CERTIFICATE-----\n'
success_response = '<h1>Hello Secure World!</h1>'
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_protocol_https_server_simple(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
"""
steps: |
1. join AP
2. connect to www.howsmyssl.com:443
3. send http request
"""
dut1 = env.get_dut('https_server_simple', 'examples/protocols/https_server/simple', dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, 'https_server.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('https_server_simple_bin_size', '{}KB'.format(bin_size // 1024))
# start test
dut1.start_app()
# Parse IP address and port of the server
dut1.expect(re.compile(r'Starting server'))
got_port = dut1.expect(re.compile(r'Server listening on port (\d+)'), timeout=30)[0]
Utility.console_log('Waiting to connect with AP')
got_ip = dut1.expect(re.compile(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)'), timeout=30)[0]
# Expected logs
Utility.console_log('Got IP : ' + got_ip)
Utility.console_log('Got Port : ' + got_port)
Utility.console_log('Performing GET request over an SSL connection with the server')
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ssl_context.verify_mode = ssl.CERT_REQUIRED
ssl_context.check_hostname = False
ssl_context.load_verify_locations(cadata=server_cert_pem)
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
Utility.console_log('Performing SSL handshake with the server')
conn.request('GET','/')
resp = conn.getresponse()
dut1.expect('performing session handshake')
got_resp = resp.read().decode('utf-8')
# Close the connection
if got_resp != success_response:
Utility.console_log('Response obtained does not match with correct response')
raise RuntimeError('Failed to test SSL connection')
Utility.console_log('Correct response obtained')
Utility.console_log('SSL connection test successful\nClosing the connection')
conn.close()
if __name__ == '__main__':
test_examples_protocol_https_server_simple() # pylint: disable=no-value-for-parameter

Wyświetl plik

@ -38,6 +38,7 @@ static esp_err_t ws_handler(httpd_req_t *req)
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
@ -48,25 +49,26 @@ static esp_err_t ws_handler(httpd_req_t *req)
return ret;
}
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
uint8_t *buf = calloc(1, ws_pkt.len + 1);
if (buf == NULL) {
ESP_LOGE(TAG, "Failed to calloc memory for buf");
return ESP_ERR_NO_MEM;
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;
}
}
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);
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);
buf = NULL;
return wss_keep_alive_client_is_active(httpd_get_global_user_ctx(req->handle),
httpd_req_to_sockfd(req));
@ -80,11 +82,9 @@ static esp_err_t ws_handler(httpd_req_t *req)
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);
buf = NULL;
return ret;
}
free(buf);
buf = NULL;
return ESP_OK;
}

Wyświetl plik

@ -0,0 +1,192 @@
#!/usr/bin/env python
#
# Copyright 2021 Espressif Systems (Shanghai) CO LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import division, print_function, unicode_literals
import os
import re
import threading
import time
from types import TracebackType
from typing import Any, Optional
import tiny_test_fw
import ttfw_idf
import websocket
from tiny_test_fw import Utility
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 conection failure at the time of handshake
self.ws.settimeout(10)
def __enter__(self): # type: ignore
self.ws.connect('wss://{}:{}/ws'.format(self.ip, self.port))
return self
def __exit__(self, exc_type, exc_value, traceback): # type: (type, RuntimeError, TracebackType) -> None
self.ws.close()
def read(self): # type: () -> Any
return self.ws.recv_data(control_frame=True)
def write(self, data, opcode=OPCODE_TEXT): # type: (str, int) -> 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): # type: () -> 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
Utility.console_log('Thread {} obtained correct async message'.format(self.name))
# Keep sending pong to update the keepalive in the server
if (time.time() - self.start_time) > 20:
break
except Exception as e:
Utility.console_log('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=0): # type:(Optional[float]) -> 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:
Utility.console_log('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()
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_protocol_https_wss_server(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
# Acquire DUT
dut1 = env.get_dut('https_server', 'examples/protocols/https_server/wss_server', dut_class=ttfw_idf.ESP32DUT)
# Get binary file
binary_file = os.path.join(dut1.app.binary_path, 'wss_server.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('https_wss_server_bin_size', '{}KB'.format(bin_size // 1024))
# Upload binary and start testing
Utility.console_log('Starting wss_server test app')
dut1.start_app()
# Parse IP address of STA
got_port = dut1.expect(re.compile(r'Server listening on port (\d+)'), timeout=60)[0]
Utility.console_log('Waiting to connect with AP')
got_ip = dut1.expect(re.compile(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)'), timeout=60)[0]
Utility.console_log('Got IP : ' + got_ip)
Utility.console_log('Got Port : ' + got_port)
ca_file = os.path.join(os.path.dirname(__file__), 'main', 'certs', 'cacert.pem')
# Start ws server test
with WsClient(got_ip, int(got_port), ca_file) as ws:
# Check for echo
DATA = 'Espressif'
dut1.expect('performing session handshake')
client_fd = dut1.expect(re.compile(r'New client connected (\d+)'), timeout=20)[0]
ws.write(data=DATA, opcode=OPCODE_TEXT)
dut1.expect(re.compile(r'Received packet with message: {}'.format(DATA)))
opcode, data = ws.read()
data = data.decode('UTF-8')
if data != DATA:
raise RuntimeError('Failed to receive the correct echo response')
Utility.console_log('Correct echo response obtained from the wss server')
# Test for keepalive
Utility.console_log('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)
Utility.console_log('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:
Utility.console_log('Failed the test for keep alive,\nthe client got abruptly disconnected')
raise
# keepalive timeout is 10 seconds so do not respond for (10 + 1) senconds
Utility.console_log('Testing if client is disconnected if it does not respond for 10s i.e. keep_alive timeout (approx time = 11s)')
try:
dut1.expect('Client not alive, closing fd {}'.format(client_fd), timeout=20)
dut1.expect('Client disconnected {}'.format(client_fd))
except Exception:
Utility.console_log('ENV_ERROR:Failed the test for keep alive,\nthe connection was not closed after timeout')
time.sleep(11)
Utility.console_log('Passed the test for keep alive')
# Test keep alive and async response for multiple simultaneous client connections
Utility.console_log('Testing for multiple simultaneous client connections (approx time = 30s)')
test_multiple_client_keep_alive_and_async_response(got_ip, int(got_port), ca_file)
Utility.console_log('Passed the test for multiple simultaneous client connections')
if __name__ == '__main__':
test_examples_protocol_https_wss_server() # pylint: disable=no-value-for-parameter