ESPHome 2026.5.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
3#ifdef USE_OTA_PASSWORD
5#endif
14#include "esphome/core/hal.h"
16#include "esphome/core/log.h"
17#include "esphome/core/util.h"
18#ifdef USE_LWIP_FAST_SELECT
20#endif
21
22#include <cerrno>
23#include <cstdio>
24#include <sys/time.h>
25
26namespace esphome {
27
28static const char *const TAG = "esphome.ota";
29static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
30static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size for OTA data transfer
31static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
32static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
33
34// Single-instance pointer — multi-port configs are rejected in final_validate.
35// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
36static ESPHomeOTAComponent *global_esphome_ota_component = nullptr;
37
38// Called from any context (LwIP TCP/IP task, RP2040 user-IRQ).
40 if (global_esphome_ota_component != nullptr) {
41 global_esphome_ota_component->enable_loop_soon_any_context();
42 }
43}
44
46 this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections
47 if (this->server_ == nullptr) {
48 this->server_failed_(LOG_STR("creation"));
49 return;
50 }
51 int enable = 1;
52 int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
53 if (err != 0) {
54 this->log_socket_error_(LOG_STR("reuseaddr"));
55 // we can still continue
56 }
57 err = this->server_->setblocking(false);
58 if (err != 0) {
59 this->server_failed_(LOG_STR("nonblocking"));
60 return;
61 }
62
63 struct sockaddr_storage server;
64
65 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
66 if (sl == 0) {
67 this->server_failed_(LOG_STR("set sockaddr"));
68 return;
69 }
70
71 err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
72 if (err != 0) {
73 this->server_failed_(LOG_STR("bind"));
74 return;
75 }
76
77 err = this->server_->listen(1); // Only one client at a time
78 if (err != 0) {
79 this->server_failed_(LOG_STR("listen"));
80 return;
81 }
82
83 // loop() self-disables on its first idle tick; no explicit disable_loop() needed here.
84 global_esphome_ota_component = this;
85#ifdef USE_LWIP_FAST_SELECT
86 // Filter fast-select wakes to this listener only. If the sock lookup returns nullptr,
87 // no wakes fire and loop() falls back to the self-disable safety net.
89#endif
90}
91
93 ESP_LOGCONFIG(TAG,
94 "Over-The-Air updates:\n"
95 " Address: %s:%u\n"
96 " Version: %d",
97 network::get_use_address(), this->port_, USE_OTA_VERSION);
98#ifdef USE_OTA_PASSWORD
99 if (!this->password_.empty()) {
100 ESP_LOGCONFIG(TAG, " Password configured");
101 }
102#endif
103}
104
106 // Self-disabling idle loop. Runs when a wake path marks us pending-enable (fast-select
107 // listener filter, raw-TCP accept_fn_, or host select), finds no work, and goes back
108 // to sleep. cleanup_connection_() deliberately leaves the loop enabled for one more
109 // iteration so a connection queued mid-session is still caught here.
110 if (this->client_ == nullptr && !this->server_->ready()) {
111 this->disable_loop();
112 return;
113 }
114 this->handle_handshake_();
115}
116
117static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
118static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
119
126
127 if (this->client_ == nullptr) {
128 // We already checked server_->ready() in loop(), so we can accept directly
129 struct sockaddr_storage source_addr;
130 socklen_t addr_len = sizeof(source_addr);
131 int enable = 1;
132
133 this->client_ = this->server_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
134 if (this->client_ == nullptr)
135 return;
136 int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
137 if (err != 0) {
138 this->log_socket_error_(LOG_STR("nodelay"));
139 this->cleanup_connection_();
140 return;
141 }
142 err = this->client_->setblocking(false);
143 if (err != 0) {
144 this->log_socket_error_(LOG_STR("non-blocking"));
145 this->cleanup_connection_();
146 return;
147 }
148 this->log_start_(LOG_STR("handshake"));
150 this->handshake_buf_pos_ = 0; // Reset handshake buffer position
152 }
153
154 // Check for handshake timeout
156 if (now - this->client_connect_time_ > OTA_SOCKET_TIMEOUT_HANDSHAKE) {
157 ESP_LOGW(TAG, "Handshake timeout");
158 this->cleanup_connection_();
159 return;
160 }
161
162 switch (this->ota_state_) {
164 // Try to read remaining magic bytes (5 total)
165 if (!this->try_read_(5, LOG_STR("read magic"))) {
166 return;
167 }
168
169 // Validate magic bytes
170 static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
171 if (memcmp(this->handshake_buf_, MAGIC_BYTES, 5) != 0) {
172 ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->handshake_buf_[0],
173 this->handshake_buf_[1], this->handshake_buf_[2], this->handshake_buf_[3], this->handshake_buf_[4]);
175 return;
176 }
177
178 // Magic bytes valid, move to next state
181 this->handshake_buf_[1] = USE_OTA_VERSION;
182 [[fallthrough]];
183 }
184
185 case OTAState::MAGIC_ACK: {
186 // Send OK and version - 2 bytes
187 if (!this->try_write_(2, LOG_STR("ack magic"))) {
188 return;
189 }
190 // All bytes sent, create backend and move to next state
193 [[fallthrough]];
194 }
195
197 // Read features - 1 byte
198 if (!this->try_read_(1, LOG_STR("read feature"))) {
199 return;
200 }
201 this->ota_features_ = this->handshake_buf_[0];
202 ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_);
204 this->handshake_buf_[0] =
205 ((this->ota_features_ & FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression())
208 [[fallthrough]];
209 }
210
212 // Acknowledge header - 1 byte
213 if (!this->try_write_(1, LOG_STR("ack feature"))) {
214 return;
215 }
216#ifdef USE_OTA_PASSWORD
217 // If password is set, move to auth phase
218 if (!this->password_.empty()) {
220 } else
221#endif
222 {
223 // No password, move directly to data phase
225 }
226 [[fallthrough]];
227 }
228
229#ifdef USE_OTA_PASSWORD
230 case OTAState::AUTH_SEND: {
231 // Non-blocking authentication send
232 if (!this->handle_auth_send_()) {
233 return;
234 }
236 [[fallthrough]];
237 }
238
239 case OTAState::AUTH_READ: {
240 // Non-blocking authentication read & verify
241 if (!this->handle_auth_read_()) {
242 return;
243 }
245 [[fallthrough]];
246 }
247#endif
248
249 case OTAState::DATA:
250 this->handle_data_();
251 return;
252
253 default:
254 break;
255 }
256}
257
292 bool update_started = false;
293 size_t total = 0;
294 uint32_t last_progress = 0;
295 uint8_t buf[OTA_BUFFER_SIZE];
296 char *sbuf = reinterpret_cast<char *>(buf);
297 size_t ota_size;
298#if USE_OTA_VERSION == 2
299 size_t size_acknowledged = 0;
300#endif
301
302 // Set socket timeouts and blocking mode (see strategy table above)
303 struct timeval tv;
304 tv.tv_sec = 2;
305 tv.tv_usec = 0;
306 this->client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
307 this->client_->setsockopt(SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
308 this->client_->setblocking(true);
309
310 // Acknowledge auth OK - 1 byte
312
313 // Read size, 4 bytes MSB first
314 if (!this->readall_(buf, 4)) {
315 this->log_read_error_(LOG_STR("size"));
316 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
317 }
318 ota_size = (static_cast<size_t>(buf[0]) << 24) | (static_cast<size_t>(buf[1]) << 16) |
319 (static_cast<size_t>(buf[2]) << 8) | buf[3];
320 ESP_LOGV(TAG, "Size is %u bytes", ota_size);
321
322 // Now that we've passed authentication and are actually
323 // starting the update, set the warning status and notify
324 // listeners. This ensures that port scanners do not
325 // accidentally trigger the update process.
326 this->log_start_(LOG_STR("update"));
327 this->status_set_warning();
328#ifdef USE_OTA_STATE_LISTENER
329 this->notify_state_(ota::OTA_STARTED, 0.0f, 0);
330#endif
331
332 // This will block for a few seconds as it locks flash
333 error_code = this->backend_->begin(ota_size);
334 if (error_code != ota::OTA_RESPONSE_OK)
335 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
336 update_started = true;
337
338 // Acknowledge prepare OK - 1 byte
340
341 // Read binary MD5, 32 bytes
342 if (!this->readall_(buf, 32)) {
343 this->log_read_error_(LOG_STR("MD5 checksum"));
344 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
345 }
346 sbuf[32] = '\0';
347 ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
348 this->backend_->set_update_md5(sbuf);
349
350 // Acknowledge MD5 OK - 1 byte
352
353 while (total < ota_size) {
354 // TODO: timeout check
355 size_t remaining = ota_size - total;
356 size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE;
357 ssize_t read = this->client_->read(buf, requested);
358 if (read == -1) {
359 const int err = errno;
360 if (this->would_block_(err)) {
361 // read() already waited up to SO_RCVTIMEO for data, just feed WDT
362 App.feed_wdt();
363 continue;
364 }
365 ESP_LOGW(TAG, "Read err %d", err);
366 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
367 } else if (read == 0) {
368 ESP_LOGW(TAG, "Remote closed");
369 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
370 }
371
372 error_code = this->backend_->write(buf, read);
373 if (error_code != ota::OTA_RESPONSE_OK) {
374 ESP_LOGW(TAG, "Flash write err %d", error_code);
375 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
376 }
377 total += read;
378#if USE_OTA_VERSION == 2
379 while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
381 size_acknowledged += OTA_BLOCK_SIZE;
382 }
383#endif
384
385 uint32_t now = millis();
386 if (now - last_progress > 1000) {
387 last_progress = now;
388 float percentage = (total * 100.0f) / ota_size;
389 ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
390#ifdef USE_OTA_STATE_LISTENER
391 this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0);
392#endif
393 // feed watchdog and give other tasks a chance to run
395 }
396 }
397
398 // Acknowledge receive OK - 1 byte
400
401 error_code = this->backend_->end();
402 if (error_code != ota::OTA_RESPONSE_OK) {
403 ESP_LOGW(TAG, "End update err %d", error_code);
404 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
405 }
406
407 // Acknowledge Update end OK - 1 byte
409
410 // Read ACK
411 if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
412 this->log_read_error_(LOG_STR("ack"));
413 // do not go to error, this is not fatal
414 }
415
416 this->cleanup_connection_();
417 delay(10);
418 ESP_LOGI(TAG, "Update complete");
419 this->status_clear_warning();
420#ifdef USE_OTA_STATE_LISTENER
421 this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0);
422#endif
423 delay(100); // NOLINT
425
426error:
427 this->write_byte_(static_cast<uint8_t>(error_code));
428
429 // Abort backend before cleanup - cleanup_connection_() destroys the backend
430 if (this->backend_ != nullptr && update_started) {
431 this->backend_->abort();
432 }
433
434 this->cleanup_connection_();
435
436 this->status_momentary_error("err", 5000);
437#ifdef USE_OTA_STATE_LISTENER
438 this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
439#endif
440}
441
442bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
443 uint32_t start = millis();
444 uint32_t at = 0;
445 while (len - at > 0) {
446 uint32_t now = millis();
447 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
448 ESP_LOGW(TAG, "Timeout reading %zu bytes", len);
449 return false;
450 }
451
452 ssize_t read = this->client_->read(buf + at, len - at);
453 if (read == -1) {
454 const int err = errno;
455 if (!this->would_block_(err)) {
456 ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, err);
457 return false;
458 }
459 } else if (read == 0) {
460 ESP_LOGW(TAG, "Remote closed");
461 return false;
462 } else {
463 at += read;
464 }
465 // read() already waited via SO_RCVTIMEO, just yield without 1ms stall
466 App.feed_wdt();
467 delay(0);
468 }
469
470 return true;
471}
472bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
473 uint32_t start = millis();
474 uint32_t at = 0;
475 while (len - at > 0) {
476 uint32_t now = millis();
477 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
478 ESP_LOGW(TAG, "Timeout writing %zu bytes", len);
479 return false;
480 }
481
482 ssize_t written = this->client_->write(buf + at, len - at);
483 if (written == -1) {
484 const int err = errno;
485 if (!this->would_block_(err)) {
486 ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, err);
487 return false;
488 }
489 // EWOULDBLOCK: on raw TCP writes never block, delay(1) prevents spinning
491 } else {
492 at += written;
493 // write() may block up to SO_SNDTIMEO on BSD/lwip sockets, feed WDT
494 App.feed_wdt();
495 }
496 }
497 return true;
498}
499
501uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
502void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
503
504void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
505 ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
506}
507
508void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
509
510void ESPHomeOTAComponent::log_start_(const LogString *phase) {
511 char peername[socket::SOCKADDR_STR_LEN];
512 this->client_->getpeername_to(peername);
513 ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername);
514}
515
516void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
517 ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
518}
519
520void ESPHomeOTAComponent::server_failed_(const LogString *msg) {
521 this->log_socket_error_(msg);
522 // No explicit close() needed — listen sockets have no active connections on
523 // failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
524 delete this->server_;
525 this->server_ = nullptr;
526 this->mark_failed();
527}
528
529bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
530 if (read == -1 && this->would_block_(errno)) {
531 return false; // No data yet, try again next loop
532 }
533
534 if (read <= 0) {
535 read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc);
536 this->cleanup_connection_();
537 return false;
538 }
539 return true;
540}
541
543 if (written == -1) {
544 if (this->would_block_(errno)) {
545 return false; // Try again next loop
546 }
547 this->log_socket_error_(desc);
548 this->cleanup_connection_();
549 return false;
550 }
551 return true;
552}
553
554bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) {
555 // Read bytes into handshake buffer, starting at handshake_buf_pos_
556 size_t bytes_to_read = to_read - this->handshake_buf_pos_;
557 ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read);
558
559 if (!this->handle_read_error_(read, desc)) {
560 return false;
561 }
562
563 this->handshake_buf_pos_ += read;
564 // Return true only if we have all the requested bytes
565 return this->handshake_buf_pos_ >= to_read;
566}
567
568bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) {
569 // Write bytes from handshake buffer, starting at handshake_buf_pos_
570 size_t bytes_to_write = to_write - this->handshake_buf_pos_;
571 ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write);
572
573 if (!this->handle_write_error_(written, desc)) {
574 return false;
575 }
576
577 this->handshake_buf_pos_ += written;
578 // Return true only if we have written all the requested bytes
579 return this->handshake_buf_pos_ >= to_write;
580}
581
583 this->client_->close();
584 this->client_ = nullptr;
585 this->client_connect_time_ = 0;
586 this->handshake_buf_pos_ = 0;
588 this->ota_features_ = 0;
589 this->backend_ = nullptr;
590#ifdef USE_OTA_PASSWORD
591 this->cleanup_auth_();
592#endif
593 // Intentionally no disable_loop() — letting loop() run one more iteration catches
594 // any connection that queued on the listener mid-session (otherwise the wake flag,
595 // set while we were in LOOP state, would be lost to enable_pending_loops_()).
596}
597
602
603#ifdef USE_OTA_PASSWORD
604void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
605
607 bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
608
609 // Require SHA256
610 if (!client_supports_sha256) {
611 this->log_auth_warning_(LOG_STR("SHA256 required"));
613 return false;
614 }
616 return true;
617}
618
620 // Initialize auth buffer if not already done
621 if (!this->auth_buf_) {
622 // Select auth type based on client capabilities and configuration
623 if (!this->select_auth_type_()) {
624 return false;
625 }
626
627 // Generate nonce - hasher must be created and used in same stack frame
628 // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
629 // 1. Hash objects must NEVER be passed to another function (different stack frame)
630 // 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA
631 // 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created
632 // Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption.
633 //
634 // Buffer layout after AUTH_READ completes:
635 // [0]: auth_type (1 byte)
636 // [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
637 // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
638 // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
639
640 // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
641 // (no passing to other functions). All hash operations must happen in this function.
642 sha256::SHA256 hasher;
643
644 const size_t hex_size = hasher.get_size() * 2;
645 const size_t nonce_len = hasher.get_size() / 4;
646 const size_t auth_buf_size = 1 + 3 * hex_size;
647 this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
648 this->auth_buf_pos_ = 0;
649
650 char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
651 if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
652 this->log_auth_warning_(LOG_STR("Random failed"));
654 return false;
655 }
656
657 hasher.init();
658 hasher.add(buf, nonce_len);
659 hasher.calculate();
660 this->auth_buf_[0] = this->auth_type_;
661 hasher.get_hex(buf);
662
663 ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf);
664 }
665
666 // Try to write auth_type + nonce
667 constexpr size_t hex_size = SHA256_HEX_SIZE;
668 const size_t to_write = 1 + hex_size;
669 size_t remaining = to_write - this->auth_buf_pos_;
670
671 ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining);
672 if (!this->handle_write_error_(written, LOG_STR("ack auth"))) {
673 return false;
674 }
675
676 this->auth_buf_pos_ += written;
677
678 // Check if we still have more to write
679 if (this->auth_buf_pos_ < to_write) {
680 return false; // More to write, try again next loop
681 }
682
683 // All written, prepare for reading phase
684 this->auth_buf_pos_ = 0;
685 return true;
686}
687
689 constexpr size_t hex_size = SHA256_HEX_SIZE;
690 const size_t to_read = hex_size * 2; // CNonce + Response
691
692 // Try to read remaining bytes (CNonce + Response)
693 // We read cnonce+response starting at offset 1+hex_size (after auth_type and our nonce)
694 size_t cnonce_offset = 1 + hex_size; // Offset where cnonce should be stored in buffer
695 size_t remaining = to_read - this->auth_buf_pos_;
696 ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining);
697
698 if (!this->handle_read_error_(read, LOG_STR("read auth"))) {
699 return false;
700 }
701
702 this->auth_buf_pos_ += read;
703
704 // Check if we still need more data
705 if (this->auth_buf_pos_ < to_read) {
706 return false; // More to read, try again next loop
707 }
708
709 // We have all the data, verify it
710 const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
711 const char *cnonce = nonce + hex_size;
712 const char *response = cnonce + hex_size;
713
714 // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
715 // (no passing to other functions). All hash operations must happen in this function.
716 sha256::SHA256 hasher;
717
718 hasher.init();
719 hasher.add(this->password_.c_str(), this->password_.length());
720 hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
721 hasher.calculate();
722
723 ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce);
724#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
725 char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator)
726 hasher.get_hex(computed_hash);
727 ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash);
728#endif
729 ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response);
730
731 // Compare response
732 bool matches = hasher.equals_hex(response);
733
734 if (!matches) {
735 this->log_auth_warning_(LOG_STR("Password mismatch"));
737 return false;
738 }
739
740 // Authentication successful - clean up auth state
741 this->cleanup_auth_();
742
743 return true;
744}
745
747 this->auth_buf_ = nullptr;
748 this->auth_buf_pos_ = 0;
749 this->auth_type_ = 0;
750}
751#endif // USE_OTA_PASSWORD
752
753} // namespace esphome
754#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:306
bool would_block_(int error_code) const
Definition ota_esphome.h:61
static constexpr size_t SHA256_HEX_SIZE
Definition ota_esphome.h:47
bool writeall_(const uint8_t *buf, size_t len)
bool try_read_(size_t to_read, const LogString *desc)
ota::OTABackendPtr backend_
Definition ota_esphome.h:89
bool try_write_(size_t to_write, const LogString *desc)
std::unique_ptr< uint8_t[]> auth_buf_
Definition ota_esphome.h:84
bool handle_write_error_(ssize_t written, const LogString *desc)
void log_auth_warning_(const LogString *msg)
float get_setup_priority() const override
void send_error_and_cleanup_(ota::OTAResponseTypes error)
Definition ota_esphome.h:75
bool handle_read_error_(ssize_t read, const LogString *desc)
void log_read_error_(const LogString *what)
bool readall_(uint8_t *buf, size_t len)
void set_port(uint16_t port)
Manually set the port OTA should listen on.
bool write_byte_(uint8_t byte)
Definition ota_esphome.h:56
void server_failed_(const LogString *msg)
void transition_ota_state_(OTAState next_state)
Definition ota_esphome.h:64
socket::ListenSocket * server_
Definition ota_esphome.h:87
void log_remote_closed_(const LogString *during)
std::unique_ptr< socket::Socket > client_
Definition ota_esphome.h:88
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
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:70
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.
ESPHOME_ALWAYS_INLINE const char * get_use_address()
Get the active network hostname.
Definition util.h:57
@ OTA_RESPONSE_UPDATE_PREPARE_OK
Definition ota_backend.h:20
@ OTA_RESPONSE_SUPPORTS_COMPRESSION
Definition ota_backend.h:24
@ OTA_RESPONSE_BIN_MD5_OK
Definition ota_backend.h:21
@ OTA_RESPONSE_UPDATE_END_OK
Definition ota_backend.h:23
@ OTA_RESPONSE_RECEIVE_OK
Definition ota_backend.h:22
@ OTA_RESPONSE_CHUNK_OK
Definition ota_backend.h:25
@ OTA_RESPONSE_ERROR_AUTH_INVALID
Definition ota_backend.h:29
@ OTA_RESPONSE_ERROR_UNKNOWN
Definition ota_backend.h:41
@ OTA_RESPONSE_REQUEST_SHA256_AUTH
Definition ota_backend.h:16
@ OTA_RESPONSE_ERROR_MAGIC
Definition ota_backend.h:27
@ OTA_RESPONSE_HEADER_OK
Definition ota_backend.h:18
std::unique_ptr< ArduinoLibreTinyOTABackend > make_ota_backend()
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.h:52
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:143
std::unique_ptr< ListenSocket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:95
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
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()
std::string size_t len
Definition helpers.h:1045
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
int written
Definition helpers.h:1089
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t