Files
esphome-lsm6ds/esphome/components/socket/lwip_raw_tcp_impl.h
T

201 lines
6.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <array>
#include <cerrno>
#include <cstring>
#include <memory>
#include <span>
#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;
/// 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();
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 *name, socklen_t *addrlen);
int getsockname(struct sockaddr *name, socklen_t *addrlen);
/// Format peer address into a fixed-size buffer (no heap allocation)
size_t getpeername_to(std::span<char, SOCKADDR_STR_LEN> buf);
/// Format local address into a fixed-size buffer (no heap allocation)
size_t getsockname_to(std::span<char, SOCKADDR_STR_LEN> 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();
// Non-listening sockets return error
std::unique_ptr<LWIPRawImpl> accept(struct sockaddr *, socklen_t *) {
errno = EINVAL;
return nullptr;
}
std::unique_ptr<LWIPRawImpl> accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) {
return this->accept(addr, addrlen);
}
// Regular sockets can't be converted to listening - this shouldn't happen
// as listen() should only be called on sockets created for listening
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) {
// return ::sendto(fd_, buf, len, flags, to, tolen);
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) {
// blocking operation not supported
errno = EINVAL;
return -1;
}
return 0;
}
int loop() { return 0; }
err_t recv_fn(struct pbuf *pb, err_t err);
static void s_err_fn(void *arg, err_t err);
static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t 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();
bool ready() const { return this->accepted_socket_count_ > 0; }
std::unique_ptr<LWIPRawImpl> accept(struct sockaddr *addr, socklen_t *addrlen);
std::unique_ptr<LWIPRawImpl> 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; }
static void s_err_fn(void *arg, err_t 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);
// 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<std::unique_ptr<LWIPRawImpl>, MAX_ACCEPTED_SOCKETS> accepted_sockets_;
uint8_t accepted_socket_count_ = 0; // Number of sockets currently in queue
};
} // namespace esphome::socket
#endif // USE_SOCKET_IMPL_LWIP_TCP