The running-app position cache lived as three function-local statics
inside get_running_app_position(). They cannot be IDFOTABackend members
(the backend is per-connection, the cache must outlive a backend that
called esp_partition_unload_all() in a prior aborted partition-table
OTA), but burying them inside the function made the lifetime and
shared-across-connections semantics implicit. Move them to file scope
with s_running_app_ prefix so the process-scoped lifetime is visible
at first read, and tighten the surrounding comments.
No behaviour change.
Splits the non-destructive validation phase out of update_partition_table()
into a dedicated method. update_partition_table() now reads top-down as
"check buffer state, find running app, validate the new table + plan the
target slot, then commit", with the destructive write isolated to the
final block.
The chosen slot and optional copy-source partition are returned via a
small PartitionTablePlan struct so the caller no longer juggles the two
candidate-index variables. Refactor only; behaviour and error semantics
are unchanged.
Split the combined "missing app, otadata, or nvs" verify failure into
three separate ESP_LOGE messages so users can see which check failed,
trim trailing spaces from the (err=0x%X) log strings, and document why
the partition-table espota2 test mocks SERVER_FEATURE_SUPPORTS_COMPRESSION
(intentional protocol-path coverage; the real IDFOTABackend never sets it).
Read the resolved partition-table file in upload_program before opening
a network connection. Reject anything that isn't 0xC00 bytes, doesn't
start with ESP_PARTITION_MAGIC, or is missing the MD5 checksum entry, so
mistakes (wrong file, swapped --file path) surface as a local error
instead of a post-handshake OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY.
Includes unit tests covering size, magic, md5-presence, missing-file,
and end-to-end upload_program rejection, plus three real partition
tables checked in as fixtures (ESPHome build, ESP-IDF Hello-world,
esphome_dashboard prebuilt).