diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 0352d7347b..df98a7246e 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -54,7 +54,8 @@ void APIServer::setup() { #endif #endif - this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections + this->socket_ = + socket::socket_ip_listen_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections if (this->socket_ == nullptr) { this->socket_failed_(LOG_STR("creation")); return; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 3abf68358c..6eff2005f8 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -257,7 +257,7 @@ class APIServer : public Component, } void socket_failed_(const LogString *msg); // Pointers and pointer-like types first (4 bytes each) - socket::Socket *socket_{nullptr}; + socket::ListenSocket *socket_{nullptr}; #ifdef USE_API_CLIENT_CONNECTED_TRIGGER Trigger client_connected_trigger_; #endif diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index bd9989a40c..4eb5b76cbf 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -53,7 +53,7 @@ void DNSServer::start(const network::IPAddress &ip) { #endif // Create loop-monitored UDP socket - this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP).release(); + this->socket_ = socket::socket_ip_listen_loop_monitored(SOCK_DGRAM, IPPROTO_UDP).release(); if (this->socket_ == nullptr) { ESP_LOGE(TAG, "Socket create failed"); return; diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.h b/esphome/components/captive_portal/dns_server_esp32_idf.h index f8e4cfec84..b30856c204 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.h +++ b/esphome/components/captive_portal/dns_server_esp32_idf.h @@ -22,7 +22,7 @@ class DNSServer { } static constexpr size_t DNS_BUFFER_SIZE = 192; - socket::Socket *socket_{nullptr}; + socket::ListenSocket *socket_{nullptr}; network::IPAddress server_ip_; uint8_t buffer_[DNS_BUFFER_SIZE]; }; diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index a1cdf59d2b..e97cdddf22 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -28,7 +28,8 @@ static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer void ESPHomeOTAComponent::setup() { - this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections + this->server_ = + socket::socket_ip_listen_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections if (this->server_ == nullptr) { this->server_failed_(LOG_STR("creation")); return; diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index 53715cfe6a..08edacad92 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -84,7 +84,7 @@ class ESPHomeOTAComponent final : public ota::OTAComponent { std::unique_ptr auth_buf_; #endif // USE_OTA_PASSWORD - socket::Socket *server_{nullptr}; + socket::ListenSocket *server_{nullptr}; std::unique_ptr client_; std::unique_ptr backend_; diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index c96713f376..efbb64990c 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -7,129 +7,75 @@ #include #include "esphome/core/application.h" -#ifdef USE_ESP32 -#include -#include -#endif - namespace esphome::socket { -class BSDSocketImpl final : public Socket { - public: - BSDSocketImpl(int fd, bool monitor_loop = false) { - this->fd_ = fd; - // Register new socket with the application for select() if monitoring requested - if (monitor_loop && this->fd_ >= 0) { - // Only set loop_monitored_ to true if registration succeeds - this->loop_monitored_ = App.register_socket_fd(this->fd_); - } - } - ~BSDSocketImpl() override { - if (!this->closed_) { - this->close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) - } - } - int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(this->fd_, addr, addrlen); } - std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - int fd = ::accept(this->fd_, addr, addrlen); - if (fd == -1) - return {}; - return make_unique(fd, false); - } - std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { - int fd = ::accept(this->fd_, addr, addrlen); - if (fd == -1) - return {}; - return make_unique(fd, true); +BSDSocketImpl::BSDSocketImpl(int fd, bool monitor_loop) { + this->fd_ = fd; + // Register new socket with the application for select() if monitoring requested + if (monitor_loop && this->fd_ >= 0) { + // Only set loop_monitored_ to true if registration succeeds + this->loop_monitored_ = App.register_socket_fd(this->fd_); } +} - int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(this->fd_, addr, addrlen); } - int close() override { - if (!this->closed_) { - // Unregister from select() before closing if monitored - if (this->loop_monitored_) { - App.unregister_socket_fd(this->fd_); - } - int ret = ::close(this->fd_); - this->closed_ = true; - return ret; +BSDSocketImpl::~BSDSocketImpl() { + if (!this->closed_) { + this->close(); + } +} + +int BSDSocketImpl::close() { + if (!this->closed_) { + // Unregister from select() before closing if monitored + if (this->loop_monitored_) { + App.unregister_socket_fd(this->fd_); } + int ret = ::close(this->fd_); + this->closed_ = true; + return ret; + } + return 0; +} + +int BSDSocketImpl::setblocking(bool blocking) { + int fl = ::fcntl(this->fd_, F_GETFL, 0); + if (blocking) { + fl &= ~O_NONBLOCK; + } else { + fl |= O_NONBLOCK; + } + ::fcntl(this->fd_, F_SETFL, fl); + return 0; +} + +bool BSDSocketImpl::ready() const { return socket_ready_fd(this->fd_, this->loop_monitored_); } + +size_t BSDSocketImpl::getpeername_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; return 0; } - int shutdown(int how) override { return ::shutdown(this->fd_, how); } + return format_sockaddr_to(reinterpret_cast(&storage), len, buf); +} - int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { - return ::getpeername(this->fd_, addr, addrlen); - } - int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { - return ::getsockname(this->fd_, addr, addrlen); - } - int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { - return ::getsockopt(this->fd_, level, optname, optval, optlen); - } - int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { - return ::setsockopt(this->fd_, level, optname, optval, optlen); - } - int listen(int backlog) override { return ::listen(this->fd_, backlog); } - ssize_t read(void *buf, size_t len) override { -#ifdef USE_ESP32 - return ::lwip_read(this->fd_, buf, len); -#else - return ::read(this->fd_, buf, len); -#endif - } - ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { -#if defined(USE_ESP32) || defined(USE_HOST) - return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len); -#else - return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); -#endif - } - ssize_t readv(const struct iovec *iov, int iovcnt) override { -#if defined(USE_ESP32) - return ::lwip_readv(this->fd_, iov, iovcnt); -#else - return ::readv(this->fd_, iov, iovcnt); -#endif - } - ssize_t write(const void *buf, size_t len) override { -#ifdef USE_ESP32 - return ::lwip_write(this->fd_, buf, len); -#else - return ::write(this->fd_, buf, len); -#endif - } - ssize_t send(void *buf, size_t len, int flags) { return ::send(this->fd_, buf, len, flags); } - ssize_t writev(const struct iovec *iov, int iovcnt) override { -#if defined(USE_ESP32) - return ::lwip_writev(this->fd_, iov, iovcnt); -#else - return ::writev(this->fd_, iov, iovcnt); -#endif - } - - ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { - return ::sendto(this->fd_, buf, len, flags, to, tolen); // NOLINT(readability-suspicious-call-argument) - } - - int setblocking(bool blocking) override { - int fl = ::fcntl(this->fd_, F_GETFL, 0); - if (blocking) { - fl &= ~O_NONBLOCK; - } else { - fl |= O_NONBLOCK; - } - ::fcntl(this->fd_, F_SETFL, fl); +size_t BSDSocketImpl::getsockname_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; return 0; } -}; + return format_sockaddr_to(reinterpret_cast(&storage), len, buf); +} // Helper to create a socket with optional monitoring -static std::unique_ptr create_socket(int domain, int type, int protocol, bool loop_monitored = false) { +static std::unique_ptr create_socket(int domain, int type, int protocol, bool loop_monitored = false) { int ret = ::socket(domain, type, protocol); if (ret == -1) return nullptr; - return std::unique_ptr{new BSDSocketImpl(ret, loop_monitored)}; + return std::unique_ptr{new BSDSocketImpl(ret, loop_monitored)}; } std::unique_ptr socket(int domain, int type, int protocol) { @@ -140,6 +86,14 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return create_socket(domain, type, protocol, true); } +std::unique_ptr socket_listen(int domain, int type, int protocol) { + return create_socket(domain, type, protocol, false); +} + +std::unique_ptr socket_listen_loop_monitored(int domain, int type, int protocol) { + return create_socket(domain, type, protocol, true); +} + } // namespace esphome::socket #endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/bsd_sockets_impl.h b/esphome/components/socket/bsd_sockets_impl.h new file mode 100644 index 0000000000..edee39f315 --- /dev/null +++ b/esphome/components/socket/bsd_sockets_impl.h @@ -0,0 +1,114 @@ +#pragma once +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + +#include +#include + +#include "esphome/core/helpers.h" +#include "headers.h" + +#ifdef USE_ESP32 +#include +#endif + +namespace esphome::socket { + +class BSDSocketImpl { + public: + BSDSocketImpl(int fd, bool monitor_loop = false); + ~BSDSocketImpl(); + BSDSocketImpl(const BSDSocketImpl &) = delete; + BSDSocketImpl &operator=(const BSDSocketImpl &) = delete; + + int connect(const struct sockaddr *addr, socklen_t addrlen) { return ::connect(this->fd_, addr, addrlen); } + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) { + int fd = ::accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, false); + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) { + int fd = ::accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, true); + } + + int bind(const struct sockaddr *addr, socklen_t addrlen) { return ::bind(this->fd_, addr, addrlen); } + int close(); + int shutdown(int how) { return ::shutdown(this->fd_, how); } + + int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return ::getpeername(this->fd_, addr, addrlen); } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) { return ::getsockname(this->fd_, addr, addrlen); } + + /// Format peer address into a fixed-size buffer (no heap allocation) + size_t getpeername_to(std::span buf); + /// Format local address into a fixed-size buffer (no heap allocation) + size_t getsockname_to(std::span buf); + + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) { + return ::getsockopt(this->fd_, level, optname, optval, optlen); + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) { + return ::setsockopt(this->fd_, level, optname, optval, optlen); + } + int listen(int backlog) { return ::listen(this->fd_, backlog); } + ssize_t read(void *buf, size_t len) { +#ifdef USE_ESP32 + return ::lwip_read(this->fd_, buf, len); +#else + return ::read(this->fd_, buf, len); +#endif + } + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) { +#if defined(USE_ESP32) || defined(USE_HOST) + return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#else + return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#endif + } + ssize_t readv(const struct iovec *iov, int iovcnt) { +#if defined(USE_ESP32) + return ::lwip_readv(this->fd_, iov, iovcnt); +#else + return ::readv(this->fd_, iov, iovcnt); +#endif + } + ssize_t write(const void *buf, size_t len) { +#ifdef USE_ESP32 + return ::lwip_write(this->fd_, buf, len); +#else + return ::write(this->fd_, buf, len); +#endif + } + ssize_t send(void *buf, size_t len, int flags) { return ::send(this->fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) { +#if defined(USE_ESP32) + return ::lwip_writev(this->fd_, iov, iovcnt); +#else + return ::writev(this->fd_, iov, iovcnt); +#endif + } + + ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) { + return ::sendto(this->fd_, buf, len, flags, to, tolen); // NOLINT(readability-suspicious-call-argument) + } + + int setblocking(bool blocking); + int loop() { return 0; } + + bool ready() const; + + int get_fd() const { return this->fd_; } + + protected: + int fd_{-1}; + bool closed_{false}; + bool loop_monitored_{false}; +}; + +} // namespace esphome::socket + +#endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index 032892072d..16e4d23d3b 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -183,3 +183,20 @@ using socklen_t = uint32_t; #endif #endif // USE_SOCKET_IMPL_BSD_SOCKETS + +#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) + +namespace esphome::socket { + +// Maximum length for formatted socket address string (IP address without port) +// IPv4: "255.255.255.255" = 15 chars + null = 16 +// IPv6: full address = 45 chars + null = 46 +#if USE_NETWORK_IPV6 +static constexpr size_t SOCKADDR_STR_LEN = 46; // INET6_ADDRSTRLEN +#else +static constexpr size_t SOCKADDR_STR_LEN = 16; // INET_ADDRSTRLEN +#endif + +} // namespace esphome::socket + +#endif diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 6e95f5bc7a..109ed60b45 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -3,13 +3,8 @@ #ifdef USE_SOCKET_IMPL_LWIP_TCP -#include "lwip/ip.h" -#include "lwip/netif.h" -#include "lwip/opt.h" -#include "lwip/tcp.h" #include #include -#include #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -41,7 +36,7 @@ void socket_delay(uint32_t ms) { esp_delay(ms, []() { return !s_socket_woke; }); } -void socket_wake() { +void IRAM_ATTR socket_wake() { s_socket_woke = true; esp_schedule(); } @@ -49,635 +44,537 @@ void socket_wake() { static const char *const TAG = "socket.lwip"; -// set to 1 to enable verbose lwip logging -#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) -#define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__) -#else -#define LWIP_LOG(msg, ...) -#endif +// ---- LWIPRawCommon methods ---- -class LWIPRawImpl : public Socket { - public: - LWIPRawImpl(sa_family_t family, struct tcp_pcb *pcb) : pcb_(pcb), family_(family) {} - ~LWIPRawImpl() override { - if (pcb_ != nullptr) { - LWIP_LOG("tcp_abort(%p)", pcb_); - tcp_abort(pcb_); - pcb_ = nullptr; - } +int LWIPRawCommon::bind(const struct sockaddr *name, socklen_t addrlen) { + if (this->pcb_ == nullptr) { + errno = EBADF; + return -1; } - - void init() { - LWIP_LOG("init(%p)", pcb_); - tcp_arg(pcb_, this); - tcp_recv(pcb_, LWIPRawImpl::s_recv_fn); - tcp_err(pcb_, LWIPRawImpl::s_err_fn); - } - - std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - // Non-listening sockets return error + if (name == nullptr) { errno = EINVAL; - return nullptr; + return 0; } - int bind(const struct sockaddr *name, socklen_t addrlen) final { - if (pcb_ == nullptr) { - errno = EBADF; - return -1; - } - if (name == nullptr) { - errno = EINVAL; - return 0; - } - ip_addr_t ip; - in_port_t port; + ip_addr_t ip; + in_port_t port; #if LWIP_IPV6 - if (family_ == AF_INET) { - if (addrlen < sizeof(sockaddr_in)) { - errno = EINVAL; - return -1; - } - auto *addr4 = reinterpret_cast(name); - port = ntohs(addr4->sin_port); - ip.type = IPADDR_TYPE_V4; - ip.u_addr.ip4.addr = addr4->sin_addr.s_addr; - LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip4addr_ntoa(&ip.u_addr.ip4), port); - } else if (family_ == AF_INET6) { - if (addrlen < sizeof(sockaddr_in6)) { - errno = EINVAL; - return -1; - } - auto *addr6 = reinterpret_cast(name); - port = ntohs(addr6->sin6_port); - ip.type = IPADDR_TYPE_ANY; - memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16); - LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip6addr_ntoa(&ip.u_addr.ip6), port); - } else { - errno = EINVAL; - return -1; - } -#else - if (family_ != AF_INET) { + if (this->family_ == AF_INET) { + if (addrlen < sizeof(sockaddr_in)) { errno = EINVAL; return -1; } auto *addr4 = reinterpret_cast(name); port = ntohs(addr4->sin_port); - ip.addr = addr4->sin_addr.s_addr; - LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); + ip.type = IPADDR_TYPE_V4; + ip.u_addr.ip4.addr = addr4->sin_addr.s_addr; + LWIP_LOG("tcp_bind(%p ip=%s port=%u)", this->pcb_, ip4addr_ntoa(&ip.u_addr.ip4), port); + } else if (this->family_ == AF_INET6) { + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return -1; + } + auto *addr6 = reinterpret_cast(name); + port = ntohs(addr6->sin6_port); + ip.type = IPADDR_TYPE_ANY; + memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16); + LWIP_LOG("tcp_bind(%p ip=%s port=%u)", this->pcb_, ip6addr_ntoa(&ip.u_addr.ip6), port); + } else { + errno = EINVAL; + return -1; + } +#else + if (this->family_ != AF_INET) { + errno = EINVAL; + return -1; + } + auto *addr4 = reinterpret_cast(name); + port = ntohs(addr4->sin_port); + ip.addr = addr4->sin_addr.s_addr; + LWIP_LOG("tcp_bind(%p ip=%u port=%u)", this->pcb_, ip.addr, port); #endif - err_t err = tcp_bind(pcb_, &ip, port); - if (err == ERR_USE) { - LWIP_LOG(" -> err ERR_USE"); - errno = EADDRINUSE; - return -1; - } - if (err == ERR_VAL) { - LWIP_LOG(" -> err ERR_VAL"); + err_t err = tcp_bind(this->pcb_, &ip, port); + if (err == ERR_USE) { + LWIP_LOG(" -> err ERR_USE"); + errno = EADDRINUSE; + return -1; + } + if (err == ERR_VAL) { + LWIP_LOG(" -> err ERR_VAL"); + errno = EINVAL; + return -1; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = EIO; + return -1; + } + return 0; +} + +int LWIPRawCommon::close() { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + LWIP_LOG("tcp_close(%p)", this->pcb_); + err_t err = tcp_close(this->pcb_); + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + tcp_abort(this->pcb_); + this->pcb_ = nullptr; + errno = err == ERR_MEM ? ENOMEM : EIO; + return -1; + } + this->pcb_ = nullptr; + return 0; +} + +int LWIPRawCommon::shutdown(int how) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + bool shut_rx = false, shut_tx = false; + if (how == SHUT_RD) { + shut_rx = true; + } else if (how == SHUT_WR) { + shut_tx = true; + } else if (how == SHUT_RDWR) { + shut_rx = shut_tx = true; + } else { + errno = EINVAL; + return -1; + } + LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", this->pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); + err_t err = tcp_shutdown(this->pcb_, shut_rx, shut_tx); + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = err == ERR_MEM ? ENOMEM : EIO; + return -1; + } + return 0; +} + +int LWIPRawCommon::getpeername(struct sockaddr *name, socklen_t *addrlen) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (name == nullptr || addrlen == nullptr) { + errno = EINVAL; + return -1; + } + return this->ip2sockaddr_(&this->pcb_->remote_ip, this->pcb_->remote_port, name, addrlen); +} + +int LWIPRawCommon::getsockname(struct sockaddr *name, socklen_t *addrlen) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (name == nullptr || addrlen == nullptr) { + errno = EINVAL; + return -1; + } + return this->ip2sockaddr_(&this->pcb_->local_ip, this->pcb_->local_port, name, addrlen); +} + +size_t LWIPRawCommon::getpeername_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(reinterpret_cast(&storage), len, buf); +} + +size_t LWIPRawCommon::getsockname_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(reinterpret_cast(&storage), len, buf); +} + +int LWIPRawCommon::getsockopt(int level, int optname, void *optval, socklen_t *optlen) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (optlen == nullptr || optval == nullptr) { + errno = EINVAL; + return -1; + } + if (level == SOL_SOCKET && optname == SO_REUSEADDR) { + if (*optlen < 4) { errno = EINVAL; return -1; } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = EIO; - return -1; - } + // lwip doesn't seem to have this feature. Don't send an error + // to prevent warnings + *reinterpret_cast(optval) = 1; + *optlen = 4; return 0; } - int close() final { - if (pcb_ == nullptr) { - errno = ECONNRESET; + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + if (*optlen < 4) { + errno = EINVAL; return -1; } - LWIP_LOG("tcp_close(%p)", pcb_); - err_t err = tcp_close(pcb_); - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - tcp_abort(pcb_); - pcb_ = nullptr; - errno = err == ERR_MEM ? ENOMEM : EIO; - return -1; - } - pcb_ = nullptr; + *reinterpret_cast(optval) = this->nodelay_; + *optlen = 4; return 0; } - int shutdown(int how) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; + + errno = EINVAL; + return -1; +} + +int LWIPRawCommon::setsockopt(int level, int optname, const void *optval, socklen_t optlen) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (level == SOL_SOCKET && optname == SO_REUSEADDR) { + if (optlen != 4) { + errno = EINVAL; return -1; } - bool shut_rx = false, shut_tx = false; - if (how == SHUT_RD) { - shut_rx = true; - } else if (how == SHUT_WR) { - shut_tx = true; - } else if (how == SHUT_RDWR) { - shut_rx = shut_tx = true; + // lwip doesn't seem to have this feature. Don't send an error + // to prevent warnings + return 0; + } + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + if (optlen != 4) { + errno = EINVAL; + return -1; + } + int val = *reinterpret_cast(optval); + this->nodelay_ = val; + return 0; + } + + errno = EINVAL; + return -1; +} + +int LWIPRawCommon::ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { + if (this->family_ == AF_INET) { + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = port; + inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip)); + return 0; + } +#if LWIP_IPV6 + else if (this->family_ == AF_INET6) { + if (*addrlen < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + struct sockaddr_in6 *addr = reinterpret_cast(name); + addr->sin6_family = AF_INET6; + *addrlen = addr->sin6_len = sizeof(struct sockaddr_in6); + addr->sin6_port = port; + + // AF_INET6 sockets are bound to IPv4 as well, so we may encounter IPv4 addresses that must be converted to IPv6. + if (IP_IS_V4(ip)) { + ip_addr_t mapped; + ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip)); + inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped)); } else { - errno = EINVAL; - return -1; - } - LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); - err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = err == ERR_MEM ? ENOMEM : EIO; - return -1; + inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip)); } return 0; } +#endif + return -1; +} - int getpeername(struct sockaddr *name, socklen_t *addrlen) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (name == nullptr || addrlen == nullptr) { - errno = EINVAL; - return -1; - } - return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen); +// ---- LWIPRawImpl methods ---- + +LWIPRawImpl::~LWIPRawImpl() { + // Base class destructor handles pcb_ cleanup via tcp_abort +} + +err_t LWIPRawImpl::recv_fn(struct pbuf *pb, err_t err) { + LWIP_LOG("recv(pb=%p err=%d)", pb, err); + if (err != 0) { + this->rx_closed_ = true; + return ERR_OK; } - int getsockname(struct sockaddr *name, socklen_t *addrlen) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (name == nullptr || addrlen == nullptr) { - errno = EINVAL; - return -1; - } - return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); + if (pb == nullptr) { + this->rx_closed_ = true; + return ERR_OK; } - int getsockopt(int level, int optname, void *optval, socklen_t *optlen) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (optlen == nullptr || optval == nullptr) { - errno = EINVAL; - return -1; - } - if (level == SOL_SOCKET && optname == SO_REUSEADDR) { - if (*optlen < 4) { - errno = EINVAL; - return -1; - } + if (this->rx_buf_ == nullptr) { + // no need to copy because lwIP gave control of it to us + this->rx_buf_ = pb; + this->rx_buf_offset_ = 0; + } else { + pbuf_cat(this->rx_buf_, pb); + } +#ifdef USE_ESP8266 + // Wake the main loop immediately so it can process the received data. + socket_wake(); +#endif + return ERR_OK; +} - // lwip doesn't seem to have this feature. Don't send an error - // to prevent warnings - *reinterpret_cast(optval) = 1; - *optlen = 4; - return 0; - } - if (level == IPPROTO_TCP && optname == TCP_NODELAY) { - if (*optlen < 4) { - errno = EINVAL; - return -1; - } - *reinterpret_cast(optval) = nodelay_; - *optlen = 4; - return 0; - } - - errno = EINVAL; +ssize_t LWIPRawImpl::read(void *buf, size_t len) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; return -1; } - int setsockopt(int level, int optname, const void *optval, socklen_t optlen) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (level == SOL_SOCKET && optname == SO_REUSEADDR) { - if (optlen != 4) { - errno = EINVAL; - return -1; - } - - // lwip doesn't seem to have this feature. Don't send an error - // to prevent warnings - return 0; - } - if (level == IPPROTO_TCP && optname == TCP_NODELAY) { - if (optlen != 4) { - errno = EINVAL; - return -1; - } - int val = *reinterpret_cast(optval); - nodelay_ = val; - return 0; - } - - errno = EINVAL; + if (this->rx_closed_ && this->rx_buf_ == nullptr) { + return 0; + } + if (len == 0) { + return 0; + } + if (this->rx_buf_ == nullptr) { + errno = EWOULDBLOCK; return -1; } - int listen(int backlog) override { - // Regular sockets can't be converted to listening - this shouldn't happen - // as listen() should only be called on sockets created for listening + + size_t read = 0; + uint8_t *buf8 = reinterpret_cast(buf); + while (len && this->rx_buf_ != nullptr) { + size_t pb_len = this->rx_buf_->len; + size_t pb_left = pb_len - this->rx_buf_offset_; + if (pb_left == 0) + break; + size_t copysize = std::min(len, pb_left); + memcpy(buf8, reinterpret_cast(this->rx_buf_->payload) + this->rx_buf_offset_, copysize); + + if (pb_left == copysize) { + // full pb copied, free it + if (this->rx_buf_->next == nullptr) { + // last buffer in chain + pbuf_free(this->rx_buf_); + this->rx_buf_ = nullptr; + this->rx_buf_offset_ = 0; + } else { + auto *old_buf = this->rx_buf_; + this->rx_buf_ = this->rx_buf_->next; + pbuf_ref(this->rx_buf_); + pbuf_free(old_buf); + this->rx_buf_offset_ = 0; + } + } else { + this->rx_buf_offset_ += copysize; + } + LWIP_LOG("tcp_recved(%p %u)", this->pcb_, copysize); + tcp_recved(this->pcb_, copysize); + + buf8 += copysize; + len -= copysize; + read += copysize; + } + + if (read == 0) { + errno = EWOULDBLOCK; + return -1; + } + + return read; +} + +ssize_t LWIPRawImpl::readv(const struct iovec *iov, int iovcnt) { + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) { + // if we already read some don't return an error + break; + } + return err; + } + ret += err; + if ((size_t) err != iov[i].iov_len) + break; + } + return ret; +} + +ssize_t LWIPRawImpl::internal_write(const void *buf, size_t len) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (len == 0) + return 0; + if (buf == nullptr) { + errno = EINVAL; + return 0; + } + auto space = tcp_sndbuf(this->pcb_); + if (space == 0) { + errno = EWOULDBLOCK; + return -1; + } + size_t to_send = std::min((size_t) space, len); + LWIP_LOG("tcp_write(%p buf=%p %u)", this->pcb_, buf, to_send); + err_t err = tcp_write(this->pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); + if (err == ERR_MEM) { + LWIP_LOG(" -> err ERR_MEM"); + errno = EWOULDBLOCK; + return -1; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } + return to_send; +} + +int LWIPRawImpl::internal_output() { + LWIP_LOG("tcp_output(%p)", this->pcb_); + err_t err = tcp_output(this->pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + return 0; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } + return 0; +} + +ssize_t LWIPRawImpl::write(const void *buf, size_t len) { + ssize_t written = this->internal_write(buf, len); + if (written == -1) + return -1; + if (written == 0) { + // no need to output if nothing written + return 0; + } + if (this->nodelay_) { + int err = this->internal_output(); + if (err == -1) + return -1; + } + return written; +} + +ssize_t LWIPRawImpl::writev(const struct iovec *iov, int iovcnt) { + ssize_t written = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = this->internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (written != 0) { + // if we already read some don't return an error + break; + } + return err; + } + written += err; + if ((size_t) err != iov[i].iov_len) + break; + } + if (written == 0) { + // no need to output if nothing written + return 0; + } + if (this->nodelay_) { + int err = this->internal_output(); + if (err == -1) + return -1; + } + return written; +} + +// ---- LWIPRawListenImpl methods ---- + +LWIPRawListenImpl::~LWIPRawListenImpl() { + // Base class destructor handles pcb_ cleanup via tcp_abort +} + +std::unique_ptr LWIPRawListenImpl::accept(struct sockaddr *addr, socklen_t *addrlen) { + if (this->pcb_ == nullptr) { + errno = EBADF; + return nullptr; + } + if (this->accepted_socket_count_ == 0) { + errno = EWOULDBLOCK; + return nullptr; + } + // Take from front for FIFO ordering + std::unique_ptr sock = std::move(this->accepted_sockets_[0]); + // Shift remaining sockets forward + for (uint8_t i = 1; i < this->accepted_socket_count_; i++) { + this->accepted_sockets_[i - 1] = std::move(this->accepted_sockets_[i]); + } + this->accepted_socket_count_--; + LWIP_LOG("Connection accepted by application, queue size: %d", this->accepted_socket_count_); + if (addr != nullptr) { + sock->getpeername(addr, addrlen); + } + LWIP_LOG("accept(%p)", sock.get()); + return sock; +} + +int LWIPRawListenImpl::listen(int backlog) { + if (this->pcb_ == nullptr) { + errno = EBADF; + return -1; + } + LWIP_LOG("tcp_listen_with_backlog(%p backlog=%d)", this->pcb_, backlog); + struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(this->pcb_, backlog); + if (listen_pcb == nullptr) { + tcp_abort(this->pcb_); + this->pcb_ = nullptr; errno = EOPNOTSUPP; return -1; } - ssize_t read(void *buf, size_t len) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (rx_closed_ && rx_buf_ == nullptr) { - return 0; - } - if (len == 0) { - return 0; - } - if (rx_buf_ == nullptr) { - errno = EWOULDBLOCK; - return -1; - } + // tcp_listen reallocates the pcb, replace ours + this->pcb_ = listen_pcb; + // set callbacks on new pcb + LWIP_LOG("tcp_arg(%p)", this->pcb_); + tcp_arg(this->pcb_, this); + tcp_accept(this->pcb_, LWIPRawListenImpl::s_accept_fn); + return 0; +} - size_t read = 0; - uint8_t *buf8 = reinterpret_cast(buf); - while (len && rx_buf_ != nullptr) { - size_t pb_len = rx_buf_->len; - size_t pb_left = pb_len - rx_buf_offset_; - if (pb_left == 0) - break; - size_t copysize = std::min(len, pb_left); - memcpy(buf8, reinterpret_cast(rx_buf_->payload) + rx_buf_offset_, copysize); - - if (pb_left == copysize) { - // full pb copied, free it - if (rx_buf_->next == nullptr) { - // last buffer in chain - pbuf_free(rx_buf_); - rx_buf_ = nullptr; - rx_buf_offset_ = 0; - } else { - auto *old_buf = rx_buf_; - rx_buf_ = rx_buf_->next; - pbuf_ref(rx_buf_); - pbuf_free(old_buf); - rx_buf_offset_ = 0; - } - } else { - rx_buf_offset_ += copysize; - } - LWIP_LOG("tcp_recved(%p %u)", pcb_, copysize); - tcp_recved(pcb_, copysize); - - buf8 += copysize; - len -= copysize; - read += copysize; - } - - if (read == 0) { - errno = EWOULDBLOCK; - return -1; - } - - return read; - } - ssize_t readv(const struct iovec *iov, int iovcnt) final { - ssize_t ret = 0; - for (int i = 0; i < iovcnt; i++) { - ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); - if (err == -1) { - if (ret != 0) { - // if we already read some don't return an error - break; - } - return err; - } - ret += err; - if ((size_t) err != iov[i].iov_len) - break; - } - return ret; - } - - ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) final { - errno = ENOTSUP; - return -1; - } - - ssize_t internal_write(const void *buf, size_t len) { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (len == 0) - return 0; - if (buf == nullptr) { - errno = EINVAL; - return 0; - } - auto space = tcp_sndbuf(pcb_); - if (space == 0) { - errno = EWOULDBLOCK; - return -1; - } - size_t to_send = std::min((size_t) space, len); - LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send); - err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); - if (err == ERR_MEM) { - LWIP_LOG(" -> err ERR_MEM"); - errno = EWOULDBLOCK; - return -1; - } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = ECONNRESET; - return -1; - } - return to_send; - } - int internal_output() { - LWIP_LOG("tcp_output(%p)", pcb_); - err_t err = tcp_output(pcb_); - if (err == ERR_ABRT) { - LWIP_LOG(" -> err ERR_ABRT"); - // sometimes lwip returns ERR_ABRT for no apparent reason - // the connection works fine afterwards, and back with ESPAsyncTCP we - // indirectly also ignored this error - // FIXME: figure out where this is returned and what it means in this context - return 0; - } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = ECONNRESET; - return -1; - } - return 0; - } - ssize_t write(const void *buf, size_t len) final { - ssize_t written = internal_write(buf, len); - if (written == -1) - return -1; - if (written == 0) { - // no need to output if nothing written - return 0; - } - if (nodelay_) { - int err = internal_output(); - if (err == -1) - return -1; - } - return written; - } - ssize_t writev(const struct iovec *iov, int iovcnt) final { - ssize_t written = 0; - for (int i = 0; i < iovcnt; i++) { - ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); - if (err == -1) { - if (written != 0) { - // if we already read some don't return an error - break; - } - return err; - } - written += err; - if ((size_t) err != iov[i].iov_len) - break; - } - if (written == 0) { - // no need to output if nothing written - return 0; - } - if (nodelay_) { - int err = internal_output(); - if (err == -1) - return -1; - } - return written; - } - ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) final { - // return ::sendto(fd_, buf, len, flags, to, tolen); - errno = ENOSYS; - return -1; - } - bool ready() const override { return this->rx_buf_ != nullptr || this->rx_closed_ || this->pcb_ == nullptr; } - - int setblocking(bool blocking) final { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return -1; - } - if (blocking) { - // blocking operation not supported - errno = EINVAL; - return -1; - } - return 0; - } - - void err_fn(err_t err) { - LWIP_LOG("err(err=%d)", err); - // "If a connection is aborted because of an error, the application is alerted of this event by - // the err callback." - // pcb is already freed when this callback is called - // ERR_RST: connection was reset by remote host - // ERR_ABRT: aborted through tcp_abort or TCP timer - pcb_ = nullptr; - } - err_t recv_fn(struct pbuf *pb, err_t err) { - LWIP_LOG("recv(pb=%p err=%d)", pb, err); - if (err != 0) { - // "An error code if there has been an error receiving Only return ERR_ABRT if you have - // called tcp_abort from within the callback function!" - rx_closed_ = true; - return ERR_OK; - } - if (pb == nullptr) { - rx_closed_ = true; - return ERR_OK; - } - if (rx_buf_ == nullptr) { - // no need to copy because lwIP gave control of it to us - rx_buf_ = pb; - rx_buf_offset_ = 0; - } else { - pbuf_cat(rx_buf_, pb); - } -#ifdef USE_ESP8266 - // Wake the main loop immediately so it can process the received data. - socket_wake(); -#endif +err_t LWIPRawListenImpl::accept_fn_(struct tcp_pcb *newpcb, err_t err) { + LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); + if (err != ERR_OK || newpcb == nullptr) { return ERR_OK; } - - static void s_err_fn(void *arg, err_t err) { - LWIPRawImpl *arg_this = reinterpret_cast(arg); - arg_this->err_fn(err); + // Check if we've reached the maximum accept queue size + if (this->accepted_socket_count_ >= MAX_ACCEPTED_SOCKETS) { + LWIP_LOG("Rejecting connection, queue full (%d)", this->accepted_socket_count_); + // Abort the connection when queue is full + tcp_abort(newpcb); + // Must return ERR_ABRT since we called tcp_abort() + return ERR_ABRT; } - - static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { - LWIPRawImpl *arg_this = reinterpret_cast(arg); - return arg_this->recv_fn(pb, err); - } - - protected: - int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { - if (family_ == AF_INET) { - if (*addrlen < sizeof(struct sockaddr_in)) { - errno = EINVAL; - return -1; - } - - struct sockaddr_in *addr = reinterpret_cast(name); - addr->sin_family = AF_INET; - *addrlen = addr->sin_len = sizeof(struct sockaddr_in); - addr->sin_port = port; - inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip)); - return 0; - } -#if LWIP_IPV6 - else if (family_ == AF_INET6) { - if (*addrlen < sizeof(struct sockaddr_in6)) { - errno = EINVAL; - return -1; - } - - struct sockaddr_in6 *addr = reinterpret_cast(name); - addr->sin6_family = AF_INET6; - *addrlen = addr->sin6_len = sizeof(struct sockaddr_in6); - addr->sin6_port = port; - - // AF_INET6 sockets are bound to IPv4 as well, so we may encounter IPv4 addresses that must be converted to IPv6. - if (IP_IS_V4(ip)) { - ip_addr_t mapped; - ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip)); - inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped)); - } else { - inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip)); - } - return 0; - } -#endif - return -1; - } - - // Member ordering optimized to minimize padding on 32-bit systems - // Largest members first (4 bytes), then smaller members (1 byte each) - struct tcp_pcb *pcb_; - pbuf *rx_buf_ = nullptr; - size_t rx_buf_offset_ = 0; - bool rx_closed_ = false; - // don't use lwip nodelay flag, it sometimes causes reconnect - // instead use it for determining whether to call lwip_output - bool nodelay_ = false; - sa_family_t family_ = 0; -}; - -// Listening socket class - only allocates accept queue when needed (for bind+listen sockets) -// This saves 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) for regular connected sockets on ESP8266/RP2040 -class LWIPRawListenImpl final : public LWIPRawImpl { - public: - LWIPRawListenImpl(sa_family_t family, struct tcp_pcb *pcb) : LWIPRawImpl(family, pcb) {} - - void init() { - LWIP_LOG("init(%p)", pcb_); - tcp_arg(pcb_, this); - tcp_accept(pcb_, LWIPRawListenImpl::s_accept_fn); - tcp_err(pcb_, LWIPRawImpl::s_err_fn); // Use base class error handler - } - - bool ready() const override { return this->accepted_socket_count_ > 0; } - - std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - if (pcb_ == nullptr) { - errno = EBADF; - return nullptr; - } - if (accepted_socket_count_ == 0) { - errno = EWOULDBLOCK; - return nullptr; - } - // Take from front for FIFO ordering - std::unique_ptr sock = std::move(accepted_sockets_[0]); - // Shift remaining sockets forward - for (uint8_t i = 1; i < accepted_socket_count_; i++) { - accepted_sockets_[i - 1] = std::move(accepted_sockets_[i]); - } - accepted_socket_count_--; - LWIP_LOG("Connection accepted by application, queue size: %d", accepted_socket_count_); - if (addr != nullptr) { - sock->getpeername(addr, addrlen); - } - LWIP_LOG("accept(%p)", sock.get()); - return std::unique_ptr(std::move(sock)); - } - - int listen(int backlog) override { - if (pcb_ == nullptr) { - errno = EBADF; - return -1; - } - LWIP_LOG("tcp_listen_with_backlog(%p backlog=%d)", pcb_, backlog); - struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog); - if (listen_pcb == nullptr) { - tcp_abort(pcb_); - pcb_ = nullptr; - errno = EOPNOTSUPP; - return -1; - } - // tcp_listen reallocates the pcb, replace ours - pcb_ = listen_pcb; - // set callbacks on new pcb - LWIP_LOG("tcp_arg(%p)", pcb_); - tcp_arg(pcb_, this); - tcp_accept(pcb_, LWIPRawListenImpl::s_accept_fn); - return 0; - } - - private: - err_t accept_fn_(struct tcp_pcb *newpcb, err_t err) { - LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); - if (err != ERR_OK || newpcb == nullptr) { - // "An error code if there has been an error accepting. Only return ERR_ABRT if you have - // called tcp_abort from within the callback function!" - // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d - // nothing to do here, we just don't push it to the queue - return ERR_OK; - } - // Check if we've reached the maximum accept queue size - if (accepted_socket_count_ >= MAX_ACCEPTED_SOCKETS) { - LWIP_LOG("Rejecting connection, queue full (%d)", accepted_socket_count_); - // Abort the connection when queue is full - tcp_abort(newpcb); - // Must return ERR_ABRT since we called tcp_abort() - return ERR_ABRT; - } - auto sock = make_unique(family_, newpcb); - sock->init(); - accepted_sockets_[accepted_socket_count_++] = std::move(sock); - LWIP_LOG("Accepted connection, queue size: %d", accepted_socket_count_); + auto sock = make_unique(this->family_, newpcb); + sock->init(); + this->accepted_sockets_[this->accepted_socket_count_++] = std::move(sock); + LWIP_LOG("Accepted connection, queue size: %d", this->accepted_socket_count_); #ifdef USE_ESP8266 - // Wake the main loop immediately so it can accept the new connection. - socket_wake(); + // Wake the main loop immediately so it can accept the new connection. + socket_wake(); #endif - return ERR_OK; - } + return ERR_OK; +} - static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) { - LWIPRawListenImpl *arg_this = reinterpret_cast(arg); - return arg_this->accept_fn_(newpcb, err); - } - - // Accept queue - holds incoming connections briefly until the event loop calls accept() - // This is NOT a connection pool - just a temporary queue between LWIP callbacks and the main loop - // 3 slots is plenty since connections are pulled out quickly by the event loop - // - // Memory analysis: std::array<3> vs original std::queue implementation: - // - std::queue uses std::deque internally which on 32-bit systems needs: - // 24 bytes (deque object) + 32+ bytes (map array) + heap allocations - // Total: ~56+ bytes minimum, plus heap fragmentation - // - std::array<3>: 12 bytes fixed (3 pointers × 4 bytes) - // Saves ~44+ bytes RAM per listening socket + avoids ALL heap allocations - // Used on ESP8266 and RP2040 (platforms using LWIP_TCP implementation) - // - // By using a separate listening socket class, regular connected sockets save - // 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) of memory overhead on 32-bit systems - static constexpr size_t MAX_ACCEPTED_SOCKETS = 3; - std::array, MAX_ACCEPTED_SOCKETS> accepted_sockets_; - uint8_t accepted_socket_count_ = 0; // Number of sockets currently in queue -}; +// ---- Factory functions ---- std::unique_ptr socket(int domain, int type, int protocol) { if (type != SOCK_STREAM) { @@ -688,9 +585,7 @@ std::unique_ptr socket(int domain, int type, int protocol) { auto *pcb = tcp_new(); if (pcb == nullptr) return nullptr; - // Create listening socket implementation since user sockets typically bind+listen - // Accepted connections are created directly as LWIPRawImpl in the accept callback - auto *sock = new LWIPRawListenImpl((sa_family_t) domain, pcb); // NOLINT(cppcoreguidelines-owning-memory) + auto *sock = new LWIPRawImpl((sa_family_t) domain, pcb); // NOLINT(cppcoreguidelines-owning-memory) sock->init(); return std::unique_ptr{sock}; } @@ -700,6 +595,25 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return socket(domain, type, protocol); } +std::unique_ptr socket_listen(int domain, int type, int protocol) { + if (type != SOCK_STREAM) { + ESP_LOGE(TAG, "UDP sockets not supported on this platform, use WiFiUDP"); + errno = EPROTOTYPE; + return nullptr; + } + auto *pcb = tcp_new(); + if (pcb == nullptr) + return nullptr; + auto *sock = new LWIPRawListenImpl((sa_family_t) domain, pcb); // NOLINT(cppcoreguidelines-owning-memory) + sock->init(); + return std::unique_ptr{sock}; +} + +std::unique_ptr socket_listen_loop_monitored(int domain, int type, int protocol) { + // LWIPRawImpl doesn't use file descriptors, so monitoring is not applicable + return socket_listen(domain, type, protocol); +} + } // namespace esphome::socket #endif // USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/components/socket/lwip_raw_tcp_impl.h b/esphome/components/socket/lwip_raw_tcp_impl.h new file mode 100644 index 0000000000..63b5c8c4d9 --- /dev/null +++ b/esphome/components/socket/lwip_raw_tcp_impl.h @@ -0,0 +1,227 @@ +#pragma once +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_LWIP_TCP + +#include +#include +#include +#include +#include + +#include "esphome/core/helpers.h" +#include "headers.h" +#include "lwip/ip.h" +#include "lwip/netif.h" +#include "lwip/opt.h" +#include "lwip/tcp.h" + +namespace esphome::socket { + +// Forward declaration +class LWIPRawImpl; + +// set to 1 to enable verbose lwip logging +#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) +#define LWIP_LOG(msg, ...) ESP_LOGVV("socket.lwip", "socket %p: " msg, this, ##__VA_ARGS__) +#else +#define LWIP_LOG(msg, ...) +#endif + +/// Non-virtual common base for LWIP raw TCP sockets. +/// Provides shared fields and methods for both connected and listening sockets. +/// No virtual methods — pure code sharing. +class LWIPRawCommon { + public: + LWIPRawCommon(sa_family_t family, struct tcp_pcb *pcb) : pcb_(pcb), family_(family) {} + ~LWIPRawCommon() { + if (this->pcb_ != nullptr) { + LWIP_LOG("tcp_abort(%p)", this->pcb_); + tcp_abort(this->pcb_); + this->pcb_ = nullptr; + } + } + LWIPRawCommon(const LWIPRawCommon &) = delete; + LWIPRawCommon &operator=(const LWIPRawCommon &) = delete; + + int bind(const struct sockaddr *name, socklen_t addrlen); + int close(); + int shutdown(int how); + + int getpeername(struct sockaddr *addr, socklen_t *addrlen); + int getsockname(struct sockaddr *addr, socklen_t *addrlen); + + /// Format peer address into a fixed-size buffer (no heap allocation) + size_t getpeername_to(std::span buf); + /// Format local address into a fixed-size buffer (no heap allocation) + size_t getsockname_to(std::span buf); + + int getsockopt(int level, int optname, void *optval, socklen_t *optlen); + int setsockopt(int level, int optname, const void *optval, socklen_t optlen); + + int get_fd() const { return -1; } + + protected: + int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen); + + // Member ordering optimized to minimize padding on 32-bit systems + struct tcp_pcb *pcb_; + // don't use lwip nodelay flag, it sometimes causes reconnect + // instead use it for determining whether to call lwip_output + bool nodelay_ = false; + sa_family_t family_ = 0; +}; + +/// Connected socket implementation for LWIP raw TCP. +/// No virtual methods — callers always use the concrete type. +class LWIPRawImpl : public LWIPRawCommon { + public: + using LWIPRawCommon::LWIPRawCommon; + ~LWIPRawImpl(); + + void init() { + LWIP_LOG("init(%p)", this->pcb_); + tcp_arg(this->pcb_, this); + tcp_recv(this->pcb_, LWIPRawImpl::s_recv_fn); + tcp_err(this->pcb_, LWIPRawImpl::s_err_fn); + } + + std::unique_ptr accept(struct sockaddr *, socklen_t *) { + errno = EINVAL; + return nullptr; + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) { + return this->accept(addr, addrlen); + } + int listen(int) { + errno = EOPNOTSUPP; + return -1; + } + ssize_t read(void *buf, size_t len); + ssize_t readv(const struct iovec *iov, int iovcnt); + ssize_t recvfrom(void *, size_t, sockaddr *, socklen_t *) { + errno = ENOTSUP; + return -1; + } + ssize_t write(const void *buf, size_t len); + ssize_t writev(const struct iovec *iov, int iovcnt); + ssize_t sendto(const void *, size_t, int, const struct sockaddr *, socklen_t) { + errno = ENOSYS; + return -1; + } + bool ready() const { return this->rx_buf_ != nullptr || this->rx_closed_ || this->pcb_ == nullptr; } + + int setblocking(bool blocking) { + if (this->pcb_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (blocking) { + errno = EINVAL; + return -1; + } + return 0; + } + int loop() { return 0; } + + void err_fn(err_t err) { + LWIP_LOG("err(err=%d)", err); + this->pcb_ = nullptr; + } + err_t recv_fn(struct pbuf *pb, err_t err); + + static void s_err_fn(void *arg, err_t err) { + auto *arg_this = reinterpret_cast(arg); + arg_this->err_fn(err); + } + + static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { + auto *arg_this = reinterpret_cast(arg); + return arg_this->recv_fn(pb, err); + } + + protected: + ssize_t internal_write(const void *buf, size_t len); + int internal_output(); + + pbuf *rx_buf_ = nullptr; + size_t rx_buf_offset_ = 0; + bool rx_closed_ = false; +}; + +/// Listening socket implementation for LWIP raw TCP. +/// Separate from LWIPRawImpl — no virtual dispatch needed. +class LWIPRawListenImpl : public LWIPRawCommon { + public: + using LWIPRawCommon::LWIPRawCommon; + ~LWIPRawListenImpl(); + + void init() { + LWIP_LOG("init(%p)", this->pcb_); + tcp_arg(this->pcb_, this); + tcp_accept(this->pcb_, LWIPRawListenImpl::s_accept_fn); + tcp_err(this->pcb_, LWIPRawListenImpl::s_err_fn); + } + + bool ready() const { return this->accepted_socket_count_ > 0; } + + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen); + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) { + return this->accept(addr, addrlen); + } + int listen(int backlog); + + // Listening sockets don't do I/O + ssize_t read(void *, size_t) { + errno = ENOTSUP; + return -1; + } + ssize_t write(const void *, size_t) { + errno = ENOTSUP; + return -1; + } + ssize_t readv(const struct iovec *, int) { + errno = ENOTSUP; + return -1; + } + ssize_t writev(const struct iovec *, int) { + errno = ENOTSUP; + return -1; + } + ssize_t recvfrom(void *, size_t, sockaddr *, socklen_t *) { + errno = ENOTSUP; + return -1; + } + ssize_t sendto(const void *, size_t, int, const struct sockaddr *, socklen_t) { + errno = ENOTSUP; + return -1; + } + int setblocking(bool) { return 0; } + int loop() { return 0; } + + void err_fn(err_t err) { + LWIP_LOG("err(err=%d)", err); + this->pcb_ = nullptr; + } + + static void s_err_fn(void *arg, err_t err) { + auto *arg_this = reinterpret_cast(arg); + arg_this->err_fn(err); + } + + private: + err_t accept_fn_(struct tcp_pcb *newpcb, err_t err); + + static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) { + auto *arg_this = reinterpret_cast(arg); + return arg_this->accept_fn_(newpcb, err); + } + + static constexpr size_t MAX_ACCEPTED_SOCKETS = 3; + std::array, MAX_ACCEPTED_SOCKETS> accepted_sockets_; + uint8_t accepted_socket_count_ = 0; +}; + +} // namespace esphome::socket + +#endif // USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index 79d68e085a..8a926c92ea 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -9,94 +9,73 @@ namespace esphome::socket { -class LwIPSocketImpl final : public Socket { - public: - LwIPSocketImpl(int fd, bool monitor_loop = false) { - this->fd_ = fd; - // Register new socket with the application for select() if monitoring requested - if (monitor_loop && this->fd_ >= 0) { - // Only set loop_monitored_ to true if registration succeeds - this->loop_monitored_ = App.register_socket_fd(this->fd_); - } - } - ~LwIPSocketImpl() override { - if (!this->closed_) { - this->close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) - } - } - int connect(const struct sockaddr *addr, socklen_t addrlen) override { - return lwip_connect(this->fd_, addr, addrlen); - } - std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - int fd = lwip_accept(this->fd_, addr, addrlen); - if (fd == -1) - return {}; - return make_unique(fd, false); - } - std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { - int fd = lwip_accept(this->fd_, addr, addrlen); - if (fd == -1) - return {}; - return make_unique(fd, true); +LwIPSocketImpl::LwIPSocketImpl(int fd, bool monitor_loop) { + this->fd_ = fd; + // Register new socket with the application for select() if monitoring requested + if (monitor_loop && this->fd_ >= 0) { + // Only set loop_monitored_ to true if registration succeeds + this->loop_monitored_ = App.register_socket_fd(this->fd_); } +} - int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(this->fd_, addr, addrlen); } - int close() override { - if (!this->closed_) { - // Unregister from select() before closing if monitored - if (this->loop_monitored_) { - App.unregister_socket_fd(this->fd_); - } - int ret = lwip_close(this->fd_); - this->closed_ = true; - return ret; +LwIPSocketImpl::~LwIPSocketImpl() { + if (!this->closed_) { + this->close(); + } +} + +int LwIPSocketImpl::close() { + if (!this->closed_) { + // Unregister from select() before closing if monitored + if (this->loop_monitored_) { + App.unregister_socket_fd(this->fd_); } + int ret = lwip_close(this->fd_); + this->closed_ = true; + return ret; + } + return 0; +} + +int LwIPSocketImpl::setblocking(bool blocking) { + int fl = lwip_fcntl(this->fd_, F_GETFL, 0); + if (blocking) { + fl &= ~O_NONBLOCK; + } else { + fl |= O_NONBLOCK; + } + lwip_fcntl(this->fd_, F_SETFL, fl); + return 0; +} + +bool LwIPSocketImpl::ready() const { return socket_ready_fd(this->fd_, this->loop_monitored_); } + +size_t LwIPSocketImpl::getpeername_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; return 0; } - int shutdown(int how) override { return lwip_shutdown(this->fd_, how); } + return format_sockaddr_to(reinterpret_cast(&storage), len, buf); +} - int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { - return lwip_getpeername(this->fd_, addr, addrlen); - } - int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { - return lwip_getsockname(this->fd_, addr, addrlen); - } - int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { - return lwip_getsockopt(this->fd_, level, optname, optval, optlen); - } - int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { - return lwip_setsockopt(this->fd_, level, optname, optval, optlen); - } - int listen(int backlog) override { return lwip_listen(this->fd_, backlog); } - ssize_t read(void *buf, size_t len) override { return lwip_read(this->fd_, buf, len); } - ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { - return lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); - } - ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(this->fd_, iov, iovcnt); } - ssize_t write(const void *buf, size_t len) override { return lwip_write(this->fd_, buf, len); } - ssize_t send(void *buf, size_t len, int flags) { return lwip_send(this->fd_, buf, len, flags); } - ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(this->fd_, iov, iovcnt); } - ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { - return lwip_sendto(this->fd_, buf, len, flags, to, tolen); - } - int setblocking(bool blocking) override { - int fl = lwip_fcntl(this->fd_, F_GETFL, 0); - if (blocking) { - fl &= ~O_NONBLOCK; - } else { - fl |= O_NONBLOCK; - } - lwip_fcntl(this->fd_, F_SETFL, fl); +size_t LwIPSocketImpl::getsockname_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; return 0; } -}; + return format_sockaddr_to(reinterpret_cast(&storage), len, buf); +} // Helper to create a socket with optional monitoring -static std::unique_ptr create_socket(int domain, int type, int protocol, bool loop_monitored = false) { +static std::unique_ptr create_socket(int domain, int type, int protocol, bool loop_monitored = false) { int ret = lwip_socket(domain, type, protocol); if (ret == -1) return nullptr; - return std::unique_ptr{new LwIPSocketImpl(ret, loop_monitored)}; + return std::unique_ptr{new LwIPSocketImpl(ret, loop_monitored)}; } std::unique_ptr socket(int domain, int type, int protocol) { @@ -107,6 +86,14 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return create_socket(domain, type, protocol, true); } +std::unique_ptr socket_listen(int domain, int type, int protocol) { + return create_socket(domain, type, protocol, false); +} + +std::unique_ptr socket_listen_loop_monitored(int domain, int type, int protocol) { + return create_socket(domain, type, protocol, true); +} + } // namespace esphome::socket #endif // USE_SOCKET_IMPL_LWIP_SOCKETS diff --git a/esphome/components/socket/lwip_sockets_impl.h b/esphome/components/socket/lwip_sockets_impl.h new file mode 100644 index 0000000000..2e319fcc4d --- /dev/null +++ b/esphome/components/socket/lwip_sockets_impl.h @@ -0,0 +1,80 @@ +#pragma once +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS + +#include +#include + +#include "esphome/core/helpers.h" +#include "headers.h" + +namespace esphome::socket { + +class LwIPSocketImpl { + public: + LwIPSocketImpl(int fd, bool monitor_loop = false); + ~LwIPSocketImpl(); + LwIPSocketImpl(const LwIPSocketImpl &) = delete; + LwIPSocketImpl &operator=(const LwIPSocketImpl &) = delete; + + int connect(const struct sockaddr *addr, socklen_t addrlen) { return lwip_connect(this->fd_, addr, addrlen); } + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) { + int fd = lwip_accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, false); + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) { + int fd = lwip_accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, true); + } + + int bind(const struct sockaddr *addr, socklen_t addrlen) { return lwip_bind(this->fd_, addr, addrlen); } + int close(); + int shutdown(int how) { return lwip_shutdown(this->fd_, how); } + + int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return lwip_getpeername(this->fd_, addr, addrlen); } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) { return lwip_getsockname(this->fd_, addr, addrlen); } + + /// Format peer address into a fixed-size buffer (no heap allocation) + size_t getpeername_to(std::span buf); + /// Format local address into a fixed-size buffer (no heap allocation) + size_t getsockname_to(std::span buf); + + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) { + return lwip_getsockopt(this->fd_, level, optname, optval, optlen); + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) { + return lwip_setsockopt(this->fd_, level, optname, optval, optlen); + } + int listen(int backlog) { return lwip_listen(this->fd_, backlog); } + ssize_t read(void *buf, size_t len) { return lwip_read(this->fd_, buf, len); } + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) { + return lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); + } + ssize_t readv(const struct iovec *iov, int iovcnt) { return lwip_readv(this->fd_, iov, iovcnt); } + ssize_t write(const void *buf, size_t len) { return lwip_write(this->fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return lwip_send(this->fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) { return lwip_writev(this->fd_, iov, iovcnt); } + ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) { + return lwip_sendto(this->fd_, buf, len, flags, to, tolen); + } + int setblocking(bool blocking); + int loop() { return 0; } + + bool ready() const; + + int get_fd() const { return this->fd_; } + + protected: + int fd_{-1}; + bool closed_{false}; + bool loop_monitored_{false}; +}; + +} // namespace esphome::socket + +#endif // USE_SOCKET_IMPL_LWIP_SOCKETS diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index 6154c497e0..8645e90e6b 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -8,10 +8,10 @@ namespace esphome::socket { -Socket::~Socket() {} - #ifdef USE_SOCKET_SELECT_SUPPORT -bool Socket::ready() const { return !this->loop_monitored_ || App.is_socket_ready_(this->fd_); } +// Shared ready() implementation for fd-based socket implementations (BSD and LWIP sockets). +// Checks if the Application's select() loop has marked this fd as ready. +bool socket_ready_fd(int fd, bool loop_monitored) { return !loop_monitored || App.is_socket_ready_(fd); } #endif // Platform-specific inet_ntop wrappers @@ -81,26 +81,6 @@ size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::s return 0; } -size_t Socket::getpeername_to(std::span buf) { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { - buf[0] = '\0'; - return 0; - } - return format_sockaddr_to(reinterpret_cast(&storage), len, buf); -} - -size_t Socket::getsockname_to(std::span buf) { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { - buf[0] = '\0'; - return 0; - } - return format_sockaddr_to(reinterpret_cast(&storage), len, buf); -} - std::unique_ptr socket_ip(int type, int protocol) { #if USE_NETWORK_IPV6 return socket(AF_INET6, type, protocol); @@ -109,11 +89,11 @@ std::unique_ptr socket_ip(int type, int protocol) { #endif /* USE_NETWORK_IPV6 */ } -std::unique_ptr socket_ip_loop_monitored(int type, int protocol) { +std::unique_ptr socket_ip_listen_loop_monitored(int type, int protocol) { #if USE_NETWORK_IPV6 - return socket_loop_monitored(AF_INET6, type, protocol); + return socket_listen_loop_monitored(AF_INET6, type, protocol); #else - return socket_loop_monitored(AF_INET, type, protocol); + return socket_listen_loop_monitored(AF_INET, type, protocol); #endif /* USE_NETWORK_IPV6 */ } diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index a771e2fe1a..fbb2de1e81 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -7,87 +7,41 @@ #include "headers.h" #if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) + +// Include only the active implementation's header. +// SOCKADDR_STR_LEN is defined in headers.h. +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS +#include "bsd_sockets_impl.h" +#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +#include "lwip_sockets_impl.h" +#elif defined(USE_SOCKET_IMPL_LWIP_TCP) +#include "lwip_raw_tcp_impl.h" +#endif + namespace esphome::socket { -// Maximum length for formatted socket address string (IP address without port) -// IPv4: "255.255.255.255" = 15 chars + null = 16 -// IPv6: full address = 45 chars + null = 46 -#if USE_NETWORK_IPV6 -static constexpr size_t SOCKADDR_STR_LEN = 46; // INET6_ADDRSTRLEN -#else -static constexpr size_t SOCKADDR_STR_LEN = 16; // INET_ADDRSTRLEN +// Type aliases — only one implementation is active per build. +// Socket is the concrete type for connected sockets. +// ListenSocket is the concrete type for listening/server sockets. +// On BSD and LWIP_SOCKETS, both aliases resolve to the same type. +// On LWIP_TCP, they are different types (no virtual dispatch between them). +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS +using Socket = BSDSocketImpl; +using ListenSocket = BSDSocketImpl; +#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +using Socket = LwIPSocketImpl; +using ListenSocket = LwIPSocketImpl; +#elif defined(USE_SOCKET_IMPL_LWIP_TCP) +using Socket = LWIPRawImpl; +using ListenSocket = LWIPRawListenImpl; #endif -class Socket { - public: - Socket() = default; - virtual ~Socket(); - Socket(const Socket &) = delete; - Socket &operator=(const Socket &) = delete; - - virtual std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) = 0; - /// Accept a connection and monitor it in the main loop - /// NOTE: This function is NOT thread-safe and must only be called from the main loop - virtual std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) { - return accept(addr, addrlen); // Default implementation for backward compatibility - } - virtual int bind(const struct sockaddr *addr, socklen_t addrlen) = 0; - virtual int close() = 0; - // not supported yet: - // virtual int connect(const std::string &address) = 0; -#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) - virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0; -#endif - virtual int shutdown(int how) = 0; - - virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0; - - /// Format peer address into a fixed-size buffer (no heap allocation) - /// Non-virtual wrapper around getpeername() - can be optimized away if unused - /// Returns number of characters written (excluding null terminator), or 0 on error - size_t getpeername_to(std::span buf); - /// Format local address into a fixed-size buffer (no heap allocation) - /// Non-virtual wrapper around getsockname() - can be optimized away if unused - size_t getsockname_to(std::span buf); - virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0; - virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; - virtual int listen(int backlog) = 0; - virtual ssize_t read(void *buf, size_t len) = 0; - virtual ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) = 0; - virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; - virtual ssize_t write(const void *buf, size_t len) = 0; - virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; - virtual ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) = 0; - - virtual int setblocking(bool blocking) = 0; - virtual int loop() { return 0; }; - - /// Get the underlying file descriptor (returns -1 if not supported) - /// Non-virtual: only one socket implementation is active per build. #ifdef USE_SOCKET_SELECT_SUPPORT - int get_fd() const { return this->fd_; } -#else - int get_fd() const { return -1; } +/// Shared ready() helper for fd-based socket implementations. +/// Checks if the Application's select() loop has marked this fd as ready. +bool socket_ready_fd(int fd, bool loop_monitored); #endif - /// Check if socket has data ready to read. Must only be called from the main loop thread. - /// For select()-based sockets: non-virtual, checks Application's select() results - /// For LWIP raw TCP sockets: virtual, checks internal buffer state -#ifdef USE_SOCKET_SELECT_SUPPORT - bool ready() const; -#else - virtual bool ready() const { return true; } -#endif - - protected: -#ifdef USE_SOCKET_SELECT_SUPPORT - int fd_{-1}; - bool closed_{false}; - bool loop_monitored_{false}; -#endif -}; - /// Create a socket of the given domain, type and protocol. std::unique_ptr socket(int domain, int type, int protocol); /// Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol. @@ -100,7 +54,13 @@ std::unique_ptr socket_ip(int type, int protocol); /// NOTE: On ESP platforms, FD_SETSIZE is typically 10, limiting the number of monitored sockets. /// File descriptors >= FD_SETSIZE will not be monitored and will log an error. std::unique_ptr socket_loop_monitored(int domain, int type, int protocol); -std::unique_ptr socket_ip_loop_monitored(int type, int protocol); + +/// Create a listening socket of the given domain, type and protocol. +std::unique_ptr socket_listen(int domain, int type, int protocol); +/// Create a listening socket and monitor it for data in the main loop. +std::unique_ptr socket_listen_loop_monitored(int domain, int type, int protocol); +/// Create a listening socket in the newest available IP domain and monitor it. +std::unique_ptr socket_ip_listen_loop_monitored(int type, int protocol); /// Set a sockaddr to the specified address and port for the IP version used by socket_ip(). /// @param addr Destination sockaddr structure @@ -126,7 +86,8 @@ size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::s /// On ESP8266, lwip callbacks set a flag and call esp_schedule() to wake the delay. void socket_delay(uint32_t ms); -/// Called by lwip callbacks to signal socket activity and wake delay. +/// Signal socket/IO activity and wake the main loop from esp_delay() early. +/// ISR-safe: uses IRAM_ATTR internally and only sets a volatile flag + esp_schedule(). void socket_wake(); #endif diff --git a/esphome/core/application.h b/esphome/core/application.h index 13e0f63885..506c5b05d2 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -105,7 +105,10 @@ #endif namespace esphome::socket { -class Socket; +#ifdef USE_SOCKET_SELECT_SUPPORT +/// Shared ready() helper for fd-based socket implementations. +bool socket_ready_fd(int fd, bool loop_monitored); +#endif } // namespace esphome::socket // Forward declarations for friend access from codegen-generated setup() @@ -514,7 +517,9 @@ class Application { protected: friend Component; - friend class socket::Socket; +#ifdef USE_SOCKET_SELECT_SUPPORT + friend bool socket::socket_ready_fd(int fd, bool loop_monitored); +#endif friend void ::setup(); friend void ::original_setup();