ESPHome 2026.11.0-dev
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
33ESPHOME_LOG_TAG(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 = 60000; // 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;
52// A handshake this old gives way to a waiting connection: a client retrying over a lossy link otherwise queues
53// behind its own stalled attempt until both time out
54static constexpr uint32_t OTA_HANDSHAKE_PREEMPT = 10000;
55static constexpr uint32_t OTA_PROGRESS_INTERVAL_MS = 1000;
56static constexpr size_t OTA_SIZE_FIELD_BYTES = 4; // sizes on the wire are 4 bytes MSB first
57
58// Single-instance pointer — multi-port configs are rejected in final_validate.
59// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
60static ESPHomeOTAComponent *global_esphome_ota_component = nullptr;
61
62// Called from any context (LwIP TCP/IP task, RP2040 user-IRQ).
64 if (global_esphome_ota_component != nullptr) {
65 global_esphome_ota_component->enable_loop_soon_any_context();
66 }
67}
68
70#ifdef USE_OTA_ENCRYPTION_PROVISIONED
71 // Safe mode never constructs the api server, so read the key it saved
72 noise::psk_t psk;
75 if (this->saved_psk_ != nullptr) {
76 this->noise_ctx_.set_psk(this->saved_psk_->data());
77 }
78 }
79#endif
80 this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections
81 if (this->server_ == nullptr) {
82 this->server_failed_(LOG_STR("creation"));
83 return;
84 }
85 int enable = 1;
86 int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
87 if (err != 0) {
88 this->log_socket_error_(LOG_STR("reuseaddr"));
89 // we can still continue
90 }
91 err = this->server_->setblocking(false);
92 if (err != 0) {
93 this->server_failed_(LOG_STR("nonblocking"));
94 return;
95 }
96
97 struct sockaddr_storage server;
98
99 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
100 if (sl == 0) {
101 this->server_failed_(LOG_STR("set sockaddr"));
102 return;
103 }
104
105 err = this->server_->bind((struct sockaddr *) &server, sl);
106 if (err != 0) {
107 this->server_failed_(LOG_STR("bind"));
108 return;
109 }
110
111 err = this->server_->listen(1); // Only one client at a time
112 if (err != 0) {
113 this->server_failed_(LOG_STR("listen"));
114 return;
115 }
116
117 // loop() self-disables on its first idle tick; no explicit disable_loop() needed here.
118 global_esphome_ota_component = this;
119#ifdef USE_LWIP_FAST_SELECT
120 // Filter fast-select wakes to this listener only. If the sock lookup returns nullptr,
121 // no wakes fire and loop() falls back to the self-disable safety net.
123#endif
124
125#ifdef USE_OTA_PARTITIONS
127#endif
128}
129
131 char addr_buf[network::USE_ADDRESS_BUFFER_SIZE];
132 ESP_LOGCONFIG(TAG,
133 "Over-The-Air updates:\n"
134 " Address: %s:%u\n"
135 " Version: %d"
136#ifdef USE_OTA_ENCRYPTION
137 "\n Encryption: %s"
138#endif
139 ,
140 network::get_use_address_to(addr_buf), this->port_, USE_OTA_VERSION
141#ifdef USE_OTA_ENCRYPTION_REQUIRED
142 ,
143 LOG_STR_LITERAL("required")
144#elif defined(USE_OTA_ENCRYPTION_PROVISIONED)
145 // A runtime provisioned key may not exist yet
146 ,
147 this->noise_context_().has_psk() ? LOG_STR_LITERAL("offered, plaintext accepted")
148 : LOG_STR_LITERAL("offered once the api key is provisioned")
149#elif defined(USE_OTA_ENCRYPTION)
150 ,
151 LOG_STR_LITERAL("offered, plaintext accepted")
152#endif
153 );
154#ifdef USE_OTA_PASSWORD
155 if (!this->password_.empty()) {
156 ESP_LOGCONFIG(TAG, " Password configured");
157 }
158#endif
159#ifdef USE_OTA_PARTITIONS
160 ESP_LOGCONFIG(TAG,
161 " Partition access allowed\n"
162 " Running app:\n"
163 " Partition address: 0x%" PRIX32 "\n"
164 " Used size: %zu bytes (0x%zX)",
166
167#ifdef USE_ESP32
168 ESP_LOGCONFIG(TAG,
169 " Partition table:\n"
170 " %-12s %-4s %-8s %-10s %-10s",
171 "Name", "Type", "Subtype", "Address", "Size");
172 esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, nullptr);
173 while (it != nullptr) {
174 const esp_partition_t *partition = esp_partition_get(it);
175 ESP_LOGCONFIG(TAG, " %-12s 0x%-2X 0x%-6X 0x%-8" PRIX32 " 0x%-8" PRIX32, partition->label, partition->type,
176 partition->subtype, partition->address, partition->size);
177 it = esp_partition_next(it);
178 }
179 esp_partition_iterator_release(it);
180 esp_bootloader_desc_t bootloader_desc;
181 esp_err_t err = esp_ota_get_bootloader_description(nullptr, &bootloader_desc);
182 ESP_LOGCONFIG(TAG, " Bootloader: ESP-IDF %s",
183 (err == ESP_OK) ? bootloader_desc.idf_ver : LOG_STR_LITERAL("version unknown"));
184#endif // USE_ESP32
185#endif // USE_OTA_PARTITIONS
186}
187
189 // Self-disable idle loop where a wake path re-enables on listener readiness
190 // (fast-select, raw-TCP accept_fn_). Host BSD select doesn't, so stay enabled.
191 if (this->client_ == nullptr && !this->server_->ready()) {
192#ifndef USE_HOST
193 this->disable_loop();
194#endif
195 return;
196 }
197 this->handle_handshake_();
198}
199
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#ifdef ESPHOME_OTA_UDP
206static constexpr uint8_t CLIENT_FEATURE_SUPPORTS_UDP = 0x20;
207#endif
208// Noise needs the extended protocol: the prologue binds the 2-byte feature ack
209static constexpr uint8_t CLIENT_NOISE_FEATURES =
210 CLIENT_FEATURE_SUPPORTS_NOISE | CLIENT_FEATURE_SUPPORTS_EXTENDED_PROTOCOL;
211static constexpr uint8_t SERVER_FEATURE_SUPPORTS_COMPRESSION = 0x01;
212static constexpr uint8_t SERVER_FEATURE_SUPPORTS_PARTITION_ACCESS = 0x02;
213static constexpr uint8_t SERVER_FEATURE_SUPPORTS_NOISE = 0x04;
214// Raw deflate, window <= OTA_INFLATE_WINDOW_SIZE. Binding once offered: the
215// client must then send the image size frame and a deflate stream.
216static constexpr uint8_t SERVER_FEATURE_SUPPORTS_DEFLATE = 0x08;
217#ifdef ESPHOME_OTA_UDP
218static constexpr uint8_t SERVER_FEATURE_SUPPORTS_UDP = 0x10;
219static constexpr uint8_t UDP_REQUEST_V1 = 0x01; // espota2.UDP_REQUEST
220#endif
221
222#ifdef USE_OTA_ENCRYPTION
224 return (this->handshake_buf_[1] & SERVER_FEATURE_SUPPORTS_NOISE) != 0 &&
225 (this->ota_features_ & CLIENT_NOISE_FEATURES) == CLIENT_NOISE_FEATURES;
226}
227#endif
228
230#ifdef USE_OTA_ENCRYPTION_REQUIRED
231 // FEATURE_READ already refused every client without the extended protocol
232 return true;
233#else
234 return (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_EXTENDED_PROTOCOL) != 0;
235#endif
236}
237
244
245 if (this->client_ == nullptr) {
246 // We already checked server_->ready() in loop(), so we can accept directly
247 struct sockaddr_storage source_addr;
248 socklen_t addr_len = sizeof(source_addr);
249 int enable = 1;
250
251 this->client_ = this->server_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
252 if (this->client_ == nullptr)
253 return;
254 int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
255 if (err != 0) {
256 this->log_socket_error_(LOG_STR("nodelay"));
257 this->cleanup_connection_();
258 return;
259 }
260 err = this->client_->setblocking(false);
261 if (err != 0) {
262 this->log_socket_error_(LOG_STR("non-blocking"));
263 this->cleanup_connection_();
264 return;
265 }
266 this->log_start_(LOG_STR("handshake"));
268 this->handshake_buf_pos_ = 0; // Reset handshake buffer position
270 }
271
272 // Check for handshake timeout
274 if (now - this->client_connect_time_ > OTA_SOCKET_TIMEOUT_HANDSHAKE) {
275 ESP_LOGW(TAG, "Handshake timeout");
276 this->cleanup_connection_();
277 return;
278 }
279 // Only unauthenticated handshakes reach here; the data phase blocks inside handle_data_
280 if (now - this->client_connect_time_ > OTA_HANDSHAKE_PREEMPT && this->server_->ready()) {
281 ESP_LOGD(TAG, "Newer connection waiting");
282 this->cleanup_connection_();
283 return;
284 }
285
286 switch (this->ota_state_) {
288 // Try to read remaining magic bytes (5 total)
289 if (!this->try_read_(5, LOG_STR("read magic"))) {
290 return;
291 }
292
293 // Validate magic bytes
294 if (memcmp(this->handshake_buf_, MAGIC_BYTES, sizeof(MAGIC_BYTES)) != 0) {
295 ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->handshake_buf_[0],
296 this->handshake_buf_[1], this->handshake_buf_[2], this->handshake_buf_[3], this->handshake_buf_[4]);
298 return;
299 }
300
301 // Magic bytes valid, move to next state
304 this->handshake_buf_[1] = USE_OTA_VERSION;
305 [[fallthrough]];
306 }
307
308 case OTAState::MAGIC_ACK: {
309 // Send OK and version - 2 bytes
310 if (!this->try_write_(2, LOG_STR("ack magic"))) {
311 return;
312 }
313 // All bytes sent, create backend and move to next state
316 [[fallthrough]];
317 }
318
320 // Read features - 1 byte
321 if (!this->try_read_(1, LOG_STR("read feature"))) {
322 return;
323 }
324 this->ota_features_ = this->handshake_buf_[0];
325 ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_);
326
327#ifdef USE_OTA_ENCRYPTION_REQUIRED
328 // `ota: encryption:` requires the client to negotiate encryption
329 if ((this->ota_features_ & CLIENT_NOISE_FEATURES) != CLIENT_NOISE_FEATURES) {
330 ESP_LOGW(TAG, "Client does not support encryption");
332 return;
333 }
334#endif
335
337
338 const bool supports_compression =
339 (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_COMPRESSION) != 0 && ota::OTABackend::supports_compression();
340
341 // Compose the feature-ack response. When the client negotiates the extended protocol we emit
342 // a 2-byte response (marker + server feature flags); otherwise we emit the single-byte
343 // legacy response.
344 if (this->extended_proto_()) {
345 static_assert(HANDSHAKE_BUF_SIZE >= 2, "handshake_buf_ must hold the 2-byte extended-protocol feature ack");
347 this->handshake_buf_[1] = (supports_compression ? SERVER_FEATURE_SUPPORTS_COMPRESSION : 0);
348#ifdef ESPHOME_OTA_UDP
349 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_UDP;
350#endif
351#ifdef USE_OTA_PARTITIONS
352 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_PARTITION_ACCESS;
353#endif
354#ifdef USE_OTA_ENCRYPTION_PROVISIONED
355 // A runtime provisioned key may not exist yet
356 if (this->noise_context_().has_psk()) {
357 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_NOISE;
358 }
359#elif defined(USE_OTA_ENCRYPTION)
360 // A yaml key always exists: validation rejects the all-zeros key
361 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_NOISE;
362#endif
363#ifdef USE_OTA_ENCRYPTION
364 // Reserve the noise session before the optional inflate buffer, so the
365 // required allocation is not starved by the compression window
366 if (this->noise_offered_()) {
368 }
369#endif
370#ifdef USE_OTA_DEFLATE
371 // Offered only once the session memory is in hand; else uncompressed
372 if ((this->ota_features_ & CLIENT_FEATURE_SUPPORTS_DEFLATE) != 0) {
373 // Value initialized: a corrupt stream that back references the
374 // window before it is filled then copies zeros, never stale memory.
375 // Default placement, PSRAM first where present: the session lives for one
376 // upload and keeps 4.9 KB of internal heap free while it runs
378 if (this->inflate_ != nullptr) {
379 this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_DEFLATE;
380 } else {
381 ESP_LOGW(TAG, "No memory to inflate");
382 }
383 }
384#endif
385 } else {
386 this->handshake_buf_[0] =
388 }
389 [[fallthrough]];
390 }
391
393 static constexpr size_t STANDARD_PROTO_ACK_SIZE = 1;
394 static constexpr size_t EXTENDED_PROTO_ACK_SIZE = 2;
395 const size_t ack_size = this->extended_proto_() ? EXTENDED_PROTO_ACK_SIZE : STANDARD_PROTO_ACK_SIZE;
396 if (!this->try_write_(ack_size, LOG_STR("ack feature"))) {
397 return;
398 }
399#ifdef USE_OTA_ENCRYPTION
400 // Latch the offer actually sent: a key activating between the two
401 // states must not start a session the client never expects
402 if (this->noise_offered_()) {
403 // handshake_buf_ still holds the feature ack composed above; a
404 // would-block re-entry lands here without rebuilding it
405 if (!this->noise_start_session_(this->handshake_buf_[1])) {
406 return;
407 }
409 return;
410 }
411#endif
412#ifdef USE_OTA_PASSWORD
413 // If password is set, move to auth phase
414 if (!this->password_.empty()) {
416 } else
417#endif
418 {
419 // No password, move directly to data phase
421 }
422 [[fallthrough]];
423 }
424
425#ifdef USE_OTA_PASSWORD
426 case OTAState::AUTH_SEND: {
427 // Non-blocking authentication send
428 if (!this->handle_auth_send_()) {
429 return;
430 }
432 [[fallthrough]];
433 }
434
435 case OTAState::AUTH_READ: {
436 // Non-blocking authentication read & verify
437 if (!this->handle_auth_read_()) {
438 return;
439 }
441 [[fallthrough]];
442 }
443#endif
444
445 case OTAState::DATA:
446 this->handle_data_();
447 return;
448
449#ifdef USE_OTA_ENCRYPTION
451 if (!this->handle_noise_handshake_()) {
452 return;
453 }
455 this->handle_data_();
456 return;
457#endif
458
459 default:
460 break;
461 }
462}
463
497 // Backend calls overwrite this with OK; reset to UNKNOWN before any
498 // goto error that follows a successful begin()/write()
500 DataTransfer xfer;
501 uint8_t buf[OTA_BUFFER_SIZE];
502 char *sbuf = reinterpret_cast<char *>(buf);
503 size_t image_size;
505#ifdef ESPHOME_OTA_UDP
506 UdpLink udp_link;
507 this->udp_ = nullptr; // client_connect_time_ is done with
508#endif
509
510 // Set socket timeouts and blocking mode (see strategy table above)
511 struct timeval tv;
512 tv.tv_sec = 2;
513 tv.tv_usec = 0;
514 this->client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
515 this->client_->setsockopt(SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
516 if (this->client_->setblocking(true) != 0) {
517 this->log_socket_error_(LOG_STR("blocking"));
518 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
519 }
520
521 // Acknowledge auth OK - 1 byte
523
524#ifdef ESPHOME_OTA_UDP
525 // The server always offers UDP on the extended protocol
526 if (this->extended_proto_() && (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_UDP) != 0) {
527 // Whether the client wants UDP (0: its link looks good), answered with our token
528 if (!this->data_readall_(buf, 1)) {
529 if (this->client_left_before_start_())
530 return;
531 this->log_read_error_(LOG_STR("UDP request"));
532 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
533 }
534 // 1 is this UDP format; other values are free for later ones, which we decline (a zero reply keeps TCP)
535 if (buf[0] == UDP_REQUEST_V1) {
536 this->udp_open_(udp_link);
537 if (udp_link.reply[0] == 0) {
538 ESP_LOGW(TAG, "UDP unavailable, using TCP");
539 }
540 }
541 if (!this->data_write_(udp_link.reply, sizeof(udp_link.reply)))
542 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
543 if (udp_link.reply[0] != 0) {
544 this->udp_ = &udp_link;
545 if (!this->udp_start_()) {
546 // Nothing started yet: just drop the session, which the client retries as a network failure
547 this->log_read_error_(LOG_STR("UDP start"));
548 this->cleanup_connection_();
549 return;
550 }
551 }
552 }
553
554#endif
555
556 if (this->extended_proto_()) {
557 // Read ota type, 1 byte
558 if (!this->data_readall_(buf, 1)) {
559 if (this->client_left_before_start_())
560 return;
561 this->log_read_error_(LOG_STR("OTA type"));
562 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
563 }
564 ota_type = static_cast<ota::OTAType>(buf[0]);
565 }
566 ESP_LOGV(TAG, "OTA type is 0x%02x", ota_type);
567
568 // Read size, 4 bytes MSB first
569 if (!this->read_size_(buf, xfer.ota_size, LOG_STR("size"))) {
570 // The first request byte is the type on the extended protocol; a close after it was a cut-off request
571 if (!this->extended_proto_() && this->client_left_before_start_())
572 return;
573 this->log_read_error_(LOG_STR("size"));
574 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
575 }
576 image_size = xfer.ota_size;
577#ifdef USE_OTA_DEFLATE
578 if (this->inflate_ != nullptr && !this->read_size_(buf, image_size, LOG_STR("image size"))) {
579 this->log_read_error_(LOG_STR("image size"));
580 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
581 }
582#endif
583
584#ifndef USE_OTA_PARTITIONS
585 if (ota_type != ota::OTA_TYPE_UPDATE_APP) {
587 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
588 }
589#endif
590
591 // Now that we've passed authentication and are actually
592 // starting the update, set the warning status and notify
593 // listeners. This ensures that port scanners do not
594 // accidentally trigger the update process.
595 this->log_start_(LOG_STR("update"));
596 this->status_set_warning();
597#ifdef USE_OTA_STATE_LISTENER
598 this->notify_state_(ota::OTA_STARTED, 0.0f, 0);
599#endif
600
601 // begin() returns quickly; flash sectors are erased incrementally during write().
602 error_code = this->backend_->begin(image_size, ota_type);
603 if (error_code != ota::OTA_RESPONSE_OK)
604 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
605
606 // Acknowledge prepare OK - 1 byte
608
609 // Read binary MD5, 32 bytes
610 if (!this->data_readall_(buf, 32)) {
611 this->log_read_error_(LOG_STR("MD5 checksum"));
613 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
614 }
615 sbuf[32] = '\0';
616 ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
617 this->backend_->set_update_md5(sbuf);
618
619 // Acknowledge MD5 OK - 1 byte
621
622 xfer.last_data_ms = millis();
623#ifdef USE_OTA_DEFLATE
624 if (this->inflate_ != nullptr) {
625 error_code = this->inflate_data_(buf, image_size, xfer);
626 if (error_code != ota::OTA_RESPONSE_OK)
627 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
628 } else
629#endif
630 {
631 while (xfer.total < xfer.ota_size) {
632 ssize_t read = this->receive_data_(buf, xfer);
633 if (read < 0) {
635 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
636 }
637 error_code = this->write_flash_(buf, read);
638 if (error_code != ota::OTA_RESPONSE_OK)
639 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
640 this->ack_written_(xfer);
641 }
642 }
643
644 // Acknowledge receive OK - 1 byte
646
647 error_code = this->backend_->end();
648 if (error_code != ota::OTA_RESPONSE_OK) {
649 ESP_LOGW(TAG, "End update err %d", error_code);
650 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
651 }
652
653 // Acknowledge Update end OK - 1 byte
655
656#ifdef ESPHOME_OTA_UDP
657 // UDP never reports a close: wait only until the client has our responses, not for its final ack
658 if (this->udp_ != nullptr) {
659 this->udp_linger_();
660 } else
661#endif
662 // Read ACK
663 if (!this->data_readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
664 this->log_read_error_(LOG_STR("ack"));
665 // do not go to error, this is not fatal
666 }
667
668 this->cleanup_connection_();
669 delay(10);
670 ESP_LOGI(TAG, "Update complete");
671 this->status_clear_warning();
672#ifdef USE_OTA_STATE_LISTENER
673 this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0);
674#endif
675 delay(100); // NOLINT
676#ifdef USE_OTA_PARTITIONS
677 if (ota_type == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
678 // Skip on_safe_shutdown: nvs_flash_deinit() has already invalidated open NVS handles, so
679 // preferences flush would emit ESP_ERR_NVS_INVALID_HANDLE for every entry. Reboot directly.
680 App.reboot();
681 }
682#endif
684
685error:
686 this->data_write_byte_(static_cast<uint8_t>(error_code));
687#ifdef ESPHOME_OTA_UDP
688 this->udp_linger_();
689#endif
690
691 // Abort backend before cleanup - cleanup_connection_() destroys the backend.
692 // Always call abort() unconditionally: backends register external partitions before
693 // esp_ota_begin (partition table / bootloader paths), and abort() is responsible for
694 // releasing those even if begin() failed before an OTA handle was opened. The IDF
695 // backend's esp_ota_abort(0) is documented as harmless.
696 if (this->backend_ != nullptr) {
697 this->backend_->abort();
698 }
699
700 this->cleanup_connection_();
701
702 this->status_momentary_error(5000);
703#ifdef USE_OTA_STATE_LISTENER
704 this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
705#endif
706}
707
708bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
709 uint32_t start = millis();
710 uint32_t at = 0;
711 while (len - at > 0) {
712 uint32_t now = millis();
713 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
714 ESP_LOGW(TAG, "Timeout reading %zu bytes", len);
715 return false;
716 }
717
718 ssize_t read = this->transport_read_(buf + at, len - at);
719 if (read == -1) {
720 const int err = errno;
721 if (!this->would_block_(err)) {
722 ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, err);
723 return false;
724 }
725 } else if (read == 0) {
726 // A partial message is a cut-off request, not a clean close; the caller reports the clean one
727 this->remote_closed_ = at == 0;
728 if (at > 0) {
729 ESP_LOGW(TAG, "Remote closed after %u of %zu bytes", (unsigned) at, len);
730 }
731 return false;
732 } else {
733 at += read;
734 }
735 // read() already waited via SO_RCVTIMEO, just yield without 1ms stall
736 App.feed_wdt();
737 delay(0);
738 }
739
740 return true;
741}
742bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
743#ifdef ESPHOME_OTA_UDP
744 if (this->udp_ != nullptr)
745 return this->udp_send_(buf, len);
746#endif
747 uint32_t start = millis();
748 uint32_t at = 0;
749 while (len - at > 0) {
750 uint32_t now = millis();
751 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
752 ESP_LOGW(TAG, "Timeout writing %zu bytes", len);
753 return false;
754 }
755
756 ssize_t written = this->client_->write(buf + at, len - at);
757 if (written == -1) {
758 const int err = errno;
759 if (!this->would_block_(err)) {
760 ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, err);
761 return false;
762 }
763 // EWOULDBLOCK: on raw TCP writes never block, delay(1) prevents spinning
765 } else {
766 at += written;
767 // write() may block up to SO_SNDTIMEO on BSD/lwip sockets, feed WDT
768 App.feed_wdt();
769 }
770 }
771 return true;
772}
773
775
776void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
777 ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
778}
779
781 // Key probes and scanners hang up right after the handshake; nothing started, so no error status or callback
782 if (!this->remote_closed_)
783 return false;
784 ESP_LOGD(TAG, "Client left after the handshake");
785 this->cleanup_connection_();
786 return true;
787}
788
789void ESPHomeOTAComponent::log_read_error_(const LogString *what) {
790 if (this->remote_closed_) {
791 this->log_remote_closed_(what);
792 return;
793 }
794 ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what));
795}
796
797void ESPHomeOTAComponent::log_start_(const LogString *phase) {
798 char peername[socket::SOCKADDR_STR_LEN];
799#ifdef ESPHOME_OTA_UDP
800 if (this->client_ == nullptr) {
801 // A committed UDP session has already dropped TCP
802#ifdef ESPHOME_OTA_UDP_LWIP
803 ipaddr_ntoa_r(&this->udp_->peer_ip, peername, sizeof(peername));
804#else
805 socket::format_sockaddr_to(reinterpret_cast<const struct sockaddr *>(&this->udp_->peer), this->udp_->peer_len,
806 peername);
807#endif
808 } else
809#endif
810 {
811 this->client_->getpeername_to(peername);
812 }
813 ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername);
814}
815
816void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
817 ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
818}
819
820void ESPHomeOTAComponent::server_failed_(const LogString *msg) {
821 this->log_socket_error_(msg);
822 // No explicit close() needed — listen sockets have no active connections on
823 // failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
824 delete this->server_;
825 this->server_ = nullptr;
826 this->mark_failed();
827}
828
829bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
830 if (read == -1 && this->would_block_(errno)) {
831 return false; // No data yet, try again next loop
832 }
833
834 if (read <= 0) {
835 read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc);
836 this->cleanup_connection_();
837 return false;
838 }
839 return true;
840}
841
843 if (written == -1) {
844 if (this->would_block_(errno)) {
845 return false; // Try again next loop
846 }
847 this->log_socket_error_(desc);
848 this->cleanup_connection_();
849 return false;
850 }
851 return true;
852}
853
854bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) {
855 // Read bytes into handshake buffer, starting at handshake_buf_pos_
856 size_t bytes_to_read = to_read - this->handshake_buf_pos_;
857 ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read);
858
859 if (!this->handle_read_error_(read, desc)) {
860 return false;
861 }
862
863 this->handshake_buf_pos_ += read;
864 // Return true only if we have all the requested bytes
865 return this->handshake_buf_pos_ >= to_read;
866}
867
868bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) {
869 // Write bytes from handshake buffer, starting at handshake_buf_pos_
870 size_t bytes_to_write = to_write - this->handshake_buf_pos_;
871 ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write);
872
873 if (!this->handle_write_error_(written, desc)) {
874 return false;
875 }
876
877 this->handshake_buf_pos_ += written;
878 // Return true only if we have written all the requested bytes
879 return this->handshake_buf_pos_ >= to_write;
880}
881
882bool ESPHomeOTAComponent::read_size_(uint8_t *buf, size_t &size, const LogString *desc) {
883 if (!this->data_readall_(buf, OTA_SIZE_FIELD_BYTES))
884 return false;
885 size = encode_uint32(buf[0], buf[1], buf[2], buf[3]);
886 ESP_LOGV(TAG, "%s is %zu bytes", LOG_STR_ARG(desc), size);
887 return true;
888}
889
891 ota::OTAResponseTypes result = this->backend_->write(data, len);
892 if (result != ota::OTA_RESPONSE_OK) {
893 ESP_LOGW(TAG, "Flash write err %d", result);
894 }
895 return result;
896}
897
899 const size_t remaining = xfer.ota_size - xfer.total;
900 const size_t requested = std::min(remaining, OTA_BUFFER_SIZE);
901 ssize_t read;
902 for (;;) {
903 // A silently-vanished peer (no FIN/RST delivered, e.g. uploader killed
904 // mid-transfer or NAT/router dropped state) must not wedge the device:
905 // read() only fails on EOF or a real error, and lwIP TCP keepalive isn't
906 // enabled here.
907 if (millis() - xfer.last_data_ms > OTA_SOCKET_TIMEOUT_DATA) {
908 ESP_LOGW(TAG, "No data received for %u ms", (unsigned) OTA_SOCKET_TIMEOUT_DATA);
909 return -1;
910 }
911#ifdef USE_OTA_ENCRYPTION
912 if (this->noise_ != nullptr) {
913 // One frame per call; noise_read_data_ waits internally (readall_), so
914 // there is no would-block retry here and failures are already logged.
915 read = this->noise_read_data_(buf, requested);
916 if (read <= 0) {
917 if (this->remote_closed_)
918 this->log_remote_closed_(LOG_STR("data"));
919 return -1;
920 }
921 break;
922 }
923#endif
924 read = this->transport_read_(buf, requested);
925 if (read > 0)
926 break;
927 if (read == 0) {
928 this->log_remote_closed_(LOG_STR("data"));
929 return -1;
930 }
931 if (!this->would_block_(errno)) {
932 this->log_socket_error_(LOG_STR("data"));
933 return -1;
934 }
935 // read() already waited up to SO_RCVTIMEO for data, just feed WDT
936 App.feed_wdt();
937 }
938
939 const uint32_t now = millis();
940 xfer.last_data_ms = now;
941 xfer.total += read;
942 this->ack_received_(xfer);
943 if (now - xfer.last_progress > OTA_PROGRESS_INTERVAL_MS) {
944 xfer.last_progress = now;
945 float percentage = (xfer.total * 100.0f) / xfer.ota_size;
946 ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
947#ifdef USE_OTA_STATE_LISTENER
948 this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0);
949#endif
950 // feed watchdog and give other tasks a chance to run
952 }
953 return read;
954}
955
957#if USE_OTA_VERSION == 2
958#ifdef ESPHOME_OTA_UDP
959 // UDP acks every message itself
960 if (this->udp_ != nullptr)
961 return;
962#endif
963 while (xfer.acknowledged + OTA_BLOCK_SIZE <= xfer.total ||
964 (xfer.total == xfer.ota_size && xfer.acknowledged < xfer.ota_size)) {
966 xfer.acknowledged += OTA_BLOCK_SIZE;
967 }
968#endif
969}
970
972#ifdef ESPHOME_OTA_UDP
973 this->udp_ = nullptr; // the link itself lives on handle_data_'s stack
974#endif
975 // Already gone once a UDP session committed
976 if (this->client_ != nullptr)
977 this->client_->close();
978 this->client_ = nullptr;
979 this->client_connect_time_ = 0;
980 this->handshake_buf_pos_ = 0;
982 this->ota_features_ = 0;
983 this->remote_closed_ = false;
984 this->backend_ = nullptr;
985#ifdef USE_OTA_PASSWORD
986 this->cleanup_auth_();
987#endif
988#ifdef USE_OTA_ENCRYPTION
989 this->noise_ = nullptr;
990#endif
991#ifdef USE_OTA_DEFLATE
992 this->inflate_ = nullptr;
993#endif
994 // Intentionally no disable_loop() — letting loop() run one more iteration catches
995 // any connection that queued on the listener mid-session (otherwise the wake flag,
996 // set while we were in LOOP state, would be lost to enable_pending_loops_()).
997}
998
1003
1004#ifdef USE_OTA_PASSWORD
1005void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
1006
1008 bool client_supports_sha256 = (this->ota_features_ & CLIENT_FEATURE_SUPPORTS_SHA256_AUTH) != 0;
1009
1010 // Require SHA256
1011 if (!client_supports_sha256) {
1012 this->log_auth_warning_(LOG_STR("SHA256 required"));
1014 return false;
1015 }
1017 return true;
1018}
1019
1021 // Initialize auth buffer if not already done
1022 if (!this->auth_buf_) {
1023 // Select auth type based on client capabilities and configuration
1024 if (!this->select_auth_type_()) {
1025 return false;
1026 }
1027
1028 // Generate nonce - hasher must be created and used in same stack frame
1029 // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
1030 // 1. Hash objects must NEVER be passed to another function (different stack frame)
1031 // 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA
1032 // 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created
1033 // Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption.
1034 //
1035 // Buffer layout after AUTH_READ completes:
1036 // [0]: auth_type (1 byte)
1037 // [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
1038 // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
1039 // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
1040
1041 // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
1042 // (no passing to other functions). All hash operations must happen in this function.
1043 sha256::SHA256 hasher;
1044
1045 const size_t hex_size = hasher.get_size() * 2;
1046 const size_t nonce_len = hasher.get_size() / 4;
1047 const size_t auth_buf_size = 1 + 3 * hex_size;
1048 // Internal RAM first: 128 of these bytes go straight into the hardware SHA engine
1049 this->auth_buf_ =
1051 if (!this->auth_buf_) {
1052 this->log_auth_warning_(LOG_STR("No memory"));
1054 return false;
1055 }
1056 this->auth_buf_pos_ = 0;
1057
1058 char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
1059 if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
1060 this->log_auth_warning_(LOG_STR("Random failed"));
1062 return false;
1063 }
1064
1065 hasher.init();
1066 hasher.add(buf, nonce_len);
1067 hasher.calculate();
1068 this->auth_buf_[0] = this->auth_type_;
1069 hasher.get_hex(buf);
1070
1071 ESP_LOGV(TAG, "Auth: Nonce is %.*s", (int) hex_size, buf);
1072 }
1073
1074 // Try to write auth_type + nonce
1075 constexpr size_t hex_size = SHA256_HEX_SIZE;
1076 const size_t to_write = 1 + hex_size;
1077 size_t remaining = to_write - this->auth_buf_pos_;
1078
1079 ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining);
1080 if (!this->handle_write_error_(written, LOG_STR("ack auth"))) {
1081 return false;
1082 }
1083
1084 this->auth_buf_pos_ += written;
1085
1086 // Check if we still have more to write
1087 if (this->auth_buf_pos_ < to_write) {
1088 return false; // More to write, try again next loop
1089 }
1090
1091 // All written, prepare for reading phase
1092 this->auth_buf_pos_ = 0;
1093 return true;
1094}
1095
1097 constexpr size_t hex_size = SHA256_HEX_SIZE;
1098 const size_t to_read = hex_size * 2; // CNonce + Response
1099
1100 // Try to read remaining bytes (CNonce + Response)
1101 // We read cnonce+response starting at offset 1+hex_size (after auth_type and our nonce)
1102 size_t cnonce_offset = 1 + hex_size; // Offset where cnonce should be stored in buffer
1103 size_t remaining = to_read - this->auth_buf_pos_;
1104 ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining);
1105
1106 if (!this->handle_read_error_(read, LOG_STR("read auth"))) {
1107 return false;
1108 }
1109
1110 this->auth_buf_pos_ += read;
1111
1112 // Check if we still need more data
1113 if (this->auth_buf_pos_ < to_read) {
1114 return false; // More to read, try again next loop
1115 }
1116
1117 // We have all the data, verify it
1118 const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
1119 const char *cnonce = nonce + hex_size;
1120 const char *response = cnonce + hex_size;
1121
1122 // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
1123 // (no passing to other functions). All hash operations must happen in this function.
1124 sha256::SHA256 hasher;
1125
1126 hasher.init();
1127 hasher.add(this->password_.c_str(), this->password_.length());
1128 hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
1129 hasher.calculate();
1130
1131 ESP_LOGV(TAG, "Auth: CNonce is %.*s", (int) hex_size, cnonce);
1132#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
1133 char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator)
1134 hasher.get_hex(computed_hash);
1135 ESP_LOGV(TAG, "Auth: Result is %.*s", (int) hex_size, computed_hash);
1136#endif
1137 ESP_LOGV(TAG, "Auth: Response is %.*s", (int) hex_size, response);
1138
1139 // Compare response
1140 bool matches = hasher.equals_hex(response);
1141
1142 if (!matches) {
1143 this->log_auth_warning_(LOG_STR("Password mismatch"));
1145 return false;
1146 }
1147
1148 // Authentication successful - clean up auth state
1149 this->cleanup_auth_();
1150
1151 return true;
1152}
1153
1155 this->auth_buf_ = nullptr;
1156 this->auth_buf_pos_ = 0;
1157 this->auth_type_ = 0;
1158}
1159#endif // USE_OTA_PASSWORD
1160
1161} // namespace esphome
1162#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 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_momentary_error(uint32_t length=5000)
Set error status flag and clear it after length ms.
void status_clear_warning()
Definition component.h:296
ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using Ardui...
Definition ota_esphome.h:40
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
static constexpr uint8_t MAGIC_BYTES[5]
bool udp_send_(const uint8_t *data, size_t len)
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_
ssize_t transport_read_(uint8_t *buf, size_t len)
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)
bool data_write_(const uint8_t *data, 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 of get_size() * 2 characters,...
Definition hash_base.h:36
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2298
RAMUniquePtr< T > make_unique(Args &&...args)
Value initialize one T; empty on exhaustion.
Definition helpers.h:2361
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:2371
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:106
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:109
__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...
ESPHOME_LOG_TAG(TAG, CRASH_LOG_TAG)
const char * get_use_address_to(std::span< char, USE_ADDRESS_BUFFER_SIZE > buf)
Get the active network address for logging.
Definition util.cpp:72
std::array< uint8_t, 32 > psk_t
Definition noise.h:14
@ OTA_TYPE_UPDATE_PARTITION_TABLE
Definition ota_backend.h:95
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:55
@ 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
size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::span< char, SOCKADDR_STR_LEN > buf)
Format sockaddr into caller-provided buffer, returns length written (excluding null)
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().
std::unique_ptr< ListenSocket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.h:175
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:68
uint16_t size
Definition helpers.cpp:34
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:1006
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:1262
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t