tcp_listen_with_backlog() converts the full tcp_pcb to a smaller
tcp_pcb_listen struct that lacks the errf field. Calling tcp_err()
on a listen PCB writes past the struct boundary, causing memory
corruption and crashes (confirmed on RP2040).
LWIP itself asserts pcb->state != LISTEN in tcp_err().
The destructor relies solely on tcp_abort() for cleanup, but LWIP
considers ownership of pbufs transferred once the recv callback
accepts them. If rx_buf_ is non-null when the socket is destroyed,
those pbufs are leaked. Free them explicitly before the base class
destructor calls tcp_abort().
Fix two pre-existing bugs found during review of #14398:
1. bind() returns 0 on nullptr name instead of -1, inconsistent with
POSIX bind() semantics and can mask invalid calls.
2. listen() doesn't re-register tcp_err after tcp_listen_with_backlog
reallocates the PCB. The error callback was set during init() on
the original PCB which is now freed, leaving the new listen PCB
without an error handler.
clang-tidy flags the forward declaration in application.h as redundant
since socket.h (included earlier via esphome.h) already declares it.
The forward declaration is needed here for the friend declaration.
- Rename internal_write/internal_output to internal_write_/internal_output_
(protected methods require trailing underscore)
- Fix getpeername/getsockname parameter name mismatch between .h and .cpp
The CI lint rule disallows ESP_LOG* references in header files.
Move LWIP_LOG macro definition and all methods that use it
(destructors, init(), static callbacks) to the .cpp file.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Replace the virtual Socket base class with compile-time type aliases.
Since only one socket implementation is active per build (selected via
#ifdef), virtual dispatch was unnecessary overhead.
Each implementation (BSDSocketImpl, LwIPSocketImpl, LWIPRawImpl) is now
a concrete class in its own header with no virtual methods. socket::Socket
and socket::ListenSocket are type aliases to the active implementation.
For LWIP_TCP (ESP8266/RP2040), the listen socket is a separate type
(LWIPRawListenImpl) sharing a non-virtual LWIPRawCommon base with the
connected socket type (LWIPRawImpl). On BSD/LWIP_SOCKETS, both aliases
resolve to the same type.
This eliminates all vtable overhead, virtual destructors, and indirect
call overhead across all platforms.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Wire Application::wake_loop_any_context() to socket::socket_wake()
on ESP8266. This allows any component calling wake_loop_any_context()
from an ISR or callback to immediately wake the main loop from
esp_delay() sleep, instead of waiting up to 16ms for the timeout.
Changes:
- Add IRAM_ATTR to socket_wake() so it's safe to call from ISR context
- Add ESP8266 wake_loop_any_context() backed by socket::socket_wake()
- Extend enable_loop_soon_any_context() to wake the loop on ESP8266
Each notify_*_update method had an identical loop that only differed
in the vtable offset for the virtual call. Extract the shared loop
into a single noinline notify() function and have each wrapper pass
a small trampoline lambda that dispatches to the correct Controller
virtual method.
Before: 8 copies of the loop at ~56 bytes each = ~448 bytes
After: 1 shared loop (56 B) + 8 wrappers (16 B) + 8 trampolines (8 B) = ~248 bytes
Saves ~200 bytes of flash.
LibreTiny lacks IRAM_ATTR support needed for ISR-safe paths,
and BK72xx doesn't support the mrs ipsr instruction in Thumb mode.
The existing fast select functionality on LibreTiny is unaffected.
API protobuf messages are never deleted through base class pointers —
they are always stack-allocated and passed by reference. The virtual
destructor occupies 2 vtable slots (complete + deleting destructor)
on 32-bit platforms using the Itanium ABI, costing 8 bytes per vtable.
With 122 message classes, removing the virtual destructor saves
~976 bytes of flash. Each vtable shrinks from 24 bytes to 16 bytes.
Wire Application::wake_loop_any_context() to socket::socket_wake()
on ESP8266. This allows any component calling wake_loop_any_context()
from an ISR or callback to immediately wake the main loop from
esp_delay() sleep, instead of waiting up to 16ms for the timeout.
Changes:
- Add IRAM_ATTR to socket_wake() so it's safe to call from ISR context
- Add ESP8266 wake_loop_any_context() backed by socket::socket_wake()
- Extend enable_loop_soon_any_context() to wake the loop on ESP8266
Wire Application::wake_loop_any_context() to socket::socket_wake()
on ESP8266. This allows any component calling wake_loop_any_context()
from an ISR or callback to immediately wake the main loop from
esp_delay() sleep, instead of waiting up to 16ms for the timeout.
Changes:
- Add IRAM_ATTR to socket_wake() so it's safe to call from ISR context
- Add ESP8266 wake_loop_any_context() backed by socket::socket_wake()
- Extend enable_loop_soon_any_context() to wake the loop on ESP8266
LibreTiny's FreeRTOS port does not provide portYIELD_FROM_ISR.
On ARM Cortex-M, FreeRTOS internally sets the PendSV bit when
vTaskNotifyGiveFromISR wakes a higher-priority task, so the
context switch happens automatically when the ISR returns.
Wire Application::wake_loop_any_context() to socket::socket_wake()
on ESP8266. This allows any component calling wake_loop_any_context()
from an ISR or callback to immediately wake the main loop from
esp_delay() sleep, instead of waiting up to 16ms for the timeout.
Changes:
- Add IRAM_ATTR to socket_wake() so it's safe to call from ISR context
- Add ESP8266 wake_loop_any_context() backed by socket::socket_wake()
- Extend enable_loop_soon_any_context() to wake the loop on ESP8266
Now that the ISR callback mechanism (from #14382) costs essentially
zero — just a function pointer registration, no task or queue — enable
wake-on-RX by default for all ESP32 UART instances.
This reduces RX buffer overflow risk by waking the main loop
immediately when data arrives instead of waiting for the ~16ms
select timeout.
Remove the opt-in request_wake_loop_on_rx() API since all ESP32
UART instances now get this automatically.
The fast select code paths and wake_loop_any_context() require
USE_SOCKET_SELECT_SUPPORT. Gate USE_LWIP_FAST_SELECT on not using
lwip_tcp implementation which does not provide select() support.
- Update ISR safety comment: point 3 now reflects that
wake_loop_any_context() is called (ISR-safe)
- Initialize ipsr = 0 as safety net before inline asm
- Update ISR safety comment: point 3 now reflects that
wake_loop_any_context() is called (ISR-safe)
- Initialize ipsr = 0 as safety net before inline asm
- Replace __get_IPSR() with inline asm to avoid CMSIS header dependency
on LibreTiny chip families that may not declare it (e.g. beken-72xx)
- Pass local variable to wake_from_isr and call portYIELD_FROM_ISR for
immediate context switch instead of passing NULL
- Update comment in component.cpp to mention both ESP32 and LibreTiny
platform-specific context detection
- Switch USE_LWIP_FAST_SELECT from cg.add_define to cg.add_build_flag
so it is visible in .c translation units, aligning guards across all
source files