Merge branch 'fix/align_ota_written_size_during_ota_resumption_to_last_16_byte_boundry_v6.0' into 'release/v6.0'

fix(esp_https_ota): align OTA resumption offset to 16-byte boundary (v6.0)

See merge request espressif/esp-idf!44033
This commit is contained in:
Mahavir Jain
2025-12-08 14:21:40 +05:30
6 changed files with 119 additions and 14 deletions
@@ -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.
+3 -10
View File
@@ -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 {
@@ -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
@@ -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
@@ -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:
@@ -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