From 2001f9f52bb3da06e2150a0208c44a73db127037 Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Mon, 3 Nov 2025 14:00:06 +0530 Subject: [PATCH 1/3] Revert "fix: add check to ensure OTA buffer size for 16-byte aligned" This reverts commit 873fd18c5a47a9494ad20d0912c3616ab6cd4411. --- components/app_update/include/esp_ota_ops.h | 4 ---- components/esp_https_ota/src/esp_https_ota.c | 9 --------- 2 files changed, 13 deletions(-) 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..9d5ab97cba 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -14,7 +14,6 @@ #include #include #include "esp_check.h" -#include "esp_flash_encrypt.h" #include "hal/efuse_hal.h" ESP_EVENT_DEFINE_BASE(ESP_HTTPS_OTA_EVENT); @@ -544,14 +543,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 { From 47e07e5c8d2adf01115f15567ba439e425c2e142 Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Tue, 21 Oct 2025 12:56:56 +0530 Subject: [PATCH 2/3] fix(esp_https_ota): align OTA resumption offset to 16-byte boundary --- components/esp_https_ota/src/esp_https_ota.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 9d5ab97cba..7a5ee46bb0 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -14,6 +14,7 @@ #include #include #include "esp_check.h" +#include "esp_flash_encrypt.h" #include "hal/efuse_hal.h" ESP_EVENT_DEFINE_BASE(ESP_HTTPS_OTA_EVENT); @@ -348,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); } } From 1ee8ced7dddc686debea9ea11e08dd95510e4bbb Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Wed, 5 Nov 2025 17:38:51 +0530 Subject: [PATCH 3/3] feat: added testcase to check ota resumption if FE is enabled This commit also added config option to set default buffer size for OTA. This testcase mainly checks if OTA resumption fails if data written is not 16 byte aligned when Flash Encryption is enabled. --- .../advanced_https_ota/main/Kconfig.projbuild | 7 ++ .../main/advanced_https_ota_example.c | 1 + .../advanced_https_ota/pytest_advanced_ota.py | 69 +++++++++++++++++++ .../sdkconfig.ci.ota_resumption_flash_enc | 39 +++++++++++ 4 files changed, 116 insertions(+) create mode 100644 examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_flash_enc 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