Skip to content

Commit dc5bba7

Browse files
PSONALlPSONALljadhavrohit924shubhamdp
authored
[ESP32] Delta OTA Feature (#29011)
* [ESP32] Delta OTA Feature * Fix some issues with header * Update readme * Some refactoring and cleanups * Address review comments * Apply suggestions from code review Co-authored-by: Shubham Patil <shubham.patil@espressif.com> --------- Co-authored-by: PSONALl <sonali.patil@espressif.com> Co-authored-by: Rohit Jadhav <rohit.jadhav@espressif.com> Co-authored-by: Rohit Jadhav <69809379+jadhavrohit924@users.noreply.github.com> Co-authored-by: Shubham Patil <shubham.patil@espressif.com>
1 parent 04ac212 commit dc5bba7

File tree

6 files changed

+324
-1
lines changed

6 files changed

+324
-1
lines changed

config/esp32/components/chip/CMakeLists.txt

+6
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,12 @@ endif()
483483
if (NOT EXECUTABLE_COMPONENT_NAME)
484484
set(EXECUTABLE_COMPONENT_NAME "main")
485485
endif()
486+
487+
if (CONFIG_ENABLE_DELTA_OTA)
488+
idf_component_get_property(esp_delta_ota_lib espressif__esp_delta_ota COMPONENT_LIB)
489+
list(APPEND chip_libraries $<TARGET_FILE:${esp_delta_ota_lib}>)
490+
endif()
491+
486492
idf_component_get_property(main_lib ${EXECUTABLE_COMPONENT_NAME} COMPONENT_LIB)
487493
list(APPEND chip_libraries $<TARGET_FILE:${main_lib}>)
488494

config/esp32/components/chip/Kconfig

+9
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ menu "CHIP Core"
256256
help
257257
Enable this option to use the pre encrypted OTA image
258258

259+
config ENABLE_DELTA_OTA
260+
bool "Enable delta OTA"
261+
depends on ENABLE_OTA_REQUESTOR
262+
default n
263+
help
264+
Enable this option for delta OTA image updates.
265+
Delta OTA updates allow for smaller, more efficient updates by only
266+
sending the changes between the current and new firmware versions.
267+
259268
config OTA_AUTO_REBOOT_ON_APPLY
260269
bool "Reboot the device after applying the OTA image"
261270
depends on ENABLE_OTA_REQUESTOR

config/esp32/components/chip/idf_component.yml

+6
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ dependencies:
3030
version: "1.0.3"
3131
rules:
3232
- if: "idf_version >=5.0"
33+
34+
espressif/esp_delta_ota:
35+
version: "^1.1.0"
36+
require: public
37+
rules:
38+
- if: "idf_version >=4.3"

docs/guides/esp32/ota.md

+63
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,66 @@ Please follow the steps below to generate an application image for OTA upgrades:
121121
```
122122
123123
3. Use the `lighting-app-encrypted-ota.bin` file with the OTA Provider app.
124+
125+
## Delta OTA
126+
127+
Delta OTA Updates is a feature that enables Over-the-Air (OTA) firmware update
128+
with compressed delta binaries. Patch files have smaller size than the original
129+
firmware file, which reduces the time and network usage to download the file
130+
from the server. Also, no additional storage partition is required for the
131+
"patch".
132+
133+
### Firmware Changes
134+
135+
- Enable configuration options for OTA requestor and Delta OTA:
136+
137+
```
138+
CONFIG_ENABLE_OTA_REQUESTOR=y
139+
CONFIG_ENABLE_DELTA_OTA=y
140+
```
141+
142+
Please follow the steps below to generate an application image for OTA upgrades:
143+
144+
1. Delta binary needs to be generated using binary delta encoding in Python
145+
3.6+. You can install `detools` using the following command.
146+
147+
```
148+
pip install detools>=0.49.0
149+
```
150+
151+
2. Generate delta binary.
152+
153+
```
154+
python managed_components/espressif__esp_delta_ota/examples/https_delta_ota/tools/esp_delta_ota_patch_gen.py --chip <chip> --base_binary <base-binary> --new_binary <new-binary> --patch_file_name <patch-file-name.bin>
155+
```
156+
157+
3. Append the Matter OTA header:
158+
`src/app/ota_image_tool.py create --vendor-id 0xFFF1 --product-id 0x8000 --version 2 --version-str "v2.0" -da sha256 <patch-file-name.bin> lighting-app-delta-ota.bin`
159+
160+
4. Use the `lighting-app-delta-ota.bin` file with the OTA Provider app.
161+
162+
## Encrypted Delta OTA
163+
164+
To use encrypted delta OTA, follow the below steps.
165+
166+
### Firmware Changes
167+
168+
- Enable configuration options for OTA requestor, Delta OTA and Encrypted OTA:
169+
170+
```
171+
CONFIG_ENABLE_OTA_REQUESTOR=y
172+
CONFIG_ENABLE_DELTA_OTA=y
173+
CONFIG_ENABLE_ENCRYPTED_OTA=y
174+
```
175+
176+
1. Follow the step of `Generate a new RSA-3072 key pair or use an existing one`
177+
of
178+
[Encrypted OTA](https://github.com/project-chip/connectedhomeip/blob/master/docs/guides/esp32/ota.md#encrypted-ota)
179+
to build your binary.
180+
181+
2. Create delta binary as shown in `Generate delta binary` of
182+
[Delta OTA](https://github.com/project-chip/connectedhomeip/blob/master/docs/guides/esp32/ota.md#delta-ota)
183+
184+
3. Encrypt the application binary.
185+
186+
4. Append the Matter OTA header.

src/platform/ESP32/OTAImageProcessorImpl.cpp

+221-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <platform/ESP32/ESP32Utils.h>
2222

2323
#include "OTAImageProcessorImpl.h"
24+
#include "esp_app_format.h"
2425
#include "esp_err.h"
2526
#include "esp_log.h"
2627
#include "esp_ota_ops.h"
@@ -31,7 +32,17 @@
3132
#include <esp_encrypted_img.h>
3233
#endif // CONFIG_ENABLE_ENCRYPTED_OTA
3334

35+
#ifdef CONFIG_ENABLE_DELTA_OTA
36+
#include <esp_delta_ota.h>
37+
#endif // CONFIG_ENABLE_DELTA_OTA
38+
3439
#define TAG "OTAImageProcessor"
40+
41+
#ifdef CONFIG_ENABLE_DELTA_OTA
42+
#define PATCH_HEADER_SIZE 64
43+
#define DIGEST_SIZE 32
44+
#endif // CONFIG_ENABLE_DELTA_OTA
45+
3546
using namespace chip::System;
3647
using namespace ::chip::DeviceLayer::Internal;
3748

@@ -123,6 +134,152 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block)
123134
return CHIP_NO_ERROR;
124135
}
125136

137+
#ifdef CONFIG_ENABLE_DELTA_OTA
138+
bool OTAImageProcessorImpl::VerifyChipId(esp_chip_id_t chipId)
139+
{
140+
if (chipId != CONFIG_IDF_FIRMWARE_CHIP_ID)
141+
{
142+
ESP_LOGE(TAG, "Mismatch chip id, expected %d, found %d", CONFIG_IDF_FIRMWARE_CHIP_ID, chipId);
143+
return false;
144+
}
145+
return true;
146+
}
147+
148+
bool OTAImageProcessorImpl::VerifyPatchHeader(void * imgHeaderData)
149+
{
150+
const uint32_t espDeltaOtaMagic = 0xfccdde10;
151+
if (!imgHeaderData)
152+
{
153+
return false;
154+
}
155+
uint32_t recvMagic = *(uint32_t *) imgHeaderData;
156+
uint8_t * digest = (uint8_t *) ((uint8_t *) imgHeaderData + 4);
157+
if (recvMagic != espDeltaOtaMagic)
158+
{
159+
ESP_LOGE(TAG, "Invalid magic word in patch");
160+
return false;
161+
}
162+
uint8_t sha_256[DIGEST_SIZE] = { 0 };
163+
esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
164+
if (memcmp(sha_256, digest, DIGEST_SIZE) != 0)
165+
{
166+
ESP_LOGE(TAG, "SHA256 of current firmware differs from than in patch header. Invalid patch for current firmware");
167+
return false;
168+
}
169+
return true;
170+
}
171+
172+
esp_err_t OTAImageProcessorImpl::VerifyHeaderData(const uint8_t * buf, size_t size, int * index)
173+
{
174+
static char patchHeader[PATCH_HEADER_SIZE];
175+
static int headerDataRead = 0;
176+
if (!patchHeaderVerified)
177+
{
178+
if (headerDataRead + size < PATCH_HEADER_SIZE)
179+
{
180+
memcpy(patchHeader + headerDataRead, buf, size);
181+
headerDataRead += size;
182+
return ESP_OK;
183+
}
184+
else
185+
{
186+
*index = PATCH_HEADER_SIZE - headerDataRead;
187+
memcpy(patchHeader + headerDataRead, buf, *index);
188+
if (!VerifyPatchHeader(patchHeader))
189+
{
190+
return ESP_ERR_INVALID_VERSION;
191+
}
192+
headerDataRead = 0;
193+
*index = PATCH_HEADER_SIZE;
194+
patchHeaderVerified = true;
195+
}
196+
}
197+
return ESP_OK;
198+
}
199+
200+
void OTAImageProcessorImpl::DeltaOTACleanUp(intptr_t context)
201+
{
202+
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
203+
if (imageProcessor == nullptr)
204+
{
205+
ChipLogError(SoftwareUpdate, "ImageProcessor context is null");
206+
return;
207+
}
208+
imageProcessor->patchHeaderVerified = false;
209+
imageProcessor->chipIdVerified = false;
210+
return;
211+
}
212+
213+
esp_err_t OTAImageProcessorImpl::DeltaOTAReadCallback(uint8_t * buf, size_t size, int srcOffset)
214+
{
215+
if (size <= 0 || buf == NULL)
216+
{
217+
return ESP_ERR_INVALID_ARG;
218+
}
219+
220+
const esp_partition_t * currentPartition = esp_ota_get_running_partition();
221+
if (currentPartition == NULL)
222+
{
223+
return ESP_FAIL;
224+
}
225+
226+
esp_err_t err = esp_partition_read(currentPartition, srcOffset, buf, size);
227+
228+
if (err != ESP_OK)
229+
{
230+
ESP_LOGE(TAG, "esp_partition_read failed (%s)!", esp_err_to_name(err));
231+
}
232+
233+
return err;
234+
}
235+
236+
esp_err_t OTAImageProcessorImpl::DeltaOTAWriteCallback(const uint8_t * buf, size_t size, void * arg)
237+
{
238+
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(arg);
239+
if (size <= 0 || buf == NULL)
240+
{
241+
return ESP_ERR_INVALID_ARG;
242+
}
243+
244+
int index = 0;
245+
static int headerDataRead = 0;
246+
static char headerData[IMG_HEADER_LEN];
247+
248+
if (!imageProcessor->chipIdVerified)
249+
{
250+
if (headerDataRead + size - index <= IMG_HEADER_LEN)
251+
{
252+
memcpy(headerData + headerDataRead, buf, size - index);
253+
headerDataRead += size - index;
254+
return ESP_OK;
255+
}
256+
else
257+
{
258+
index = IMG_HEADER_LEN - headerDataRead;
259+
memcpy(headerData + headerDataRead, buf, index);
260+
261+
esp_image_header_t * header = (esp_image_header_t *) headerData;
262+
if (!VerifyChipId(header->chip_id))
263+
{
264+
return ESP_ERR_INVALID_VERSION;
265+
}
266+
imageProcessor->chipIdVerified = true;
267+
268+
// Write data in headerData buffer.
269+
return esp_ota_write(imageProcessor->mOTAUpdateHandle, headerData, IMG_HEADER_LEN);
270+
}
271+
}
272+
273+
esp_err_t err = esp_ota_write(imageProcessor->mOTAUpdateHandle, buf + index, size - index);
274+
if (err != ESP_OK)
275+
{
276+
ESP_LOGE(TAG, "esp_ota_write failed (%s)!", esp_err_to_name(err));
277+
}
278+
279+
return err;
280+
}
281+
#endif // CONFIG_ENABLE_DELTA_OTA
282+
126283
void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context)
127284
{
128285
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
@@ -142,13 +299,32 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context)
142299
ChipLogError(SoftwareUpdate, "OTA partition not found");
143300
return;
144301
}
302+
#ifdef CONFIG_ENABLE_DELTA_OTA
303+
// New image size is unknown for delta OTA, so we use OTA_SIZE_UNKNOWN flag.
304+
esp_err_t err = esp_ota_begin(imageProcessor->mOTAUpdatePartition, OTA_SIZE_UNKNOWN, &(imageProcessor->mOTAUpdateHandle));
305+
#else
145306
esp_err_t err =
146307
esp_ota_begin(imageProcessor->mOTAUpdatePartition, OTA_WITH_SEQUENTIAL_WRITES, &(imageProcessor->mOTAUpdateHandle));
308+
#endif // CONFIG_ENABLE_DELTA_OTA
309+
147310
if (err != ESP_OK)
148311
{
149312
imageProcessor->mDownloader->OnPreparedForDownload(ESP32Utils::MapError(err));
150313
return;
151314
}
315+
#ifdef CONFIG_ENABLE_DELTA_OTA
316+
imageProcessor->deltaOtaCfg.user_data = imageProcessor,
317+
imageProcessor->deltaOtaCfg.read_cb = &(imageProcessor->DeltaOTAReadCallback),
318+
imageProcessor->deltaOtaCfg.write_cb_with_user_data = &(imageProcessor->DeltaOTAWriteCallback),
319+
320+
imageProcessor->mDeltaOTAUpdateHandle = esp_delta_ota_init(&imageProcessor->deltaOtaCfg);
321+
if (imageProcessor->mDeltaOTAUpdateHandle == NULL)
322+
{
323+
ChipLogError(SoftwareUpdate, "esp_delta_ota_init failed");
324+
imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_INTERNAL);
325+
return;
326+
}
327+
#endif // CONFIG_ENABLE_DELTA_OTA
152328

153329
#ifdef CONFIG_ENABLE_ENCRYPTED_OTA
154330
CHIP_ERROR chipError = imageProcessor->DecryptStart();
@@ -182,7 +358,30 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context)
182358
}
183359
#endif // CONFIG_ENABLE_ENCRYPTED_OTA
184360

361+
#ifdef CONFIG_ENABLE_DELTA_OTA
362+
esp_err_t err = esp_delta_ota_finalize(imageProcessor->mDeltaOTAUpdateHandle);
363+
if (err != ESP_OK)
364+
{
365+
ESP_LOGE(TAG, "esp_delta_ota_finalize() failed (%s)!", esp_err_to_name(err));
366+
esp_ota_abort(imageProcessor->mOTAUpdateHandle);
367+
imageProcessor->ReleaseBlock();
368+
PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed);
369+
}
370+
371+
err = esp_delta_ota_deinit(imageProcessor->mDeltaOTAUpdateHandle);
372+
if (err != ESP_OK)
373+
{
374+
ESP_LOGE(TAG, "esp_delta_ota_deinit() failed (%s)!", esp_err_to_name(err));
375+
esp_ota_abort(imageProcessor->mOTAUpdateHandle);
376+
imageProcessor->ReleaseBlock();
377+
PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed);
378+
}
379+
380+
err = esp_ota_end(imageProcessor->mOTAUpdateHandle);
381+
DeltaOTACleanUp(reinterpret_cast<intptr_t>(imageProcessor));
382+
#else
185383
esp_err_t err = esp_ota_end(imageProcessor->mOTAUpdateHandle);
384+
#endif // CONFIG_ENABLE_DELTA_OTA
186385
if (err != ESP_OK)
187386
{
188387
if (err == ESP_ERR_OTA_VALIDATE_FAILED)
@@ -217,6 +416,10 @@ void OTAImageProcessorImpl::HandleAbort(intptr_t context)
217416
imageProcessor->DecryptAbort();
218417
#endif // CONFIG_ENABLE_ENCRYPTED_OTA
219418

419+
#ifdef CONFIG_ENABLE_DELTA_OTA
420+
DeltaOTACleanUp(reinterpret_cast<intptr_t>(imageProcessor));
421+
#endif // CONFIG_ENABLE_DELTA_OTA
422+
220423
if (esp_ota_abort(imageProcessor->mOTAUpdateHandle) != ESP_OK)
221424
{
222425
ESP_LOGE(TAG, "ESP OTA abort failed");
@@ -264,7 +467,24 @@ void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context)
264467
}
265468
#endif // CONFIG_ENABLE_ENCRYPTED_OTA
266469

267-
err = esp_ota_write(imageProcessor->mOTAUpdateHandle, blockToWrite.data(), blockToWrite.size());
470+
#ifdef CONFIG_ENABLE_DELTA_OTA
471+
472+
int index = 0;
473+
err = imageProcessor->VerifyHeaderData(blockToWrite.data(), blockToWrite.size(), &index);
474+
475+
if (err != ESP_OK)
476+
{
477+
ESP_LOGE(TAG, "Header data verification failed (%s)", esp_err_to_name(err));
478+
imageProcessor->mDownloader->EndDownload(CHIP_ERROR_INVALID_SIGNATURE);
479+
PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed);
480+
return;
481+
}
482+
483+
// Apply the patch and writes that data to the passive partition.
484+
err = esp_delta_ota_feed_patch(imageProcessor->mDeltaOTAUpdateHandle, blockToWrite.data() + index, blockToWrite.size() - index);
485+
#else
486+
err = esp_ota_write(imageProcessor->mOTAUpdateHandle, blockToWrite.data(), blockToWrite.size());
487+
#endif // CONFIG_ENABLE_DELTA_OTA
268488

269489
#ifdef CONFIG_ENABLE_ENCRYPTED_OTA
270490
free((void *) (blockToWrite.data()));

0 commit comments

Comments
 (0)