mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Socket component (#2250)
This commit is contained in:
parent
6180ee8065
commit
f924e80f43
8 changed files with 779 additions and 2 deletions
|
@ -119,6 +119,7 @@ esphome/components/sht4x/* @sjtrny
|
|||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
|
|
28
esphome/components/socket/__init__.py
Normal file
28
esphome/components/socket/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
CONF_IMPLEMENTATION = "implementation"
|
||||
IMPLEMENTATION_LWIP_TCP = "lwip_tcp"
|
||||
IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.SplitDefault(
|
||||
CONF_IMPLEMENTATION,
|
||||
esp8266=IMPLEMENTATION_LWIP_TCP,
|
||||
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
||||
): cv.one_of(
|
||||
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
impl = config[CONF_IMPLEMENTATION]
|
||||
if impl == IMPLEMENTATION_LWIP_TCP:
|
||||
cg.add_define("USE_SOCKET_IMPL_LWIP_TCP")
|
||||
elif impl == IMPLEMENTATION_BSD_SOCKETS:
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
105
esphome/components/socket/bsd_sockets_impl.cpp
Normal file
105
esphome/components/socket/bsd_sockets_impl.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#include "socket.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
std::string format_sockaddr(const struct sockaddr_storage &storage) {
|
||||
if (storage.ss_family == AF_INET) {
|
||||
const struct sockaddr_in *addr = reinterpret_cast<const struct sockaddr_in *>(&storage);
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
const char *ret = inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf));
|
||||
if (ret == NULL)
|
||||
return {};
|
||||
return std::string{buf};
|
||||
} else if (storage.ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr = reinterpret_cast<const struct sockaddr_in6 *>(&storage);
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
const char *ret = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf));
|
||||
if (ret == NULL)
|
||||
return {};
|
||||
return std::string{buf};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
class BSDSocketImpl : public Socket {
|
||||
public:
|
||||
BSDSocketImpl(int fd) : Socket(), fd_(fd) {}
|
||||
~BSDSocketImpl() override {
|
||||
if (!closed_) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
int fd = ::accept(fd_, addr, addrlen);
|
||||
if (fd == -1)
|
||||
return {};
|
||||
return std::unique_ptr<BSDSocketImpl>{new BSDSocketImpl(fd)};
|
||||
}
|
||||
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); }
|
||||
int close() override {
|
||||
int ret = ::close(fd_);
|
||||
closed_ = true;
|
||||
return ret;
|
||||
}
|
||||
int shutdown(int how) override { return ::shutdown(fd_, how); }
|
||||
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(fd_, addr, addrlen); }
|
||||
std::string getpeername() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
int err = this->getpeername((struct sockaddr *) &storage, &len);
|
||||
if (err != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(fd_, addr, addrlen); }
|
||||
std::string getsockname() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
int err = this->getsockname((struct sockaddr *) &storage, &len);
|
||||
if (err != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
return ::getsockopt(fd_, level, optname, optval, optlen);
|
||||
}
|
||||
int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override {
|
||||
return ::setsockopt(fd_, level, optname, optval, optlen);
|
||||
}
|
||||
int listen(int backlog) override { return ::listen(fd_, backlog); }
|
||||
ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); }
|
||||
ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); }
|
||||
int setblocking(bool blocking) override {
|
||||
int fl = ::fcntl(fd_, F_GETFL, 0);
|
||||
if (blocking) {
|
||||
fl &= ~O_NONBLOCK;
|
||||
} else {
|
||||
fl |= O_NONBLOCK;
|
||||
}
|
||||
::fcntl(fd_, F_SETFL, fl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
int fd_;
|
||||
bool closed_ = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
|
||||
int ret = ::socket(domain, type, protocol);
|
||||
if (ret == -1)
|
||||
return nullptr;
|
||||
return std::unique_ptr<Socket>{new BSDSocketImpl(ret)};
|
||||
}
|
||||
|
||||
} // namespace socket
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_SOCKET_IMPL_BSD_SOCKETS
|
117
esphome/components/socket/headers.h
Normal file
117
esphome/components/socket/headers.h
Normal file
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
// Helper file to include all socket-related system headers (or use our own
|
||||
// definitions where system ones don't exist)
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_TCP
|
||||
|
||||
#define LWIP_INTERNAL
|
||||
#include <sys/types.h>
|
||||
#include "lwip/inet.h"
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Address families. */
|
||||
#define AF_UNSPEC 0
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
#define PF_INET AF_INET
|
||||
#define PF_INET6 AF_INET6
|
||||
#define PF_UNSPEC AF_UNSPEC
|
||||
#define IPPROTO_IP 0
|
||||
#define IPPROTO_TCP 6
|
||||
#define IPPROTO_IPV6 41
|
||||
#define IPPROTO_ICMPV6 58
|
||||
|
||||
#define TCP_NODELAY 0x01
|
||||
|
||||
#define F_GETFL 3
|
||||
#define F_SETFL 4
|
||||
#define O_NONBLOCK 1
|
||||
|
||||
#define SHUT_RD 0
|
||||
#define SHUT_WR 1
|
||||
#define SHUT_RDWR 2
|
||||
|
||||
/* Socket protocol types (TCP/UDP/RAW) */
|
||||
#define SOCK_STREAM 1
|
||||
#define SOCK_DGRAM 2
|
||||
#define SOCK_RAW 3
|
||||
|
||||
#define SO_REUSEADDR 0x0004 /* Allow local address reuse */
|
||||
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
|
||||
#define SO_BROADCAST 0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
|
||||
|
||||
#define SOL_SOCKET 0xfff /* options for socket level */
|
||||
|
||||
typedef uint8_t sa_family_t;
|
||||
typedef uint16_t in_port_t;
|
||||
|
||||
struct sockaddr_in {
|
||||
uint8_t sin_len;
|
||||
sa_family_t sin_family;
|
||||
in_port_t sin_port;
|
||||
struct in_addr sin_addr;
|
||||
#define SIN_ZERO_LEN 8
|
||||
char sin_zero[SIN_ZERO_LEN];
|
||||
};
|
||||
|
||||
struct sockaddr_in6 {
|
||||
uint8_t sin6_len; /* length of this structure */
|
||||
sa_family_t sin6_family; /* AF_INET6 */
|
||||
in_port_t sin6_port; /* Transport layer port # */
|
||||
uint32_t sin6_flowinfo; /* IPv6 flow information */
|
||||
struct in6_addr sin6_addr; /* IPv6 address */
|
||||
uint32_t sin6_scope_id; /* Set of interfaces for scope */
|
||||
};
|
||||
|
||||
struct sockaddr {
|
||||
uint8_t sa_len;
|
||||
sa_family_t sa_family;
|
||||
char sa_data[14];
|
||||
};
|
||||
|
||||
struct sockaddr_storage {
|
||||
uint8_t s2_len;
|
||||
sa_family_t ss_family;
|
||||
char s2_data1[2];
|
||||
uint32_t s2_data2[3];
|
||||
uint32_t s2_data3[3];
|
||||
};
|
||||
typedef uint32_t socklen_t;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
// arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define
|
||||
#ifdef INADDR_ANY
|
||||
#undef INADDR_ANY
|
||||
#endif
|
||||
#ifdef INADDR_NONE
|
||||
#undef INADDR_NONE
|
||||
#endif
|
||||
|
||||
#define INADDR_ANY ((uint32_t) 0x00000000UL)
|
||||
#endif
|
||||
|
||||
#endif // USE_SOCKET_IMPL_LWIP_TCP
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
||||
// by the define
|
||||
#ifdef INADDR_NONE
|
||||
#undef INADDR_NONE
|
||||
#endif
|
||||
// not defined for ESP32
|
||||
typedef uint32_t socklen_t;
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#endif // USE_SOCKET_IMPL_BSD_SOCKETS
|
475
esphome/components/socket/lwip_raw_tcp_impl.cpp
Normal file
475
esphome/components/socket/lwip_raw_tcp_impl.cpp
Normal file
|
@ -0,0 +1,475 @@
|
|||
#include "socket.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_TCP
|
||||
|
||||
#include "lwip/ip.h"
|
||||
#include "lwip/netif.h"
|
||||
#include "lwip/opt.h"
|
||||
#include "lwip/tcp.h"
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <queue>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
static const char *const TAG = "lwip";
|
||||
|
||||
class LWIPRawImpl : public Socket {
|
||||
public:
|
||||
LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {}
|
||||
~LWIPRawImpl() override {
|
||||
if (pcb_ != nullptr) {
|
||||
tcp_abort(pcb_);
|
||||
pcb_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
tcp_arg(pcb_, this);
|
||||
tcp_accept(pcb_, LWIPRawImpl::s_accept_fn);
|
||||
tcp_recv(pcb_, LWIPRawImpl::s_recv_fn);
|
||||
tcp_err(pcb_, LWIPRawImpl::s_err_fn);
|
||||
}
|
||||
|
||||
std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return nullptr;
|
||||
}
|
||||
if (accepted_sockets_.empty()) {
|
||||
errno = EWOULDBLOCK;
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<LWIPRawImpl> sock = std::move(accepted_sockets_.front());
|
||||
accepted_sockets_.pop();
|
||||
if (addr != nullptr) {
|
||||
sock->getpeername(addr, addrlen);
|
||||
}
|
||||
sock->init();
|
||||
return std::unique_ptr<Socket>(std::move(sock));
|
||||
}
|
||||
int bind(const struct sockaddr *name, socklen_t addrlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (name == nullptr) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
ip_addr_t ip;
|
||||
in_port_t port;
|
||||
auto family = name->sa_family;
|
||||
#if LWIP_IPV6
|
||||
if (family == AF_INET) {
|
||||
if (addrlen < sizeof(sockaddr_in6)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
auto *addr4 = reinterpret_cast<const sockaddr_in *>(name);
|
||||
port = ntohs(addr4->sin_port);
|
||||
ip.type = IPADDR_TYPE_V4;
|
||||
ip.u_addr.ip4.addr = addr4->sin_addr.s_addr;
|
||||
|
||||
} else if (family == AF_INET6) {
|
||||
if (addrlen < sizeof(sockaddr_in)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
auto *addr6 = reinterpret_cast<const sockaddr_in6 *>(name);
|
||||
port = ntohs(addr6->sin6_port);
|
||||
ip.type = IPADDR_TYPE_V6;
|
||||
memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16);
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (family != AF_INET) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
auto *addr4 = reinterpret_cast<const sockaddr_in *>(name);
|
||||
port = ntohs(addr4->sin_port);
|
||||
ip.addr = addr4->sin_addr.s_addr;
|
||||
#endif
|
||||
err_t err = tcp_bind(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 close() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
err_t err = tcp_close(pcb_);
|
||||
if (err != ERR_OK) {
|
||||
tcp_abort(pcb_);
|
||||
pcb_ = nullptr;
|
||||
errno = err == ERR_MEM ? ENOMEM : EIO;
|
||||
return -1;
|
||||
}
|
||||
pcb_ = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int shutdown(int how) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
bool shut_rx = false, shut_tx = false;
|
||||
if (how == SHUT_RD) {
|
||||
shut_rx = true;
|
||||
} else if (how == SHUT_WR) {
|
||||
shut_tx = true;
|
||||
} else if (how == SHUT_RDWR) {
|
||||
shut_rx = shut_tx = true;
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx);
|
||||
if (err != ERR_OK) {
|
||||
errno = err == ERR_MEM ? ENOMEM : EIO;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getpeername(struct sockaddr *name, socklen_t *addrlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (name == nullptr || addrlen == nullptr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
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 = pcb_->remote_port;
|
||||
addr->sin_addr.s_addr = pcb_->remote_ip.addr;
|
||||
return 0;
|
||||
}
|
||||
std::string getpeername() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return "";
|
||||
}
|
||||
char buffer[24];
|
||||
uint32_t ip4 = pcb_->remote_ip.addr;
|
||||
snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF,
|
||||
(ip4 >> 0) & 0xFF);
|
||||
return std::string(buffer);
|
||||
}
|
||||
int getsockname(struct sockaddr *name, socklen_t *addrlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (name == nullptr || addrlen == nullptr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
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 = pcb_->local_port;
|
||||
addr->sin_addr.s_addr = pcb_->local_ip.addr;
|
||||
return 0;
|
||||
}
|
||||
std::string getsockname() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return "";
|
||||
}
|
||||
char buffer[24];
|
||||
uint32_t ip4 = pcb_->local_ip.addr;
|
||||
snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF,
|
||||
(ip4 >> 0) & 0xFF);
|
||||
return std::string(buffer);
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (optlen == nullptr || optval == nullptr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
|
||||
if (*optlen < 4) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// lwip doesn't seem to have this feature. Don't send an error
|
||||
// to prevent warnings
|
||||
*reinterpret_cast<int *>(optval) = 1;
|
||||
*optlen = 4;
|
||||
return 0;
|
||||
}
|
||||
if (level == IPPROTO_TCP && optname == TCP_NODELAY) {
|
||||
if (*optlen < 4) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*reinterpret_cast<int *>(optval) = tcp_nagle_disabled(pcb_);
|
||||
*optlen = 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
|
||||
if (optlen != 4) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// lwip doesn't seem to have this feature. Don't send an error
|
||||
// to prevent warnings
|
||||
return 0;
|
||||
}
|
||||
if (level == IPPROTO_TCP && optname == TCP_NODELAY) {
|
||||
if (optlen != 4) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
int val = *reinterpret_cast<const int *>(optval);
|
||||
if (val != 0) {
|
||||
tcp_nagle_disable(pcb_);
|
||||
} else {
|
||||
tcp_nagle_enable(pcb_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
int listen(int backlog) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog);
|
||||
if (listen_pcb == nullptr) {
|
||||
tcp_abort(pcb_);
|
||||
pcb_ = nullptr;
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
// tcp_listen reallocates the pcb, replace ours
|
||||
pcb_ = listen_pcb;
|
||||
// set callbacks on new pcb
|
||||
tcp_arg(pcb_, this);
|
||||
tcp_accept(pcb_, LWIPRawImpl::s_accept_fn);
|
||||
return 0;
|
||||
}
|
||||
ssize_t read(void *buf, size_t len) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (rx_closed_ && rx_buf_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (rx_buf_ == nullptr) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t read = 0;
|
||||
uint8_t *buf8 = reinterpret_cast<uint8_t *>(buf);
|
||||
while (len && rx_buf_ != nullptr) {
|
||||
size_t pb_len = rx_buf_->len;
|
||||
size_t pb_left = pb_len - rx_buf_offset_;
|
||||
if (pb_left == 0)
|
||||
break;
|
||||
size_t copysize = std::min(len, pb_left);
|
||||
memcpy(buf8, reinterpret_cast<uint8_t *>(rx_buf_->payload) + rx_buf_offset_, copysize);
|
||||
|
||||
if (pb_left == copysize) {
|
||||
// full pb copied, free it
|
||||
if (rx_buf_->next == nullptr) {
|
||||
// last buffer in chain
|
||||
pbuf_free(rx_buf_);
|
||||
rx_buf_ = nullptr;
|
||||
rx_buf_offset_ = 0;
|
||||
} else {
|
||||
auto *old_buf = rx_buf_;
|
||||
rx_buf_ = rx_buf_->next;
|
||||
pbuf_ref(rx_buf_);
|
||||
pbuf_free(old_buf);
|
||||
rx_buf_offset_ = 0;
|
||||
}
|
||||
} else {
|
||||
rx_buf_offset_ += copysize;
|
||||
}
|
||||
tcp_recved(pcb_, copysize);
|
||||
|
||||
buf8 += copysize;
|
||||
len -= copysize;
|
||||
read += copysize;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
ssize_t write(const void *buf, size_t len) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (len == 0)
|
||||
return 0;
|
||||
if (buf == nullptr) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
auto space = tcp_sndbuf(pcb_);
|
||||
if (space == 0) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
size_t to_send = std::min((size_t) space, len);
|
||||
err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY);
|
||||
if (err == ERR_MEM) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
if (err != ERR_OK) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
err = tcp_output(pcb_);
|
||||
if (err != ERR_OK) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return to_send;
|
||||
}
|
||||
int setblocking(bool blocking) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (blocking) {
|
||||
// blocking operation not supported
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
err_t accept_fn(struct tcp_pcb *newpcb, err_t err) {
|
||||
if (err != ERR_OK || newpcb == nullptr) {
|
||||
// "An error code if there has been an error accepting. Only return ERR_ABRT if you have
|
||||
// called tcp_abort from within the callback function!"
|
||||
// https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d
|
||||
// nothing to do here, we just don't push it to the queue
|
||||
return ERR_OK;
|
||||
}
|
||||
accepted_sockets_.emplace(new LWIPRawImpl(newpcb));
|
||||
return ERR_OK;
|
||||
}
|
||||
void err_fn(err_t err) {
|
||||
// "If a connection is aborted because of an error, the application is alerted of this event by
|
||||
// the err callback."
|
||||
// pcb is already freed when this callback is called
|
||||
// ERR_RST: connection was reset by remote host
|
||||
// ERR_ABRT: aborted through tcp_abort or TCP timer
|
||||
pcb_ = nullptr;
|
||||
}
|
||||
err_t recv_fn(struct pbuf *pb, err_t err) {
|
||||
if (err != 0) {
|
||||
// "An error code if there has been an error receiving Only return ERR_ABRT if you have
|
||||
// called tcp_abort from within the callback function!"
|
||||
rx_closed_ = true;
|
||||
return ERR_OK;
|
||||
}
|
||||
if (pb == nullptr) {
|
||||
rx_closed_ = true;
|
||||
return ERR_OK;
|
||||
}
|
||||
if (rx_buf_ == nullptr) {
|
||||
// no need to copy because lwIP gave control of it to us
|
||||
rx_buf_ = pb;
|
||||
rx_buf_offset_ = 0;
|
||||
} else {
|
||||
pbuf_cat(rx_buf_, pb);
|
||||
}
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) {
|
||||
LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
|
||||
return arg_this->accept_fn(newpcb, err);
|
||||
}
|
||||
|
||||
static void s_err_fn(void *arg, err_t err) {
|
||||
LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
|
||||
return arg_this->err_fn(err);
|
||||
}
|
||||
|
||||
static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) {
|
||||
LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
|
||||
return arg_this->recv_fn(pb, err);
|
||||
}
|
||||
|
||||
protected:
|
||||
struct tcp_pcb *pcb_;
|
||||
std::queue<std::unique_ptr<LWIPRawImpl>> accepted_sockets_;
|
||||
bool rx_closed_ = false;
|
||||
pbuf *rx_buf_ = nullptr;
|
||||
size_t rx_buf_offset_ = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
|
||||
auto *pcb = tcp_new();
|
||||
if (pcb == nullptr)
|
||||
return nullptr;
|
||||
auto *sock = new LWIPRawImpl(pcb);
|
||||
sock->init();
|
||||
return std::unique_ptr<Socket>{sock};
|
||||
}
|
||||
|
||||
} // namespace socket
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_SOCKET_IMPL_LWIP_TCP
|
42
esphome/components/socket/socket.h
Normal file
42
esphome/components/socket/socket.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "headers.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
Socket() = default;
|
||||
virtual ~Socket() = default;
|
||||
Socket(const Socket &) = delete;
|
||||
Socket &operator=(const Socket &) = delete;
|
||||
|
||||
virtual std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual int bind(const struct sockaddr *addr, socklen_t addrlen) = 0;
|
||||
virtual int close() = 0;
|
||||
// not supported yet:
|
||||
// virtual int connect(const std::string &address) = 0;
|
||||
// virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0;
|
||||
virtual int shutdown(int how) = 0;
|
||||
|
||||
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual std::string getpeername() = 0;
|
||||
virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual std::string getsockname() = 0;
|
||||
virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0;
|
||||
virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0;
|
||||
virtual int listen(int backlog) = 0;
|
||||
virtual ssize_t read(void *buf, size_t len) = 0;
|
||||
virtual ssize_t write(const void *buf, size_t len) = 0;
|
||||
virtual int setblocking(bool blocking) = 0;
|
||||
virtual int loop() { return 0; };
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> socket(int domain, int type, int protocol);
|
||||
|
||||
} // namespace socket
|
||||
} // namespace esphome
|
|
@ -48,5 +48,11 @@
|
|||
#define USE_IMPROV
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#else
|
||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#endif
|
||||
|
||||
// Disabled feature flags
|
||||
//#define USE_BSEC // Requires a library with proprietary license.
|
||||
|
|
|
@ -261,7 +261,7 @@ def highlight(s):
|
|||
@lint_re_check(
|
||||
r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL,
|
||||
include=cpp_include,
|
||||
exclude=["esphome/core/log.h"],
|
||||
exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"],
|
||||
)
|
||||
def lint_no_defines(fname, match):
|
||||
s = highlight(
|
||||
|
@ -493,7 +493,10 @@ def lint_relative_py_import(fname):
|
|||
"esphome/components/*.h",
|
||||
"esphome/components/*.cpp",
|
||||
"esphome/components/*.tcc",
|
||||
]
|
||||
],
|
||||
exclude=[
|
||||
"esphome/components/socket/headers.h",
|
||||
],
|
||||
)
|
||||
def lint_namespace(fname, content):
|
||||
expected_name = re.match(
|
||||
|
|
Loading…
Reference in a new issue