mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	merge
This commit is contained in:
		| @@ -124,11 +124,11 @@ static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02; | ||||
| #define ALLOW_OTA_DOWNGRADE_MD5 | ||||
|  | ||||
| void ESPHomeOTAComponent::handle_handshake_() { | ||||
|   /// Handle the initial OTA handshake. | ||||
|   /// Handle the OTA handshake and authentication. | ||||
|   /// | ||||
|   /// This method is non-blocking and will return immediately if no data is available. | ||||
|   /// It reads all 5 magic bytes (0x6C, 0x26, 0xF7, 0x5C, 0x45) non-blocking | ||||
|   /// before proceeding to handle_data_(). A 10-second timeout is enforced from initial connection. | ||||
|   /// It manages the state machine through connection, magic bytes validation, feature | ||||
|   /// negotiation, and authentication before entering the blocking data transfer phase. | ||||
|  | ||||
|   if (this->client_ == nullptr) { | ||||
|     // We already checked server_->ready() in loop(), so we can accept directly | ||||
| @@ -168,7 +168,7 @@ void ESPHomeOTAComponent::handle_handshake_() { | ||||
|   switch (this->ota_state_) { | ||||
|     case OTAState::MAGIC_READ: { | ||||
|       // Try to read remaining magic bytes (5 total) | ||||
|       if (!this->try_read_(5, LOG_STR("reading magic bytes"), LOG_STR("handshake"))) { | ||||
|       if (!this->try_read_(5, LOG_STR("read magic"))) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
| @@ -183,21 +183,16 @@ void ESPHomeOTAComponent::handle_handshake_() { | ||||
|  | ||||
|       // Magic bytes valid, move to next state | ||||
|       this->transition_ota_state_(OTAState::MAGIC_ACK); | ||||
|       this->handshake_buf_[0] = ota::OTA_RESPONSE_OK; | ||||
|       this->handshake_buf_[1] = USE_OTA_VERSION; | ||||
|       [[fallthrough]]; | ||||
|     } | ||||
|  | ||||
|     case OTAState::MAGIC_ACK: { | ||||
|       // Send OK and version - 2 bytes | ||||
|       // Prepare response in handshake buffer if not already done | ||||
|       if (this->handshake_buf_pos_ == 0) { | ||||
|         this->handshake_buf_[0] = ota::OTA_RESPONSE_OK; | ||||
|         this->handshake_buf_[1] = USE_OTA_VERSION; | ||||
|       } | ||||
|  | ||||
|       if (!this->try_write_(2, LOG_STR("writing magic ack"))) { | ||||
|       if (!this->try_write_(2, LOG_STR("ack magic"))) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // All bytes sent, create backend and move to next state | ||||
|       this->backend_ = ota::make_ota_backend(); | ||||
|       this->transition_ota_state_(OTAState::FEATURE_READ); | ||||
| @@ -206,30 +201,24 @@ void ESPHomeOTAComponent::handle_handshake_() { | ||||
|  | ||||
|     case OTAState::FEATURE_READ: { | ||||
|       // Read features - 1 byte | ||||
|       if (!this->try_read_(1, LOG_STR("reading features"), LOG_STR("feature read"))) { | ||||
|       if (!this->try_read_(1, LOG_STR("read feature"))) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       this->ota_features_ = this->handshake_buf_[0]; | ||||
|       ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_); | ||||
|       this->transition_ota_state_(OTAState::FEATURE_ACK); | ||||
|       this->handshake_buf_[0] = | ||||
|           ((this->ota_features_ & FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression()) | ||||
|               ? ota::OTA_RESPONSE_SUPPORTS_COMPRESSION | ||||
|               : ota::OTA_RESPONSE_HEADER_OK; | ||||
|       [[fallthrough]]; | ||||
|     } | ||||
|  | ||||
|     case OTAState::FEATURE_ACK: { | ||||
|       // Acknowledge header - 1 byte | ||||
|       // Prepare response in handshake buffer if not already done | ||||
|       if (this->handshake_buf_pos_ == 0) { | ||||
|         this->handshake_buf_[0] = | ||||
|             ((this->ota_features_ & FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression()) | ||||
|                 ? ota::OTA_RESPONSE_SUPPORTS_COMPRESSION | ||||
|                 : ota::OTA_RESPONSE_HEADER_OK; | ||||
|       } | ||||
|  | ||||
|       if (!this->try_write_(1, LOG_STR("writing feature ack"))) { | ||||
|       if (!this->try_write_(1, LOG_STR("ack feature"))) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
| #ifdef USE_OTA_PASSWORD | ||||
|       // If password is set, move to auth phase | ||||
|       if (!this->password_.empty()) { | ||||
| @@ -266,11 +255,10 @@ void ESPHomeOTAComponent::handle_handshake_() { | ||||
|  | ||||
|     case OTAState::DATA: | ||||
|       this->handle_data_(); | ||||
|       return; | ||||
|       [[fallthrough]]; | ||||
|  | ||||
|     case OTAState::IDLE: | ||||
|       // This shouldn't happen | ||||
|       return; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -287,20 +275,12 @@ void ESPHomeOTAComponent::handle_data_() { | ||||
|   size_t total = 0; | ||||
|   uint32_t last_progress = 0; | ||||
|   uint8_t buf[OTA_BUFFER_SIZE]; | ||||
|   const size_t buf_size = sizeof(buf); | ||||
|   char *sbuf = reinterpret_cast<char *>(buf); | ||||
|   size_t ota_size; | ||||
| #if USE_OTA_VERSION == 2 | ||||
|   size_t size_acknowledged = 0; | ||||
| #endif | ||||
|  | ||||
|   // The handshake and auth have already been completed | ||||
|   // We already have: | ||||
|   // - this->backend_ created | ||||
|   // - this->ota_features_ set | ||||
|   // - Feature acknowledgment sent | ||||
|   // - Authentication completed (if password was set) | ||||
|  | ||||
|   // Acknowledge auth OK - 1 byte | ||||
|   buf[0] = ota::OTA_RESPONSE_AUTH_OK; | ||||
|   this->writeall_(buf, 1); | ||||
| @@ -353,26 +333,23 @@ void ESPHomeOTAComponent::handle_data_() { | ||||
|   while (total < ota_size) { | ||||
|     // TODO: timeout check | ||||
|     size_t remaining = ota_size - total; | ||||
|     size_t requested = remaining < buf_size ? remaining : buf_size; | ||||
|     size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE; | ||||
|     ssize_t read = this->client_->read(buf, requested); | ||||
|     if (read == -1) { | ||||
|       if (this->would_block_(errno)) { | ||||
|         this->yield_and_feed_watchdog_(); | ||||
|         continue; | ||||
|       } | ||||
|       ESP_LOGW(TAG, "Read error, errno %d", errno); | ||||
|       ESP_LOGW(TAG, "Read err %d", errno); | ||||
|       goto error;  // NOLINT(cppcoreguidelines-avoid-goto) | ||||
|     } else if (read == 0) { | ||||
|       // $ man recv | ||||
|       // "When  a  stream socket peer has performed an orderly shutdown, the return value will | ||||
|       // be 0 (the traditional "end-of-file" return)." | ||||
|       ESP_LOGW(TAG, "Remote closed connection"); | ||||
|       ESP_LOGW(TAG, "Remote closed"); | ||||
|       goto error;  // NOLINT(cppcoreguidelines-avoid-goto) | ||||
|     } | ||||
|  | ||||
|     error_code = this->backend_->write(buf, read); | ||||
|     if (error_code != ota::OTA_RESPONSE_OK) { | ||||
|       ESP_LOGW(TAG, "Flash write error, code: %d", error_code); | ||||
|       ESP_LOGW(TAG, "Flash write err %d", error_code); | ||||
|       goto error;  // NOLINT(cppcoreguidelines-avoid-goto) | ||||
|     } | ||||
|     total += read; | ||||
| @@ -403,7 +380,7 @@ void ESPHomeOTAComponent::handle_data_() { | ||||
|  | ||||
|   error_code = this->backend_->end(); | ||||
|   if (error_code != ota::OTA_RESPONSE_OK) { | ||||
|     ESP_LOGW(TAG, "Error ending update! code: %d", error_code); | ||||
|     ESP_LOGW(TAG, "End update err %d", error_code); | ||||
|     goto error;  // NOLINT(cppcoreguidelines-avoid-goto) | ||||
|   } | ||||
|  | ||||
| @@ -455,11 +432,11 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) { | ||||
|     ssize_t read = this->client_->read(buf + at, len - at); | ||||
|     if (read == -1) { | ||||
|       if (!this->would_block_(errno)) { | ||||
|         ESP_LOGW(TAG, "Error reading %d bytes, errno %d", len, errno); | ||||
|         ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno); | ||||
|         return false; | ||||
|       } | ||||
|     } else if (read == 0) { | ||||
|       ESP_LOGW(TAG, "Remote closed connection"); | ||||
|       ESP_LOGW(TAG, "Remote closed"); | ||||
|       return false; | ||||
|     } else { | ||||
|       at += read; | ||||
| @@ -482,7 +459,7 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) { | ||||
|     ssize_t written = this->client_->write(buf + at, len - at); | ||||
|     if (written == -1) { | ||||
|       if (!this->would_block_(errno)) { | ||||
|         ESP_LOGW(TAG, "Error writing %d bytes, errno %d", len, errno); | ||||
|         ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno); | ||||
|         return false; | ||||
|       } | ||||
|     } else { | ||||
| @@ -508,40 +485,40 @@ void ESPHomeOTAComponent::log_start_(const LogString *phase) { | ||||
| } | ||||
|  | ||||
| void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) { | ||||
|   ESP_LOGW(TAG, "Remote closed during %s", LOG_STR_ARG(during)); | ||||
|   ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during)); | ||||
| } | ||||
|  | ||||
| bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *error_desc, const LogString *close_desc) { | ||||
| bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) { | ||||
|   if (read == -1 && this->would_block_(errno)) { | ||||
|     return false;  // No data yet, try again next loop | ||||
|   } | ||||
|  | ||||
|   if (read <= 0) { | ||||
|     read == 0 ? this->log_remote_closed_(close_desc) : this->log_socket_error_(error_desc); | ||||
|     read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc); | ||||
|     this->cleanup_connection_(); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool ESPHomeOTAComponent::handle_write_error_(ssize_t written, const LogString *error_desc) { | ||||
| bool ESPHomeOTAComponent::handle_write_error_(ssize_t written, const LogString *desc) { | ||||
|   if (written == -1) { | ||||
|     if (this->would_block_(errno)) { | ||||
|       return false;  // Try again next loop | ||||
|     } | ||||
|     this->log_socket_error_(error_desc); | ||||
|     this->log_socket_error_(desc); | ||||
|     this->cleanup_connection_(); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *error_desc, const LogString *close_desc) { | ||||
| bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) { | ||||
|   // Read bytes into handshake buffer, starting at handshake_buf_pos_ | ||||
|   size_t bytes_to_read = to_read - this->handshake_buf_pos_; | ||||
|   ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read); | ||||
|  | ||||
|   if (!this->handle_read_error_(read, error_desc, close_desc)) { | ||||
|   if (!this->handle_read_error_(read, desc)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| @@ -550,12 +527,12 @@ bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *error_desc, | ||||
|   return this->handshake_buf_pos_ >= to_read; | ||||
| } | ||||
|  | ||||
| bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *error_desc) { | ||||
| bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) { | ||||
|   // Write bytes from handshake buffer, starting at handshake_buf_pos_ | ||||
|   size_t bytes_to_write = to_write - this->handshake_buf_pos_; | ||||
|   ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write); | ||||
|  | ||||
|   if (!this->handle_write_error_(written, error_desc)) { | ||||
|   if (!this->handle_write_error_(written, desc)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| @@ -564,11 +541,6 @@ bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *error_des | ||||
|   return this->handshake_buf_pos_ >= to_write; | ||||
| } | ||||
|  | ||||
| void ESPHomeOTAComponent::transition_ota_state_(OTAState next_state) { | ||||
|   this->ota_state_ = next_state; | ||||
|   this->handshake_buf_pos_ = 0;  // Reset buffer position for next state | ||||
| } | ||||
|  | ||||
| void ESPHomeOTAComponent::cleanup_connection_() { | ||||
|   this->client_->close(); | ||||
|   this->client_ = nullptr; | ||||
| @@ -582,12 +554,6 @@ void ESPHomeOTAComponent::cleanup_connection_() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void ESPHomeOTAComponent::send_error_and_cleanup_(ota::OTAResponseTypes error) { | ||||
|   uint8_t error_byte = static_cast<uint8_t>(error); | ||||
|   this->client_->write(&error_byte, 1);  // Best effort, non-blocking | ||||
|   this->cleanup_connection_(); | ||||
| } | ||||
|  | ||||
| void ESPHomeOTAComponent::yield_and_feed_watchdog_() { | ||||
|   App.feed_wdt(); | ||||
|   delay(1); | ||||
| @@ -675,7 +641,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() { | ||||
|   size_t remaining = to_write - this->auth_buf_pos_; | ||||
|  | ||||
|   ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining); | ||||
|   if (!this->handle_write_error_(written, LOG_STR("auth write"))) { | ||||
|   if (!this->handle_write_error_(written, LOG_STR("ack auth"))) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| @@ -701,8 +667,7 @@ bool ESPHomeOTAComponent::handle_auth_read_() { | ||||
|   size_t remaining = to_read - this->auth_buf_pos_; | ||||
|   ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining); | ||||
|  | ||||
|   auto *auth_read_desc = LOG_STR("auth read"); | ||||
|   if (!this->handle_read_error_(read, auth_read_desc, auth_read_desc)) { | ||||
|   if (!this->handle_read_error_(read, LOG_STR("read auth"))) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| @@ -760,7 +725,7 @@ bool ESPHomeOTAComponent::prepare_auth_nonce_(HashBase *hasher) { | ||||
|   char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1); | ||||
|   if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) { | ||||
|     this->log_auth_warning_(LOG_STR("Random failed")); | ||||
|     this->cleanup_connection_(); | ||||
|     this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -56,20 +56,27 @@ class ESPHomeOTAComponent : public ota::OTAComponent { | ||||
|   bool readall_(uint8_t *buf, size_t len); | ||||
|   bool writeall_(const uint8_t *buf, size_t len); | ||||
|  | ||||
|   bool try_read_(size_t to_read, const LogString *error_desc, const LogString *close_desc); | ||||
|   bool try_write_(size_t to_write, const LogString *error_desc); | ||||
|   bool try_read_(size_t to_read, const LogString *desc); | ||||
|   bool try_write_(size_t to_write, const LogString *desc); | ||||
|  | ||||
|   bool would_block_(int error_code) const { return error_code == EAGAIN || error_code == EWOULDBLOCK; } | ||||
|   bool handle_read_error_(ssize_t read, const LogString *error_desc, const LogString *close_desc); | ||||
|   bool handle_write_error_(ssize_t written, const LogString *error_desc); | ||||
|   void transition_ota_state_(OTAState next_state); | ||||
|   inline bool would_block_(int error_code) const { return error_code == EAGAIN || error_code == EWOULDBLOCK; } | ||||
|   bool handle_read_error_(ssize_t read, const LogString *desc); | ||||
|   bool handle_write_error_(ssize_t written, const LogString *desc); | ||||
|   inline void transition_ota_state_(OTAState next_state) { | ||||
|     this->ota_state_ = next_state; | ||||
|     this->handshake_buf_pos_ = 0;  // Reset buffer position for next state | ||||
|   } | ||||
|  | ||||
|   void log_socket_error_(const LogString *msg); | ||||
|   void log_read_error_(const LogString *what); | ||||
|   void log_start_(const LogString *phase); | ||||
|   void log_remote_closed_(const LogString *during); | ||||
|   void cleanup_connection_(); | ||||
|   void send_error_and_cleanup_(ota::OTAResponseTypes error); | ||||
|   inline void send_error_and_cleanup_(ota::OTAResponseTypes error) { | ||||
|     uint8_t error_byte = static_cast<uint8_t>(error); | ||||
|     this->client_->write(&error_byte, 1);  // Best effort, non-blocking | ||||
|     this->cleanup_connection_(); | ||||
|   } | ||||
|   void yield_and_feed_watchdog_(); | ||||
|  | ||||
| #ifdef USE_OTA_PASSWORD | ||||
|   | ||||
		Reference in New Issue
	
	Block a user