diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 0cda4439d..434d0bc1b 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -297,17 +297,21 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle) handle->ota_upgrade_buf_size); if (data_read == 0) { /* - * As esp_http_client_read never returns negative error code, we rely on - * `errno` to check for underlying transport connectivity closure if any + * esp_https_ota_is_complete_data_received is added to check whether + * complete image is received. */ - if (errno == ENOTCONN || errno == ECONNRESET) { + bool is_recv_complete = esp_https_ota_is_complete_data_received(https_ota_handle); + /* + * As esp_http_client_read never returns negative error code, we rely on + * `errno` to check for underlying transport connectivity closure if any. + * Incase the complete data has not been received but the server has sent + * an ENOTCONN or ECONNRESET, failure is returned. We close with success + * if complete data has been received. + */ + if ((errno == ENOTCONN || errno == ECONNRESET) && !is_recv_complete) { ESP_LOGE(TAG, "Connection closed, errno = %d", errno); return ESP_FAIL; - } - /* esp_https_ota_is_complete_data_received is added to check whether - complete image is received. - */ - if (!esp_https_ota_is_complete_data_received(https_ota_handle)) { + } else if (!is_recv_complete) { return ESP_ERR_HTTPS_OTA_IN_PROGRESS; } ESP_LOGI(TAG, "Connection closed"); diff --git a/examples/system/ota/advanced_https_ota/example_test.py b/examples/system/ota/advanced_https_ota/example_test.py index 5dcdf054d..8ee52eb34 100644 --- a/examples/system/ota/advanced_https_ota/example_test.py +++ b/examples/system/ota/advanced_https_ota/example_test.py @@ -9,6 +9,7 @@ import ssl from tiny_test_fw import DUT import ttfw_idf import random +import subprocess server_cert = "-----BEGIN CERTIFICATE-----\n" \ "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\ @@ -92,6 +93,20 @@ def start_https_server(ota_image_dir, server_ip, server_port): httpd.serve_forever() +def start_chunked_server(ota_image_dir, server_port): + os.chdir(ota_image_dir) + + server_file = os.path.join(ota_image_dir, "server_cert.pem") + cert_file_handle = open(server_file, "w+") + cert_file_handle.write(server_cert) + + key_file = os.path.join(ota_image_dir, "server_key.pem") + key_file_handle = open("server_key.pem", "w+") + key_file_handle.write(server_key) + chunked_server = subprocess.Popen(["openssl", "s_server", "-WWW", "-key", key_file, "-cert", server_file, "-port", str(server_port)]) + return chunked_server + + @ttfw_idf.idf_example_test(env_tag="Example_WIFI") def test_examples_protocol_advanced_https_ota_example(env, extra_data): """ @@ -179,6 +194,7 @@ def test_examples_protocol_advanced_https_ota_example_truncated_bin(env, extra_d print("writing to device: {}".format("https://" + host_ip + ":8001/" + truncated_bin_name)) dut1.write("https://" + host_ip + ":8001/" + truncated_bin_name) dut1.expect("Image validation failed, image is corrupted", timeout=30) + os.remove(binary_file) @ttfw_idf.idf_example_test(env_tag="Example_WIFI") @@ -224,6 +240,7 @@ def test_examples_protocol_advanced_https_ota_example_truncated_header(env, extr print("writing to device: {}".format("https://" + host_ip + ":8001/" + truncated_bin_name)) dut1.write("https://" + host_ip + ":8001/" + truncated_bin_name) dut1.expect("advanced_https_ota_example: esp_https_ota_read_img_desc failed", timeout=30) + os.remove(binary_file) @ttfw_idf.idf_example_test(env_tag="Example_WIFI") @@ -268,10 +285,51 @@ def test_examples_protocol_advanced_https_ota_example_random(env, extra_data): print("writing to device: {}".format("https://" + host_ip + ":8001/" + random_bin_name)) dut1.write("https://" + host_ip + ":8001/" + random_bin_name) dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=10) + os.remove(binary_file) + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_advanced_https_ota_example_chunked(env, extra_data): + """ + This is a positive test case, which downloads complete binary file multiple number of times. + Number of iterations can be specified in variable iterations. + steps: | + 1. join AP + 2. Fetch OTA image over HTTPS + 3. Reboot with the new OTA image + """ + dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT) + # File to be downloaded. This file is generated after compilation + bin_name = "advanced_https_ota.bin" + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, bin_name) + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024) + # start test + host_ip = get_my_ip() + chunked_server = start_chunked_server(dut1.app.binary_path, 8070) + dut1.start_app() + dut1.expect("Loaded app from partition at offset", timeout=30) + try: + ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30) + print("Connected to AP with IP: {}".format(ip_address)) + except DUT.ExpectTimeout: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP') + dut1.expect("Starting Advanced OTA example", timeout=30) + + print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name)) + dut1.write("https://" + host_ip + ":8070/" + bin_name) + dut1.expect("Loaded app from partition at offset", timeout=60) + dut1.expect("Starting Advanced OTA example", timeout=30) + chunked_server.kill() + os.remove(os.path.join(dut1.app.binary_path, "server_cert.pem")) + os.remove(os.path.join(dut1.app.binary_path, "server_key.pem")) if __name__ == '__main__': test_examples_protocol_advanced_https_ota_example() + test_examples_protocol_advanced_https_ota_example_chunked() test_examples_protocol_advanced_https_ota_example_truncated_bin() test_examples_protocol_advanced_https_ota_example_truncated_header() test_examples_protocol_advanced_https_ota_example_random() diff --git a/examples/system/ota/advanced_https_ota/sdkconfig.ci b/examples/system/ota/advanced_https_ota/sdkconfig.ci index c4df1c1b1..577551e00 100644 --- a/examples/system/ota/advanced_https_ota/sdkconfig.ci +++ b/examples/system/ota/advanced_https_ota/sdkconfig.ci @@ -1,4 +1,4 @@ CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y -CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=300 +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=2000 diff --git a/examples/system/ota/native_ota_example/example_test.py b/examples/system/ota/native_ota_example/example_test.py index 0bce3cffd..1c3fe426d 100644 --- a/examples/system/ota/native_ota_example/example_test.py +++ b/examples/system/ota/native_ota_example/example_test.py @@ -9,6 +9,7 @@ import ssl from tiny_test_fw import DUT import ttfw_idf import random +import subprocess server_cert = "-----BEGIN CERTIFICATE-----\n" \ "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\ @@ -92,6 +93,22 @@ def start_https_server(ota_image_dir, server_ip, server_port): httpd.serve_forever() +def start_chunked_server(ota_image_dir, server_port): + os.chdir(ota_image_dir) + + server_file = os.path.join(ota_image_dir, "server_cert.pem") + cert_file_handle = open(server_file, "w+") + cert_file_handle.write(server_cert) + cert_file_handle.close() + + key_file = os.path.join(ota_image_dir, "server_key.pem") + key_file_handle = open("server_key.pem", "w+") + key_file_handle.write(server_key) + key_file_handle.close() + chunked_server = subprocess.Popen(["openssl", "s_server", "-WWW", "-key", key_file, "-cert", server_file, "-port", str(server_port)]) + return chunked_server + + @ttfw_idf.idf_example_test(env_tag="Example_WIFI") def test_examples_protocol_native_ota_example(env, extra_data): """ @@ -179,6 +196,7 @@ def test_examples_protocol_native_ota_example_truncated_bin(env, extra_data): print("writing to device: {}".format("https://" + host_ip + ":8002/" + truncated_bin_name)) dut1.write("https://" + host_ip + ":8002/" + truncated_bin_name) dut1.expect("native_ota_example: Image validation failed, image is corrupted", timeout=20) + os.remove(binary_file) @ttfw_idf.idf_example_test(env_tag="Example_WIFI") @@ -224,6 +242,7 @@ def test_examples_protocol_native_ota_example_truncated_header(env, extra_data): print("writing to device: {}".format("https://" + host_ip + ":8002/" + truncated_bin_name)) dut1.write("https://" + host_ip + ":8002/" + truncated_bin_name) dut1.expect("native_ota_example: received package is not fit len", timeout=20) + os.remove(binary_file) @ttfw_idf.idf_example_test(env_tag="Example_WIFI") @@ -268,10 +287,51 @@ def test_examples_protocol_native_ota_example_random(env, extra_data): print("writing to device: {}".format("https://" + host_ip + ":8002/" + random_bin_name)) dut1.write("https://" + host_ip + ":8002/" + random_bin_name) dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=20) + os.remove(binary_file) + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_native_ota_example_chunked(env, extra_data): + """ + This is a positive test case, which downloads complete binary file multiple number of times. + Number of iterations can be specified in variable iterations. + steps: | + 1. join AP + 2. Fetch OTA image over HTTPS + 3. Reboot with the new OTA image + """ + dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example", dut_class=ttfw_idf.ESP32DUT) + # File to be downloaded. This file is generated after compilation + bin_name = "native_ota.bin" + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, bin_name) + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024) + # start test + host_ip = get_my_ip() + chunked_server = start_chunked_server(dut1.app.binary_path, 8070) + dut1.start_app() + dut1.expect("Loaded app from partition at offset", timeout=30) + try: + ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30) + print("Connected to AP with IP: {}".format(ip_address)) + except DUT.ExpectTimeout: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP') + + dut1.expect("Starting OTA example", timeout=30) + print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name)) + dut1.write("https://" + host_ip + ":8070/" + bin_name) + dut1.expect("Loaded app from partition at offset", timeout=60) + dut1.expect("Starting OTA example", timeout=30) + chunked_server.kill() + os.remove(os.path.join(dut1.app.binary_path, "server_cert.pem")) + os.remove(os.path.join(dut1.app.binary_path, "server_key.pem")) if __name__ == '__main__': test_examples_protocol_native_ota_example() + test_examples_protocol_native_ota_example_chunked() test_examples_protocol_native_ota_example_truncated_bin() test_examples_protocol_native_ota_example_truncated_header() test_examples_protocol_native_ota_example_random() diff --git a/examples/system/ota/native_ota_example/sdkconfig.ci b/examples/system/ota/native_ota_example/sdkconfig.ci index 8a61d2a32..fad2c0a8c 100644 --- a/examples/system/ota/native_ota_example/sdkconfig.ci +++ b/examples/system/ota/native_ota_example/sdkconfig.ci @@ -1,4 +1,4 @@ CONFIG_EXAMPLE_FIRMWARE_UPG_URL="FROM_STDIN" CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y -CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=300 +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=2000