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