OTA: Fixed OTA with chunked servers and added example_test with chunked server

This commit is contained in:
Shubham Kulkarni 2020-01-27 17:33:36 +05:30
parent 2241dda536
commit 4c09dc6270
5 changed files with 132 additions and 10 deletions

View file

@ -297,17 +297,21 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
handle->ota_upgrade_buf_size); handle->ota_upgrade_buf_size);
if (data_read == 0) { if (data_read == 0) {
/* /*
* As esp_http_client_read never returns negative error code, we rely on * esp_https_ota_is_complete_data_received is added to check whether
* `errno` to check for underlying transport connectivity closure if any * 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); ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
return ESP_FAIL; return ESP_FAIL;
} } else if (!is_recv_complete) {
/* 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)) {
return ESP_ERR_HTTPS_OTA_IN_PROGRESS; return ESP_ERR_HTTPS_OTA_IN_PROGRESS;
} }
ESP_LOGI(TAG, "Connection closed"); ESP_LOGI(TAG, "Connection closed");

View file

@ -9,6 +9,7 @@ import ssl
from tiny_test_fw import DUT from tiny_test_fw import DUT
import ttfw_idf import ttfw_idf
import random import random
import subprocess
server_cert = "-----BEGIN CERTIFICATE-----\n" \ server_cert = "-----BEGIN CERTIFICATE-----\n" \
"MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\ "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
@ -92,6 +93,20 @@ def start_https_server(ota_image_dir, server_ip, server_port):
httpd.serve_forever() 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") @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
def test_examples_protocol_advanced_https_ota_example(env, extra_data): 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)) print("writing to device: {}".format("https://" + host_ip + ":8001/" + truncated_bin_name))
dut1.write("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) dut1.expect("Image validation failed, image is corrupted", timeout=30)
os.remove(binary_file)
@ttfw_idf.idf_example_test(env_tag="Example_WIFI") @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)) print("writing to device: {}".format("https://" + host_ip + ":8001/" + truncated_bin_name))
dut1.write("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) 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") @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)) print("writing to device: {}".format("https://" + host_ip + ":8001/" + random_bin_name))
dut1.write("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) 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__': if __name__ == '__main__':
test_examples_protocol_advanced_https_ota_example() 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_bin()
test_examples_protocol_advanced_https_ota_example_truncated_header() test_examples_protocol_advanced_https_ota_example_truncated_header()
test_examples_protocol_advanced_https_ota_example_random() test_examples_protocol_advanced_https_ota_example_random()

View file

@ -1,4 +1,4 @@
CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN"
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y
CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=300 CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=2000

View file

@ -9,6 +9,7 @@ import ssl
from tiny_test_fw import DUT from tiny_test_fw import DUT
import ttfw_idf import ttfw_idf
import random import random
import subprocess
server_cert = "-----BEGIN CERTIFICATE-----\n" \ server_cert = "-----BEGIN CERTIFICATE-----\n" \
"MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\ "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
@ -92,6 +93,22 @@ def start_https_server(ota_image_dir, server_ip, server_port):
httpd.serve_forever() 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") @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
def test_examples_protocol_native_ota_example(env, extra_data): 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)) print("writing to device: {}".format("https://" + host_ip + ":8002/" + truncated_bin_name))
dut1.write("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) 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") @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)) print("writing to device: {}".format("https://" + host_ip + ":8002/" + truncated_bin_name))
dut1.write("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) 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") @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)) print("writing to device: {}".format("https://" + host_ip + ":8002/" + random_bin_name))
dut1.write("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) 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__': if __name__ == '__main__':
test_examples_protocol_native_ota_example() 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_bin()
test_examples_protocol_native_ota_example_truncated_header() test_examples_protocol_native_ota_example_truncated_header()
test_examples_protocol_native_ota_example_random() test_examples_protocol_native_ota_example_random()

View file

@ -1,4 +1,4 @@
CONFIG_EXAMPLE_FIRMWARE_UPG_URL="FROM_STDIN" CONFIG_EXAMPLE_FIRMWARE_UPG_URL="FROM_STDIN"
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y
CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=300 CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=2000