ESPHome 2025.12.5
Loading...
Searching...
No Matches
lwip_raw_tcp_impl.cpp
Go to the documentation of this file.
1#include "socket.h"
3
4#ifdef USE_SOCKET_IMPL_LWIP_TCP
5
6#include "lwip/ip.h"
7#include "lwip/netif.h"
8#include "lwip/opt.h"
9#include "lwip/tcp.h"
10#include <cerrno>
11#include <cstring>
12#include <array>
13
15#include "esphome/core/log.h"
16
17#ifdef USE_ESP8266
18#include <coredecls.h> // For esp_schedule()
19#endif
20
21namespace esphome {
22namespace socket {
23
24#ifdef USE_ESP8266
25// Flag to signal socket activity - checked by socket_delay() to exit early
26// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
27static volatile bool s_socket_woke = false;
28
29void socket_delay(uint32_t ms) {
30 // Use esp_delay with a callback that checks if socket data arrived.
31 // This allows the delay to exit early when socket_wake() is called by
32 // lwip recv_fn/accept_fn callbacks, reducing socket latency.
33 s_socket_woke = false;
34 esp_delay(ms, []() { return !s_socket_woke; });
35}
36
38 s_socket_woke = true;
39 esp_schedule();
40}
41#endif
42
43static const char *const TAG = "socket.lwip";
44
45// set to 1 to enable verbose lwip logging
46#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)
47#define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__)
48#else
49#define LWIP_LOG(msg, ...)
50#endif
51
52class LWIPRawImpl : public Socket {
53 public:
54 LWIPRawImpl(sa_family_t family, struct tcp_pcb *pcb) : pcb_(pcb), family_(family) {}
55 ~LWIPRawImpl() override {
56 if (pcb_ != nullptr) {
57 LWIP_LOG("tcp_abort(%p)", pcb_);
58 tcp_abort(pcb_);
59 pcb_ = nullptr;
60 }
61 }
62
63 void init() {
64 LWIP_LOG("init(%p)", pcb_);
65 tcp_arg(pcb_, this);
66 tcp_recv(pcb_, LWIPRawImpl::s_recv_fn);
67 tcp_err(pcb_, LWIPRawImpl::s_err_fn);
68 }
69
70 std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
71 // Non-listening sockets return error
72 errno = EINVAL;
73 return nullptr;
74 }
75 int bind(const struct sockaddr *name, socklen_t addrlen) override {
76 if (pcb_ == nullptr) {
77 errno = EBADF;
78 return -1;
79 }
80 if (name == nullptr) {
81 errno = EINVAL;
82 return 0;
83 }
84 ip_addr_t ip;
85 in_port_t port;
86#if LWIP_IPV6
87 if (family_ == AF_INET) {
88 if (addrlen < sizeof(sockaddr_in)) {
89 errno = EINVAL;
90 return -1;
91 }
92 auto *addr4 = reinterpret_cast<const sockaddr_in *>(name);
93 port = ntohs(addr4->sin_port);
94 ip.type = IPADDR_TYPE_V4;
95 ip.u_addr.ip4.addr = addr4->sin_addr.s_addr;
96 LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip4addr_ntoa(&ip.u_addr.ip4), port);
97 } else if (family_ == AF_INET6) {
98 if (addrlen < sizeof(sockaddr_in6)) {
99 errno = EINVAL;
100 return -1;
101 }
102 auto *addr6 = reinterpret_cast<const sockaddr_in6 *>(name);
103 port = ntohs(addr6->sin6_port);
104 ip.type = IPADDR_TYPE_ANY;
105 memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16);
106 LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip6addr_ntoa(&ip.u_addr.ip6), port);
107 } else {
108 errno = EINVAL;
109 return -1;
110 }
111#else
112 if (family_ != AF_INET) {
113 errno = EINVAL;
114 return -1;
115 }
116 auto *addr4 = reinterpret_cast<const sockaddr_in *>(name);
117 port = ntohs(addr4->sin_port);
118 ip.addr = addr4->sin_addr.s_addr;
119 LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port);
120#endif
121 err_t err = tcp_bind(pcb_, &ip, port);
122 if (err == ERR_USE) {
123 LWIP_LOG(" -> err ERR_USE");
124 errno = EADDRINUSE;
125 return -1;
126 }
127 if (err == ERR_VAL) {
128 LWIP_LOG(" -> err ERR_VAL");
129 errno = EINVAL;
130 return -1;
131 }
132 if (err != ERR_OK) {
133 LWIP_LOG(" -> err %d", err);
134 errno = EIO;
135 return -1;
136 }
137 return 0;
138 }
139 int close() override {
140 if (pcb_ == nullptr) {
141 errno = ECONNRESET;
142 return -1;
143 }
144 LWIP_LOG("tcp_close(%p)", pcb_);
145 err_t err = tcp_close(pcb_);
146 if (err != ERR_OK) {
147 LWIP_LOG(" -> err %d", err);
148 tcp_abort(pcb_);
149 pcb_ = nullptr;
150 errno = err == ERR_MEM ? ENOMEM : EIO;
151 return -1;
152 }
153 pcb_ = nullptr;
154 return 0;
155 }
156 int shutdown(int how) override {
157 if (pcb_ == nullptr) {
158 errno = ECONNRESET;
159 return -1;
160 }
161 bool shut_rx = false, shut_tx = false;
162 if (how == SHUT_RD) {
163 shut_rx = true;
164 } else if (how == SHUT_WR) {
165 shut_tx = true;
166 } else if (how == SHUT_RDWR) {
167 shut_rx = shut_tx = true;
168 } else {
169 errno = EINVAL;
170 return -1;
171 }
172 LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0);
173 err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx);
174 if (err != ERR_OK) {
175 LWIP_LOG(" -> err %d", err);
176 errno = err == ERR_MEM ? ENOMEM : EIO;
177 return -1;
178 }
179 return 0;
180 }
181
182 int getpeername(struct sockaddr *name, socklen_t *addrlen) override {
183 if (pcb_ == nullptr) {
184 errno = ECONNRESET;
185 return -1;
186 }
187 if (name == nullptr || addrlen == nullptr) {
188 errno = EINVAL;
189 return -1;
190 }
191 return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen);
192 }
193 std::string getpeername() override {
194 if (pcb_ == nullptr) {
195 errno = ECONNRESET;
196 return "";
197 }
198 return this->format_ip_address_(pcb_->remote_ip);
199 }
200 int getsockname(struct sockaddr *name, socklen_t *addrlen) override {
201 if (pcb_ == nullptr) {
202 errno = ECONNRESET;
203 return -1;
204 }
205 if (name == nullptr || addrlen == nullptr) {
206 errno = EINVAL;
207 return -1;
208 }
209 return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen);
210 }
211 std::string getsockname() override {
212 if (pcb_ == nullptr) {
213 errno = ECONNRESET;
214 return "";
215 }
216 return this->format_ip_address_(pcb_->local_ip);
217 }
218 int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
219 if (pcb_ == nullptr) {
220 errno = ECONNRESET;
221 return -1;
222 }
223 if (optlen == nullptr || optval == nullptr) {
224 errno = EINVAL;
225 return -1;
226 }
227 if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
228 if (*optlen < 4) {
229 errno = EINVAL;
230 return -1;
231 }
232
233 // lwip doesn't seem to have this feature. Don't send an error
234 // to prevent warnings
235 *reinterpret_cast<int *>(optval) = 1;
236 *optlen = 4;
237 return 0;
238 }
239 if (level == IPPROTO_TCP && optname == TCP_NODELAY) {
240 if (*optlen < 4) {
241 errno = EINVAL;
242 return -1;
243 }
244 *reinterpret_cast<int *>(optval) = nodelay_;
245 *optlen = 4;
246 return 0;
247 }
248
249 errno = EINVAL;
250 return -1;
251 }
252 int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override {
253 if (pcb_ == nullptr) {
254 errno = ECONNRESET;
255 return -1;
256 }
257 if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
258 if (optlen != 4) {
259 errno = EINVAL;
260 return -1;
261 }
262
263 // lwip doesn't seem to have this feature. Don't send an error
264 // to prevent warnings
265 return 0;
266 }
267 if (level == IPPROTO_TCP && optname == TCP_NODELAY) {
268 if (optlen != 4) {
269 errno = EINVAL;
270 return -1;
271 }
272 int val = *reinterpret_cast<const int *>(optval);
273 nodelay_ = val;
274 return 0;
275 }
276
277 errno = EINVAL;
278 return -1;
279 }
280 int listen(int backlog) override {
281 // Regular sockets can't be converted to listening - this shouldn't happen
282 // as listen() should only be called on sockets created for listening
283 errno = EOPNOTSUPP;
284 return -1;
285 }
286 ssize_t read(void *buf, size_t len) override {
287 if (pcb_ == nullptr) {
288 errno = ECONNRESET;
289 return -1;
290 }
291 if (rx_closed_ && rx_buf_ == nullptr) {
292 return 0;
293 }
294 if (len == 0) {
295 return 0;
296 }
297 if (rx_buf_ == nullptr) {
298 errno = EWOULDBLOCK;
299 return -1;
300 }
301
302 size_t read = 0;
303 uint8_t *buf8 = reinterpret_cast<uint8_t *>(buf);
304 while (len && rx_buf_ != nullptr) {
305 size_t pb_len = rx_buf_->len;
306 size_t pb_left = pb_len - rx_buf_offset_;
307 if (pb_left == 0)
308 break;
309 size_t copysize = std::min(len, pb_left);
310 memcpy(buf8, reinterpret_cast<uint8_t *>(rx_buf_->payload) + rx_buf_offset_, copysize);
311
312 if (pb_left == copysize) {
313 // full pb copied, free it
314 if (rx_buf_->next == nullptr) {
315 // last buffer in chain
316 pbuf_free(rx_buf_);
317 rx_buf_ = nullptr;
318 rx_buf_offset_ = 0;
319 } else {
320 auto *old_buf = rx_buf_;
321 rx_buf_ = rx_buf_->next;
322 pbuf_ref(rx_buf_);
323 pbuf_free(old_buf);
324 rx_buf_offset_ = 0;
325 }
326 } else {
327 rx_buf_offset_ += copysize;
328 }
329 LWIP_LOG("tcp_recved(%p %u)", pcb_, copysize);
330 tcp_recved(pcb_, copysize);
331
332 buf8 += copysize;
333 len -= copysize;
334 read += copysize;
335 }
336
337 if (read == 0) {
338 errno = EWOULDBLOCK;
339 return -1;
340 }
341
342 return read;
343 }
344 ssize_t readv(const struct iovec *iov, int iovcnt) override {
345 ssize_t ret = 0;
346 for (int i = 0; i < iovcnt; i++) {
347 ssize_t err = read(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
348 if (err == -1) {
349 if (ret != 0) {
350 // if we already read some don't return an error
351 break;
352 }
353 return err;
354 }
355 ret += err;
356 if ((size_t) err != iov[i].iov_len)
357 break;
358 }
359 return ret;
360 }
361
362 ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override {
363 errno = ENOTSUP;
364 return -1;
365 }
366
367 ssize_t internal_write(const void *buf, size_t len) {
368 if (pcb_ == nullptr) {
369 errno = ECONNRESET;
370 return -1;
371 }
372 if (len == 0)
373 return 0;
374 if (buf == nullptr) {
375 errno = EINVAL;
376 return 0;
377 }
378 auto space = tcp_sndbuf(pcb_);
379 if (space == 0) {
380 errno = EWOULDBLOCK;
381 return -1;
382 }
383 size_t to_send = std::min((size_t) space, len);
384 LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send);
385 err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY);
386 if (err == ERR_MEM) {
387 LWIP_LOG(" -> err ERR_MEM");
388 errno = EWOULDBLOCK;
389 return -1;
390 }
391 if (err != ERR_OK) {
392 LWIP_LOG(" -> err %d", err);
393 errno = ECONNRESET;
394 return -1;
395 }
396 return to_send;
397 }
398 int internal_output() {
399 LWIP_LOG("tcp_output(%p)", pcb_);
400 err_t err = tcp_output(pcb_);
401 if (err == ERR_ABRT) {
402 LWIP_LOG(" -> err ERR_ABRT");
403 // sometimes lwip returns ERR_ABRT for no apparent reason
404 // the connection works fine afterwards, and back with ESPAsyncTCP we
405 // indirectly also ignored this error
406 // FIXME: figure out where this is returned and what it means in this context
407 return 0;
408 }
409 if (err != ERR_OK) {
410 LWIP_LOG(" -> err %d", err);
411 errno = ECONNRESET;
412 return -1;
413 }
414 return 0;
415 }
416 ssize_t write(const void *buf, size_t len) override {
417 ssize_t written = internal_write(buf, len);
418 if (written == -1)
419 return -1;
420 if (written == 0) {
421 // no need to output if nothing written
422 return 0;
423 }
424 if (nodelay_) {
425 int err = internal_output();
426 if (err == -1)
427 return -1;
428 }
429 return written;
430 }
431 ssize_t writev(const struct iovec *iov, int iovcnt) override {
432 ssize_t written = 0;
433 for (int i = 0; i < iovcnt; i++) {
434 ssize_t err = internal_write(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
435 if (err == -1) {
436 if (written != 0) {
437 // if we already read some don't return an error
438 break;
439 }
440 return err;
441 }
442 written += err;
443 if ((size_t) err != iov[i].iov_len)
444 break;
445 }
446 if (written == 0) {
447 // no need to output if nothing written
448 return 0;
449 }
450 if (nodelay_) {
451 int err = internal_output();
452 if (err == -1)
453 return -1;
454 }
455 return written;
456 }
457 ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override {
458 // return ::sendto(fd_, buf, len, flags, to, tolen);
459 errno = ENOSYS;
460 return -1;
461 }
462 int setblocking(bool blocking) override {
463 if (pcb_ == nullptr) {
464 errno = ECONNRESET;
465 return -1;
466 }
467 if (blocking) {
468 // blocking operation not supported
469 errno = EINVAL;
470 return -1;
471 }
472 return 0;
473 }
474
475 void err_fn(err_t err) {
476 LWIP_LOG("err(err=%d)", err);
477 // "If a connection is aborted because of an error, the application is alerted of this event by
478 // the err callback."
479 // pcb is already freed when this callback is called
480 // ERR_RST: connection was reset by remote host
481 // ERR_ABRT: aborted through tcp_abort or TCP timer
482 pcb_ = nullptr;
483 }
484 err_t recv_fn(struct pbuf *pb, err_t err) {
485 LWIP_LOG("recv(pb=%p err=%d)", pb, err);
486 if (err != 0) {
487 // "An error code if there has been an error receiving Only return ERR_ABRT if you have
488 // called tcp_abort from within the callback function!"
489 rx_closed_ = true;
490 return ERR_OK;
491 }
492 if (pb == nullptr) {
493 rx_closed_ = true;
494 return ERR_OK;
495 }
496 if (rx_buf_ == nullptr) {
497 // no need to copy because lwIP gave control of it to us
498 rx_buf_ = pb;
499 rx_buf_offset_ = 0;
500 } else {
501 pbuf_cat(rx_buf_, pb);
502 }
503#ifdef USE_ESP8266
504 // Wake the main loop immediately so it can process the received data.
505 socket_wake();
506#endif
507 return ERR_OK;
508 }
509
510 static void s_err_fn(void *arg, err_t err) {
511 LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
512 arg_this->err_fn(err);
513 }
514
515 static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) {
516 LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
517 return arg_this->recv_fn(pb, err);
518 }
519
520 protected:
521 std::string format_ip_address_(const ip_addr_t &ip) {
522 char buffer[50] = {};
523 if (IP_IS_V4_VAL(ip)) {
524 inet_ntoa_r(ip, buffer, sizeof(buffer));
525 }
526#if LWIP_IPV6
527 else if (IP_IS_V6_VAL(ip)) {
528 inet6_ntoa_r(ip, buffer, sizeof(buffer));
529 }
530#endif
531 return std::string(buffer);
532 }
533
534 int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
535 if (family_ == AF_INET) {
536 if (*addrlen < sizeof(struct sockaddr_in)) {
537 errno = EINVAL;
538 return -1;
539 }
540
541 struct sockaddr_in *addr = reinterpret_cast<struct sockaddr_in *>(name);
542 addr->sin_family = AF_INET;
543 *addrlen = addr->sin_len = sizeof(struct sockaddr_in);
544 addr->sin_port = port;
545 inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip));
546 return 0;
547 }
548#if LWIP_IPV6
549 else if (family_ == AF_INET6) {
550 if (*addrlen < sizeof(struct sockaddr_in6)) {
551 errno = EINVAL;
552 return -1;
553 }
554
555 struct sockaddr_in6 *addr = reinterpret_cast<struct sockaddr_in6 *>(name);
556 addr->sin6_family = AF_INET6;
557 *addrlen = addr->sin6_len = sizeof(struct sockaddr_in6);
558 addr->sin6_port = port;
559
560 // AF_INET6 sockets are bound to IPv4 as well, so we may encounter IPv4 addresses that must be converted to IPv6.
561 if (IP_IS_V4(ip)) {
562 ip_addr_t mapped;
563 ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip));
564 inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped));
565 } else {
566 inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip));
567 }
568 return 0;
569 }
570#endif
571 return -1;
572 }
573
574 // Member ordering optimized to minimize padding on 32-bit systems
575 // Largest members first (4 bytes), then smaller members (1 byte each)
576 struct tcp_pcb *pcb_;
577 pbuf *rx_buf_ = nullptr;
578 size_t rx_buf_offset_ = 0;
579 bool rx_closed_ = false;
580 // don't use lwip nodelay flag, it sometimes causes reconnect
581 // instead use it for determining whether to call lwip_output
582 bool nodelay_ = false;
583 sa_family_t family_ = 0;
584};
585
586// Listening socket class - only allocates accept queue when needed (for bind+listen sockets)
587// This saves 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) for regular connected sockets on ESP8266/RP2040
588class LWIPRawListenImpl : public LWIPRawImpl {
589 public:
590 LWIPRawListenImpl(sa_family_t family, struct tcp_pcb *pcb) : LWIPRawImpl(family, pcb) {}
591
592 void init() {
593 LWIP_LOG("init(%p)", pcb_);
594 tcp_arg(pcb_, this);
595 tcp_accept(pcb_, LWIPRawListenImpl::s_accept_fn);
596 tcp_err(pcb_, LWIPRawImpl::s_err_fn); // Use base class error handler
597 }
598
599 std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
600 if (pcb_ == nullptr) {
601 errno = EBADF;
602 return nullptr;
603 }
604 if (accepted_socket_count_ == 0) {
605 errno = EWOULDBLOCK;
606 return nullptr;
607 }
608 // Take from front for FIFO ordering
609 std::unique_ptr<LWIPRawImpl> sock = std::move(accepted_sockets_[0]);
610 // Shift remaining sockets forward
611 for (uint8_t i = 1; i < accepted_socket_count_; i++) {
612 accepted_sockets_[i - 1] = std::move(accepted_sockets_[i]);
613 }
614 accepted_socket_count_--;
615 LWIP_LOG("Connection accepted by application, queue size: %d", accepted_socket_count_);
616 if (addr != nullptr) {
617 sock->getpeername(addr, addrlen);
618 }
619 LWIP_LOG("accept(%p)", sock.get());
620 return std::unique_ptr<Socket>(std::move(sock));
621 }
622
623 int listen(int backlog) override {
624 if (pcb_ == nullptr) {
625 errno = EBADF;
626 return -1;
627 }
628 LWIP_LOG("tcp_listen_with_backlog(%p backlog=%d)", pcb_, backlog);
629 struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog);
630 if (listen_pcb == nullptr) {
631 tcp_abort(pcb_);
632 pcb_ = nullptr;
633 errno = EOPNOTSUPP;
634 return -1;
635 }
636 // tcp_listen reallocates the pcb, replace ours
637 pcb_ = listen_pcb;
638 // set callbacks on new pcb
639 LWIP_LOG("tcp_arg(%p)", pcb_);
640 tcp_arg(pcb_, this);
641 tcp_accept(pcb_, LWIPRawListenImpl::s_accept_fn);
642 return 0;
643 }
644
645 private:
646 err_t accept_fn_(struct tcp_pcb *newpcb, err_t err) {
647 LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err);
648 if (err != ERR_OK || newpcb == nullptr) {
649 // "An error code if there has been an error accepting. Only return ERR_ABRT if you have
650 // called tcp_abort from within the callback function!"
651 // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d
652 // nothing to do here, we just don't push it to the queue
653 return ERR_OK;
654 }
655 // Check if we've reached the maximum accept queue size
656 if (accepted_socket_count_ >= MAX_ACCEPTED_SOCKETS) {
657 LWIP_LOG("Rejecting connection, queue full (%d)", accepted_socket_count_);
658 // Abort the connection when queue is full
659 tcp_abort(newpcb);
660 // Must return ERR_ABRT since we called tcp_abort()
661 return ERR_ABRT;
662 }
663 auto sock = make_unique<LWIPRawImpl>(family_, newpcb);
664 sock->init();
665 accepted_sockets_[accepted_socket_count_++] = std::move(sock);
666 LWIP_LOG("Accepted connection, queue size: %d", accepted_socket_count_);
667#ifdef USE_ESP8266
668 // Wake the main loop immediately so it can accept the new connection.
669 socket_wake();
670#endif
671 return ERR_OK;
672 }
673
674 static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) {
675 LWIPRawListenImpl *arg_this = reinterpret_cast<LWIPRawListenImpl *>(arg);
676 return arg_this->accept_fn_(newpcb, err);
677 }
678
679 // Accept queue - holds incoming connections briefly until the event loop calls accept()
680 // This is NOT a connection pool - just a temporary queue between LWIP callbacks and the main loop
681 // 3 slots is plenty since connections are pulled out quickly by the event loop
682 //
683 // Memory analysis: std::array<3> vs original std::queue implementation:
684 // - std::queue uses std::deque internally which on 32-bit systems needs:
685 // 24 bytes (deque object) + 32+ bytes (map array) + heap allocations
686 // Total: ~56+ bytes minimum, plus heap fragmentation
687 // - std::array<3>: 12 bytes fixed (3 pointers × 4 bytes)
688 // Saves ~44+ bytes RAM per listening socket + avoids ALL heap allocations
689 // Used on ESP8266 and RP2040 (platforms using LWIP_TCP implementation)
690 //
691 // By using a separate listening socket class, regular connected sockets save
692 // 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) of memory overhead on 32-bit systems
693 static constexpr size_t MAX_ACCEPTED_SOCKETS = 3;
694 std::array<std::unique_ptr<LWIPRawImpl>, MAX_ACCEPTED_SOCKETS> accepted_sockets_;
695 uint8_t accepted_socket_count_ = 0; // Number of sockets currently in queue
696};
697
698std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
699 auto *pcb = tcp_new();
700 if (pcb == nullptr)
701 return nullptr;
702 // Create listening socket implementation since user sockets typically bind+listen
703 // Accepted connections are created directly as LWIPRawImpl in the accept callback
704 auto *sock = new LWIPRawListenImpl((sa_family_t) domain, pcb); // NOLINT(cppcoreguidelines-owning-memory)
705 sock->init();
706 return std::unique_ptr<Socket>{sock};
707}
708
709std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol) {
710 // LWIPRawImpl doesn't use file descriptors, so monitoring is not applicable
711 return socket(domain, type, protocol);
712}
713
714} // namespace socket
715} // namespace esphome
716
717#endif // USE_SOCKET_IMPL_LWIP_TCP
uint16_t type
uint16_t flags
uint16_t addr_len
uint16_t in_port_t
Definition headers.h:58
uint32_t socklen_t
Definition headers.h:97
uint8_t sa_family_t
Definition headers.h:57
__int64 ssize_t
Definition httplib.h:178
in_addr ip_addr_t
Definition ip_address.h:22
mopeka_std_values val[4]
void socket_wake()
Called by lwip callbacks to signal socket activity and wake delay.
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.
void socket_delay(uint32_t ms)
Delay that can be woken early by socket activity.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:503
uint8_t sin6_len
Definition headers.h:73
in_port_t sin6_port
Definition headers.h:75
struct in6_addr sin6_addr
Definition headers.h:77
sa_family_t sin6_family
Definition headers.h:74
struct in_addr sin_addr
Definition headers.h:65
uint8_t sin_len
Definition headers.h:62
sa_family_t sin_family
Definition headers.h:63
in_port_t sin_port
Definition headers.h:64