ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
ota_backend_esp_idf.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
3
7#include "esphome/core/log.h"
8
9#include <esp_ota_ops.h>
10#include <esp_task_wdt.h>
11#include <spi_flash_mmap.h>
12#ifdef USE_OTA_DOWNGRADE_PROTECTION
13#include <esp_app_desc.h>
14#endif
15
16namespace esphome::ota {
17
18static const char *const TAG = "ota.idf";
19
20std::unique_ptr<IDFOTABackend> make_ota_backend() { return make_unique<IDFOTABackend>(); }
21
23#ifdef USE_OTA_PARTITIONS
24 this->ota_type_ = ota_type;
25 if (this->ota_type_ == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
26 // Reject any size other than ESP_PARTITION_TABLE_MAX_LEN
27 if (image_size != ESP_PARTITION_TABLE_MAX_LEN) {
28 ESP_LOGE(TAG, "Wrong partition table size: expected %u bytes, got %zu", ESP_PARTITION_TABLE_MAX_LEN, image_size);
30 }
31 memset(this->buf_, 0xFF, sizeof this->buf_);
32 this->buf_written_ = 0;
33 this->image_size_ = image_size;
34 this->md5_.init();
35 return OTA_RESPONSE_OK;
36 }
37 if (this->ota_type_ == ota::OTA_TYPE_UPDATE_BOOTLOADER) {
38 OTAResponseTypes result = this->prepare_bootloader_update_(image_size);
39 if (result != OTA_RESPONSE_OK) {
40 return result;
41 }
42 }
43 if (!this->is_app_or_bootloader_update_()) {
45 }
46#else
47 if (ota_type != ota::OTA_TYPE_UPDATE_APP) {
49 }
50#endif
51#ifdef USE_OTA_ROLLBACK
52 // If we're starting an OTA, the current boot is good enough - mark it valid
53 // to prevent rollback and allow the OTA to proceed even if the safe mode
54 // timer hasn't expired yet.
55 esp_ota_mark_app_valid_cancel_rollback();
56#endif
57
58 this->partition_ = esp_ota_get_next_update_partition(nullptr);
59 if (this->partition_ == nullptr) {
61 }
62
63 // esp_ota_begin() erases the destination region, which blocks loopTask and
64 // scales with the erase size -- a fixed watchdog overruns on large OTA slots.
65 // An unknown size (0, e.g. web_server uploads) erases the whole partition, so
66 // budget against the bytes actually erased. ~10ms/KiB (conservative
67 // ~100 KiB/s erase) over a 15s floor; panic stays on so a stuck erase still
68 // resets rather than hanging forever.
69 size_t erase_size = image_size;
70 if (erase_size == 0 || erase_size > this->partition_->size) {
71 erase_size = this->partition_->size;
72 }
73 const uint32_t erase_budget_ms = 15000 + (erase_size >> 10) * 10;
74 watchdog::WatchdogManager watchdog(erase_budget_ms);
75 esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
76
77 if (err != ESP_OK) {
78 ESP_LOGE(TAG, "esp_ota_begin failed (err=0x%X)", err);
79 esp_ota_abort(this->update_handle_);
80 this->update_handle_ = 0;
81 if (err == ESP_ERR_INVALID_SIZE) {
83 } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) {
85 } else if (err == ESP_ERR_OTA_PARTITION_CONFLICT) {
86 // This error appears with 1 factory and 1 ota partition
88 }
90 }
91#ifdef USE_OTA_PARTITIONS
92 if (this->ota_type_ == ota::OTA_TYPE_UPDATE_BOOTLOADER) {
94 if (result != OTA_RESPONSE_OK) {
95 return result;
96 }
97 }
98#endif
99 this->md5_.init();
100 return OTA_RESPONSE_OK;
101}
102
103void IDFOTABackend::set_update_md5(const char *expected_md5) {
104 memcpy(this->expected_bin_md5_, expected_md5, 32);
105 this->md5_set_ = true;
106}
107
109#ifdef USE_OTA_PARTITIONS
110 if (this->ota_type_ == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
111 if (len > PARTITION_TABLE_BUFFER_SIZE - this->buf_written_) {
112 ESP_LOGE(TAG, "Wrong partition table size");
114 }
115 memcpy(this->buf_ + this->buf_written_, data, len);
116 this->buf_written_ += len;
117 this->md5_.add(data, len);
118 return OTA_RESPONSE_OK;
119 }
120 if (!this->is_app_or_bootloader_update_()) {
122 }
123#endif
124 esp_err_t err = esp_ota_write(this->update_handle_, data, len);
125 this->md5_.add(data, len);
126 if (err != ESP_OK) {
127 ESP_LOGE(TAG, "esp_ota_write failed (err=0x%X)", err);
128 if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
130 } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) {
132 }
134 }
135 return OTA_RESPONSE_OK;
136}
137
139 if (this->md5_set_) {
140 this->md5_.calculate();
141 if (!this->md5_.equals_hex(this->expected_bin_md5_)) {
142 this->abort();
144 }
145 }
146#ifdef USE_OTA_PARTITIONS
147 if (this->ota_type_ == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
148 return this->update_partition_table();
149 }
150 if (!this->is_app_or_bootloader_update_()) {
152 }
153#endif
154 esp_err_t err = esp_ota_end(this->update_handle_);
155 this->update_handle_ = 0;
156 if (err != ESP_OK) {
157 ESP_LOGE(TAG, "esp_ota_end failed (err=0x%X)", err);
158 }
159#ifdef USE_OTA_PARTITIONS
160 if (this->ota_type_ == ota::OTA_TYPE_UPDATE_BOOTLOADER) {
161 return this->finalize_bootloader_update_(err);
162 }
163#endif
164 if (err == ESP_OK) {
165#ifdef USE_OTA_DOWNGRADE_PROTECTION
166 // The image is written and (when signing is enabled) signature-verified by
167 // esp_ota_end(), so its embedded project version can be trusted. Reject the
168 // update if it is older than the running version by leaving the boot
169 // partition unchanged -- the staged image simply never boots.
170 esp_app_desc_t incoming;
171 esp_err_t desc_err = esp_ota_get_partition_description(this->partition_, &incoming);
172 if (desc_err != ESP_OK) {
173 // Couldn't read the staged image's version, so the comparison is skipped.
174 // Warn so the bypassed check is observable rather than silent.
175 ESP_LOGW(TAG, "Downgrade protection: could not read image version (err=0x%X); allowing update", desc_err);
176 } else if (version_is_older(incoming.version, ESPHOME_PROJECT_VERSION)) {
177 ESP_LOGE(TAG, "Rejecting downgrade: image version '%s' is older than running version '%s'", incoming.version,
178 ESPHOME_PROJECT_VERSION);
180 }
181#endif
182 err = esp_ota_set_boot_partition(this->partition_);
183 if (err == ESP_OK) {
184 return OTA_RESPONSE_OK;
185 }
186 }
187 if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
188#ifdef USE_OTA_SIGNED_VERIFICATION
189 ESP_LOGE(TAG, "OTA validation failed (err=0x%X) - possible signature verification failure", err);
191#else
193#endif
194 }
195 if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) {
197 }
199}
200
202#ifdef USE_OTA_PARTITIONS
203 if (this->partition_table_part_ != nullptr) {
204 esp_partition_deregister_external(this->partition_table_part_);
205 this->partition_table_part_ = nullptr;
206 }
207 if (this->bootloader_part_ != nullptr) {
208 esp_partition_deregister_external(this->bootloader_part_);
209 this->bootloader_part_ = nullptr;
210 }
211#endif
212 // esp_ota_abort with handle 0 returns ESP_ERR_INVALID_ARG harmlessly, so this is safe whether
213 // or not an update is in flight.
214 esp_ota_abort(this->update_handle_);
215 this->update_handle_ = 0;
216}
217
218} // namespace esphome::ota
219#endif // USE_ESP32
bool equals_hex(const char *expected)
Compare the hash against a provided hex-encoded hash.
Definition hash_base.h:35
void calculate() override
Compute the digest, based on the provided data.
Definition md5.cpp:16
void add(const uint8_t *data, size_t len) override
Add bytes of data for the digest.
Definition md5.cpp:14
void init() override
Initialize a new MD5 digest computation.
Definition md5.cpp:9
void set_update_md5(const char *md5)
OTAResponseTypes finalize_bootloader_update_(esp_err_t ota_end_err)
OTAResponseTypes begin(size_t image_size, ota::OTAType ota_type=ota::OTA_TYPE_UPDATE_APP)
OTAResponseTypes prepare_bootloader_update_(size_t image_size)
OTAResponseTypes write(uint8_t *data, size_t len)
@ OTA_TYPE_UPDATE_BOOTLOADER
Definition ota_backend.h:78
@ OTA_TYPE_UPDATE_PARTITION_TABLE
Definition ota_backend.h:77
bool version_is_older(const char *candidate, const char *reference)
Compare two dotted-numeric version strings (such as "1.2.3").
@ OTA_RESPONSE_ERROR_MD5_MISMATCH
Definition ota_backend.h:41
@ OTA_RESPONSE_ERROR_VERSION_DOWNGRADE
Definition ota_backend.h:49
@ OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE
Definition ota_backend.h:39
@ OTA_RESPONSE_ERROR_WRITING_FLASH
Definition ota_backend.h:33
@ OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE
Definition ota_backend.h:44
@ OTA_RESPONSE_ERROR_UPDATE_END
Definition ota_backend.h:34
@ OTA_RESPONSE_ERROR_SIGNATURE_INVALID
Definition ota_backend.h:43
@ OTA_RESPONSE_ERROR_UNKNOWN
Definition ota_backend.h:50
@ OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION
Definition ota_backend.h:40
@ OTA_RESPONSE_ERROR_MAGIC
Definition ota_backend.h:30
@ OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY
Definition ota_backend.h:45
std::unique_ptr< ArduinoLibreTinyOTABackend > make_ota_backend()
const void size_t len
Definition hal.h:64
static void uint32_t