[socket] Add lwip raw UDP socket implementation

Add native UDP support to the lwip raw TCP socket layer used by
ESP8266 and RP2040, eliminating the need for Arduino WiFiUDP fallback.

Two new classes:
- LWIPRawUDPImpl: send-only UDP (8 bytes overhead)
- LWIPRawUDPRecvImpl: send+recv with fixed-size ring buffer (no heap
  allocation in recv callback)

Factory functions: socket_udp(), socket_udp_recv(), socket_ip_udp(),
socket_ip_udp_recv() with UDPSocket/UDPRecvSocket type aliases.

Additive only — no consumer migration in this PR.
This commit is contained in:
J. Nick Koston
2026-03-10 01:12:15 -10:00
parent 81d12fd14a
commit 49ba08cec9
5 changed files with 549 additions and 40 deletions
+10
View File
@@ -20,6 +20,16 @@
#define IPPROTO_IP 0
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IP_ADD_MEMBERSHIP 3
#define IP_DROP_MEMBERSHIP 4
// NOLINTNEXTLINE(readability-identifier-naming)
struct ip_mreq {
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
#if LWIP_IPV6
#define AF_INET6 10
+409 -39
View File
@@ -9,6 +9,10 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "lwip/igmp.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
#ifdef USE_ESP8266
#include <coredecls.h> // For esp_schedule()
#elif defined(USE_RP2040)
@@ -138,6 +142,48 @@ static const char *const TAG = "socket.lwip";
#define LWIP_LOG(msg, ...)
#endif
// ---- Shared helpers ----
/// Convert lwip ip_addr_t + host-order port to sockaddr, based on the socket's address family.
/// Shared by both TCP (LWIPRawCommon) and UDP (LWIPRawUDPImpl) implementations.
static int lwip_ip_to_sockaddr(sa_family_t family, const ip_addr_t *ip, uint16_t port_host, struct sockaddr *name,
socklen_t *addrlen) {
if (family == AF_INET) {
if (*addrlen < sizeof(struct sockaddr_in)) {
errno = EINVAL;
return -1;
}
auto *addr = reinterpret_cast<struct sockaddr_in *>(name);
addr->sin_family = AF_INET;
*addrlen = addr->sin_len = sizeof(struct sockaddr_in);
addr->sin_port = htons(port_host);
inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip));
return 0;
}
#if LWIP_IPV6
if (family == AF_INET6) {
if (*addrlen < sizeof(struct sockaddr_in6)) {
errno = EINVAL;
return -1;
}
auto *addr = reinterpret_cast<struct sockaddr_in6 *>(name);
addr->sin6_family = AF_INET6;
*addrlen = addr->sin6_len = sizeof(struct sockaddr_in6);
addr->sin6_port = htons(port_host);
// AF_INET6 sockets may receive IPv4 packets; convert to IPv4-mapped 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;
}
// ---- LWIPRawCommon methods ----
LWIPRawCommon::~LWIPRawCommon() {
@@ -372,43 +418,8 @@ int LWIPRawCommon::setsockopt(int level, int optname, const void *optval, sockle
}
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<struct sockaddr_in *>(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<struct sockaddr_in6 *>(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;
// TCP pcb stores port in network byte order; convert to host order for the shared helper
return lwip_ip_to_sockaddr(this->family_, ip, ntohs(port), name, addrlen);
}
// ---- LWIPRawImpl methods ----
@@ -774,11 +785,350 @@ err_t LWIPRawListenImpl::accept_fn_(struct tcp_pcb *newpcb, err_t err) {
return ERR_OK;
}
// ---- LWIPRawUDPImpl (send-only) methods ----
LWIPRawUDPImpl::LWIPRawUDPImpl(sa_family_t family) : family_(family) {
#if LWIP_IPV6
this->pcb_ = udp_new_ip_type(family == AF_INET6 ? IPADDR_TYPE_ANY : IPADDR_TYPE_V4);
#else
this->pcb_ = udp_new();
#endif
}
LWIPRawUDPImpl::~LWIPRawUDPImpl() {
if (this->pcb_ != nullptr) {
udp_remove(this->pcb_);
this->pcb_ = nullptr;
}
}
int LWIPRawUDPImpl::bind_internal_(const struct sockaddr *name, socklen_t addrlen) {
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (name == nullptr) {
errno = EINVAL;
return -1;
}
ip_addr_t ip;
uint16_t port;
if (!sockaddr_to_lwip(name, addrlen, &ip, &port)) {
errno = EINVAL;
return -1;
}
#if LWIP_IPV6
// For bind, use IPADDR_TYPE_ANY on IPv6 sockets to accept both IPv4 and IPv6
// packets (dual-stack). sockaddr_to_lwip uses IPADDR_TYPE_V6 which is correct
// for sendto destinations but too restrictive for bind.
if (this->family_ == AF_INET6) {
ip.type = IPADDR_TYPE_ANY;
}
#endif
err_t err = udp_bind(this->pcb_, &ip, port);
if (err == ERR_USE) {
errno = EADDRINUSE;
return -1;
}
if (err == ERR_VAL) {
errno = EINVAL;
return -1;
}
if (err != ERR_OK) {
errno = EIO;
return -1;
}
return 0;
}
int LWIPRawUDPImpl::bind(const struct sockaddr *name, socklen_t addrlen) { return this->bind_internal_(name, addrlen); }
int LWIPRawUDPImpl::close() {
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
udp_remove(this->pcb_);
this->pcb_ = nullptr;
return 0;
}
bool LWIPRawUDPImpl::sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port) {
if (addrlen < sizeof(sa_family_t))
return false;
#if LWIP_IPV6
if (addr->sa_family == AF_INET) {
if (addrlen < sizeof(sockaddr_in))
return false;
auto *addr4 = reinterpret_cast<const sockaddr_in *>(addr);
*port = ntohs(addr4->sin_port);
ip->type = IPADDR_TYPE_V4;
ip->u_addr.ip4.addr = addr4->sin_addr.s_addr;
return true;
}
if (addr->sa_family == AF_INET6) {
if (addrlen < sizeof(sockaddr_in6))
return false;
auto *addr6 = reinterpret_cast<const sockaddr_in6 *>(addr);
*port = ntohs(addr6->sin6_port);
ip->type = IPADDR_TYPE_V6;
memcpy(&ip->u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16);
return true;
}
#else
if (addr->sa_family == AF_INET) {
if (addrlen < sizeof(sockaddr_in))
return false;
auto *addr4 = reinterpret_cast<const sockaddr_in *>(addr);
*port = ntohs(addr4->sin_port);
ip->addr = addr4->sin_addr.s_addr;
return true;
}
#endif
return false;
}
int LWIPRawUDPImpl::ip2sockaddr_(const ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
// UDP recv callback provides port in host byte order
return lwip_ip_to_sockaddr(this->family_, ip, port, name, addrlen);
}
ssize_t LWIPRawUDPImpl::sendto(const void *buf, size_t len, int flags, const struct sockaddr *dest_addr,
socklen_t addrlen) {
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (buf == nullptr || dest_addr == nullptr) {
errno = EINVAL;
return -1;
}
// pbuf_alloc takes u16_t length; reject oversized packets
if (len > UINT16_MAX) {
errno = EMSGSIZE;
return -1;
}
ip_addr_t dst_ip;
uint16_t dst_port;
if (!sockaddr_to_lwip(dest_addr, addrlen, &dst_ip, &dst_port)) {
errno = EINVAL;
return -1;
}
// Allocate pbuf and copy data
struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, (uint16_t) len, PBUF_RAM);
if (pb == nullptr) {
errno = ENOMEM;
return -1;
}
memcpy(pb->payload, buf, len);
err_t err = udp_sendto(this->pcb_, pb, &dst_ip, dst_port);
pbuf_free(pb);
if (err != ERR_OK) {
errno = err == ERR_MEM ? ENOMEM : EIO;
return -1;
}
return (ssize_t) len;
}
int LWIPRawUDPImpl::setsockopt(int level, int optname, const void *optval, socklen_t optlen) {
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
// lwip raw UDP doesn't enforce port exclusivity the same way,
// but we accept this silently for compatibility
return 0;
}
if (level == SOL_SOCKET && optname == SO_BROADCAST) {
if (optval == nullptr || optlen < sizeof(int)) {
errno = EINVAL;
return -1;
}
int val = *reinterpret_cast<const int *>(optval);
if (val) {
ip_set_option(this->pcb_, SOF_BROADCAST);
} else {
ip_reset_option(this->pcb_, SOF_BROADCAST);
}
return 0;
}
if (level == IPPROTO_IP && optname == IP_ADD_MEMBERSHIP) {
if (optval == nullptr || optlen < sizeof(struct ip_mreq)) {
errno = EINVAL;
return -1;
}
auto *mreq = reinterpret_cast<const struct ip_mreq *>(optval);
ip4_addr_t multiaddr;
multiaddr.addr = mreq->imr_multiaddr.s_addr;
ip4_addr_t ifaddr;
ifaddr.addr = mreq->imr_interface.s_addr;
err_t err = igmp_joingroup(&ifaddr, &multiaddr);
if (err != ERR_OK) {
errno = EIO;
return -1;
}
return 0;
}
if (level == IPPROTO_IP && optname == IP_DROP_MEMBERSHIP) {
if (optval == nullptr || optlen < sizeof(struct ip_mreq)) {
errno = EINVAL;
return -1;
}
auto *mreq = reinterpret_cast<const struct ip_mreq *>(optval);
ip4_addr_t multiaddr;
multiaddr.addr = mreq->imr_multiaddr.s_addr;
ip4_addr_t ifaddr;
ifaddr.addr = mreq->imr_interface.s_addr;
err_t err = igmp_leavegroup(&ifaddr, &multiaddr);
if (err != ERR_OK) {
errno = EIO;
return -1;
}
return 0;
}
errno = ENOPROTOOPT;
return -1;
}
int LWIPRawUDPImpl::getsockopt(int level, int optname, void *optval, socklen_t *optlen) {
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
if (optval == nullptr || optlen == nullptr || *optlen < sizeof(int)) {
errno = EINVAL;
return -1;
}
*reinterpret_cast<int *>(optval) = 1;
*optlen = sizeof(int);
return 0;
}
errno = ENOPROTOOPT;
return -1;
}
int LWIPRawUDPImpl::setblocking(bool blocking) {
if (blocking) {
// blocking operation not supported on raw lwip
errno = EINVAL;
return -1;
}
return 0;
}
// ---- LWIPRawUDPRecvImpl methods ----
LWIPRawUDPRecvImpl::~LWIPRawUDPRecvImpl() {
// Flush rx queue and unregister callback before base destructor removes pcb
if (this->pcb_ != nullptr)
this->close();
}
int LWIPRawUDPRecvImpl::close() {
// Unregister recv callback before removing pcb
if (this->pcb_ != nullptr) {
udp_recv(this->pcb_, nullptr, nullptr);
}
// Flush any queued rx packets
while (this->rx_read_idx_ != this->rx_write_idx_) {
auto &pkt = this->rx_queue_[this->rx_read_idx_];
if (pkt.pb != nullptr) {
pbuf_free(pkt.pb);
pkt.pb = nullptr;
}
this->rx_read_idx_ = (this->rx_read_idx_ + 1) & UDP_RX_MASK;
}
// close() returns EBADF if already closed, which is fine from destructor
return LWIPRawUDPImpl::close();
}
int LWIPRawUDPRecvImpl::bind(const struct sockaddr *name, socklen_t addrlen) {
int ret = this->bind_internal_(name, addrlen);
if (ret != 0)
return ret;
// Register recv callback now that we're bound and ready to receive
udp_recv(this->pcb_, LWIPRawUDPRecvImpl::s_recv_fn, this);
return 0;
}
ssize_t LWIPRawUDPRecvImpl::read(void *buf, size_t len) { return this->recvfrom(buf, len, nullptr, nullptr); }
ssize_t LWIPRawUDPRecvImpl::recvfrom(void *buf, size_t len, struct sockaddr *src_addr, socklen_t *addrlen) {
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (buf == nullptr && len > 0) {
errno = EINVAL;
return -1;
}
if (this->rx_read_idx_ == this->rx_write_idx_) {
errno = EWOULDBLOCK;
return -1;
}
auto &pkt = this->rx_queue_[this->rx_read_idx_];
size_t pkt_len = pkt.pb->tot_len;
size_t copy_len = std::min(len, pkt_len);
// Copy data from pbuf chain
pbuf_copy_partial(pkt.pb, buf, copy_len, 0);
// Fill in source address if requested
if (src_addr != nullptr && addrlen != nullptr) {
this->ip2sockaddr_(&pkt.src_addr, pkt.src_port, src_addr, addrlen);
}
// Free the pbuf and advance the read pointer — must be last,
// as this publishes the slot to the producer (recv callback).
pbuf_free(pkt.pb);
pkt.pb = nullptr;
this->rx_read_idx_ = (this->rx_read_idx_ + 1) & UDP_RX_MASK;
return (ssize_t) copy_len;
}
void LWIPRawUDPRecvImpl::s_recv_fn(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) {
auto *self = reinterpret_cast<LWIPRawUDPRecvImpl *>(arg);
self->recv_fn_(p, addr, port);
}
void LWIPRawUDPRecvImpl::recv_fn_(struct pbuf *p, const ip_addr_t *addr, u16_t port) {
if (p == nullptr)
return;
// Check if queue is full (next write position would collide with read position)
uint8_t next_write = (this->rx_write_idx_ + 1) & UDP_RX_MASK;
if (next_write == this->rx_read_idx_) {
// Drop packet — queue full
pbuf_free(p);
return;
}
// Enqueue the packet — write data first, then publish by advancing write index.
auto &slot = this->rx_queue_[this->rx_write_idx_];
slot.pb = p;
slot.src_addr = *addr;
slot.src_port = port;
this->rx_write_idx_ = next_write;
#if defined(USE_ESP8266) || defined(USE_RP2040)
socket_wake();
#endif
}
// ---- Factory functions ----
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
if (type != SOCK_STREAM) {
ESP_LOGE(TAG, "UDP sockets not supported on this platform, use WiFiUDP");
ESP_LOGE(TAG, "Use socket_udp() for UDP sockets on this platform");
errno = EPROTOTYPE;
return nullptr;
}
@@ -796,9 +1146,29 @@ std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol
return socket(domain, type, protocol);
}
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol) {
(void) protocol; // Raw lwip UDP ignores protocol; kept for API compatibility
auto sock = make_unique<LWIPRawUDPImpl>((sa_family_t) domain);
if (!sock->is_valid()) {
errno = ENOMEM;
return nullptr;
}
return sock;
}
std::unique_ptr<UDPRecvSocket> socket_udp_recv(int domain, int protocol) {
(void) protocol; // Raw lwip UDP ignores protocol; kept for API compatibility
auto sock = make_unique<LWIPRawUDPRecvImpl>((sa_family_t) domain);
if (!sock->is_valid()) {
errno = ENOMEM;
return nullptr;
}
return sock;
}
std::unique_ptr<ListenSocket> socket_listen(int domain, int type, int protocol) {
if (type != SOCK_STREAM) {
ESP_LOGE(TAG, "UDP sockets not supported on this platform, use WiFiUDP");
ESP_LOGE(TAG, "Use socket_udp() for UDP sockets on this platform");
errno = EPROTOTYPE;
return nullptr;
}
@@ -15,6 +15,7 @@
#include "lwip/netif.h"
#include "lwip/opt.h"
#include "lwip/tcp.h"
#include "lwip/udp.h"
namespace esphome::socket {
@@ -201,6 +202,91 @@ class LWIPRawListenImpl : public LWIPRawCommon {
uint8_t accepted_socket_count_ = 0; // Number of sockets currently in queue
};
/// Send-only UDP socket implementation for LWIP raw API.
/// Non-virtual, concrete type. Uses lwip/udp.h raw API.
/// No receive capability — use LWIPRawUDPRecvImpl for sockets that need to receive.
class LWIPRawUDPImpl {
public:
LWIPRawUDPImpl(sa_family_t family);
~LWIPRawUDPImpl();
LWIPRawUDPImpl(const LWIPRawUDPImpl &) = delete;
LWIPRawUDPImpl &operator=(const LWIPRawUDPImpl &) = delete;
int bind(const struct sockaddr *name, socklen_t addrlen);
int close();
/// Send a UDP packet to the specified destination.
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, 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);
int setblocking(bool blocking);
bool is_valid() const { return this->pcb_ != nullptr; }
bool ready() const { return false; }
int get_fd() const { return -1; }
protected:
/// Convert sockaddr to lwip ip_addr_t and port.
static bool sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port);
/// Convert lwip ip_addr_t and port to sockaddr.
int ip2sockaddr_(const ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen);
/// Shared bind logic — parses sockaddr and calls udp_bind.
int bind_internal_(const struct sockaddr *name, socklen_t addrlen);
struct udp_pcb *pcb_{nullptr};
sa_family_t family_{0};
};
/// UDP socket with receive support for LWIP raw API.
/// Extends LWIPRawUDPImpl with a fixed-size ring buffer for incoming packets.
/// The recv callback is registered on bind().
class LWIPRawUDPRecvImpl : public LWIPRawUDPImpl {
public:
using LWIPRawUDPImpl::LWIPRawUDPImpl;
~LWIPRawUDPRecvImpl();
/// Close the socket, flushing any queued rx packets first.
int close();
/// Bind and register the recv callback for incoming packets.
int bind(const struct sockaddr *name, socklen_t addrlen);
/// Read the next queued packet, discarding source address info.
/// If buf is smaller than the packet, data is silently truncated (returns bytes copied).
ssize_t read(void *buf, size_t len);
/// Read the next queued packet and return the source address.
/// If buf is smaller than the packet, data is silently truncated (returns bytes copied).
ssize_t recvfrom(void *buf, size_t len, struct sockaddr *src_addr, socklen_t *addrlen);
/// Returns true if there are packets available to read.
bool ready() const { return this->rx_read_idx_ != this->rx_write_idx_; }
protected:
static void s_recv_fn(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
void recv_fn_(struct pbuf *p, const ip_addr_t *addr, u16_t port);
/// Lock-free SPSC ring buffer for received UDP packets.
/// Producer (recv callback, possibly IRQ context on RP2040) writes rx_write_idx_.
/// Consumer (main loop) writes rx_read_idx_.
/// No shared read-modify-write — safe without locking.
/// One slot is reserved to distinguish full from empty, giving 3 usable slots.
/// No heap allocation in the recv callback — packets are dropped if the queue is full.
static constexpr uint8_t UDP_RX_QUEUE_SIZE = 4; // Must be power of 2
static constexpr uint8_t UDP_RX_MASK = UDP_RX_QUEUE_SIZE - 1;
static_assert((UDP_RX_QUEUE_SIZE & UDP_RX_MASK) == 0, "UDP_RX_QUEUE_SIZE must be power of 2");
struct UDPRxPacket {
struct pbuf *pb{nullptr};
ip_addr_t src_addr{};
uint16_t src_port{0};
};
std::array<UDPRxPacket, UDP_RX_QUEUE_SIZE> rx_queue_{};
volatile uint8_t rx_read_idx_{0}; ///< Written by consumer (main loop), read by producer
volatile uint8_t rx_write_idx_{0}; ///< Written by producer (recv callback), read by consumer
};
} // namespace esphome::socket
#endif // USE_SOCKET_IMPL_LWIP_TCP
+26
View File
@@ -101,6 +101,32 @@ std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int protocol) {
}
#endif
#if !defined(USE_SOCKET_IMPL_LWIP_TCP)
// BSD and LWIP_SOCKETS: UDPSocket == UDPRecvSocket == Socket, so these just delegate.
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol) {
return esphome::socket::socket(domain, SOCK_DGRAM, protocol);
}
std::unique_ptr<UDPRecvSocket> socket_udp_recv(int domain, int protocol) {
return esphome::socket::socket(domain, SOCK_DGRAM, protocol);
}
#endif
std::unique_ptr<UDPSocket> socket_ip_udp(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp(AF_INET6, protocol);
#else
return socket_udp(AF_INET, protocol);
#endif
}
std::unique_ptr<UDPRecvSocket> socket_ip_udp_recv(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp_recv(AF_INET6, protocol);
#else
return socket_udp_recv(AF_INET, protocol);
#endif
}
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) {
#if USE_NETWORK_IPV6
if (strchr(ip_address, ':') != nullptr) {
+18 -1
View File
@@ -27,17 +27,24 @@ namespace esphome::socket {
// 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.
// UDPSocket is the concrete type for UDP sockets.
// On BSD and LWIP_SOCKETS, all 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;
using UDPSocket = BSDSocketImpl;
using UDPRecvSocket = BSDSocketImpl;
#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
using Socket = LwIPSocketImpl;
using ListenSocket = LwIPSocketImpl;
using UDPSocket = LwIPSocketImpl;
using UDPRecvSocket = LwIPSocketImpl;
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
using Socket = LWIPRawImpl;
using ListenSocket = LWIPRawListenImpl;
using UDPSocket = LWIPRawUDPImpl;
using UDPRecvSocket = LWIPRawUDPRecvImpl;
#endif
#ifdef USE_LWIP_FAST_SELECT
@@ -68,6 +75,16 @@ std::unique_ptr<Socket> 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.
std::unique_ptr<Socket> socket_ip(int type, int protocol);
/// Create a send-only UDP socket of the given domain and protocol.
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol);
/// Create a send-only UDP socket in the newest available IP domain.
std::unique_ptr<UDPSocket> socket_ip_udp(int protocol);
/// Create a UDP socket with receive support of the given domain and protocol.
std::unique_ptr<UDPRecvSocket> socket_udp_recv(int domain, int protocol);
/// Create a UDP socket with receive support in the newest available IP domain.
std::unique_ptr<UDPRecvSocket> socket_ip_udp_recv(int protocol);
/// Create a socket and monitor it for data in the main loop.
/// Like socket() but also registers the socket with the Application's select() loop.
/// WARNING: These functions are NOT thread-safe. They must only be called from the main loop