mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +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/shutdown/* @esphome/core | ||||||
| esphome/components/sim800l/* @glmnet | esphome/components/sim800l/* @glmnet | ||||||
| esphome/components/sm2135/* @BoukeHaarsma23 | esphome/components/sm2135/* @BoukeHaarsma23 | ||||||
|  | esphome/components/socket/* @esphome/core | ||||||
| esphome/components/spi/* @esphome/core | esphome/components/spi/* @esphome/core | ||||||
| esphome/components/ssd1322_base/* @kbx81 | esphome/components/ssd1322_base/* @kbx81 | ||||||
| esphome/components/ssd1322_spi/* @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 | #define USE_IMPROV | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef ARDUINO_ARCH_ESP8266 | ||||||
|  | #define USE_SOCKET_IMPL_LWIP_TCP | ||||||
|  | #else | ||||||
|  | #define USE_SOCKET_IMPL_BSD_SOCKETS | ||||||
|  | #endif | ||||||
|  |  | ||||||
| // Disabled feature flags | // Disabled feature flags | ||||||
| //#define USE_BSEC  // Requires a library with proprietary license. | //#define USE_BSEC  // Requires a library with proprietary license. | ||||||
|   | |||||||
| @@ -261,7 +261,7 @@ def highlight(s): | |||||||
| @lint_re_check( | @lint_re_check( | ||||||
|     r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, |     r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, | ||||||
|     include=cpp_include, |     include=cpp_include, | ||||||
|     exclude=["esphome/core/log.h"], |     exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"], | ||||||
| ) | ) | ||||||
| def lint_no_defines(fname, match): | def lint_no_defines(fname, match): | ||||||
|     s = highlight( |     s = highlight( | ||||||
| @@ -493,7 +493,10 @@ def lint_relative_py_import(fname): | |||||||
|         "esphome/components/*.h", |         "esphome/components/*.h", | ||||||
|         "esphome/components/*.cpp", |         "esphome/components/*.cpp", | ||||||
|         "esphome/components/*.tcc", |         "esphome/components/*.tcc", | ||||||
|     ] |     ], | ||||||
|  |     exclude=[ | ||||||
|  |         "esphome/components/socket/headers.h", | ||||||
|  |     ], | ||||||
| ) | ) | ||||||
| def lint_namespace(fname, content): | def lint_namespace(fname, content): | ||||||
|     expected_name = re.match( |     expected_name = re.match( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user