mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Socket component (#2250)
This commit is contained in:
		| @@ -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( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user