4#ifdef USE_SOCKET_IMPL_LWIP_TCP
15#ifdef USE_OTA_PLATFORM_ESPHOME
22#include <hardware/sync.h>
31#define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard
37static constexpr uint32_t ESP8266_YIELD_INTERVAL_US = 1000;
39static inline void yield_to_sys() { optimistic_yield(ESP8266_YIELD_INTERVAL_US); }
41static inline void yield_to_sys() {}
46#define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__)
48#define LWIP_LOG(msg, ...)
58static void pcb_detach_abort(
struct tcp_pcb *pcb) {
59 tcp_arg(pcb,
nullptr);
60 tcp_recv(pcb,
nullptr);
61 tcp_err(pcb,
nullptr);
72static err_t pcb_detach_close(
struct tcp_pcb *pcb) {
73 tcp_arg(pcb,
nullptr);
74 tcp_recv(pcb,
nullptr);
75 tcp_err(pcb,
nullptr);
76 err_t err = tcp_close(pcb);
87 if (this->
pcb_ !=
nullptr) {
88 LWIP_LOG(
"tcp_abort(%p)", this->
pcb_);
89 pcb_detach_abort(this->
pcb_);
95 bool for_bind)
const {
96 if (name !=
nullptr && name->
sa_family == this->family_ &&
106 if (this->
pcb_ ==
nullptr) {
112 if (!this->
sockaddr2ip_(name, addrlen, &ip, &port,
true)) {
115 LWIP_LOG(
"tcp_bind(%p ip=%s port=%u)", this->
pcb_, ipaddr_ntoa(&ip), port);
116 err_t err = tcp_bind(this->
pcb_, &ip, port);
118 LWIP_LOG(
" -> err %d", err);
127 if (this->
pcb_ ==
nullptr) {
131 LWIP_LOG(
"tcp_close(%p)", this->
pcb_);
132 err_t err = pcb_detach_close(this->
pcb_);
133 this->
pcb_ =
nullptr;
135 LWIP_LOG(
" -> err %d", err);
144 if (this->
pcb_ ==
nullptr) {
148 bool shut_rx =
false, shut_tx =
false;
149 if (how == SHUT_RD) {
151 }
else if (how == SHUT_WR) {
153 }
else if (how == SHUT_RDWR) {
154 shut_rx = shut_tx =
true;
159 LWIP_LOG(
"tcp_shutdown(%p shut_rx=%d shut_tx=%d)", this->
pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0);
160 err_t err = tcp_shutdown(this->
pcb_, shut_rx, shut_tx);
162 LWIP_LOG(
" -> err %d", err);
171 if (this->
pcb_ ==
nullptr) {
175 if (name ==
nullptr || addrlen ==
nullptr) {
179 return this->
ip2sockaddr_(&this->
pcb_->remote_ip, this->pcb_->remote_port, name, addrlen);
184 if (this->
pcb_ ==
nullptr) {
188 if (name ==
nullptr || addrlen ==
nullptr) {
192 return this->
ip2sockaddr_(&this->
pcb_->local_ip, this->pcb_->local_port, name, addrlen);
217 if (this->
pcb_ ==
nullptr) {
221 if (optlen ==
nullptr || optval ==
nullptr) {
225 if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
232 *
reinterpret_cast<int *
>(optval) = 1;
236 if (level == SOL_SOCKET && optname == SO_RCVTIMEO) {
237 if (*optlen <
sizeof(
struct timeval)) {
242 auto *tv =
reinterpret_cast<struct timeval *
>(optval);
243 tv->tv_sec = ms / 1000;
244 tv->tv_usec = (ms % 1000) * 1000;
245 *optlen =
sizeof(
struct timeval);
248 if (level == IPPROTO_TCP && optname == TCP_NODELAY) {
253 *
reinterpret_cast<int *
>(optval) = this->
nodelay_;
264 if (this->
pcb_ ==
nullptr) {
268 if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
277 if (level == SOL_SOCKET && optname == SO_RCVTIMEO) {
278 if (optlen <
sizeof(
struct timeval)) {
282 const auto *tv =
reinterpret_cast<const struct timeval *
>(optval);
283 uint32_t ms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
288 if (level == SOL_SOCKET && optname == SO_SNDTIMEO) {
292 if (level == IPPROTO_TCP && optname == TCP_NODELAY) {
297 int val = *
reinterpret_cast<const int *
>(optval);
318 if (this->
rx_buf_ !=
nullptr) {
327 LWIP_LOG(
"init(%p)", this->
pcb_);
328 tcp_arg(this->
pcb_,
this);
331 if (initial_rx !=
nullptr) {
346 auto *arg_this =
reinterpret_cast<LWIPRawImpl *
>(arg);
347 ESP_LOGVV(TAG,
"socket %p: err(err=%d)", arg_this, err);
348 if (arg_this->connect_err_ == EINPROGRESS) {
351 arg_this->connect_err_ = err == ERR_RST ? ECONNREFUSED : ETIMEDOUT;
353 arg_this->pcb_ =
nullptr;
359 auto *arg_this =
reinterpret_cast<LWIPRawImpl *
>(arg);
367 if (this->
pcb_ ==
nullptr) {
372 errno = this->
connect_err_ == EINPROGRESS ? EALREADY : EISCONN;
378 if (!this->
sockaddr2ip_(addr, addrlen, &ip, &port,
false)) {
381 LWIP_LOG(
"tcp_connect(%p ip=%s port=%u)", this->
pcb_, ipaddr_ntoa(&ip), port);
384 LWIP_LOG(
" -> err %d", err);
395 if (this->
pcb_ ==
nullptr) {
417 auto *arg_this =
reinterpret_cast<LWIPRawImpl *
>(arg);
418 return arg_this->
recv_fn(pb, err);
424 LWIP_LOG(
"recv(pb=%p err=%d)", pb, err);
438 if (this->
rx_buf_ ==
nullptr) {
461 if (elapsed >= timeout_ms)
469 if (this->
pcb_ ==
nullptr) {
479 if (this->
rx_buf_ ==
nullptr) {
485 uint8_t *buf8 =
reinterpret_cast<uint8_t *
>(buf);
487 size_t pb_len = this->
rx_buf_->len;
491 size_t copysize = std::min(
len, pb_left);
492 memcpy(buf8,
reinterpret_cast<uint8_t *
>(this->
rx_buf_->payload) + this->rx_buf_offset_, copysize);
494 if (pb_left == copysize) {
496 if (this->
rx_buf_->next ==
nullptr) {
500 this->rx_buf_offset_ = 0;
506 this->rx_buf_offset_ = 0;
509 this->rx_buf_offset_ += copysize;
511 LWIP_LOG(
"tcp_recved(%p %u)", this->
pcb_, copysize);
512 tcp_recved(this->
pcb_, copysize);
552 for (
int i = 0; i < iovcnt; i++) {
553 ssize_t err = this->
read_locked_(
reinterpret_cast<uint8_t *
>(iov[i].iov_base), iov[i].iov_len);
562 if ((
size_t) err != iov[i].iov_len)
570 if (this->
pcb_ ==
nullptr) {
576 if (buf ==
nullptr) {
580 auto space = tcp_sndbuf(this->
pcb_);
585 size_t to_send = std::min((
size_t) space,
len);
586 LWIP_LOG(
"tcp_write(%p buf=%p %u)", this->
pcb_, buf, to_send);
587 err_t err = tcp_write(this->
pcb_, buf, to_send, TCP_WRITE_FLAG_COPY);
588 if (err == ERR_MEM) {
589 LWIP_LOG(
" -> err ERR_MEM");
594 LWIP_LOG(
" -> err %d", err);
603 if (this->
pcb_ ==
nullptr) {
607 LWIP_LOG(
"tcp_output(%p)", this->
pcb_);
608 err_t err = tcp_output(this->
pcb_);
610 LWIP_LOG(
" -> err %d", err);
616 if (err != ERR_ABRT) {
648 for (
int i = 0; i < iovcnt; i++) {
658 if ((
size_t) err != iov[i].iov_len)
678 for (uint8_t i = 0; i < this->accepted_socket_count_; i++) {
679 auto &entry = this->accepted_pcbs_[i];
680 if (entry.pcb !=
nullptr) {
681 pcb_detach_abort(entry.pcb);
684 if (entry.rx_buf !=
nullptr) {
685 pbuf_free(entry.rx_buf);
686 entry.rx_buf =
nullptr;
689 this->accepted_socket_count_ = 0;
698 if (this->
pcb_ !=
nullptr) {
699 tcp_close(this->
pcb_);
700 this->
pcb_ =
nullptr;
706 LWIP_LOG(
"init(%p)", this->
pcb_);
707 tcp_arg(this->
pcb_,
this);
708 tcp_accept(this->
pcb_, LWIPRawListenImpl::s_accept_fn);
716 ESP_LOGVV(TAG,
"socket %p: err(err=%d)", arg_this, err);
717 arg_this->pcb_ =
nullptr;
720void LWIPRawListenImpl::s_queued_err_fn(
void *arg, err_t err) {
726 auto *entry =
reinterpret_cast<QueuedPcb *
>(arg);
727 entry->pcb =
nullptr;
731err_t LWIPRawListenImpl::s_queued_recv_fn(
void *arg,
struct tcp_pcb *pcb,
struct pbuf *pb, err_t err) {
738 auto *entry =
reinterpret_cast<QueuedPcb *
>(arg);
739 if (pb ==
nullptr || err != ERR_OK) {
744 entry->rx_closed =
true;
748 if (entry->rx_buf ==
nullptr) {
751 pbuf_cat(entry->rx_buf, pb);
756err_t LWIPRawListenImpl::s_accept_fn(
void *arg,
struct tcp_pcb *newpcb, err_t err) {
757 auto *arg_this =
reinterpret_cast<LWIPRawListenImpl *
>(arg);
758 return arg_this->accept_fn_(newpcb, err);
763 if (this->
pcb_ ==
nullptr) {
769 while (this->accepted_socket_count_ > 0) {
770 QueuedPcb entry = this->accepted_pcbs_[0];
773 for (uint8_t i = 1; i < this->accepted_socket_count_; i++) {
774 this->accepted_pcbs_[i - 1] = this->accepted_pcbs_[i];
775 if (this->accepted_pcbs_[i - 1].pcb !=
nullptr) {
776 tcp_arg(this->accepted_pcbs_[i - 1].pcb, &this->accepted_pcbs_[i - 1]);
779 this->accepted_pcbs_[this->accepted_socket_count_ - 1] = {};
780 this->accepted_socket_count_--;
781 if (entry.pcb ==
nullptr) {
783 if (entry.rx_buf !=
nullptr) {
784 pbuf_free(entry.rx_buf);
788 LWIP_LOG(
"Connection accepted by application, queue size: %d", this->accepted_socket_count_);
791 auto sock = make_unique<LWIPRawImpl>(this->
family_, entry.pcb);
792 sock->init(entry.rx_buf, entry.rx_closed);
793 if (addr !=
nullptr) {
794 sock->getpeername(addr, addrlen);
796 LWIP_LOG(
"accept(%p)", sock.get());
805 if (this->
pcb_ ==
nullptr) {
809 LWIP_LOG(
"tcp_listen_with_backlog(%p backlog=%d)", this->
pcb_, backlog);
810 struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(this->
pcb_, backlog);
811 if (listen_pcb ==
nullptr) {
812 tcp_abort(this->
pcb_);
813 this->
pcb_ =
nullptr;
818 this->
pcb_ = listen_pcb;
820 LWIP_LOG(
"tcp_arg(%p)", this->
pcb_);
821 tcp_arg(this->
pcb_,
this);
822 tcp_accept(this->
pcb_, LWIPRawListenImpl::s_accept_fn);
829err_t LWIPRawListenImpl::accept_fn_(
struct tcp_pcb *newpcb, err_t err) {
832 LWIP_LOG(
"accept(newpcb=%p err=%d)", newpcb, err);
833 if (err != ERR_OK || newpcb ==
nullptr) {
841 if (this->accepted_socket_count_ >= MAX_ACCEPTED_SOCKETS) {
842 LWIP_LOG(
"Rejecting connection, queue full (%d)", this->accepted_socket_count_);
850 uint8_t idx = this->accepted_socket_count_++;
851 this->accepted_pcbs_[idx] = {newpcb,
nullptr,
false};
857 tcp_arg(newpcb, &this->accepted_pcbs_[idx]);
858 tcp_err(newpcb, LWIPRawListenImpl::s_queued_err_fn);
859 tcp_recv(newpcb, LWIPRawListenImpl::s_queued_recv_fn);
860 LWIP_LOG(
"Accepted connection, queue size: %d", this->accepted_socket_count_);
861#ifdef USE_OTA_PLATFORM_ESPHOME
872std::unique_ptr<Socket>
socket(
int domain,
int type,
int protocol) {
873 if (
type != SOCK_STREAM) {
874 ESP_LOGE(TAG,
"Use socket_udp() for UDP sockets on this platform");
879 auto *pcb = tcp_new();
882 auto *sock =
new LWIPRawImpl((
sa_family_t) domain, pcb);
884 return std::unique_ptr<Socket>{sock};
893 if (
type != SOCK_STREAM) {
894 ESP_LOGE(TAG,
"Use socket_udp() for UDP sockets on this platform");
899 auto *pcb = tcp_new();
904 return std::unique_ptr<ListenSocket>{sock};
int getsockname(struct sockaddr *name, socklen_t *addrlen)
size_t getsockname_to(std::span< char, SOCKADDR_STR_LEN > buf)
Format local address into a fixed-size buffer (no heap allocation)
int bind(const struct sockaddr *name, socklen_t addrlen)
int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen)
int setsockopt(int level, int optname, const void *optval, socklen_t optlen)
int getsockopt(int level, int optname, void *optval, socklen_t *optlen)
bool sockaddr2ip_(const struct sockaddr *name, socklen_t addrlen, ip_addr_t *ip, uint16_t *port, bool for_bind) const
sockaddr of this socket's family to lwip address and port; false with errno on mismatch.
int getpeername(struct sockaddr *name, socklen_t *addrlen)
size_t getpeername_to(std::span< char, SOCKADDR_STR_LEN > buf)
Format peer address into a fixed-size buffer (no heap allocation)
Connected socket implementation for LWIP raw TCP.
ssize_t read_locked_(void *buf, size_t len)
ConnectPollResult poll_connect(int &err_out) const
int connect(const struct sockaddr *addr, socklen_t addrlen)
Non-blocking: returns -1/EINPROGRESS once the SYN is queued, see poll_connect().
static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err)
void init(struct pbuf *initial_rx=nullptr, bool initial_rx_closed=false)
bool waiting_for_data_() const
ssize_t readv(const struct iovec *iov, int iovcnt)
static void s_err_fn(void *arg, err_t err)
err_t recv_fn(struct pbuf *pb, err_t err)
ssize_t internal_write_(const void *buf, size_t len)
ssize_t write(const void *buf, size_t len)
ssize_t read(void *buf, size_t len)
static err_t s_connected_fn(void *arg, struct tcp_pcb *pcb, err_t err)
ssize_t writev(const struct iovec *iov, int iovcnt)
Listening socket implementation for LWIP raw TCP.
static void s_err_fn(void *arg, err_t err)
std::unique_ptr< LWIPRawImpl > accept(struct sockaddr *addr, socklen_t *addrlen)
void esphome_wake_ota_component_any_context()
void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms)
Host wakeable_delay uses select() over the registered fds — defined in wake_host.cpp.
bool sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port)
Convert sockaddr to lwip ip_addr_t and host-order port.
size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::span< char, SOCKADDR_STR_LEN > buf)
Format sockaddr into caller-provided buffer, returns length written (excluding null)
std::unique_ptr< ListenSocket > socket_listen(int domain, int type, int protocol)
Create a listening socket of the given domain, type and protocol.
ConnectPollResult
Outcome of polling a non-blocking connect(); see socket::poll_connect().
@ CONNECT_POLL_RESULT_CONNECTED
@ CONNECT_POLL_RESULT_ERROR
@ CONNECT_POLL_RESULT_PENDING
std::unique_ptr< ListenSocket > socket_listen_loop_monitored(int domain, int type, int protocol)
int lwip_err_to_errno(err_t err)
errno for a failed lwip call
ESPHOME_LOG_TAG(TAG, "socket")
bool sockaddr_to_lwip_bind(sa_family_t family, const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port)
sockaddr_to_lwip variant for bind: promotes AF_INET6 sockets to IPADDR_TYPE_ANY so they accept both I...
std::unique_ptr< Socket > socket(int domain, int type, int protocol)
Create a socket of the given domain, type and protocol.
std::unique_ptr< Socket > socket_loop_monitored(int domain, int type, int protocol)
Create a socket and monitor it for data in the main loop.
int lwip_ip_to_sockaddr(sa_family_t family, const ip_addr_t *ip, uint16_t port_host, struct sockaddr *name, socklen_t *addrlen)
Convert lwip ip_addr_t + host-order port to sockaddr, based on the socket's address family.
void esphome_wake_ota_component_any_context()
uint32_t IRAM_ATTR HOT millis()
void IRAM_ATTR wake_loop_any_context()
IRAM_ATTR entry point for ISR callers — defined in wake_esp8266.cpp.
Platform-specific main loop wake primitives.