diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5ee64c44f1..9714f8bd60 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1706,7 +1706,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes // Safe: decode is complete, byte after string data was already consumed during parse, // and frame helpers reserve RX_BUF_NULL_TERMINATOR extra byte in rx_buf_. if (!msg.state.empty()) { - const_cast(msg.state.c_str())[msg.state.size()] = '\0'; + msg.state.null_terminate_in_place(); } for (auto &it : this->parent_->get_state_subs()) { @@ -1714,8 +1714,8 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes continue; } - // Compare attribute: either both have matching attribute, or both have none - // it.attribute can be nullptr (meaning no attribute filter) + // If subscriber has attribute filter (non-null), message attribute must match it; + // if subscriber has no filter (nullptr), message must have no attribute. if (it.attribute != nullptr ? msg.attribute != it.attribute : !msg.attribute.empty()) { continue; } @@ -1731,7 +1731,7 @@ void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) // and frame helpers reserve RX_BUF_NULL_TERMINATOR extra byte for the last field. for (auto &arg : msg.args) { if (!arg.string_.empty()) { - const_cast(arg.string_.c_str())[arg.string_.size()] = '\0'; + arg.string_.null_terminate_in_place(); } } bool found = false; diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 5a16b8018a..5cd3cbc307 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -411,8 +411,9 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { NoiseBuffer mbuf; noise_buffer_init(mbuf); - // rx_buf_ has RX_BUF_NULL_TERMINATOR extra byte for null termination, - // but only the actual message bytes contain encrypted data + // rx_buf_ has RX_BUF_NULL_TERMINATOR extra byte for null termination + // (only added in DATA state — see try_read_frame_), so subtract it + // to get the actual encrypted data size for decryption. size_t encrypted_size = this->rx_buf_.size() - RX_BUF_NULL_TERMINATOR; noise_buffer_set_inout(mbuf, this->rx_buf_.data(), encrypted_size, encrypted_size); int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf); diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index d502c4d27f..60e5fc76b1 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -81,6 +81,11 @@ class StringRef { operator std::string() const { return str(); } + /// Write a null terminator at base_[len_] in-place. + /// Caller must guarantee that the byte at base_[len_] is writable memory + /// (e.g., the RX_BUF_NULL_TERMINATOR byte reserved by frame helpers after decode). + void null_terminate_in_place() const { const_cast(base_)[len_] = '\0'; } + /// Find first occurrence of substring, returns std::string::npos if not found. /// Note: Requires the underlying string to be null-terminated. size_type find(const char *s, size_type pos = 0) const {