mirror of
https://github.com/espressif/esp-idf
synced 2025-03-12 10:39:11 -04:00
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
This commit is contained in:
commit
e7d37aa7db
72
examples/protocols/http2_request/example_test.py
Normal file
72
examples/protocols/http2_request/example_test.py
Normal file
@ -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
|
11
examples/protocols/http2_request/sdkconfig.ci
Normal file
11
examples/protocols/http2_request/sdkconfig.ci
Normal file
@ -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
|
52
examples/protocols/http_request/example_test.py
Normal file
52
examples/protocols/http_request/example_test.py
Normal file
@ -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
|
9
examples/protocols/http_request/sdkconfig.ci
Normal file
9
examples/protocols/http_request/sdkconfig.ci
Normal file
@ -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
|
@ -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
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -0,0 +1,3 @@
|
||||
CONFIG_EXAMPLE_MOUNT_SD_CARD=y
|
||||
CONFIG_EXAMPLE_FORMAT_IF_MOUNT_SDCARD_FAILED=y
|
||||
CONFIG_EXAMPLE_USE_SDMMC_HOST=y
|
@ -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;
|
||||
}
|
||||
|
||||
|
51
examples/protocols/https_mbedtls/example_test.py
Normal file
51
examples/protocols/https_mbedtls/example_test.py
Normal file
@ -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
|
11
examples/protocols/https_mbedtls/sdkconfig.ci
Normal file
11
examples/protocols/https_mbedtls/sdkconfig.ci
Normal file
@ -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
|
98
examples/protocols/https_server/simple/example_test.py
Normal file
98
examples/protocols/https_server/simple/example_test.py
Normal file
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user