ESPHome 2025.10.3
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
4#ifdef USE_OTA_MD5
6#endif
7#ifdef USE_OTA_SHA256
9#endif
10#endif
19#include "esphome/core/hal.h"
21#include "esphome/core/log.h"
22#include "esphome/core/util.h"
23
24#include <cerrno>
25#include <cstdio>
26
27namespace esphome {
28
29static const char *const TAG = "esphome.ota";
30static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
31static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size for OTA data transfer
32static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
33static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
34
35#ifdef USE_OTA_PASSWORD
36#ifdef USE_OTA_MD5
37static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2)
38#endif
39#ifdef USE_OTA_SHA256
40static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
41#endif
42#endif // USE_OTA_PASSWORD
43
45#ifdef USE_OTA_STATE_CALLBACK
47#endif
48
49 this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
50 if (this->server_ == nullptr) {
51 this->log_socket_error_(LOG_STR("creation"));
52 this->mark_failed();
53 return;
54 }
55 int enable = 1;
56 int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
57 if (err != 0) {
58 this->log_socket_error_(LOG_STR("reuseaddr"));
59 // we can still continue
60 }
61 err = this->server_->setblocking(false);
62 if (err != 0) {
63 this->log_socket_error_(LOG_STR("non-blocking"));
64 this->mark_failed();
65 return;
66 }
67
68 struct sockaddr_storage server;
69
70 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
71 if (sl == 0) {
72 this->log_socket_error_(LOG_STR("set sockaddr"));
73 this->mark_failed();
74 return;
75 }
76
77 err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
78 if (err != 0) {
79 this->log_socket_error_(LOG_STR("bind"));
80 this->mark_failed();
81 return;
82 }
83
84 err = this->server_->listen(1); // Only one client at a time
85 if (err != 0) {
86 this->log_socket_error_(LOG_STR("listen"));
87 this->mark_failed();
88 return;
89 }
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().c_str(), 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 // Skip handle_handshake_() call if no client connected and no incoming connections
107 // This optimization reduces idle loop overhead when OTA is not active
108 // Note: No need to check server_ for null as the component is marked failed in setup()
109 // if server_ creation fails
110 if (this->client_ != nullptr || this->server_->ready()) {
111 this->handle_handshake_();
112 }
113}
114
115static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
116#ifdef USE_OTA_SHA256
117static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
118#endif
119
120// Temporary flag to allow MD5 downgrade for ~3 versions (until 2026.1.0)
121// This allows users to downgrade via OTA if they encounter issues after updating.
122// Without this, users would need to do a serial flash to downgrade.
123// TODO: Remove this flag and all associated code in 2026.1.0
124#define ALLOW_OTA_DOWNGRADE_MD5
125
132
133 if (this->client_ == nullptr) {
134 // We already checked server_->ready() in loop(), so we can accept directly
135 struct sockaddr_storage source_addr;
136 socklen_t addr_len = sizeof(source_addr);
137 int enable = 1;
138
139 this->client_ = this->server_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
140 if (this->client_ == nullptr)
141 return;
142 int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
143 if (err != 0) {
144 this->log_socket_error_(LOG_STR("nodelay"));
145 this->cleanup_connection_();
146 return;
147 }
148 err = this->client_->setblocking(false);
149 if (err != 0) {
150 this->log_socket_error_(LOG_STR("non-blocking"));
151 this->cleanup_connection_();
152 return;
153 }
154 this->log_start_(LOG_STR("handshake"));
156 this->handshake_buf_pos_ = 0; // Reset handshake buffer position
158 }
159
160 // Check for handshake timeout
161 uint32_t now = App.get_loop_component_start_time();
162 if (now - this->client_connect_time_ > OTA_SOCKET_TIMEOUT_HANDSHAKE) {
163 ESP_LOGW(TAG, "Handshake timeout");
164 this->cleanup_connection_();
165 return;
166 }
167
168 switch (this->ota_state_) {
170 // Try to read remaining magic bytes (5 total)
171 if (!this->try_read_(5, LOG_STR("read magic"))) {
172 return;
173 }
174
175 // Validate magic bytes
176 static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
177 if (memcmp(this->handshake_buf_, MAGIC_BYTES, 5) != 0) {
178 ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->handshake_buf_[0],
179 this->handshake_buf_[1], this->handshake_buf_[2], this->handshake_buf_[3], this->handshake_buf_[4]);
181 return;
182 }
183
184 // Magic bytes valid, move to next state
187 this->handshake_buf_[1] = USE_OTA_VERSION;
188 [[fallthrough]];
189 }
190
191 case OTAState::MAGIC_ACK: {
192 // Send OK and version - 2 bytes
193 if (!this->try_write_(2, LOG_STR("ack magic"))) {
194 return;
195 }
196 // All bytes sent, create backend and move to next state
199 [[fallthrough]];
200 }
201
203 // Read features - 1 byte
204 if (!this->try_read_(1, LOG_STR("read feature"))) {
205 return;
206 }
207 this->ota_features_ = this->handshake_buf_[0];
208 ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_);
210 this->handshake_buf_[0] =
211 ((this->ota_features_ & FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression())
214 [[fallthrough]];
215 }
216
218 // Acknowledge header - 1 byte
219 if (!this->try_write_(1, LOG_STR("ack feature"))) {
220 return;
221 }
222#ifdef USE_OTA_PASSWORD
223 // If password is set, move to auth phase
224 if (!this->password_.empty()) {
226 } else
227#endif
228 {
229 // No password, move directly to data phase
231 }
232 [[fallthrough]];
233 }
234
235#ifdef USE_OTA_PASSWORD
236 case OTAState::AUTH_SEND: {
237 // Non-blocking authentication send
238 if (!this->handle_auth_send_()) {
239 return;
240 }
242 [[fallthrough]];
243 }
244
245 case OTAState::AUTH_READ: {
246 // Non-blocking authentication read & verify
247 if (!this->handle_auth_read_()) {
248 return;
249 }
251 [[fallthrough]];
252 }
253#endif
254
255 case OTAState::DATA:
256 this->handle_data_();
257 return;
258
259 default:
260 break;
261 }
262}
263
273 bool update_started = false;
274 size_t total = 0;
275 uint32_t last_progress = 0;
276 uint8_t buf[OTA_BUFFER_SIZE];
277 char *sbuf = reinterpret_cast<char *>(buf);
278 size_t ota_size;
279#if USE_OTA_VERSION == 2
280 size_t size_acknowledged = 0;
281#endif
282
283 // Acknowledge auth OK - 1 byte
285 this->writeall_(buf, 1);
286
287 // Read size, 4 bytes MSB first
288 if (!this->readall_(buf, 4)) {
289 this->log_read_error_(LOG_STR("size"));
290 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
291 }
292 ota_size = 0;
293 for (uint8_t i = 0; i < 4; i++) {
294 ota_size <<= 8;
295 ota_size |= buf[i];
296 }
297 ESP_LOGV(TAG, "Size is %u bytes", ota_size);
298
299 // Now that we've passed authentication and are actually
300 // starting the update, set the warning status and notify
301 // listeners. This ensures that port scanners do not
302 // accidentally trigger the update process.
303 this->log_start_(LOG_STR("update"));
304 this->status_set_warning();
305#ifdef USE_OTA_STATE_CALLBACK
306 this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
307#endif
308
309 // This will block for a few seconds as it locks flash
310 error_code = this->backend_->begin(ota_size);
311 if (error_code != ota::OTA_RESPONSE_OK)
312 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
313 update_started = true;
314
315 // Acknowledge prepare OK - 1 byte
317 this->writeall_(buf, 1);
318
319 // Read binary MD5, 32 bytes
320 if (!this->readall_(buf, 32)) {
321 this->log_read_error_(LOG_STR("MD5 checksum"));
322 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
323 }
324 sbuf[32] = '\0';
325 ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
326 this->backend_->set_update_md5(sbuf);
327
328 // Acknowledge MD5 OK - 1 byte
330 this->writeall_(buf, 1);
331
332 while (total < ota_size) {
333 // TODO: timeout check
334 size_t remaining = ota_size - total;
335 size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE;
336 ssize_t read = this->client_->read(buf, requested);
337 if (read == -1) {
338 if (this->would_block_(errno)) {
340 continue;
341 }
342 ESP_LOGW(TAG, "Read err %d", errno);
343 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
344 } else if (read == 0) {
345 ESP_LOGW(TAG, "Remote closed");
346 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
347 }
348
349 error_code = this->backend_->write(buf, read);
350 if (error_code != ota::OTA_RESPONSE_OK) {
351 ESP_LOGW(TAG, "Flash write err %d", error_code);
352 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
353 }
354 total += read;
355#if USE_OTA_VERSION == 2
356 while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
358 this->writeall_(buf, 1);
359 size_acknowledged += OTA_BLOCK_SIZE;
360 }
361#endif
362
363 uint32_t now = millis();
364 if (now - last_progress > 1000) {
365 last_progress = now;
366 float percentage = (total * 100.0f) / ota_size;
367 ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
368#ifdef USE_OTA_STATE_CALLBACK
369 this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
370#endif
371 // feed watchdog and give other tasks a chance to run
373 }
374 }
375
376 // Acknowledge receive OK - 1 byte
378 this->writeall_(buf, 1);
379
380 error_code = this->backend_->end();
381 if (error_code != ota::OTA_RESPONSE_OK) {
382 ESP_LOGW(TAG, "End update err %d", error_code);
383 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
384 }
385
386 // Acknowledge Update end OK - 1 byte
388 this->writeall_(buf, 1);
389
390 // Read ACK
391 if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
392 this->log_read_error_(LOG_STR("ack"));
393 // do not go to error, this is not fatal
394 }
395
396 this->cleanup_connection_();
397 delay(10);
398 ESP_LOGI(TAG, "Update complete");
399 this->status_clear_warning();
400#ifdef USE_OTA_STATE_CALLBACK
401 this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0);
402#endif
403 delay(100); // NOLINT
405
406error:
407 buf[0] = static_cast<uint8_t>(error_code);
408 this->writeall_(buf, 1);
409 this->cleanup_connection_();
410
411 if (this->backend_ != nullptr && update_started) {
412 this->backend_->abort();
413 }
414
415 this->status_momentary_error("onerror", 5000);
416#ifdef USE_OTA_STATE_CALLBACK
417 this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
418#endif
419}
420
421bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
422 uint32_t start = millis();
423 uint32_t at = 0;
424 while (len - at > 0) {
425 uint32_t now = millis();
426 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
427 ESP_LOGW(TAG, "Timeout reading %d bytes", len);
428 return false;
429 }
430
431 ssize_t read = this->client_->read(buf + at, len - at);
432 if (read == -1) {
433 if (!this->would_block_(errno)) {
434 ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno);
435 return false;
436 }
437 } else if (read == 0) {
438 ESP_LOGW(TAG, "Remote closed");
439 return false;
440 } else {
441 at += read;
442 }
444 }
445
446 return true;
447}
448bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
449 uint32_t start = millis();
450 uint32_t at = 0;
451 while (len - at > 0) {
452 uint32_t now = millis();
453 if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
454 ESP_LOGW(TAG, "Timeout writing %d bytes", len);
455 return false;
456 }
457
458 ssize_t written = this->client_->write(buf + at, len - at);
459 if (written == -1) {
460 if (!this->would_block_(errno)) {
461 ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno);
462 return false;
463 }
464 } else {
465 at += written;
466 }
468 }
469 return true;
470}
471
473uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
474void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
475
476void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
477 ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
478}
479
480void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
481
482void ESPHomeOTAComponent::log_start_(const LogString *phase) {
483 ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
484}
485
486void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
487 ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
488}
489
490bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
491 if (read == -1 && this->would_block_(errno)) {
492 return false; // No data yet, try again next loop
493 }
494
495 if (read <= 0) {
496 read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc);
497 this->cleanup_connection_();
498 return false;
499 }
500 return true;
501}
502
503bool ESPHomeOTAComponent::handle_write_error_(ssize_t written, const LogString *desc) {
504 if (written == -1) {
505 if (this->would_block_(errno)) {
506 return false; // Try again next loop
507 }
508 this->log_socket_error_(desc);
509 this->cleanup_connection_();
510 return false;
511 }
512 return true;
513}
514
515bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) {
516 // Read bytes into handshake buffer, starting at handshake_buf_pos_
517 size_t bytes_to_read = to_read - this->handshake_buf_pos_;
518 ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read);
519
520 if (!this->handle_read_error_(read, desc)) {
521 return false;
522 }
523
524 this->handshake_buf_pos_ += read;
525 // Return true only if we have all the requested bytes
526 return this->handshake_buf_pos_ >= to_read;
527}
528
529bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) {
530 // Write bytes from handshake buffer, starting at handshake_buf_pos_
531 size_t bytes_to_write = to_write - this->handshake_buf_pos_;
532 ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write);
533
534 if (!this->handle_write_error_(written, desc)) {
535 return false;
536 }
537
538 this->handshake_buf_pos_ += written;
539 // Return true only if we have written all the requested bytes
540 return this->handshake_buf_pos_ >= to_write;
541}
542
544 this->client_->close();
545 this->client_ = nullptr;
546 this->client_connect_time_ = 0;
547 this->handshake_buf_pos_ = 0;
549 this->ota_features_ = 0;
550 this->backend_ = nullptr;
551#ifdef USE_OTA_PASSWORD
552 this->cleanup_auth_();
553#endif
554}
555
560
561#ifdef USE_OTA_PASSWORD
562void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
563
565#ifdef USE_OTA_SHA256
566 bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
567
568#ifdef ALLOW_OTA_DOWNGRADE_MD5
569 // Allow fallback to MD5 if client doesn't support SHA256
570 if (client_supports_sha256) {
572 return true;
573 }
574#ifdef USE_OTA_MD5
575 this->log_auth_warning_(LOG_STR("Using deprecated MD5"));
577 return true;
578#else
579 this->log_auth_warning_(LOG_STR("SHA256 required"));
581 return false;
582#endif // USE_OTA_MD5
583
584#else // !ALLOW_OTA_DOWNGRADE_MD5
585 // Require SHA256
586 if (!client_supports_sha256) {
587 this->log_auth_warning_(LOG_STR("SHA256 required"));
589 return false;
590 }
592 return true;
593#endif // ALLOW_OTA_DOWNGRADE_MD5
594
595#else // !USE_OTA_SHA256
596#ifdef USE_OTA_MD5
597 // Only MD5 available
599 return true;
600#else
601 // No auth methods available
602 this->log_auth_warning_(LOG_STR("No auth methods available"));
604 return false;
605#endif // USE_OTA_MD5
606#endif // USE_OTA_SHA256
607}
608
610 // Initialize auth buffer if not already done
611 if (!this->auth_buf_) {
612 // Select auth type based on client capabilities and configuration
613 if (!this->select_auth_type_()) {
614 return false;
615 }
616
617 // Generate nonce - hasher must be created and used in same stack frame
618 // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
619 // 1. Hash objects must NEVER be passed to another function (different stack frame)
620 // 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA
621 // 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created
622 // Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption.
623 //
624 // Buffer layout after AUTH_READ completes:
625 // [0]: auth_type (1 byte)
626 // [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
627 // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
628 // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
629
630 // Declare both hash objects in same stack frame, use pointer to select.
631 // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
632 // hardware SHA acceleration - the object must exist in this stack frame for all operations.
633 // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
634#ifdef USE_OTA_SHA256
635 sha256::SHA256 sha_hasher;
636#endif
637#ifdef USE_OTA_MD5
638 md5::MD5Digest md5_hasher;
639#endif
640 HashBase *hasher = nullptr;
641
642#ifdef USE_OTA_SHA256
644 hasher = &sha_hasher;
645 }
646#endif
647#ifdef USE_OTA_MD5
649 hasher = &md5_hasher;
650 }
651#endif
652
653 const size_t hex_size = hasher->get_size() * 2;
654 const size_t nonce_len = hasher->get_size() / 4;
655 const size_t auth_buf_size = 1 + 3 * hex_size;
656 this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
657 this->auth_buf_pos_ = 0;
658
659 char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
660 if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
661 this->log_auth_warning_(LOG_STR("Random failed"));
663 return false;
664 }
665
666 hasher->init();
667 hasher->add(buf, nonce_len);
668 hasher->calculate();
669 this->auth_buf_[0] = this->auth_type_;
670 hasher->get_hex(buf);
671
672#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
673 char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
674 memcpy(log_buf, buf, hex_size);
675 log_buf[hex_size] = '\0';
676 ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf);
677#endif
678 }
679
680 // Try to write auth_type + nonce
681 size_t hex_size = this->get_auth_hex_size_();
682 const size_t to_write = 1 + hex_size;
683 size_t remaining = to_write - this->auth_buf_pos_;
684
685 ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining);
686 if (!this->handle_write_error_(written, LOG_STR("ack auth"))) {
687 return false;
688 }
689
690 this->auth_buf_pos_ += written;
691
692 // Check if we still have more to write
693 if (this->auth_buf_pos_ < to_write) {
694 return false; // More to write, try again next loop
695 }
696
697 // All written, prepare for reading phase
698 this->auth_buf_pos_ = 0;
699 return true;
700}
701
703 size_t hex_size = this->get_auth_hex_size_();
704 const size_t to_read = hex_size * 2; // CNonce + Response
705
706 // Try to read remaining bytes (CNonce + Response)
707 // We read cnonce+response starting at offset 1+hex_size (after auth_type and our nonce)
708 size_t cnonce_offset = 1 + hex_size; // Offset where cnonce should be stored in buffer
709 size_t remaining = to_read - this->auth_buf_pos_;
710 ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining);
711
712 if (!this->handle_read_error_(read, LOG_STR("read auth"))) {
713 return false;
714 }
715
716 this->auth_buf_pos_ += read;
717
718 // Check if we still need more data
719 if (this->auth_buf_pos_ < to_read) {
720 return false; // More to read, try again next loop
721 }
722
723 // We have all the data, verify it
724 const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
725 const char *cnonce = nonce + hex_size;
726 const char *response = cnonce + hex_size;
727
728 // CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions).
729 // Declare both hash objects in same stack frame, use pointer to select.
730 // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
731 // hardware SHA acceleration - the object must exist in this stack frame for all operations.
732 // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
733#ifdef USE_OTA_SHA256
734 sha256::SHA256 sha_hasher;
735#endif
736#ifdef USE_OTA_MD5
737 md5::MD5Digest md5_hasher;
738#endif
739 HashBase *hasher = nullptr;
740
741#ifdef USE_OTA_SHA256
743 hasher = &sha_hasher;
744 }
745#endif
746#ifdef USE_OTA_MD5
748 hasher = &md5_hasher;
749 }
750#endif
751
752 hasher->init();
753 hasher->add(this->password_.c_str(), this->password_.length());
754 hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
755 hasher->calculate();
756
757#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
758 char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
759 // Log CNonce
760 memcpy(log_buf, cnonce, hex_size);
761 log_buf[hex_size] = '\0';
762 ESP_LOGV(TAG, "Auth: CNonce is %s", log_buf);
763
764 // Log computed hash
765 hasher->get_hex(log_buf);
766 log_buf[hex_size] = '\0';
767 ESP_LOGV(TAG, "Auth: Result is %s", log_buf);
768
769 // Log received response
770 memcpy(log_buf, response, hex_size);
771 log_buf[hex_size] = '\0';
772 ESP_LOGV(TAG, "Auth: Response is %s", log_buf);
773#endif
774
775 // Compare response
776 bool matches = hasher->equals_hex(response);
777
778 if (!matches) {
779 this->log_auth_warning_(LOG_STR("Password mismatch"));
781 return false;
782 }
783
784 // Authentication successful - clean up auth state
785 this->cleanup_auth_();
786
787 return true;
788}
789
791#ifdef USE_OTA_SHA256
793 return SHA256_HEX_SIZE;
794 }
795#endif
796#ifdef USE_OTA_MD5
797 return MD5_HEX_SIZE;
798#else
799#ifndef USE_OTA_SHA256
800#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled"
801#endif
802#endif
803}
804
806 this->auth_buf_ = nullptr;
807 this->auth_buf_pos_ = 0;
808 this->auth_type_ = 0;
809}
810#endif // USE_OTA_PASSWORD
811
812} // namespace esphome
813#endif
void feed_wdt(uint32_t time=0)
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.
virtual void mark_failed()
Mark this component as failed.
void status_set_warning(const char *message=nullptr)
void status_momentary_error(const std::string &name, uint32_t length=5000)
void status_clear_warning()
bool would_block_(int error_code) const
Definition ota_esphome.h:60
std::unique_ptr< ota::OTABackend > backend_
Definition ota_esphome.h:86
bool writeall_(const uint8_t *buf, size_t len)
bool try_read_(size_t to_read, const LogString *desc)
bool try_write_(size_t to_write, const LogString *desc)
std::unique_ptr< uint8_t[]> auth_buf_
Definition ota_esphome.h:95
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:73
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.
std::unique_ptr< socket::Socket > server_
Definition ota_esphome.h:84
void transition_ota_state_(OTAState next_state)
Definition ota_esphome.h:63
void log_remote_closed_(const LogString *during)
std::unique_ptr< socket::Socket > client_
Definition ota_esphome.h:85
void log_start_(const LogString *phase)
void log_socket_error_(const LogString *msg)
Base class for hash algorithms.
Definition hash_base.h:11
void get_hex(char *output)
Retrieve the hash as hex characters.
Definition hash_base.h:29
bool equals_hex(const char *expected)
Compare the hash against a provided hex-encoded hash.
Definition hash_base.h:41
virtual void calculate()=0
Compute the hash based on provided data.
virtual void init()=0
Initialize a new hash computation.
virtual size_t get_size() const =0
Get the size of the hash in bytes (16 for MD5, 32 for SHA256)
virtual void add(const uint8_t *data, size_t len)=0
Add bytes of data for the hash.
StateCallbackManager state_callback_
Definition ota_backend.h:92
uint16_t addr_len
uint32_t socklen_t
Definition headers.h:97
__int64 ssize_t
Definition httplib.h:178
std::string get_use_address()
Get the active network hostname.
Definition util.cpp:88
void register_ota_platform(OTAComponent *ota_caller)
std::unique_ptr< ota::OTABackend > make_ota_backend()
@ OTA_RESPONSE_UPDATE_PREPARE_OK
Definition ota_backend.h:21
@ OTA_RESPONSE_SUPPORTS_COMPRESSION
Definition ota_backend.h:25
@ OTA_RESPONSE_BIN_MD5_OK
Definition ota_backend.h:22
@ OTA_RESPONSE_UPDATE_END_OK
Definition ota_backend.h:24
@ OTA_RESPONSE_RECEIVE_OK
Definition ota_backend.h:23
@ OTA_RESPONSE_CHUNK_OK
Definition ota_backend.h:26
@ OTA_RESPONSE_ERROR_AUTH_INVALID
Definition ota_backend.h:30
@ OTA_RESPONSE_ERROR_UNKNOWN
Definition ota_backend.h:41
@ OTA_RESPONSE_REQUEST_SHA256_AUTH
Definition ota_backend.h:17
@ OTA_RESPONSE_ERROR_MAGIC
Definition ota_backend.h:28
@ OTA_RESPONSE_HEADER_OK
Definition ota_backend.h:19
@ OTA_RESPONSE_REQUEST_AUTH
Definition ota_backend.h:16
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.cpp:66
std::unique_ptr< Socket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:44
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:82
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 number of random bytes.
Definition helpers.cpp:18
std::string size_t len
Definition helpers.h:304
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:30
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:29
Application App
Global storage of Application pointer - only one Application can exist.