[api] Fix ESP8266 noise API handshake deadlock and prompt socket cleanup

Two fixes for ESP8266 with noise encryption:

1. Cache socket ready() before the handshake loop. On ESP8266 LWIP raw
   TCP, ready() returns the live state (false once rx buffer is consumed),
   unlike ESP32 where it is cached until the next main loop. Re-checking
   each iteration blocked handshake writes that must follow reads,
   deadlocking the handshake.

2. Process client removal immediately after loop() instead of deferring
   to the next server loop iteration. This closes the socket promptly
   to free LWIP PCB resources and prevent retransmit crashes on ESP8266.
This commit is contained in:
J. Nick Koston
2026-02-12 17:54:32 -06:00
parent 136d17366f
commit 375fc1db84
2 changed files with 12 additions and 6 deletions

View File

@@ -138,10 +138,12 @@ APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func
/// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() {
// During handshake phase, process as many actions as possible until we can't progress
// socket_->ready() stays true until next main loop, but state_action() will return
// WOULD_BLOCK when no more data is available to read
while (state_ != State::DATA && this->socket_->ready()) {
// Cache ready() outside the loop. On ESP8266 LWIP raw TCP, ready() returns false once
// the rx buffer is consumed. Re-checking each iteration would block handshake writes
// that must follow reads, deadlocking the handshake. state_action() will return
// WOULD_BLOCK when no more data is available to read.
bool socket_ready = this->socket_->ready();
while (state_ != State::DATA && socket_ready) {
APIError err = state_action_();
if (err == APIError::WOULD_BLOCK) {
break;

View File

@@ -148,12 +148,16 @@ void APIServer::loop() {
while (client_index < this->clients_.size()) {
auto &client = this->clients_[client_index];
// Common case: process active client
if (!client->flags_.remove) {
client->loop();
}
// Handle disconnection promptly - close socket to free LWIP PCB
// resources and prevent retransmit crashes on ESP8266.
if (client->flags_.remove) {
// Rare case: handle disconnection (don't increment - swapped element needs processing)
this->remove_client_(client_index);
} else {
// Common case: process active client
client->loop();
client_index++;
}
}