diff --git a/components/app_update/include/esp_ota_ops.h b/components/app_update/include/esp_ota_ops.h index 02a3cf0e38..029a7db473 100644 --- a/components/app_update/include/esp_ota_ops.h +++ b/components/app_update/include/esp_ota_ops.h @@ -89,10 +89,6 @@ esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp * Unlike esp_ota_begin(), this function does not erase the partition which receives the OTA update, but rather expects that part of the image * has already been written correctly, and it resumes writing from the given offset. * - * @note When flash encryption is enabled, data writes must be 16-byte aligned. - * Any leftover (non-aligned) data is temporarily cached and may be lost after reboot. - * Therefore, during resumption, ensure that image offset is always 16-byte aligned. - * * @param partition Pointer to info for the partition which is receiving the OTA update. Required. * @param erase_size Specifies how much flash memory to erase before resuming OTA, depending on whether a sequential write or a bulk erase is being used. * @param image_offset Offset from where to resume the OTA process. Should be set to the number of bytes already written. diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 59e7887e1c..7a5ee46bb0 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -349,8 +349,9 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http if (ota_config->ota_resumption) { // We allow resumption only if we have minimum buffer size already written to flash if (ota_config->ota_image_bytes_written >= DEFAULT_OTA_BUF_SIZE) { - ESP_LOGI(TAG, "Valid OTA resumption case, offset %d", ota_config->ota_image_bytes_written); - https_ota_handle->binary_file_len = ota_config->ota_image_bytes_written; + // For FE case the flash is written in multiples of 16 bytes. So, we need to align the offset to 16 bytes. + https_ota_handle->binary_file_len = esp_flash_encryption_enabled() ? (ota_config->ota_image_bytes_written & ~0xF) : ota_config->ota_image_bytes_written; + ESP_LOGD(TAG, "Resuming OTA from offset: %d", https_ota_handle->binary_file_len); } } @@ -544,14 +545,6 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http } const int alloc_size = MAX(ota_config->http_config->buffer_size, DEFAULT_OTA_BUF_SIZE); - if (ota_config->ota_resumption) { - if (esp_flash_encryption_enabled() && (alloc_size & 0xFU) != 0) { - // For FE case the flash is written in multiples of 16 bytes - ESP_LOGE(TAG, "Buffer size must be multiple of 16 bytes for FE and ota resumption case"); - goto http_cleanup; - } - } - if (ota_config->buffer_caps != 0) { https_ota_handle->ota_upgrade_buf = (char *)heap_caps_malloc(alloc_size, ota_config->buffer_caps); } else { diff --git a/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild b/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild index f947d9fcc3..3d4fb481e4 100644 --- a/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild +++ b/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild @@ -54,6 +54,13 @@ menu "Example Configuration" This option allows one to configure the OTA process to resume downloading the OTA image from where it left off in case of an error or reboot. + config EXAMPLE_OTA_BUF_SIZE + int "OTA buffer size" + default 1024 + range 1024 65536 + help + Buffer size for OTA data transfers in bytes. + config EXAMPLE_USE_CERT_BUNDLE bool "Enable certificate bundle" default y diff --git a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c index 7635912fd3..42507c489c 100644 --- a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c +++ b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c @@ -236,6 +236,7 @@ void advanced_ota_example_task(void *pvParameter) #ifdef CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD .save_client_session = true, #endif + .buffer_size = CONFIG_EXAMPLE_OTA_BUF_SIZE, }; #ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN diff --git a/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py b/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py index c61196646e..df0fec0b26 100644 --- a/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py +++ b/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py @@ -273,6 +273,75 @@ def test_examples_protocol_advanced_https_ota_example_ota_resumption(dut: Dut) - thread1.terminate() +@pytest.mark.flash_encryption_ota +@pytest.mark.parametrize('config', ['ota_resumption_flash_enc'], indirect=True) +@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_examples_protocol_advanced_https_ota_example_ota_resumption_flash_enc(dut: Dut) -> None: + """ + This is a positive test case, which stops the download midway and resumes downloading again with + flash encryption enabled. + steps: | + 1. join AP/Ethernet + 2. Fetch OTA image over HTTPS + 3. Reboot with the new OTA image + """ + # Number of iterations to validate OTA + server_port = 8001 + bin_name = 'advanced_https_ota.bin' + + # For flash encryption, we need to manually erase and flash + dut.serial.erase_flash() + dut.serial.flash() + + # Erase NVS partition + dut.serial.erase_partition(NVS_PARTITION) + + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', server_port)) + thread1.daemon = True + thread1.start() + try: + # start test + dut.expect('Loaded app from partition at offset', timeout=30) + + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode() + print(f'Connected to AP/Ethernet with IP: {ip_address}') + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Advanced OTA example', timeout=30) + host_ip = get_host_ip4_by_dest_ip(ip_address) + + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name) + dut.expect('Starting OTA...', timeout=60) + + restart_device_with_random_delay(dut, 5, 15) + + # Validate that the device restarts correctly + dut.expect('Loaded app from partition at offset', timeout=180) + + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode() + print(f'Connected to AP/Ethernet with IP: {ip_address}') + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Advanced OTA example', timeout=30) + host_ip = get_host_ip4_by_dest_ip(ip_address) + + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name) + dut.expect('Starting OTA...', timeout=60) + + dut.expect('upgrade successful. Rebooting ...', timeout=150) + + finally: + thread1.terminate() + + @pytest.mark.ethernet_ota @idf_parametrize('target', ['esp32'], indirect=['target']) def test_examples_protocol_advanced_https_ota_example_truncated_bin(dut: Dut) -> None: diff --git a/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_flash_enc b/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_flash_enc new file mode 100644 index 0000000000..160e8fdf22 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_flash_enc @@ -0,0 +1,39 @@ +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=15000 +CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION=y + +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_ETHERNET_INTERNAL_SUPPORT=y +CONFIG_ETHERNET_PHY_GENERIC=y +CONFIG_ETHERNET_MDC_GPIO=23 +CONFIG_ETHERNET_MDIO_GPIO=18 +CONFIG_ETHERNET_PHY_RST_GPIO=5 +CONFIG_ETHERNET_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_ETHERNET_RX_TASK_STACK_SIZE=3072 +CONFIG_PARTITION_TABLE_OFFSET=0xd000 + +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_EXAMPLE_CONNECT_IPV6=n + +# Default settings for testing this example in CI. +# This configuration is not secure, don't use it in production! +# See Flash Encryption API Guide for more details. + +CONFIG_SECURE_FLASH_ENC_ENABLED=y +CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y +CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y +CONFIG_SECURE_BOOT_ALLOW_JTAG=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y +CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y +CONFIG_NVS_ENCRYPTION=n # this test combination is only for flash encryption and ota resumption use-case and hence disabling it. +CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC=y + +# Custom OTA buffer size configuration +CONFIG_EXAMPLE_OTA_BUF_SIZE=2047