From dc62958241426f714934c742a133e201a94b8d0d Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 12 Jan 2021 23:17:32 +0530 Subject: [PATCH 1/7] http2_request_example: Added example test --- .../protocols/http2_request/example_test.py | 72 +++++++++++++++++++ examples/protocols/http2_request/sdkconfig.ci | 11 +++ 2 files changed, 83 insertions(+) create mode 100644 examples/protocols/http2_request/example_test.py create mode 100644 examples/protocols/http2_request/sdkconfig.ci diff --git a/examples/protocols/http2_request/example_test.py b/examples/protocols/http2_request/example_test.py new file mode 100644 index 0000000000..c97d7f7f8e --- /dev/null +++ b/examples/protocols/http2_request/example_test.py @@ -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 diff --git a/examples/protocols/http2_request/sdkconfig.ci b/examples/protocols/http2_request/sdkconfig.ci new file mode 100644 index 0000000000..42f4b389e1 --- /dev/null +++ b/examples/protocols/http2_request/sdkconfig.ci @@ -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 From 31ddfbb7a617cdd79634b1b4f687bb133eb0e788 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 12 Jan 2021 23:43:46 +0530 Subject: [PATCH 2/7] https_mbedtls_example: Added example test --- .../protocols/https_mbedtls/example_test.py | 51 +++++++++++++++++++ examples/protocols/https_mbedtls/sdkconfig.ci | 11 ++++ 2 files changed, 62 insertions(+) create mode 100644 examples/protocols/https_mbedtls/example_test.py create mode 100644 examples/protocols/https_mbedtls/sdkconfig.ci diff --git a/examples/protocols/https_mbedtls/example_test.py b/examples/protocols/https_mbedtls/example_test.py new file mode 100644 index 0000000000..41f151e30d --- /dev/null +++ b/examples/protocols/https_mbedtls/example_test.py @@ -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 diff --git a/examples/protocols/https_mbedtls/sdkconfig.ci b/examples/protocols/https_mbedtls/sdkconfig.ci new file mode 100644 index 0000000000..42f4b389e1 --- /dev/null +++ b/examples/protocols/https_mbedtls/sdkconfig.ci @@ -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 From 4b7eaa1d1fcfb3bbe7c71eae765f253ff67ba617 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 13 Jan 2021 17:41:43 +0530 Subject: [PATCH 3/7] http_request_example: Added example test --- .../protocols/http_request/example_test.py | 52 +++++++++++++++++++ examples/protocols/http_request/sdkconfig.ci | 9 ++++ 2 files changed, 61 insertions(+) create mode 100644 examples/protocols/http_request/example_test.py create mode 100644 examples/protocols/http_request/sdkconfig.ci diff --git a/examples/protocols/http_request/example_test.py b/examples/protocols/http_request/example_test.py new file mode 100644 index 0000000000..3c10c11949 --- /dev/null +++ b/examples/protocols/http_request/example_test.py @@ -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 diff --git a/examples/protocols/http_request/sdkconfig.ci b/examples/protocols/http_request/sdkconfig.ci new file mode 100644 index 0000000000..539fa3b43d --- /dev/null +++ b/examples/protocols/http_request/sdkconfig.ci @@ -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 From d51c41d4c57bc3083dd60e971c5b90c9c2ec4f25 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 20 Jan 2021 12:38:16 +0530 Subject: [PATCH 4/7] http_server/file_serving: Added example test * Fixed unsused function warning --- .../http_server_file_serving_test.py | 147 ++++++++++++++++++ .../file_serving/main/file_server.c | 2 +- .../http_server/file_serving/sdkconfig.ci | 6 +- .../file_serving/sdkconfig.ci.sdcard | 3 + 4 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 examples/protocols/http_server/file_serving/http_server_file_serving_test.py create mode 100644 examples/protocols/http_server/file_serving/sdkconfig.ci.sdcard diff --git a/examples/protocols/http_server/file_serving/http_server_file_serving_test.py b/examples/protocols/http_server/file_serving/http_server_file_serving_test.py new file mode 100644 index 0000000000..690b84c9c6 --- /dev/null +++ b/examples/protocols/http_server/file_serving/http_server_file_serving_test.py @@ -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 diff --git a/examples/protocols/http_server/file_serving/main/file_server.c b/examples/protocols/http_server/file_serving/main/file_server.c index 878fdab4d0..021749ae86 100644 --- a/examples/protocols/http_server/file_serving/main/file_server.c +++ b/examples/protocols/http_server/file_serving/main/file_server.c @@ -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; diff --git a/examples/protocols/http_server/file_serving/sdkconfig.ci b/examples/protocols/http_server/file_serving/sdkconfig.ci index a34935d196..b9bb0c0a5d 100644 --- a/examples/protocols/http_server/file_serving/sdkconfig.ci +++ b/examples/protocols/http_server/file_serving/sdkconfig.ci @@ -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" diff --git a/examples/protocols/http_server/file_serving/sdkconfig.ci.sdcard b/examples/protocols/http_server/file_serving/sdkconfig.ci.sdcard new file mode 100644 index 0000000000..a34935d196 --- /dev/null +++ b/examples/protocols/http_server/file_serving/sdkconfig.ci.sdcard @@ -0,0 +1,3 @@ +CONFIG_EXAMPLE_MOUNT_SD_CARD=y +CONFIG_EXAMPLE_FORMAT_IF_MOUNT_SDCARD_FAILED=y +CONFIG_EXAMPLE_USE_SDMMC_HOST=y From 7c68c7a3188a43ccd12b5930b21eeb258a9b88d4 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 21 Jan 2021 09:44:11 +0530 Subject: [PATCH 5/7] https_server_simple: Added example test --- .../https_server/simple/example_test.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 examples/protocols/https_server/simple/example_test.py diff --git a/examples/protocols/https_server/simple/example_test.py b/examples/protocols/https_server/simple/example_test.py new file mode 100644 index 0000000000..d6385451b8 --- /dev/null +++ b/examples/protocols/https_server/simple/example_test.py @@ -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 = '

Hello Secure World!

' + + +@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 From 54b2b88e9e9bf8dd8a78c524150c5088e9ac8497 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 2 Feb 2021 21:06:34 +0530 Subject: [PATCH 6/7] https_server/wss_server: Added example_test --- .../wss_server/wss_server_example_test.py | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 examples/protocols/https_server/wss_server/wss_server_example_test.py diff --git a/examples/protocols/https_server/wss_server/wss_server_example_test.py b/examples/protocols/https_server/wss_server/wss_server_example_test.py new file mode 100644 index 0000000000..d829ecb80c --- /dev/null +++ b/examples/protocols/https_server/wss_server/wss_server_example_test.py @@ -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 From 3874c20b5df93bc9a1c71c7f06227f8d7cbde3fa Mon Sep 17 00:00:00 2001 From: yuanjm Date: Tue, 9 Mar 2021 17:23:06 +0800 Subject: [PATCH 7/7] esp_http_server: Fix examples parse ws keep-alive packet fail. --- .../ws_echo_server/main/ws_echo_server.c | 32 ++++++++++--------- .../wss_server/main/wss_server_example.c | 32 +++++++++---------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c b/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c index 03e810f73d..cac6365045 100644 --- a/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c +++ b/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c @@ -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; } diff --git a/examples/protocols/https_server/wss_server/main/wss_server_example.c b/examples/protocols/https_server/wss_server/main/wss_server_example.c index 5286bbe2d9..093033c4b7 100644 --- a/examples/protocols/https_server/wss_server/main/wss_server_example.c +++ b/examples/protocols/https_server/wss_server/main/wss_server_example.c @@ -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; }