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