diff --git a/CHANGELOG.md b/CHANGELOG.md index 11631b509..1bf7ddb77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [13.3.0.2] ### Added - HASPmota type `chart` (#20372) +- Berry add support for `tcpclientasync` in `tcpserver` ### Breaking Changed diff --git a/lib/libesp32/berry_tasmota/src/be_tcpserver_class.c b/lib/libesp32/berry_tasmota/src/be_tcpserver_class.c index c8aa216d4..292b758ae 100644 --- a/lib/libesp32/berry_tasmota/src/be_tcpserver_class.c +++ b/lib/libesp32/berry_tasmota/src/be_tcpserver_class.c @@ -13,7 +13,7 @@ extern void tcpserver_deinit(void *server_tcp); BE_FUNC_ extern void tcpserver_close(void *server); BE_FUNC_CTYPE_DECLARE(tcpserver_close, "", ".") extern bbool tcpserver_hasclient(void *server); BE_FUNC_CTYPE_DECLARE(tcpserver_hasclient, "b", ".") extern void * tcpserver_accept(struct bvm *vm, void *server); BE_FUNC_CTYPE_DECLARE(tcpserver_accept, "tcpclient", "@.") - +extern void * tcpserver_acceptasync(struct bvm *vm, void *server); BE_FUNC_CTYPE_DECLARE(tcpserver_acceptasync, "tcpclientasync", "@.") #include "be_fixed_be_class_tcpserver.h" @@ -26,6 +26,7 @@ class be_class_tcpserver (scope: global, name: tcpserver) { close, ctype_func(tcpserver_close) hasclient, ctype_func(tcpserver_hasclient) accept, ctype_func(tcpserver_accept) + acceptasync, ctype_func(tcpserver_acceptasync) } @const_object_info_end */ diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpclientasync.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpclientasync.ino index 720ebfb8d..948351494 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpclientasync.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpclientasync.ino @@ -47,6 +47,13 @@ public: } + // following is used when accepting a new connection as server + AsyncTCPClient(int fd) : sockfd(fd), state(AsyncTCPState::CONNECTED), _timeout_ms(1), local_port(-1) { + if (sockfd < 0) { + state = AsyncTCPState::REFUSED; + } + } + ~AsyncTCPClient() { this->stop(); } @@ -324,25 +331,58 @@ public: struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&local_address; local_port = ntohs(saddr6->sin6_port); if (T_IN6_IS_ADDR_V4MAPPED(saddr6->sin6_addr.un.u32_addr)) { - local_addr = IPAddress(IPv4, (uint8_t*)saddr6->sin6_addr.s6_addr+12); + local_addr = IPAddress(IPv4, (uint8_t*)saddr6->sin6_addr.s6_addr+12, 0); } else { -#if ESP_IDF_VERSION_MAJOR >= 5 local_addr = IPAddress(IPv6, (uint8_t*)(saddr6->sin6_addr.s6_addr), saddr6->sin6_scope_id); -#else - local_addr = IPAddress(IPv6, (uint8_t*)(saddr6->sin6_addr.s6_addr)); -#endif } } #endif // USE_IPV6 } } + + IPAddress remoteIP() const { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(sockfd, (struct sockaddr*)&addr, &len); + + // IPv4 socket, old way + if (((struct sockaddr*)&addr)->sa_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); + } + +#if LWIP_IPV6 + // IPv6, but it might be IPv4 mapped address + if (((struct sockaddr*)&addr)->sa_family == AF_INET6) { + struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&addr; + if (T_IN6_IS_ADDR_V4MAPPED(saddr6->sin6_addr.un.u32_addr)) { + return IPAddress(IPv4, (uint8_t*)saddr6->sin6_addr.s6_addr+12, 0); + } else { + return IPAddress(IPv6, (uint8_t*)(saddr6->sin6_addr.s6_addr), saddr6->sin6_scope_id); + } + } +#endif + return (IPAddress(0,0,0,0)); + + } + uint16_t remotePort() const { + + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(sockfd, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); + } + + const IPAddress localIP() const { return local_addr; } + uint16_t localPort() const { return local_port; } + + public: int sockfd; AsyncTCPState state; uint32_t _timeout_ms; - String remota_addr; // address in numerical format (after DNS resolution), either IPv4 or IPv6 - uint16_t remote_port; // remote port number IPAddress local_addr; int32_t local_port; // -1 if unknown or invalid }; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpserver.ino index 1040ff255..9ac27ee6e 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpserver.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tcpserver.ino @@ -24,6 +24,176 @@ #include +// WiFiServerAsync can return either `WiFiClient` (sync) or `AsyncTCPClient` (async) +// +class WiFiServerAsync { + protected: + int sockfd; + int _accepted_sockfd = -1; + IPAddress _addr; + uint16_t _port; + uint8_t _max_clients; + bool _listening; + bool _noDelay = false; + + + public: + void listenOnLocalhost(){} + + WiFiServerAsync(uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_addr(),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) { + } + WiFiServerAsync(const IPAddress& addr, uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_addr(addr),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) { + } + ~WiFiServerAsync(){ end();} + WiFiClient available(); + WiFiClient accept(){return available();} + AsyncTCPClient * availableAsync(); + AsyncTCPClient * acceptAsync(){return availableAsync();} + void begin(uint16_t port=0); + void begin(uint16_t port, int reuse_enable); + void setNoDelay(bool nodelay); + bool getNoDelay(); + bool hasClient(); + + void end(); + void close(); + void stop(); + operator bool(){return _listening;} + int setTimeout(uint32_t seconds); + void stopAll(); public: +}; + + +int WiFiServerAsync::setTimeout(uint32_t seconds){ + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) + return -1; + return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); +} + +void WiFiServerAsync::stopAll(){} + +WiFiClient WiFiServerAsync::available(){ + if(!_listening) + return WiFiClient(); + int client_sock; + if (_accepted_sockfd >= 0) { + client_sock = _accepted_sockfd; + _accepted_sockfd = -1; + } + else { + struct sockaddr_in6 _client; + int cs = sizeof(struct sockaddr_in6); + client_sock = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); + } + if(client_sock >= 0){ + int val = 1; + if(setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(int)) == ESP_OK) { + val = _noDelay; + if(setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(int)) == ESP_OK) + return WiFiClient(client_sock); + } + } + return WiFiClient(); +} + +// specific Async version +AsyncTCPClient * WiFiServerAsync::availableAsync(){ + if(!_listening) + return new AsyncTCPClient(); + int client_sock; + if (_accepted_sockfd >= 0) { + client_sock = _accepted_sockfd; + _accepted_sockfd = -1; + } + else { + struct sockaddr_in6 _client; + int cs = sizeof(struct sockaddr_in6); + client_sock = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); + } + if(client_sock >= 0){ + int val = 1; + if(setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(int)) == ESP_OK) { + val = _noDelay; + if(setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(int)) == ESP_OK) + return new AsyncTCPClient(client_sock); + } + } + return new AsyncTCPClient(); +} + +void WiFiServerAsync::begin(uint16_t port){ + begin(port, 1); +} + +void WiFiServerAsync::begin(uint16_t port, int enable){ + if(_listening) + return; + if(port){ + _port = port; + } + struct sockaddr_in6 server; + sockfd = socket(AF_INET6 , SOCK_STREAM, 0); + if (sockfd < 0) + return; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + server.sin6_family = AF_INET6; + if (_addr.type() == IPv4) { + memcpy(server.sin6_addr.s6_addr+11, (uint8_t*)&_addr[0], 4); + server.sin6_addr.s6_addr[10] = 0xFF; + server.sin6_addr.s6_addr[11] = 0xFF; + } else { + memcpy(server.sin6_addr.s6_addr, (uint8_t*)&_addr[0], 16); + } + memset(server.sin6_addr.s6_addr, 0x0, 16); + server.sin6_port = htons(_port); + if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + return; + if(listen(sockfd , _max_clients) < 0) + return; + fcntl(sockfd, F_SETFL, O_NONBLOCK); + _listening = true; + _noDelay = false; + _accepted_sockfd = -1; +} + +void WiFiServerAsync::setNoDelay(bool nodelay) { + _noDelay = nodelay; +} + +bool WiFiServerAsync::getNoDelay() { + return _noDelay; +} + +bool WiFiServerAsync::hasClient() { + if (_accepted_sockfd >= 0) { + return true; + } + struct sockaddr_in6 _client; + int cs = sizeof(struct sockaddr_in6); + _accepted_sockfd = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); + if (_accepted_sockfd >= 0) { + return true; + } + return false; +} + +void WiFiServerAsync::end(){ + lwip_close(sockfd); + sockfd = -1; + _listening = false; +} + +void WiFiServerAsync::close(){ + end(); +} + +void WiFiServerAsync::stop(){ + end(); +} + /*********************************************************************************************\ * Native functions mapped to Berry functions * @@ -31,7 +201,7 @@ extern "C" { const void* tcpserver_init(struct bvm *vm, int32_t tcp_port) { if (tcp_port > 0 && tcp_port < 65535) { - WiFiServer *server_tcp = new WiFiServer(tcp_port); + WiFiServerAsync *server_tcp = new WiFiServerAsync(tcp_port); server_tcp->begin(); // start TCP server if (!*server_tcp) { be_raise(vm, "network_error", "Failed to open socket"); @@ -45,7 +215,7 @@ extern "C" { } void tcpserver_deinit(void *server) { - WiFiServer *server_tcp = (WiFiServer*) server; + WiFiServerAsync *server_tcp = (WiFiServerAsync*) server; if (server_tcp) { server_tcp->stop(); delete server_tcp; @@ -53,23 +223,23 @@ extern "C" { } void tcpserver_close(void *server) { - WiFiServer *server_tcp = (WiFiServer*) server; + WiFiServerAsync *server_tcp = (WiFiServerAsync*) server; if (server_tcp) { server_tcp->stop(); } } bbool tcpserver_hasclient(void *server) { - WiFiServer *server_tcp = (WiFiServer*) server; + WiFiServerAsync *server_tcp = (WiFiServerAsync*) server; return server_tcp->hasClient(); } void * tcpserver_accept(struct bvm *vm, void *server) { - WiFiServer *server_tcp = (WiFiServer*) server; + WiFiServerAsync *server_tcp = (WiFiServerAsync*) server; WiFiClient * client_ptr = nullptr; if (server_tcp->hasClient()) { WiFiClient new_client = server_tcp->available(); - AddLog(LOG_LEVEL_INFO, "BRY: Got connection from %s", new_client.remoteIP().toString().c_str()); + AddLog(LOG_LEVEL_INFO, "BRY: Got connection from %s:%i local %s:%i", new_client.remoteIP().toString().c_str(), new_client.remotePort(), new_client.localIP().toString().c_str(), new_client.localPort()); client_ptr = new WiFiClient(); *client_ptr = new_client; } else { @@ -78,6 +248,18 @@ extern "C" { return client_ptr; } + void * tcpserver_acceptasync(struct bvm *vm, void *server) { + WiFiServerAsync *server_tcp = (WiFiServerAsync*) server; + AsyncTCPClient * client_ptr = nullptr; + if (server_tcp->hasClient()) { + client_ptr = server_tcp->availableAsync(); + AddLog(LOG_LEVEL_INFO, "BRY: Got connection from %s:%i local %s:%i", client_ptr->remoteIP().toString().c_str(), client_ptr->remotePort(), client_ptr->localIP().toString().c_str(), client_ptr->localPort()); + } else { + be_raise(vm, "internal_error", "tcpserver has no client connected"); + } + return client_ptr; + } + } #endif // USE_BERRY_TCPSERVER