ESPHome 2026.10.2
Loading...
Searching...
No Matches
ota_esphome.cpp
Go to the documentation of this file.
1#include "ota_esphome.h"
2#ifdef USE_OTA_ENCRYPTION_PROVISIONED
4#endif
5#ifdef USE_OTA
6#ifdef USE_OTA_PASSWORD
8#endif
17#include "esphome/core/hal.h"
19#include "esphome/core/log.h"
20#include "esphome/core/util.h"
21#ifdef USE_LWIP_FAST_SELECT
23#endif
24
25#include <algorithm>
26#include <cerrno>
27#include <cstdio>
28#include <cstring>
29#include <sys/time.h>
30
31namespace esphome {
32
33static const char *const TAG = "esphome.ota";
34
35#ifdef USE_OTA_ENCRYPTION
37#ifdef USE_OTA_ENCRYPTION_PROVISIONED
38 // The api server holds the live key; safe mode never constructs it, and then
39 // noise_ctx_ holds the saved key setup() found, if any
40 if (api::global_api_server != nullptr)
42#endif
43 return this->noise_ctx_;
44}
45#endif
46static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
47static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
48// Milliseconds for data transfer. Covers the lwIP retransmit run seen in
49// practice for a lost chunk ack (1.5 + 3 + 6 + 12 + 24 + 48 s); the CLI waits
50// longer (espota2.DATA_PHASE_TIMEOUT) so the device is free before it retries
51static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 105000;
52static constexpr uint32_t OTA_PROGRESS_INTERVAL_MS = 1000;
53static constexpr size_t OTA_SIZE_FIELD_BYTES = 4; // sizes on the wire are 4 bytes MSB first
54
55// Single-instance pointer — multi-port configs are rejected in final_validate.
56// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
57static ESPHomeOTAComponent *global_esphome_ota_component = nullptr;
58
59// Called from any context (LwIP TCP/IP task, RP2040 user-IRQ).
61 if (global_esphome_ota_component != nullptr) {
62 global_esphome_ota_component->enable_loop_soon_any_context();
63 }
64}
65
67#ifdef USE_OTA_ENCRYPTION_PROVISIONED
68 // Safe mode never constructs the api server, so read the key it saved
69 noise::psk_t psk;
72 if (this->saved_psk_ != nullptr) {
73 this->noise_ctx_.set_psk(this->saved_psk_->data());
74 }
75 }
76#endif
77 this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections
78 if (this->server_ == nullptr) {
79 this->server_failed_(LOG_STR("creation"));
80 return;
81 }
82 int enable = 1;
83 int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
84 if (err != 0) {
85 this->log_socket_error_(LOG_STR("reuseaddr"));
86 // we can still continue
87 }
88 err = this->server_->setblocking(false);
89 if (err != 0) {
90 this->server_failed_(LOG_STR("nonblocking"));
91 return;
92 }
93
94 struct sockaddr_storage server;
95
96 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
97 if (sl == 0) {
98 this->server_failed_(LOG_STR("set sockaddr"));
99 return;
100 }
101
102 err = this->server_->bind((struct sockaddr *) &server, sl);
103 if (err != 0) {
104 this->server_failed_(LOG_STR("bind"));
105 return;
106 }
107
108 err = this->server_->listen(1); // Only one client at a time
109 if (err != 0) {
110 this->server_failed_(LOG_STR("listen"));
111 return;
112 }
113
114 // loop() self-disables on its first idle tick; no explicit disable_loop() needed here.
115 global_esphome_ota_component = this;
116#ifdef USE_LWIP_FAST_SELECT
117 // Filter fast-select wakes to this listener only. If the sock lookup returns nullptr,
118 // no wakes fire and loop() falls back to the self-disable safety net.
120#endif
121
122#ifdef USE_OTA_PARTITIONS
124#endif
125}
126
128 char addr_buf[network::USE_ADDRESS_BUFFER_SIZE];
129 ESP_LOGCONFIG(TAG,
130 "Over-The-Air updates:\n"
131 " Address: %s:%u\n"
132 " Version: %d"
133#ifdef USE_OTA_ENCRYPTION
134 "\n Encryption: %s"
135#endif
136 ,
137 network::get_use_address_to(addr_buf), this->port_, USE_OTA_VERSION
138#ifdef USE_OTA_ENCRYPTION_REQUIRED
139 ,
140 LOG_STR_LITERAL("required")
141#elif defined(USE_OTA_ENCRYPTION_PROVISIONED)
142 // A runtime provisioned key may not exist yet
143 ,
144 this->noise_context_().has_psk() ? LOG_STR_LITERAL("offered, plaintext accepted")
145 : LOG_STR_LITERAL("offered once the api key is provisioned")
146#elif defined(USE_OTA_ENCRYPTION)
147 ,
148 LOG_STR_LITERAL("offered, plaintext accepted")
149#endif
150 );
151#ifdef USE_OTA_PASSWORD
152 if (!this->password_.empty()) {
153 ESP_LOGCONFIG(TAG, " Password configured");
154 }
155#endif
156#ifdef USE_OTA_PARTITIONS
157 ESP_LOGCONFIG(TAG,
158 " Partition access allowed\n"
159 " Running app:\n"
160 " Partition address: 0x%" PRIX32 "\n"
161 " Used size: %zu bytes (0x%zX)",
163
164#ifdef USE_ESP32
165 ESP_LOGCONFIG(TAG,
166 " Partition table:\n"
167 " %-12s %-4s %-8s %-10s %-10s",
168 "Name", "Type", "Subtype", "Address", "Size");
169 esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, nullptr);
170 while (it != nullptr) {
171 const esp_partition_t *partition = esp_partition_get(it);
172 ESP_LOGCONFIG(TAG, " %-12s 0x%-2X 0x%-6X 0x%-8" PRIX32 " 0x%-8" PRIX32, partition->label, partition->type,
173 partition->subtype, partition->address, partition->size);
174 it = esp_partition_next(it);
175 }
176 esp_partition_iterator_release(it);
177 esp_bootloader_desc_t bootloader_desc;
178 esp_err_t err = esp_ota_get_bootloader_description(nullptr, &bootloader_desc);
179 ESP_LOGCONFIG(TAG, " Bootloader: ESP-IDF %s",
180 (err == ESP_OK) ? bootloader_desc.idf_ver : LOG_STR_LITERAL("version unknown"));
181#endif // USE_ESP32
182#endif // USE_OTA_PARTITIONS
183}
184
186 // Self-disable idle loop where a wake path re-enables on listener readiness
187 // (fast-select, raw-TCP accept_fn_). Host BSD select doesn't, so stay enabled.
188 if (this->client_ == nullptr && !this->server_->ready()) {
189#ifndef USE_HOST
190 this->disable_loop();
191#endif
192 return;
193 }
194 this->handle_handshake_();
195}
196
197// Deflate for an RTL8710B image: 2026.10 firmware does not know this bit, and
198// there LibreTiny corrupts a deflate upload into OTA2 (esphome#22012)
199static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_DEFLATE_RTL8710B = 0x40;
200static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_COMPRESSION = 0x01;
201static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
202static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_EXTENDED_PROTOCOL = 0x04;
203static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_NOISE = 0x08;
204static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_DEFLATE = 0x10;
205// Noise needs the extended protocol: the prologue binds the 2-byte feature ack
206static constexpr uint8_t CLIENT_NOISE_FEATURES =
207 CLIENT_FEATURE_SUPPORTS_NOISE | CLIENT_FEATURE_SUPPORTS_EXTENDED_PROTOCOL;
208static constexpr uint8_t SERVER_FEATURE_SUPPORTS_COMPRESSION = 0x01;
209static constexpr uint8_t SERVER_FEATURE_SUPPORTS_PARTITION_ACCESS = 0x02;
210static constexpr uint8_t SERVER_FEATURE_SUPPORTS_NOISE = 0x04;
211// Raw deflate, window <= OTA_INFLATE_WINDOW_SIZE. Binding once offered: the
212// client must then send the image size frame and a deflate stream.
213static constexpr uint8_t SERVER_FEATURE_SUPPORTS_DEFLATE = 0x08;
214
215#ifdef USE_OTA_ENCRYPTION
217 return (this->handshake_buf_[1] & SERVER_FEATURE_SUPPORTS_NOISE) != 0 &&
218 (this->ota_features_ & CLIENT_NOISE_FEATURES) == CLIENT_NOISE_FEATURES;
219}
220#endif
221
223#ifdef USE_OTA_ENCRYPTION_REQUIRED
224 // FEATURE_READ already refused every client without the extended protocol
225 return true;
226#else
227 return (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_EXTENDED_PROTOCOL) != 0;
228#endif
229}
230
237
238 if (this->client_ == nullptr) {
239 // We already checked server_->ready() in loop(), so we can accept directly
240 struct sockaddr_storage source_addr;
241 socklen_t addr_len = sizeof(source_addr);
242 int enable = 1;
243
244 this->client_ = this->server_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
245 if (this->client_ == nullptr)
246 return;
247 int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
248 if (err != 0) {
249 this->log_socket_error_(LOG_STR("nodelay"));
250 this->cleanup_connection_();
251 return;
252 }
253 err = this->client_->setblocking(false);
254 if (err != 0) {
255 this->log_socket_error_(LOG_STR("non-blocking"));
256 this->cleanup_connection_();
257 return;
258 }
259 this->log_start_(LOG_STR("handshake"));
261 this->handshake_buf_pos_ = 0; // Reset handshake buffer position
263 }
264
265 // Check for handshake timeout
267 if (now - this->client_connect_time_ > OTA_SOCKET_TIMEOUT_HANDSHAKE) {
268 ESP_LOGW(TAG, "Handshake timeout");
269 this->cleanup_connection_();
270 return;
271 }
272
273 switch (this->ota_state_) {
275 // Try to read remaining magic bytes (5 total)
276 if (!this->try_read_(5, LOG_STR("read magic"))) {
277 return;
278 }
279
280 // Validate magic bytes
281 if (memcmp(this->handshake_buf_, MAGIC_BYTES, sizeof(MAGIC_BYTES)) != 0) {
282 ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->handshake_buf_[0],
283 this->handshake_buf_[1], this->handshake_buf_[2], this->handshake_buf_[3], this->handshake_buf_[4]);
285 return;
286 }
287
288 // Magic bytes valid, move to next state
291 this->handshake_buf_[1] = USE_OTA_VERSION;
292 [[fallthrough]];
293 }
294
295 case OTAState::MAGIC_ACK: {
296 // Send OK and version - 2 bytes
297 if (!this->try_write_(2, LOG_STR("ack magic"))) {
298 return;
299 }
300 // All bytes sent, create backend and move to next state
303 [[fallthrough]];
304 }
305
307 // Read features - 1 byte
308 if (!this->try_read_(1, LOG_STR("read feature"))) {
309 return;
310 }
311 this->ota_features_ = this->handshake_buf_[0];
312 ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_);
313
314#ifdef USE_OTA_ENCRYPTION_REQUIRED
315 // `ota: encryption:` requires the client to negotiate encryption
316 if ((this->ota_features_ & CLIENT_NOISE_FEATURES) != CLIENT_NOISE_FEATURES) {
317 ESP_LOGW(TAG, "Client does not support encryption");
319 return;
320 }
321#endif
322
324
325 const bool supports_compression =
326 (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_COMPRESSION) != 0 && ota::OTABackend::supports_compression();
327
328 // Compose the feature-ack response. When the client negotiates the extended protocol we emit
329 // a 2-byte response (marker + server feature flags); otherwise we emit the single-byte
330 // legacy response.
331 if (this->extended_proto_()) {
332 static_assert(HANDSHAKE_BUF_SIZE >= 2, "handshake_buf_ must hold the 2-byte extended-protocol feature ack");
334 this->handshake_buf_[1] = (supports_compression ? SERVER_FEATURE_SUPPORTS_COMPRESSION : 0);
335#ifdef USE_OTA_PARTITIONS
336 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_PARTITION_ACCESS;
337#endif
338#ifdef USE_OTA_ENCRYPTION_PROVISIONED
339 // A runtime provisioned key may not exist yet
340 if (this->noise_context_().has_psk()) {
341 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_NOISE;
342 }
343#elif defined(USE_OTA_ENCRYPTION)
344 // A yaml key always exists: validation rejects the all-zeros key
345 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_NOISE;
346#endif
347#ifdef USE_OTA_ENCRYPTION
348 // Reserve the noise session before the optional inflate buffer, so the
349 // required allocation is not starved by the compression window
350 if (this->noise_offered_()) {
352 }
353#endif
354#ifdef USE_OTA_DEFLATE
355 // Offered only once the session memory is in hand; else uncompressed
356 if ((this->ota_features_ & (CLIENT_FEATURE_SUPPORTS_DEFLATE | CLIENT_FEATURE_SUPPORTS_DEFLATE_RTL8710B)) != 0) {
357 // Value initialized: a corrupt stream that back references the
358 // window before it is filled then copies zeros, never stale memory.
359 // Default placement, PSRAM first where present: the session lives for one
360 // upload and keeps 4.9 KB of internal heap free while it runs
362 if (this->inflate_ != nullptr) {
363 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_DEFLATE;
364 } else {
365 ESP_LOGW(TAG, "No memory to inflate");
366 }
367 }
368#endif
369 } else {
370 this->handshake_buf_[0] =
372 }
373 [[fallthrough]];
374 }
375
377 static constexpr size_t STANDARD_PROTO_ACK_SIZE = 1;
378 static constexpr size_t EXTENDED_PROTO_ACK_SIZE = 2;
379 const size_t ack_size = this->extended_proto_() ? EXTENDED_PROTO_ACK_SIZE : STANDARD_PROTO_ACK_SIZE;
380 if (!this->try_write_(ack_size, LOG_STR("ack feature"))) {
381 return;
382 }
383#ifdef USE_OTA_ENCRYPTION
384 // Latch the offer actually sent: a key activating between the two
385 // states must not start a session the client never expects
386 if (this->noise_offered_()) {
387 // handshake_buf_ still holds the feature ack composed above; a
388 // would-block re-entry lands here without rebuilding it
389 if (!this->noise_start_session_(this->handshake_buf_[1])) {
390 return;
391 }
393 return;
394 }
395#endif
396#ifdef USE_OTA_PASSWORD
397 // If password is set, move to auth phase
398 if (!this->password_.empty()) {
400 } else
401#endif
402 {
403 // No password, move directly to data phase
405 }
406 [[fallthrough]];
407 }
408
409#ifdef USE_OTA_PASSWORD
410 case OTAState::AUTH_SEND: {
411 // Non-blocking authentication send
412 if (!this->handle_auth_send_()) {
413 return;
414 }
416 [[fallthrough]];
417 }
418
419 case OTAState::AUTH_READ: {
420 // Non-blocking authentication read & verify
421 if (!this->handle_auth_read_()) {
422 return;
423 }
425 [[fallthrough]];
426 }
427#endif
428
429 case OTAState::DATA:
430 this->handle_data_();
431 return;
432
433#ifdef USE_OTA_ENCRYPTION
435 if (!this->handle_noise_handshake_()) {
436 return;
437 }
439 this->handle_data_();
440 return;
441#endif
442
443 default:
444 break;
445 }
446}
447
481 // Backend calls overwrite this with OK; reset to UNKNOWN before any
482 // goto error that follows a successful begin()/write()
484 DataTransfer xfer;
485 uint8_t buf[OTA_BUFFER_SIZE];
486 char *sbuf = reinterpret_cast<char *>(buf);
487 size_t image_size;
489
490 // Set socket timeouts and blocking mode (see strategy table above)
491 struct timeval tv;
492 tv.tv_sec = 2;
493 tv.tv_usec = 0;
494 this->client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
495 this->client_->setsockopt(SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
496 if (this->client_->setblocking(true) != 0) {
497 this->log_socket_error_(LOG_STR("blocking"));
498 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
499 }
500
501 // Acknowledge auth OK - 1 byte
503
504 if (this->extended_proto_()) {
505 // Read ota type, 1 byte
506 if (!this->data_readall_(buf, 1)) {
507 if (this->client_left_before_start_())
508 return;
509 this->log_read_error_(LOG_STR("OTA type"));
510 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
511 }
512 ota_type = static_cast<ota::OTAType>(buf[0]);
513 }
514 ESP_LOGV(TAG, "OTA type is 0x%02x", ota_type);
515
516 // Read size, 4 bytes MSB first
517 if (!this->read_size_(buf, xfer.ota_size, LOG_STR("size"))) {
518 // The first request byte is the type on the extended protocol; a close after it was a cut-off request
519 if (!this->extended_proto_() && this->client_left_before_start_())
520 return;
521 this->log_read_error_(LOG_STR("size"));
522 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
523 }
524 image_size = xfer.ota_size;
525#ifdef USE_OTA_DEFLATE
526 if (this->inflate_ != nullptr && !this->read_size_(buf, image_size, LOG_STR("image size"))) {
527 this->log_read_error_(LOG_STR("image size"));
528 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
529 }
530#endif
531
532#ifndef USE_OTA_PARTITIONS
533 if (ota_type != ota::OTA_TYPE_UPDATE_APP) {
535 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
536 }
537#endif
538
539 // Now that we've passed authentication and are actually
540 // starting the update, set the warning status and notify
541 // listeners. This ensures that port scanners do not
542 // accidentally trigger the update process.
543 this->log_start_(LOG_STR("update"));
544 this->status_set_warning();
545#ifdef USE_OTA_STATE_LISTENER
546 this->notify_state_(ota::OTA_STARTED, 0.0f, 0);
547#endif
548
549 // begin() returns quickly; flash sectors are erased incrementally during write().
550 error_code = this->backend_->begin(image_size, ota_type);
551 if (error_code != ota::OTA_RESPONSE_OK)
552 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
553
554 // Acknowledge prepare OK - 1 byte
556
557 // Read binary MD5, 32 bytes
558 if (!this->data_readall_(buf, 32)) {
559 this->log_read_error_(LOG_STR("MD5 checksum"));
561 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
562 }
563 sbuf[32] = '\0';
564 ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
565 this->backend_->set_update_md5(sbuf);
566
567 // Acknowledge MD5 OK - 1 byte
569
570 xfer.last_data_ms = millis();
571#ifdef USE_OTA_DEFLATE
572 if (this->inflate_ != nullptr) {
573 error_code = this->inflate_data_(buf, image_size, xfer);
574 if (error_code != ota::OTA_RESPONSE_OK)
575 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
576 } else
577#endif
578 {
579 while (xfer.total < xfer.ota_size) {
580 ssize_t read = this->receive_data_(buf, xfer);
581 if (read < 0) {
583 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
584 }
585 error_code = this->write_flash_(buf, read);
586 if (error_code != ota::OTA_RESPONSE_OK)
587 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
588 this->ack_written_(xfer);
589 }
590 }
591
592 // Acknowledge receive OK - 1 byte
594
595 error_code = this->backend_->end();
596 if (error_code != ota::OTA_RESPONSE_OK) {
597 ESP_LOGW(TAG, "End update err %d", error_code);
598 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
599 }
600
601 // Acknowledge Update end OK - 1 byte
603
604 // Read ACK
605 if (!this->data_readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
606 this->log_read_error_(LOG_STR("ack"));
607 // do not go to error, this is not fatal
608 }
609
610 this->cleanup_connection_();
611 delay(10);
612 ESP_LOGI(TAG, "Update complete");
613 this->status_clear_warning();
614#ifdef USE_OTA_STATE_LISTENER
615 this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0);
616#endif
617 delay(100); // NOLINT
618#ifdef USE_OTA_PARTITIONS
619 if (ota_type == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
620 // Skip on_safe_shutdown: nvs_flash_deinit() has already invalidated open NVS handles, so
621 // preferences flush would emit ESP_ERR_NVS_INVALID_HANDLE for every entry. Reboot directly.
622 App.reboot();
623 }
624#endif
626
627error:
628 this->data_write_byte_(static_cast<uint8_t>(error_code));
629
630 // Abort backend before cleanup - cleanup_connection_() destroys the backend.
631 // Always call abort() unconditionally: backends register external partitions before
632 // esp_ota_begin (partition table / bootloader paths), and abort() is responsible for
633 // releasing those even if begin() failed before an OTA handle was opened. The IDF
634 // backend's esp_ota_abort(0) is documented as harmless.
635 if (this->backend_ != nullptr) {
636 this->backend_->abort();
637 }
638
639 this->cleanup_connection_();
640
641 this->status_momentary_error("err", 5000);
642#ifdef USE_OTA_STATE_LISTENER
643 this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
644#endif
645}
646
647bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
648 uint32_t start = millis();
649 uint32_t at = 0;
650 while (len - at > 0) {
651 uint32_t now = millis();
652 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
653 ESP_LOGW(TAG, "Timeout reading %zu bytes", len);
654 return false;
655 }
656
657 ssize_t read = this->client_->read(buf + at, len - at);
658 if (read == -1) {
659 const int err = errno;
660 if (!this->would_block_(err)) {
661 ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, err);
662 return false;
663 }
664 } else if (read == 0) {
665 // A partial message is a cut-off request, not a clean close; the caller reports the clean one
666 this->remote_closed_ = at == 0;
667 if (at > 0) {
668 ESP_LOGW(TAG, "Remote closed after %u of %zu bytes", (unsigned) at, len);
669 }
670 return false;
671 } else {
672 at += read;
673 }
674 // read() already waited via SO_RCVTIMEO, just yield without 1ms stall
675 App.feed_wdt();
676 delay(0);
677 }
678
679 return true;
680}
681bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
682 uint32_t start = millis();
683 uint32_t at = 0;
684 while (len - at > 0) {
685 uint32_t now = millis();
686 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
687 ESP_LOGW(TAG, "Timeout writing %zu bytes", len);
688 return false;
689 }
690
691 ssize_t written = this->client_->write(buf + at, len - at);
692 if (written == -1) {
693 const int err = errno;
694 if (!this->would_block_(err)) {
695 ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, err);
696 return false;
697 }
698 // EWOULDBLOCK: on raw TCP writes never block, delay(1) prevents spinning
700 } else {
701 at += written;
702 // write() may block up to SO_SNDTIMEO on BSD/lwip sockets, feed WDT
703 App.feed_wdt();
704 }
705 }
706 return true;
707}
708
710
711void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
712 ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
713}
714
716 // Key probes and scanners hang up right after the handshake; nothing started, so no error status or callback
717 if (!this->remote_closed_)
718 return false;
719 ESP_LOGD(TAG, "Client left after the handshake");
720 this->cleanup_connection_();
721 return true;
722}
723
724void ESPHomeOTAComponent::log_read_error_(const LogString *what) {
725 if (this->remote_closed_) {
726 this->log_remote_closed_(what);
727 return;
728 }
729 ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what));
730}
731
732void ESPHomeOTAComponent::log_start_(const LogString *phase) {
733 char peername[socket::SOCKADDR_STR_LEN];
734 this->client_->getpeername_to(peername);
735 ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername);
736}
737
738void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
739 ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
740}
741
742void ESPHomeOTAComponent::server_failed_(const LogString *msg) {
743 this->log_socket_error_(msg);
744 // No explicit close() needed — listen sockets have no active connections on
745 // failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
746 delete this->server_;
747 this->server_ = nullptr;
748 this->mark_failed();
749}
750
751bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
752 if (read == -1 && this->would_block_(errno)) {
753 return false; // No data yet, try again next loop
754 }
755
756 if (read <= 0) {
757 read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc);
758 this->cleanup_connection_();
759 return false;
760 }
761 return true;
762}
763
765 if (written == -1) {
766 if (this->would_block_(errno)) {
767 return false; // Try again next loop
768 }
769 this->log_socket_error_(desc);
770 this->cleanup_connection_();
771 return false;
772 }
773 return true;
774}
775
776bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) {
777 // Read bytes into handshake buffer, starting at handshake_buf_pos_
778 size_t bytes_to_read = to_read - this->handshake_buf_pos_;
779 ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read);
780
781 if (!this->handle_read_error_(read, desc)) {
782 return false;
783 }
784
785 this->handshake_buf_pos_ += read;
786 // Return true only if we have all the requested bytes
787 return this->handshake_buf_pos_ >= to_read;
788}
789
790bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) {
791 // Write bytes from handshake buffer, starting at handshake_buf_pos_
792 size_t bytes_to_write = to_write - this->handshake_buf_pos_;
793 ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write);
794
795 if (!this->handle_write_error_(written, desc)) {
796 return false;
797 }
798
799 this->handshake_buf_pos_ += written;
800 // Return true only if we have written all the requested bytes
801 return this->handshake_buf_pos_ >= to_write;
802}
803
804bool ESPHomeOTAComponent::read_size_(uint8_t *buf, size_t &size, const LogString *desc) {
805 if (!this->data_readall_(buf, OTA_SIZE_FIELD_BYTES))
806 return false;
807 size = encode_uint32(buf[0], buf[1], buf[2], buf[3]);
808 ESP_LOGV(TAG, "%s is %zu bytes", LOG_STR_ARG(desc), size);
809 return true;
810}
811
813 ota::OTAResponseTypes result = this->backend_->write(data, len);
814 if (result != ota::OTA_RESPONSE_OK) {
815 ESP_LOGW(TAG, "Flash write err %d", result);
816 }
817 return result;
818}
819
821 const size_t remaining = xfer.ota_size - xfer.total;
822 const size_t requested = std::min(remaining, OTA_BUFFER_SIZE);
823 ssize_t read;
824 for (;;) {
825 // A silently-vanished peer (no FIN/RST delivered, e.g. uploader killed
826 // mid-transfer or NAT/router dropped state) must not wedge the device:
827 // read() only fails on EOF or a real error, and lwIP TCP keepalive isn't
828 // enabled here.
829 if (millis() - xfer.last_data_ms > OTA_SOCKET_TIMEOUT_DATA) {
830 ESP_LOGW(TAG, "No data received for %u ms", (unsigned) OTA_SOCKET_TIMEOUT_DATA);
831 return -1;
832 }
833#ifdef USE_OTA_ENCRYPTION
834 if (this->noise_ != nullptr) {
835 // One frame per call; noise_read_data_ waits internally (readall_), so
836 // there is no would-block retry here and failures are already logged.
837 read = this->noise_read_data_(buf, requested);
838 if (read <= 0) {
839 if (this->remote_closed_)
840 this->log_remote_closed_(LOG_STR("data"));
841 return -1;
842 }
843 break;
844 }
845#endif
846 read = this->client_->read(buf, requested);
847 if (read > 0)
848 break;
849 if (read == 0) {
850 this->log_remote_closed_(LOG_STR("data"));
851 return -1;
852 }
853 if (!this->would_block_(errno)) {
854 this->log_socket_error_(LOG_STR("data"));
855 return -1;
856 }
857 // read() already waited up to SO_RCVTIMEO for data, just feed WDT
858 App.feed_wdt();
859 }
860
861 const uint32_t now = millis();
862 xfer.last_data_ms = now;
863 xfer.total += read;
864 this->ack_received_(xfer);
865 if (now - xfer.last_progress > OTA_PROGRESS_INTERVAL_MS) {
866 xfer.last_progress = now;
867 float percentage = (xfer.total * 100.0f) / xfer.ota_size;
868 ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
869#ifdef USE_OTA_STATE_LISTENER
870 this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0);
871#endif
872 // feed watchdog and give other tasks a chance to run
874 }
875 return read;
876}
877
879#if USE_OTA_VERSION == 2
880 while (xfer.acknowledged + OTA_BLOCK_SIZE <= xfer.total ||
881 (xfer.total == xfer.ota_size && xfer.acknowledged < xfer.ota_size)) {
883 xfer.acknowledged += OTA_BLOCK_SIZE;
884 }
885#endif
886}
887
889 this->client_->close();
890 this->client_ = nullptr;
891 this->client_connect_time_ = 0;
892 this->handshake_buf_pos_ = 0;
894 this->ota_features_ = 0;
895 this->remote_closed_ = false;
896 this->backend_ = nullptr;
897#ifdef USE_OTA_PASSWORD
898 this->cleanup_auth_();
899#endif
900#ifdef USE_OTA_ENCRYPTION
901 this->noise_ = nullptr;
902#endif
903#ifdef USE_OTA_DEFLATE
904 this->inflate_ = nullptr;
905#endif
906 // Intentionally no disable_loop() — letting loop() run one more iteration catches
907 // any connection that queued on the listener mid-session (otherwise the wake flag,
908 // set while we were in LOOP state, would be lost to enable_pending_loops_()).
909}
910
915
916#ifdef USE_OTA_PASSWORD
917void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
918
920 bool client_supports_sha256 = (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_SHA256_AUTH) != 0;
921
922 // Require SHA256
923 if (!client_supports_sha256) {
924 this->log_auth_warning_(LOG_STR("SHA256 required"));
926 return false;
927 }
929 return true;
930}
931
933 // Initialize auth buffer if not already done
934 if (!this->auth_buf_) {
935 // Select auth type based on client capabilities and configuration
936 if (!this->select_auth_type_()) {
937 return false;
938 }
939
940 // Generate nonce - hasher must be created and used in same stack frame
941 // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
942 // 1. Hash objects must NEVER be passed to another function (different stack frame)
943 // 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA
944 // 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created
945 // Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption.
946 //
947 // Buffer layout after AUTH_READ completes:
948 // [0]: auth_type (1 byte)
949 // [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
950 // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
951 // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
952
953 // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
954 // (no passing to other functions). All hash operations must happen in this function.
955 sha256::SHA256 hasher;
956
957 const size_t hex_size = hasher.get_size() * 2;
958 const size_t nonce_len = hasher.get_size() / 4;
959 const size_t auth_buf_size = 1 + 3 * hex_size;
960 // Internal RAM first: 128 of these bytes go straight into the hardware SHA engine
961 this->auth_buf_ =
963 if (!this->auth_buf_) {
964 this->log_auth_warning_(LOG_STR("No memory"));
966 return false;
967 }
968 this->auth_buf_pos_ = 0;
969
970 char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
971 if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
972 this->log_auth_warning_(LOG_STR("Random failed"));
974 return false;
975 }
976
977 hasher.init();
978 hasher.add(buf, nonce_len);
979 hasher.calculate();
980 this->auth_buf_[0] = this->auth_type_;
981 hasher.get_hex(buf);
982
983 ESP_LOGV(TAG, "Auth: Nonce is %.*s", (int) hex_size, buf);
984 }
985
986 // Try to write auth_type + nonce
987 constexpr size_t hex_size = SHA256_HEX_SIZE;
988 const size_t to_write = 1 + hex_size;
989 size_t remaining = to_write - this->auth_buf_pos_;
990
991 ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining);
992 if (!this->handle_write_error_(written, LOG_STR("ack auth"))) {
993 return false;
994 }
995
996 this->auth_buf_pos_ += written;
997
998 // Check if we still have more to write
999 if (this->auth_buf_pos_ < to_write) {
1000 return false; // More to write, try again next loop
1001 }
1002
1003 // All written, prepare for reading phase
1004 this->auth_buf_pos_ = 0;
1005 return true;
1006}
1007
1009 constexpr size_t hex_size = SHA256_HEX_SIZE;
1010 const size_t to_read = hex_size * 2; // CNonce + Response
1011
1012 // Try to read remaining bytes (CNonce + Response)
1013 // We read cnonce+response starting at offset 1+hex_size (after auth_type and our nonce)
1014 size_t cnonce_offset = 1 + hex_size; // Offset where cnonce should be stored in buffer
1015 size_t remaining = to_read - this->auth_buf_pos_;
1016 ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining);
1017
1018 if (!this->handle_read_error_(read, LOG_STR("read auth"))) {
1019 return false;
1020 }
1021
1022 this->auth_buf_pos_ += read;
1023
1024 // Check if we still need more data
1025 if (this->auth_buf_pos_ < to_read) {
1026 return false; // More to read, try again next loop
1027 }
1028
1029 // We have all the data, verify it
1030 const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
1031 const char *cnonce = nonce + hex_size;
1032 const char *response = cnonce + hex_size;
1033
1034 // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
1035 // (no passing to other functions). All hash operations must happen in this function.
1036 sha256::SHA256 hasher;
1037
1038 hasher.init();
1039 hasher.add(this->password_.c_str(), this->password_.length());
1040 hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
1041 hasher.calculate();
1042
1043 ESP_LOGV(TAG, "Auth: CNonce is %.*s", (int) hex_size, cnonce);
1044#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
1045 char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator)
1046 hasher.get_hex(computed_hash);
1047 ESP_LOGV(TAG, "Auth: Result is %.*s", (int) hex_size, computed_hash);
1048#endif
1049 ESP_LOGV(TAG, "Auth: Response is %.*s", (int) hex_size, response);
1050
1051 // Compare response
1052 bool matches = hasher.equals_hex(response);
1053
1054 if (!matches) {
1055 this->log_auth_warning_(LOG_STR("Password mismatch"));
1057 return false;
1058 }
1059
1060 // Authentication successful - clean up auth state
1061 this->cleanup_auth_();
1062
1063 return true;
1064}
1065
1067 this->auth_buf_ = nullptr;
1068 this->auth_buf_pos_ = 0;
1069 this->auth_type_ = 0;
1070}
1071#endif // USE_OTA_PASSWORD
1072
1073} // namespace esphome
1074#endif
void feed_wdt()
Feed the task watchdog.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
void status_momentary_error(const char *name, uint32_t length=5000)
Set error status flag and automatically clear it after a timeout.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:289
ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using Ardui...
Definition ota_esphome.h:21
static constexpr size_t OTA_BUFFER_SIZE
bool handle_noise_handshake_()
Drive the non-blocking handshake from loop(); returns true once the transport ciphers are ready.
ssize_t receive_data_(uint8_t *buf, DataTransfer &xfer)
bool would_block_(int error_code) const
static constexpr size_t SHA256_HEX_SIZE
Definition ota_esphome.h:69
static constexpr uint8_t MAGIC_BYTES[5]
ota::OTAResponseTypes write_flash_(uint8_t *data, size_t len)
bool writeall_(const uint8_t *buf, size_t len)
bool try_read_(size_t to_read, const LogString *desc)
bool data_readall_(uint8_t *buf, size_t len)
noise::NoiseContext noise_ctx_
void ack_written_(DataTransfer &xfer)
bool noise_start_session_(uint8_t server_feature_flags)
Start the responder handshake, on the session reserved at offer time.
ota::OTABackendPtr backend_
bool try_write_(size_t to_write, const LogString *desc)
RAMUniquePtr< NoiseSession > noise_
bool handle_write_error_(ssize_t written, const LogString *desc)
void send_chunk_acks_(DataTransfer &xfer)
bool data_write_byte_(uint8_t byte)
void log_auth_warning_(const LogString *msg)
RAMUniquePtr< InflateSession > inflate_
float get_setup_priority() const override
void send_error_and_cleanup_(ota::OTAResponseTypes error)
bool handle_read_error_(ssize_t read, const LogString *desc)
ssize_t noise_read_data_(uint8_t *buf, size_t capacity)
Blocking read of one data-phase frame, decrypted in place; returns the plaintext size,...
void log_read_error_(const LogString *what)
bool readall_(uint8_t *buf, size_t len)
RAMUniquePtr< uint8_t[]> auth_buf_
RAMUniquePtr< noise::psk_t > saved_psk_
void ack_received_(DataTransfer &xfer)
uint8_t handshake_buf_[HANDSHAKE_BUF_SIZE]
static constexpr size_t HANDSHAKE_BUF_SIZE
ota::OTAResponseTypes inflate_data_(uint8_t *in, size_t image_size, DataTransfer &xfer)
const noise::NoiseContext & noise_context_() const
void server_failed_(const LogString *msg)
void transition_ota_state_(OTAState next_state)
bool read_size_(uint8_t *buf, size_t &size, const LogString *desc)
socket::ListenSocket * server_
void log_remote_closed_(const LogString *during)
std::unique_ptr< socket::Socket > client_
void log_start_(const LogString *phase)
void log_socket_error_(const LogString *msg)
void get_hex(char *output)
Retrieve the hash as hex characters. Output buffer must hold get_size() * 2 + 1 bytes.
Definition hash_base.h:29
bool equals_hex(const char *expected)
Compare the hash against a provided hex-encoded hash.
Definition hash_base.h:35
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2231
RAMUniquePtr< T > make_unique(Args &&...args)
Value initialize one T; empty on exhaustion.
Definition helpers.h:2294
RAMUniquePtr< T[]> make_unique_array_for_overwrite(size_t n)
n elements left uninitialized, as std::make_unique_for_overwrite does; empty on exhaustion,...
Definition helpers.h:2304
noise::NoiseContext & get_noise_ctx()
Definition api_server.h:92
void set_psk(const uint8_t *psk)
psk points at 32 bytes that outlive the context (PROGMEM or caller owned RAM); nullptr means no key.
Definition noise.h:32
void notify_state_(OTAState state, float progress, uint8_t error)
SHA256 hash implementation.
Definition sha256.h:51
void calculate() override
Definition sha256.cpp:27
size_t get_size() const override
Get the size of the hash in bytes (32 for SHA256)
Definition sha256.h:64
void add(const uint8_t *data, size_t len) override
Definition sha256.cpp:25
void init() override
Definition sha256.cpp:19
bool ready() const
Check if the socket has buffered data ready to read.
Definition socket.h:85
int bind(const struct sockaddr *addr, socklen_t addrlen)
int setsockopt(int level, int optname, const void *optval, socklen_t optlen)
std::unique_ptr< BSDSocketImpl > accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen)
uint16_t addr_len
uint32_t socklen_t
Definition headers.h:99
__int64 ssize_t
Definition httplib.h:178
struct lwip_sock * esphome_lwip_get_sock(int fd)
Look up a LwIP socket struct from a file descriptor.
void esphome_fast_select_set_ota_listener_sock(struct lwip_sock *sock)
Set the listener netconn that the fast-select callback filters OTA wakes against.
APIServer * global_api_server
bool load_saved_noise_psk(noise::psk_t &out)
One-shot read of the provisioned key for a boot without an api server (safe mode); false when there i...
const char * get_use_address_to(std::span< char, USE_ADDRESS_BUFFER_SIZE > buf)
Get the active network address for logging.
Definition util.cpp:42
std::array< uint8_t, 32 > psk_t
Definition noise.h:14
@ OTA_TYPE_UPDATE_PARTITION_TABLE
Definition ota_backend.h:94
void get_running_app_position(uint32_t &offset, size_t &size)
@ OTA_RESPONSE_UPDATE_PREPARE_OK
Definition ota_backend.h:25
@ OTA_RESPONSE_ERROR_ENCRYPTION_REQUIRED
Definition ota_backend.h:53
@ OTA_RESPONSE_SUPPORTS_COMPRESSION
Definition ota_backend.h:29
@ OTA_RESPONSE_BIN_MD5_OK
Definition ota_backend.h:26
@ OTA_RESPONSE_UPDATE_END_OK
Definition ota_backend.h:28
@ OTA_RESPONSE_RECEIVE_OK
Definition ota_backend.h:27
@ OTA_RESPONSE_CHUNK_OK
Definition ota_backend.h:30
@ OTA_RESPONSE_FEATURE_FLAGS
Definition ota_backend.h:31
@ OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE
Definition ota_backend.h:47
@ OTA_RESPONSE_ERROR_AUTH_INVALID
Definition ota_backend.h:35
@ OTA_RESPONSE_ERROR_UNKNOWN
Definition ota_backend.h:54
@ OTA_RESPONSE_REQUEST_SHA256_AUTH
Definition ota_backend.h:21
@ OTA_RESPONSE_ERROR_MAGIC
Definition ota_backend.h:33
@ OTA_RESPONSE_HEADER_OK
Definition ota_backend.h:23
std::unique_ptr< ArduinoLibreTinyOTABackend > make_ota_backend()
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.h:55
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port)
Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
Definition socket.cpp:245
std::unique_ptr< ListenSocket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:133
bool random_bytes(uint8_t *data, size_t len)
Generate len random bytes using the platform's secure RNG (hardware RNG or OS CSPRNG).
Definition helpers.cpp:20
void esphome_wake_ota_component_any_context()
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:33
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:986
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
int written
Definition helpers.h:1193
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t