From 3187ab920859a634e1a3adea1f21fbe8f2d871f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 08:43:51 -1000 Subject: [PATCH 1/3] [api] Avoid heap allocation in PSK update timeout lambda MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The set_timeout lambda captured a psk_t (std::array) by value, making the lambda 36 bytes — well beyond the std::function small buffer optimization threshold, forcing a heap allocation. Re-read the PSK from preferences inside the timeout callback instead. The PSK was just saved to flash moments before the timeout was scheduled, so re-reading it is reliable and avoids the large capture. Extract load_and_apply_noise_psk_() helper to deduplicate the load-from-preferences-and-apply pattern used in both setup() and the timeout callback. --- esphome/components/api/api_server.cpp | 21 ++++++++++++++++----- esphome/components/api/api_server.h | 2 ++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 17d69405adf..a845a693522 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -46,10 +46,8 @@ void APIServer::setup() { #ifndef USE_API_NOISE_PSK_FROM_YAML // Only load saved PSK if not set from YAML - SavedNoisePsk noise_pref_saved{}; - if (this->noise_pref_.load(&noise_pref_saved)) { + if (this->load_and_apply_noise_psk_()) { ESP_LOGD(TAG, "Loaded saved Noise PSK"); - this->set_noise_psk(noise_pref_saved.psk); } #endif #endif @@ -526,9 +524,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString } ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg)); if (make_active) { - this->set_timeout(100, [this, active_psk]() { + this->set_timeout(100, [this]() { + // Re-read the PSK from preferences rather than capturing the 32-byte array + // in the lambda (which would exceed std::function SBO and heap-allocate). + if (!this->load_and_apply_noise_psk_()) { + ESP_LOGW(TAG, "Failed to load saved PSK for activation"); + return; + } ESP_LOGW(TAG, "Disconnecting all clients to reset PSK"); - this->set_noise_psk(active_psk); for (auto &c : this->clients_) { DisconnectRequest req; c->send_message(req); @@ -538,6 +541,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString return true; } +bool APIServer::load_and_apply_noise_psk_() { + SavedNoisePsk saved{}; + if (!this->noise_pref_.load(&saved)) + return false; + this->set_noise_psk(saved.psk); + return true; +} + bool APIServer::save_noise_psk(psk_t psk, bool make_active) { #ifdef USE_API_NOISE_PSK_FROM_YAML // When PSK is set from YAML, this function should never be called diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index ccba6deb00a..0f236e4f716 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -240,6 +240,8 @@ class APIServer final : public Component, #ifdef USE_API_NOISE bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, const psk_t &active_psk, bool make_active); + /// Load saved PSK from preferences and apply it. Returns true on success. + bool load_and_apply_noise_psk_(); #endif // USE_API_NOISE #ifdef USE_API_HOMEASSISTANT_STATES // Helper methods to reduce code duplication From 97f2fa7e9d14e4ce2aa683152dd572f1743648bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 09:51:25 -1000 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- esphome/components/api/api_server.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 0f236e4f716..3f66ca0c524 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -240,7 +240,7 @@ class APIServer final : public Component, #ifdef USE_API_NOISE bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, const psk_t &active_psk, bool make_active); - /// Load saved PSK from preferences and apply it. Returns true on success. + // Load saved PSK from preferences and apply it. Returns true on success. bool load_and_apply_noise_psk_(); #endif // USE_API_NOISE #ifdef USE_API_HOMEASSISTANT_STATES From d06c81fb0fa9918d8d746a9be3cdea394e58276e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 10:42:35 -1000 Subject: [PATCH 3/3] Remove unused active_psk parameter from update_noise_psk_ Now that the timeout lambda re-reads from preferences instead of capturing the PSK, the active_psk parameter is no longer used. --- esphome/components/api/api_server.cpp | 7 +++---- esphome/components/api/api_server.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index a845a693522..1151bc5983d 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -512,7 +512,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo #ifdef USE_API_NOISE bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, - const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) { + const LogString *fail_log_msg, bool make_active) { if (!this->noise_pref_.save(&new_psk)) { ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg)); return false; @@ -563,7 +563,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { } SavedNoisePsk new_saved_psk{psk}; - return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk, + return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), make_active); #endif } @@ -575,8 +575,7 @@ bool APIServer::clear_noise_psk(bool make_active) { return false; #else SavedNoisePsk empty_psk{}; - psk_t empty{}; - return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty, + return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), make_active); #endif } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 3f66ca0c524..65076879a24 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -239,7 +239,7 @@ class APIServer final : public Component, #ifdef USE_API_NOISE bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, - const psk_t &active_psk, bool make_active); + bool make_active); // Load saved PSK from preferences and apply it. Returns true on success. bool load_and_apply_noise_psk_(); #endif // USE_API_NOISE