diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a4c49dccf4..55f3bdc1f3 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -319,6 +319,7 @@ class APIConnection final : public APIServerConnectionBase { bool is_authenticated() { return static_cast(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; } + bool is_handshake_complete() const { return this->helper_->is_handshake_complete(); } bool is_connection_setup() { return static_cast(this->flags_.connection_state) == ConnectionState::CONNECTED || this->is_authenticated(); diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index ff8aa7834c..3319fad391 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -118,6 +118,9 @@ class APIFrameHelper { virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; bool can_write_without_blocking() { return this->state_ == State::DATA && this->overflow_buf_.empty(); } + /// True once the transport handshake is done and the connection carries + /// api messages (immediately for plaintext, after the noise handshake). + bool is_handshake_complete() const { return this->state_ == State::DATA; } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { if (state_ == State::CLOSED) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1d9eb297a9..7fab3697aa 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -41,6 +41,12 @@ void APIServer::setup() { ControllerRegistry::register_controller(this); #ifdef USE_API_NOISE +#ifdef USE_ESP8266 + // Refilling the spare ephemeral key blocks the loop for about 60 ms on + // this core; the same generation used to run inside every handshake. + // Cover it so the first refill does not log a blocking warning at boot. + this->warn_if_blocking_over_ = 8; // centiseconds +#endif // Always reserve the slot: flash preferences are positional on esp8266, so // a yaml key build must keep the layout of a runtime key build uint32_t hash = 88491486UL; @@ -193,19 +199,15 @@ void APIServer::loop() { #ifdef USE_API_NOISE // Refill the spare ephemeral key while nobody is waiting for it: not before -// the network is up, and not while an api client is still in its handshake -// (an OTA handshake is not visible here; it took the previous spare and -// pays the refill instead, no worse than generating its own key). The -// generation blocks the loop for about 60 ms on ESP8266, over the 50 ms -// blocking warning, so the api component's warning threshold ratchets up -// on the first refill; the same generation used to run inside the -// handshake, where it tripped the warning for longer. +// the network is up, and not while an api client is still in its noise +// handshake (an OTA handshake is not visible here; it took the previous +// spare and pays the refill instead, no worse than generating its own key). void APIServer::prepare_spare_ephemeral_() { if (noise::has_spare_ephemeral() || !network::is_connected()) { return; } for (auto &client : this->active_clients()) { - if (!client->is_connection_setup()) { + if (!client->is_handshake_complete()) { return; } } diff --git a/esphome/components/noise/noise.cpp b/esphome/components/noise/noise.cpp index f4743bbe03..2fc0f2787c 100644 --- a/esphome/components/noise/noise.cpp +++ b/esphome/components/noise/noise.cpp @@ -33,6 +33,8 @@ static bool spare_ephemeral_ready = false; // NOLINT(cppcoreguidel bool has_spare_ephemeral() { return spare_ephemeral_ready; } void prepare_spare_ephemeral() { + // A partial fill must never look ready + spare_ephemeral_ready = false; uint8_t *private_key = spare_ephemeral; uint8_t *public_key = spare_ephemeral + EPHEMERAL_PRIVATE_KEY_SIZE; // Same generation as noise-c's curve25519 backend: random bytes, X25519