diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 045aa133d64..f9133a515f6 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -223,20 +223,22 @@ void ESPHomeOTAComponent::handle_handshake_() { this->ota_features_ = this->handshake_buf_[0]; ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_); this->transition_ota_state_(OTAState::FEATURE_ACK); + + const bool supports_compression = ((this->ota_features_ & CLIENT_FEATURE_SUPPORTS_COMPRESSION) != 0 && + this->backend_->supports_compression()); #ifdef USE_OTA_PARTITIONS this->extended_proto_ = this->ota_features_ & CLIENT_FEATURE_SUPPORTS_EXTENDED_PROTOCOL; if (this->extended_proto_) { - this->handshake_buf_[0] = ota::OTA_RESPONSE_FEATURE_FLAGS; - this->handshake_buf_[1] = 0; - if ((this->ota_features_ & CLIENT_FEATURE_SUPPORTS_COMPRESSION) != 0 && - this->backend_->supports_compression()) { + // If the client supports the extended protocol, send 2 bytes: response type and server feature flags + this->handshake_buf_[0] = ota::OTA_RESPONSE_FEATURE_FLAGS; // indicates the following byte contains feature flags + this->handshake_buf_[1] = SERVER_FEATURE_SUPPORTS_PARTITION_ACCESS; // supported if USE_OTA_PARTITIONS + if (supports_compression) { this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_COMPRESSION; } - this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_PARTITION_ACCESS; } else { #endif - this->handshake_buf_[0] = - ((this->ota_features_ & CLIENT_FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression()) + // Standard protocol without server feature flags + this->handshake_buf_[0] = (supports_compression) ? ota::OTA_RESPONSE_SUPPORTS_COMPRESSION : ota::OTA_RESPONSE_HEADER_OK; #ifdef USE_OTA_PARTITIONS diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 269d9153b2b..3e79b69e674 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -178,6 +178,10 @@ void IDFOTABackend::abort() { } #ifdef USE_OTA_PARTITIONS +static inline bool check_overlap(uint32_t a_offset, size_t a_size, uint32_t b_offset, size_t b_size) { + return (a_offset + a_size > b_offset && b_offset + b_size > a_offset); +} + OTAResponseTypes IDFOTABackend::update_partition_table() { int num_partitions; if (this->buf_written_ == 0 || this->image_size_ != this->buf_written_) { @@ -239,28 +243,32 @@ OTAResponseTypes IDFOTABackend::update_partition_table() { // Check if the required app and otadata partitions exist in the new partition table // Check which app slot to boot from in the new partition table int app_partitions_found = 0; - int app_index = -1; - int app_index_with_copy = -1; - int otadata_index = -1; - bool otadata_no_overlap = false; + int new_app_part_index = -1; + int new_app_part_index_with_copy = -1; + int new_otadata_part_index = -1; + bool otadata_overlap = true; const esp_partition_t *app_copy_target_part{nullptr}; - for (int i = 0; i < num_partitions; i++) { + for (int i = 0; i < num_partitions; i++) { // Iterate over new partition table const esp_partition_info_t *new_part = &new_partition_table[i]; if (new_part->type == ESP_PARTITION_TYPE_APP) { + // Found an app partition in the new partition table app_partitions_found++; if (new_part->pos.size >= running_app_size) { + // Running app can fit inside this partition if (new_part->pos.offset == running_app_offset) { - app_index = i; - } else if (new_part->pos.offset >= running_app_offset + running_app_size || - running_app_offset >= new_part->pos.offset + running_app_size) { - // New app partition has no overlap with running app + // This new app partition can be used for the running app without copying because the offsets are the same + new_app_part_index = i; + } else if (!check_overlap(running_app_offset, running_app_size, new_part->pos.offset, running_app_size)) { + // This new app partition can be used for the running app after copying the app into it + // Check if there is an app partition in the old partition table at the right offset + // This is for esp_partition_copy and won't be needed after implementing a better copy function in the future esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); while (it != NULL) { const esp_partition_t *p = esp_partition_get(it); if (p->address == new_part->pos.offset && p->size >= running_app_size) { - // App partition exists in old and new partition table - app_index_with_copy = i; - app_copy_target_part = p; + // Found a suitable pair of partitions in the old and new partition table to copy the running app to + new_app_part_index_with_copy = i; // The partition index in the new partition table + app_copy_target_part = p; // The partition in the old partition table } it = esp_partition_next(it); } @@ -268,20 +276,20 @@ OTAResponseTypes IDFOTABackend::update_partition_table() { } } } else if (new_part->type == ESP_PARTITION_TYPE_DATA && new_part->subtype == ESP_PARTITION_SUBTYPE_DATA_OTA) { - otadata_index = i; - otadata_no_overlap = new_part->pos.offset >= running_app_offset + running_app_size || - running_app_offset >= new_part->pos.offset + new_part->pos.size; + // Found the otadata partition in the new partition table + new_otadata_part_index = i; + otadata_overlap = check_overlap(running_app_offset, running_app_size, new_part->pos.offset, new_part->pos.size); } } - if (app_index == -1 && app_index_with_copy == -1) { + if (new_app_part_index == -1 && new_app_part_index_with_copy == -1) { ESP_LOGE(TAG, "No compatible app partition found in the new partition table"); return OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY; } - if (app_partitions_found < 2 || otadata_index == -1) { + if (app_partitions_found < 2 || new_otadata_part_index == -1) { ESP_LOGE(TAG, "New partition table is missing the required app or otadata partitions"); return OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY; } - if (!otadata_no_overlap) { + if (otadata_overlap) { ESP_LOGE(TAG, "New otadata partition overlaps with running app"); return OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY; } @@ -292,7 +300,7 @@ OTAResponseTypes IDFOTABackend::update_partition_table() { nvs_flash_deinit(); // Copy the running app partition to new position if needed - if (app_index == -1) { + if (new_app_part_index == -1) { const esp_partition_t *running_app_part = nullptr; esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); while (it != NULL) { @@ -341,7 +349,7 @@ OTAResponseTypes IDFOTABackend::update_partition_table() { esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); while (it != NULL) { const esp_partition_t *p = esp_partition_get(it); - const esp_partition_info_t *new_part = &new_partition_table[app_index == -1 ? app_index_with_copy : app_index]; + const esp_partition_info_t *new_part = &new_partition_table[new_app_part_index == -1 ? new_app_part_index_with_copy : new_app_part_index]; if (p->address == new_part->pos.offset) { new_boot_partition = p; } diff --git a/tests/components/ota/test-partition_access.esp32-idf.yaml b/tests/components/ota/test-partition_access.esp32-idf.yaml new file mode 100644 index 00000000000..0cbf8549520 --- /dev/null +++ b/tests/components/ota/test-partition_access.esp32-idf.yaml @@ -0,0 +1,5 @@ +ota: + - platform: esphome + allow_partition_access: true + +<<: !include common.yaml diff --git a/tests/components/ota/test.esp32-idf.yaml b/tests/components/ota/test.esp32-idf.yaml index 0cbf8549520..dade44d145b 100644 --- a/tests/components/ota/test.esp32-idf.yaml +++ b/tests/components/ota/test.esp32-idf.yaml @@ -1,5 +1 @@ -ota: - - platform: esphome - allow_partition_access: true - <<: !include common.yaml