From 73eccb64ec7374ef1fa263e0345a7f5dedddff06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sat, 11 Jan 2025 20:00:57 +0200 Subject: [PATCH] Replace AsyncTCP with AsyncTCPsock library --- Software/src/devboard/webserver/webserver.h | 2 +- .../ayushsharma82-ElegantOTA/src/ElegantOTA.h | 2 +- .../LICENSE | 0 .../mathieucarbou-AsyncTCPSock/library.json | 22 + .../library.properties | 9 + .../src/AsyncTCP.cpp | 1301 ++++++++++++++++ .../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h | 321 ++++ .../src/AsyncTCP_SSL.h | 6 + .../src/AsyncTCP_SSL.hpp | 17 + .../src/AsyncTCP_TLS_Context.cpp | 346 ++++ .../src/AsyncTCP_TLS_Context.h | 79 + .../src/AsyncEventSource.h | 2 +- .../src/AsyncWebSocket.h | 2 +- .../src/ESPAsyncWebServer.h | 2 +- Software/src/lib/me-no-dev-AsyncTCP/README.md | 15 - .../src/lib/me-no-dev-AsyncTCP/library.json | 22 - .../lib/me-no-dev-AsyncTCP/library.properties | 9 - .../lib/me-no-dev-AsyncTCP/src/AsyncTCP.cpp | 1387 ----------------- .../src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.h | 220 --- 19 files changed, 2106 insertions(+), 1658 deletions(-) rename Software/src/lib/{me-no-dev-AsyncTCP => mathieucarbou-AsyncTCPSock}/LICENSE (100%) create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/library.json create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/library.properties create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp create mode 100644 Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h delete mode 100644 Software/src/lib/me-no-dev-AsyncTCP/README.md delete mode 100644 Software/src/lib/me-no-dev-AsyncTCP/library.json delete mode 100644 Software/src/lib/me-no-dev-AsyncTCP/library.properties delete mode 100644 Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.cpp delete mode 100644 Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.h diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 6f869a08..0e285d31 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -6,8 +6,8 @@ #include "../../include.h" #include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h" #include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h" +#include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h" #include "../../lib/mathieucarbou-ESPAsyncWebServer/src/ESPAsyncWebServer.h" -#include "../../lib/me-no-dev-AsyncTCP/src/AsyncTCP.h" #include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" extern const char* version_number; // The current software version, shown on webserver diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h b/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h index 828e4541..6086a4e3 100644 --- a/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h +++ b/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h @@ -64,7 +64,7 @@ _____ _ _ ___ _____ _ #include "Update.h" #include "StreamString.h" #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1 - #include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h" + #include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h" #include "../../mathieucarbou-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #define ELEGANTOTA_WEBSERVER AsyncWebServer #else diff --git a/Software/src/lib/me-no-dev-AsyncTCP/LICENSE b/Software/src/lib/mathieucarbou-AsyncTCPSock/LICENSE similarity index 100% rename from Software/src/lib/me-no-dev-AsyncTCP/LICENSE rename to Software/src/lib/mathieucarbou-AsyncTCPSock/LICENSE diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/library.json b/Software/src/lib/mathieucarbou-AsyncTCPSock/library.json new file mode 100644 index 00000000..5d2b5ed1 --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/library.json @@ -0,0 +1,22 @@ +{ + "name":"AsyncTCPSock", + "description":"Reimplementation of an Asynchronous TCP Library for ESP32, using BSD Sockets", + "keywords":"async,tcp", + "authors": + { + "name": "Alex Villacís Lasso", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/yubox-node-org/AsyncTCPSock.git" + }, + "version": "1.0.2-dev", + "license": "LGPL-3.0", + "frameworks": "arduino", + "platforms": "espressif32", + "build": { + "libCompatMode": 2 + } +} diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/library.properties b/Software/src/lib/mathieucarbou-AsyncTCPSock/library.properties new file mode 100644 index 00000000..5c12940e --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/library.properties @@ -0,0 +1,9 @@ +name=AsyncTCPSock +version=1.0.2-dev +author=avillacis +maintainer=avillacis +sentence=Reimplemented Async TCP Library for ESP32 using BSD Sockets +paragraph=This is a reimplementation of AsyncTCP (Async TCP Library for ESP32) by Me No Dev, using high-level BSD Sockets instead of the low-level packet API and a message queue. +category=Other +url=https://github.com/yubox-node-org/AsyncTCPSock +architectures=* diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp new file mode 100644 index 00000000..e13fa764 --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp @@ -0,0 +1,1301 @@ +/* + Reimplementation of an asynchronous TCP library for Espressif MCUs, using + BSD sockets. + + Copyright (c) 2020 Alex Villacís Lasso. + + Original AsyncTCP API Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "Arduino.h" + +#include "AsyncTCP.h" +#include "esp_task_wdt.h" + +#include +#include +#include + +#include + +#include + +#undef close +#undef connect +#undef write +#undef read + +static TaskHandle_t _asyncsock_service_task_handle = NULL; +static SemaphoreHandle_t _asyncsock_mutex = NULL; + +typedef std::list::iterator sockIterator; + +void _asynctcpsock_task(void *); + +#define ASYNCTCPSOCK_POLL_INTERVAL 125 + +#define MAX_PAYLOAD_SIZE 1360 + +// Since the only task reading from these sockets is the asyncTcpPSock task +// and all socket clients are serviced sequentially, only one read buffer +// is needed, and it can therefore be statically allocated +static uint8_t _readBuffer[MAX_PAYLOAD_SIZE]; + +// Start async socket task +static bool _start_asyncsock_task(void) +{ + if (!_asyncsock_service_task_handle) { + log_i("Creating asyncTcpSock task running in core %d (-1 for any available core)...", CONFIG_ASYNC_TCP_RUNNING_CORE); + xTaskCreateUniversal( + _asynctcpsock_task, + CONFIG_ASYNC_TCP_TASK_NAME, + CONFIG_ASYNC_TCP_STACK, + NULL, + CONFIG_ASYNC_TCP_TASK_PRIORITY, + &_asyncsock_service_task_handle, + CONFIG_ASYNC_TCP_RUNNING_CORE); + if (!_asyncsock_service_task_handle) return false; + } + return true; +} + +// Actual asynchronous socket task +void _asynctcpsock_task(void *) +{ + auto & _socketBaseList = AsyncSocketBase::_getSocketBaseList(); + + while (true) { + sockIterator it; + fd_set sockSet_r; + fd_set sockSet_w; + int max_sock = 0; + + std::list sockList; + + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + + // Collect all of the active sockets into socket set. Half-destroyed + // connections should have set _socket to -1 and therefore should not + // end up in the sockList. + FD_ZERO(&sockSet_r); FD_ZERO(&sockSet_w); + for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { + if ((*it)->_socket != -1) { +#ifdef CONFIG_LWIP_MAX_SOCKETS + if (!(*it)->_isServer() || _socketBaseList.size() < CONFIG_LWIP_MAX_SOCKETS) { +#endif + FD_SET((*it)->_socket, &sockSet_r); + if (max_sock <= (*it)->_socket) max_sock = (*it)->_socket + 1; +#ifdef CONFIG_LWIP_MAX_SOCKETS + } +#endif + if ((*it)->_pendingWrite()) { + FD_SET((*it)->_socket, &sockSet_w); + if (max_sock <= (*it)->_socket) max_sock = (*it)->_socket + 1; + } + (*it)->_selected = true; + } + } + + // Wait for activity on all monitored sockets + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = ASYNCTCPSOCK_POLL_INTERVAL * 1000; + + xSemaphoreGiveRecursive(_asyncsock_mutex); + + int r = select(max_sock, &sockSet_r, &sockSet_w, NULL, &tv); + + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + + // Check all sockets to see which ones are active + uint32_t nActive = 0; + if (r > 0) { + // Collect and notify all writable sockets. Half-destroyed connections + // should have set _socket to -1 and therefore should not end up in + // the sockList. + for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { + if ((*it)->_selected && FD_ISSET((*it)->_socket, &sockSet_w)) { + sockList.push_back(*it); + } + } + for (it = sockList.begin(); it != sockList.end(); it++) { +#if CONFIG_ASYNC_TCP_USE_WDT + if (esp_task_wdt_add(NULL) != ESP_OK) { + log_e("Failed to add async task to WDT"); + } +#endif + if ((*it)->_sockIsWriteable()) { + (*it)->_sock_lastactivity = millis(); + nActive++; + } +#if CONFIG_ASYNC_TCP_USE_WDT + if (esp_task_wdt_delete(NULL) != ESP_OK) { + log_e("Failed to remove loop task from WDT"); + } +#endif + } + sockList.clear(); + + // Collect and notify all readable sockets. Half-destroyed connections + // should have set _socket to -1 and therefore should not end up in + // the sockList. + for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { + if ((*it)->_selected && FD_ISSET((*it)->_socket, &sockSet_r)) { + sockList.push_back(*it); + } + } + for (it = sockList.begin(); it != sockList.end(); it++) { +#if CONFIG_ASYNC_TCP_USE_WDT + if (esp_task_wdt_add(NULL) != ESP_OK) { + log_e("Failed to add async task to WDT"); + } +#endif + (*it)->_sock_lastactivity = millis(); + (*it)->_sockIsReadable(); + nActive++; +#if CONFIG_ASYNC_TCP_USE_WDT + if (esp_task_wdt_delete(NULL) != ESP_OK) { + log_e("Failed to remove loop task from WDT"); + } +#endif + } + sockList.clear(); + } + + // Collect and notify all sockets waiting for DNS completion + for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { + // Collect socket that has finished resolving DNS (with or without error) + if ((*it)->_isdnsfinished) { + sockList.push_back(*it); + } + } + for (it = sockList.begin(); it != sockList.end(); it++) { +#if CONFIG_ASYNC_TCP_USE_WDT + if(esp_task_wdt_add(NULL) != ESP_OK){ + log_e("Failed to add async task to WDT"); + } +#endif + (*it)->_isdnsfinished = false; + (*it)->_sockDelayedConnect(); +#if CONFIG_ASYNC_TCP_USE_WDT + if(esp_task_wdt_delete(NULL) != ESP_OK){ + log_e("Failed to remove loop task from WDT"); + } +#endif + } + sockList.clear(); + + xSemaphoreGiveRecursive(_asyncsock_mutex); + + // Collect and run activity poll on all pollable sockets + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { + (*it)->_selected = false; + if (millis() - (*it)->_sock_lastactivity >= ASYNCTCPSOCK_POLL_INTERVAL) { + (*it)->_sock_lastactivity = millis(); + sockList.push_back(*it); + } + } + + // Run activity poll on all pollable sockets + for (it = sockList.begin(); it != sockList.end(); it++) { +#if CONFIG_ASYNC_TCP_USE_WDT + if(esp_task_wdt_add(NULL) != ESP_OK){ + log_e("Failed to add async task to WDT"); + } +#endif + (*it)->_sockPoll(); +#if CONFIG_ASYNC_TCP_USE_WDT + if(esp_task_wdt_delete(NULL) != ESP_OK){ + log_e("Failed to remove loop task from WDT"); + } +#endif + } + sockList.clear(); + + xSemaphoreGiveRecursive(_asyncsock_mutex); + } + + vTaskDelete(NULL); + _asyncsock_service_task_handle = NULL; +} + +AsyncSocketBase::AsyncSocketBase() +{ + if (_asyncsock_mutex == NULL) _asyncsock_mutex = xSemaphoreCreateRecursiveMutex(); + + _sock_lastactivity = millis(); + _selected = false; + + // Add this base socket to the monitored list + auto & _socketBaseList = AsyncSocketBase::_getSocketBaseList(); + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _socketBaseList.push_back(this); + xSemaphoreGiveRecursive(_asyncsock_mutex); +} + +std::list & AsyncSocketBase::_getSocketBaseList(void) +{ + // List of monitored socket objects + static std::list _socketBaseList; + return _socketBaseList; +} + +AsyncSocketBase::~AsyncSocketBase() +{ + // Remove this base socket from the monitored list + auto & _socketBaseList = AsyncSocketBase::_getSocketBaseList(); + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _socketBaseList.remove(this); + xSemaphoreGiveRecursive(_asyncsock_mutex); +} + + +AsyncClient::AsyncClient(int sockfd) +: _connect_cb(0) +, _connect_cb_arg(0) +, _discard_cb(0) +, _discard_cb_arg(0) +, _sent_cb(0) +, _sent_cb_arg(0) +, _error_cb(0) +, _error_cb_arg(0) +, _recv_cb(0) +, _recv_cb_arg(0) +, _timeout_cb(0) +, _timeout_cb_arg(0) +, _rx_last_packet(0) +, _rx_since_timeout(0) +, _ack_timeout(ASYNC_MAX_ACK_TIME) +, _connect_port(0) +#if ASYNC_TCP_SSL_ENABLED +, _root_ca_len(0) +, _root_ca(NULL) +, _cli_cert_len(0) +, _cli_cert(NULL) +, _cli_key_len(0) +, _cli_key(NULL) +, _secure(false) +, _handshake_done(true) +, _psk_ident(0) +, _psk(0) +, _sslctx(NULL) +#endif // ASYNC_TCP_SSL_ENABLED +, _writeSpaceRemaining(TCP_SND_BUF) +, _conn_state(0) +{ + _write_mutex = xSemaphoreCreateMutex(); + if (sockfd != -1) { + fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); + + // Updating state visible to asyncTcpSock task + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _conn_state = 4; + _socket = sockfd; + _rx_last_packet = millis(); + xSemaphoreGiveRecursive(_asyncsock_mutex); + } +} + +AsyncClient::~AsyncClient() +{ + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + if (_socket != -1) _close(); + _removeAllCallbacks(); + vSemaphoreDelete(_write_mutex); + _write_mutex = NULL; + xSemaphoreGiveRecursive(_asyncsock_mutex); +} + +void AsyncClient::setRxTimeout(uint32_t timeout){ + _rx_since_timeout = timeout; +} + +uint32_t AsyncClient::getRxTimeout(){ + return _rx_since_timeout; +} + +uint32_t AsyncClient::getAckTimeout(){ + return _ack_timeout; +} + +void AsyncClient::setAckTimeout(uint32_t timeout){ + _ack_timeout = timeout; +} + +void AsyncClient::setNoDelay(bool nodelay){ + if (_socket == -1) return; + + int flag = nodelay; + int res = setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); + if(res < 0) { + log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + } +} + +bool AsyncClient::getNoDelay(){ + if (_socket == -1) return false; + + int flag = 0; + socklen_t size = sizeof(int); + int res = getsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, &size); + if(res < 0) { + log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + } + return flag; +} + +/* + * Callback Setters + * */ + +void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ + _connect_cb = cb; + _connect_cb_arg = arg; +} + +void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ + _discard_cb = cb; + _discard_cb_arg = arg; +} + +void AsyncClient::onAck(AcAckHandler cb, void* arg){ + _sent_cb = cb; + _sent_cb_arg = arg; +} + +void AsyncClient::onError(AcErrorHandler cb, void* arg){ + _error_cb = cb; + _error_cb_arg = arg; +} + +void AsyncClient::onData(AcDataHandler cb, void* arg){ + _recv_cb = cb; + _recv_cb_arg = arg; +} + +void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ + _timeout_cb = cb; + _timeout_cb_arg = arg; +} + +void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ + _poll_cb = cb; + _poll_cb_arg = arg; +} + +bool AsyncClient::connected(){ + if (_socket == -1) { + return false; + } + return _conn_state == 4; +} + +bool AsyncClient::freeable(){ + if (_socket == -1) { + return true; + } + return _conn_state == 0 || _conn_state > 4; +} + +uint32_t AsyncClient::getRemoteAddress() { + if(_socket == -1) { + return 0; + } + + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(_socket, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + + return s->sin_addr.s_addr; +} + +uint16_t AsyncClient::getRemotePort() { + if(_socket == -1) { + return 0; + } + + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(_socket, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + + return ntohs(s->sin_port); +} + +uint32_t AsyncClient::getLocalAddress() { + if(_socket == -1) { + return 0; + } + + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(_socket, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + + return s->sin_addr.s_addr; +} + +uint16_t AsyncClient::getLocalPort() { + if(_socket == -1) { + return 0; + } + + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(_socket, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + + return ntohs(s->sin_port); +} + +IPAddress AsyncClient::remoteIP() { + return IPAddress(getRemoteAddress()); +} + +uint16_t AsyncClient::remotePort() { + return getRemotePort(); +} + +IPAddress AsyncClient::localIP() { + return IPAddress(getLocalAddress()); +} + +uint16_t AsyncClient::localPort() { + return getLocalPort(); +} + + +#if ASYNC_TCP_SSL_ENABLED +bool AsyncClient::connect(IPAddress ip, uint16_t port, bool secure) +#else +bool AsyncClient::connect(IPAddress ip, uint16_t port) +#endif // ASYNC_TCP_SSL_ENABLED +{ + if (_socket != -1) { + log_w("already connected, state %d", _conn_state); + return false; + } + + if(!_start_asyncsock_task()){ + log_e("failed to start task"); + return false; + } + +#if ASYNC_TCP_SSL_ENABLED + _secure = secure; + _handshake_done = !secure; +#endif // ASYNC_TCP_SSL_ENABLED + + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + log_e("socket: %d", errno); + return false; + } + int r = fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); + + uint32_t ip_addr = ip; + struct sockaddr_in serveraddr; + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + memcpy(&(serveraddr.sin_addr.s_addr), &ip_addr, 4); + serveraddr.sin_port = htons(port); + +#ifdef EINPROGRESS + #if EINPROGRESS != 119 + #error EINPROGRESS invalid + #endif +#endif + + //Serial.printf("DEBUG: connect to %08x port %d using IP... ", ip_addr, port); + errno = 0; r = ::connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); + //Serial.printf("r=%d errno=%d\r\n", r, errno); + if (r < 0 && errno != EINPROGRESS) { + //Serial.println("\t(connect failed)"); + log_e("connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + ::close(sockfd); + return false; + } + + // Updating state visible to asyncTcpSock task + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _conn_state = 2; + _socket = sockfd; + _rx_last_packet = millis(); + xSemaphoreGiveRecursive(_asyncsock_mutex); + + // Socket is now connecting. Should become writable in asyncTcpSock task + //Serial.printf("\twaiting for connect finished on socket: %d\r\n", _socket); + return true; +} + +void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg); +#if ASYNC_TCP_SSL_ENABLED +bool AsyncClient::connect(const char* host, uint16_t port, bool secure){ +#else +bool AsyncClient::connect(const char* host, uint16_t port){ +#endif // ASYNC_TCP_SSL_ENABLED + ip_addr_t addr; + + if(!_start_asyncsock_task()){ + log_e("failed to start task"); + return false; + } + + log_v("connect to %s port %d using DNS...", host, port); + err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcpsock_dns_found, this); + if(err == ERR_OK) { + log_v("\taddr resolved as %08x, connecting...", addr.u_addr.ip4.addr); +#if ASYNC_TCP_SSL_ENABLED + _hostname = host; + return connect(IPAddress(addr.u_addr.ip4.addr), port, secure); +#else + return connect(IPAddress(addr.u_addr.ip4.addr), port); +#endif // ASYNC_TCP_SSL_ENABLED + } else if(err == ERR_INPROGRESS) { + log_v("\twaiting for DNS resolution"); + _conn_state = 1; + _connect_port = port; +#if ASYNC_TCP_SSL_ENABLED + _hostname = host; + _secure = secure; + _handshake_done = !secure; +#endif // ASYNC_TCP_SSL_ENABLED + return true; + } + log_e("error: %d", err); + return false; +} + +// This function runs in the LWIP thread +void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) +{ + AsyncClient * c = (AsyncClient *)arg; + if (ipaddr) { + memcpy(&(c->_connect_addr), ipaddr, sizeof(struct ip_addr)); + } else { + memset(&(c->_connect_addr), 0, sizeof(struct ip_addr)); + } + + // Updating state visible to asyncTcpSock task + // MUST NOT take _asyncsock_mutex lock, risks a deadlock if task holding lock + // attempts a LWIP network call. + c->_isdnsfinished = true; + + // TODO: actually use name +} + +// DNS resolving has finished. Check for error or connect +void AsyncClient::_sockDelayedConnect(void) +{ + if (_connect_addr.u_addr.ip4.addr) { +#if ASYNC_TCP_SSL_ENABLED + connect(IPAddress(_connect_addr.u_addr.ip4.addr), _connect_port, _secure); +#else + connect(IPAddress(_connect_addr.u_addr.ip4.addr), _connect_port); +#endif + } else { + _conn_state = 0; + if(_error_cb) { + _error_cb(_error_cb_arg, this, -55); + } + if(_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + } +} + +#if ASYNC_TCP_SSL_ENABLED +int AsyncClient::_runSSLHandshakeLoop(void) +{ + int res = 0; + + while (!_handshake_done) { + res = _sslctx->runSSLHandshake(); + if (res == 0) { + // Handshake successful + _handshake_done = true; + } else if (ASYNCTCP_TLS_CAN_RETRY(res)) { + // Ran out of readable data or writable space on socket, must continue later + break; + } else { + // SSL handshake for AsyncTCP does not inform SSL errors + log_e("TLS setup failed with error %d, closing socket...", res); + _close(); + // _sslctx should be NULL after this + break; + } + } + + return res; +} +#endif + +bool AsyncClient::_sockIsWriteable(void) +{ + int res; + int sockerr; + socklen_t len; + bool activity = false; + + int sent_errno = 0; + std::deque notifylist; + + // Socket is now writeable. What should we do? + switch (_conn_state) { + case 2: + case 3: + // Socket has finished connecting. What happened? + len = (socklen_t)sizeof(int); + res = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); + if (res < 0) { + _error(errno); + } else if (sockerr != 0) { + _error(sockerr); + } else { +#if ASYNC_TCP_SSL_ENABLED + if (_secure) { + int res = 0; + + if (_sslctx == NULL) { + String remIP_str = remoteIP().toString(); + const char * host_or_ip = _hostname.isEmpty() + ? remIP_str.c_str() + : _hostname.c_str(); + + _sslctx = new AsyncTCP_TLS_Context(); + if (_root_ca != NULL) { + res = _sslctx->startSSLClient(_socket, host_or_ip, + (const unsigned char *)_root_ca, _root_ca_len, + (const unsigned char *)_cli_cert, _cli_cert_len, + (const unsigned char *)_cli_key, _cli_key_len); + } else if (_psk_ident != NULL) { + res = _sslctx->startSSLClient(_socket, host_or_ip, + _psk_ident, _psk); + } else { + res = _sslctx->startSSLClientInsecure(_socket, host_or_ip); + } + + if (res != 0) { + // SSL setup for AsyncTCP does not inform SSL errors + log_e("TLS setup failed with error %d, closing socket...", res); + _close(); + // _sslctx should be NULL after this + } + } + + // _handshake_done is set to FALSE on connect() if encrypted connection + if (_sslctx != NULL && res == 0) res = _runSSLHandshakeLoop(); + + if (!_handshake_done) return ASYNCTCP_TLS_CAN_RETRY(res); + + // Fallthrough to ordinary successful connection + } +#endif + + // Socket is now fully connected + _conn_state = 4; + activity = true; + _rx_last_packet = millis(); + _ack_timeout_signaled = false; + + if(_connect_cb) { + _connect_cb(_connect_cb_arg, this); + } + } + break; + case 4: + default: + // Socket can accept some new data... + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + if (_writeQueue.size() > 0) { + activity = _flushWriteQueue(); + _collectNotifyWrittenBuffers(notifylist, sent_errno); + } + xSemaphoreGive(_write_mutex); + + _notifyWrittenBuffers(notifylist, sent_errno); + + break; + } + + return activity; +} + +bool AsyncClient::_flushWriteQueue(void) +{ + bool activity = false; + + if (_socket == -1) return false; + + for (auto it = _writeQueue.begin(); it != _writeQueue.end(); it++) { + // Abort iteration if error found while writing a buffer + if (it->write_errno != 0) break; + + // Skip over head buffers already fully written + if (it->written >= it->length) continue; + + bool keep_writing = true; + do { + uint8_t * p = it->data + it->written; + size_t n = it->length - it->written; + errno = 0; + ssize_t r; + +#if ASYNC_TCP_SSL_ENABLED + if (_sslctx != NULL) { + r = _sslctx->write(p, n); + if (ASYNCTCP_TLS_CAN_RETRY(r)) { + r = -1; + errno = EAGAIN; + } else if (ASYNCTCP_TLS_EOF(r)) { + r = -1; + errno = EPIPE; + } else if (r < 0) { + if (errno == 0) errno = EIO; + } + } else { +#endif + r = lwip_write(_socket, p, n); +#if ASYNC_TCP_SSL_ENABLED + } +#endif + + if (r >= 0) { + // Written some data into the socket + it->written += r; + _writeSpaceRemaining += r; + activity = true; + + if (it->written >= it->length) { + it->written_at = millis(); + if (it->owned) ::free(it->data); + it->data = NULL; + } + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Socket is full, could not write anything + keep_writing = false; + } else { + // A write error happened that should be reported + it->write_errno = errno; + keep_writing = false; + log_e("socket %d lwip_write() failed errno=%d", _socket, it->write_errno); + } + } while (keep_writing && it->written < it->length); + } + + return activity; +} + +// This method MUST be called with _write_mutex held +void AsyncClient::_collectNotifyWrittenBuffers(std::deque & notifyqueue, int & write_errno) +{ + write_errno = 0; + notifyqueue.clear(); + + while (_writeQueue.size() > 0) { + if (_writeQueue.front().write_errno != 0) { + write_errno = _writeQueue.front().write_errno; + return; + } + + if (_writeQueue.front().written >= _writeQueue.front().length) { + // Collect information on fully-written buffer, and stash it into notify queue + if (_writeQueue.front().written_at > _rx_last_packet) { + _rx_last_packet = _writeQueue.front().written_at; + } + if (_writeQueue.front().owned && _writeQueue.front().data != NULL) ::free(_writeQueue.front().data); + + notify_writebuf noti; + noti.length = _writeQueue.front().length; + noti.delay = _writeQueue.front().written_at - _writeQueue.front().queued_at; + _writeQueue.pop_front(); + notifyqueue.push_back(noti); + } else { + // Found first not-fully-written buffer, stop here + return; + } + } +} + +void AsyncClient::_notifyWrittenBuffers(std::deque & notifyqueue, int write_errno) +{ + while (notifyqueue.size() > 0) { + if (notifyqueue.front().length > 0 && _sent_cb) { + _sent_cb(_sent_cb_arg, this, notifyqueue.front().length, notifyqueue.front().delay); + } + notifyqueue.pop_front(); + } + + if (write_errno != 0) _error(write_errno); +} + +void AsyncClient::_sockIsReadable(void) +{ + _rx_last_packet = millis(); + errno = 0; + ssize_t r; + +#if ASYNC_TCP_SSL_ENABLED + if (_sslctx != NULL) { + if (!_handshake_done) { + // Handshake process has stopped for want of data, must be + // continued here for connection to complete. + _runSSLHandshakeLoop(); + + // If handshake was successful, this will be recognized when the socket + // next becomes writable. No other read operation should be done here. + return; + } else { + r = _sslctx->read(_readBuffer, MAX_PAYLOAD_SIZE); + if (ASYNCTCP_TLS_CAN_RETRY(r)) { + r = -1; + errno = EAGAIN; + } else if (ASYNCTCP_TLS_EOF(r)) { + // Simulate "successful" end-of-stream condition + r = 0; + } else if (r < 0) { + if (errno == 0) errno = EIO; + } + } + } else { +#endif + r = lwip_read(_socket, _readBuffer, MAX_PAYLOAD_SIZE); +#if ASYNC_TCP_SSL_ENABLED + } +#endif + + if (r > 0) { + if(_recv_cb) { + _recv_cb(_recv_cb_arg, this, _readBuffer, r); + } + } else if (r == 0) { + // A successful read of 0 bytes indicates remote side closed connection + _close(); + } else if (r < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Do nothing, will try later + } else { + _error(errno); + } + } +} + +void AsyncClient::_sockPoll(void) +{ + if (!connected()) return; + + // The AsyncClient::send() call may be invoked from tasks other than "asyncTcpSock" + // and may have written buffers via _flushWriteQueue(), but the ack callbacks have + // not been called yet, nor buffers removed from the write queue. For consistency, + // written buffers are now acked here. + std::deque notifylist; + int sent_errno = 0; + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + if (_writeQueue.size() > 0) { + _collectNotifyWrittenBuffers(notifylist, sent_errno); + } + xSemaphoreGive(_write_mutex); + + _notifyWrittenBuffers(notifylist, sent_errno); + + /* Connection migh be closed after ACK notification. */ + if (!connected()) return; + + uint32_t now = millis(); + + // ACK Timeout - simulated by write queue staleness + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + if (_writeQueue.size() > 0 && !_ack_timeout_signaled && _ack_timeout) { + uint32_t sent_delay = now - _writeQueue.front().queued_at; + if (sent_delay >= _ack_timeout && _writeQueue.front().written_at == 0) { + _ack_timeout_signaled = true; + //log_w("ack timeout %d", pcb->state); + xSemaphoreGive(_write_mutex); + if(_timeout_cb) + _timeout_cb(_timeout_cb_arg, this, sent_delay); + return; + } + } + xSemaphoreGive(_write_mutex); + + // RX Timeout? Check for readable socket before bailing out + if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) { + fd_set sockSet_r; + struct timeval tv; + + FD_ZERO(&sockSet_r); + FD_SET(_socket, &sockSet_r); + tv.tv_sec = 0; + tv.tv_usec = 0; + + int r = select(_socket + 1, &sockSet_r, NULL, NULL, &tv); + if (r > 0) _rx_last_packet = now; + } + + // RX Timeout + if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) { + //log_w("rx timeout %d", pcb->state); + _close(); + return; + } + // Everything is fine + if(_poll_cb) { + _poll_cb(_poll_cb_arg, this); + } +} + +void AsyncClient::_removeAllCallbacks(void) +{ + _connect_cb = NULL; + _connect_cb_arg = NULL; + _discard_cb = NULL; + _discard_cb_arg = NULL; + _sent_cb = NULL; + _sent_cb_arg = NULL; + _error_cb = NULL; + _error_cb_arg = NULL; + _recv_cb = NULL; + _recv_cb_arg = NULL; + _timeout_cb = NULL; + _timeout_cb_arg = NULL; + _poll_cb = NULL; + _poll_cb_arg = NULL; +} + +void AsyncClient::_close(void) +{ + //Serial.print("AsyncClient::_close: "); Serial.println(_socket); + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _conn_state = 0; + ::close(_socket); + _socket = -1; +#if ASYNC_TCP_SSL_ENABLED + if (_sslctx != NULL) { + delete _sslctx; + _sslctx = NULL; + } +#endif + xSemaphoreGiveRecursive(_asyncsock_mutex); + + _clearWriteQueue(); + if (_discard_cb) _discard_cb(_discard_cb_arg, this); +} + +void AsyncClient::_error(int8_t err) +{ + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _conn_state = 0; + ::close(_socket); + _socket = -1; +#if ASYNC_TCP_SSL_ENABLED + if (_sslctx != NULL) { + delete _sslctx; + _sslctx = NULL; + } +#endif + xSemaphoreGiveRecursive(_asyncsock_mutex); + + _clearWriteQueue(); + if (_error_cb) _error_cb(_error_cb_arg, this, err); + if (_discard_cb) _discard_cb(_discard_cb_arg, this); +} + +size_t AsyncClient::space() +{ + if (!connected()) return 0; + return _writeSpaceRemaining; +} + +size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) +{ + queued_writebuf n_entry; + + if (!connected() || data == NULL || size <= 0) return 0; + + size_t room = space(); + if (!room) return 0; + + size_t will_send = (room < size) ? room : size; + if (apiflags & ASYNC_WRITE_FLAG_COPY) { + n_entry.data = (uint8_t *)malloc(will_send); + if (n_entry.data == NULL) { + return 0; + } + memcpy(n_entry.data, data, will_send); + n_entry.owned = true; + } else { + n_entry.data = (uint8_t *)data; + n_entry.owned = false; + } + n_entry.length = will_send; + n_entry.written = 0; + n_entry.queued_at = millis(); + n_entry.written_at = 0; + n_entry.write_errno = 0; + + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + _writeQueue.push_back(n_entry); + _writeSpaceRemaining -= will_send; + _ack_timeout_signaled = false; + xSemaphoreGive(_write_mutex); + + return will_send; +} + +bool AsyncClient::send() +{ + if (!connected()) return false; + + fd_set sockSet_w; + struct timeval tv; + + FD_ZERO(&sockSet_w); + FD_SET(_socket, &sockSet_w); + tv.tv_sec = 0; + tv.tv_usec = 0; + + // Write as much data as possible from queue if socket is writable + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + int r = select(_socket + 1, NULL, &sockSet_w, NULL, &tv); + if (r > 0) _flushWriteQueue(); + xSemaphoreGive(_write_mutex); + return true; +} + +bool AsyncClient::_pendingWrite(void) +{ + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + bool pending = ((_conn_state > 0 && _conn_state < 4) || _writeQueue.size() > 0); + xSemaphoreGive(_write_mutex); + return pending; +} + +// In normal operation this should be a no-op. Will only free something in case +// of errors before all data was written. +void AsyncClient::_clearWriteQueue(void) +{ + xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); + while (_writeQueue.size() > 0) { + if (_writeQueue.front().owned) { + if (_writeQueue.front().data != NULL) ::free(_writeQueue.front().data); + } + _writeQueue.pop_front(); + } + xSemaphoreGive(_write_mutex); +} + +bool AsyncClient::free(){ + if (_socket == -1) return true; + return (_conn_state == 0 || _conn_state > 4); +} + +size_t AsyncClient::write(const char* data) { + if(data == NULL) { + return 0; + } + return write(data, strlen(data)); +} + +size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { + size_t will_send = add(data, size, apiflags); + if(!will_send || !send()) { + return 0; + } + return will_send; +} + +void AsyncClient::close(bool now) +{ + if (_socket != -1) _close(); +} + +int8_t AsyncClient::abort(){ + if (_socket != -1) { + // Note: needs LWIP_SO_LINGER to be enabled in order to work, otherwise + // this call is equivalent to close(). + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + setsockopt(_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + _close(); + } + return ERR_ABRT; +} + +#if ASYNC_TCP_SSL_ENABLED +void AsyncClient::setRootCa(const char* rootca, const size_t len) { + _root_ca = (char*)rootca; + _root_ca_len = len; +} + +void AsyncClient::setClientCert(const char* cli_cert, const size_t len) { + _cli_cert = (char*)cli_cert; + _cli_cert_len = len; +} + +void AsyncClient::setClientKey(const char* cli_key, const size_t len) { + _cli_key = (char*)cli_key; + _cli_key_len = len; +} + +void AsyncClient::setPsk(const char* psk_ident, const char* psk) { + _psk_ident = psk_ident; + _psk = psk; +} +#endif // ASYNC_TCP_SSL_ENABLED + +const char * AsyncClient::errorToString(int8_t error){ + switch(error){ + case ERR_OK: return "OK"; + case ERR_MEM: return "Out of memory error"; + case ERR_BUF: return "Buffer error"; + case ERR_TIMEOUT: return "Timeout"; + case ERR_RTE: return "Routing problem"; + case ERR_INPROGRESS: return "Operation in progress"; + case ERR_VAL: return "Illegal value"; + case ERR_WOULDBLOCK: return "Operation would block"; + case ERR_USE: return "Address in use"; + case ERR_ALREADY: return "Already connected"; + case ERR_CONN: return "Not connected"; + case ERR_IF: return "Low-level netif error"; + case ERR_ABRT: return "Connection aborted"; + case ERR_RST: return "Connection reset"; + case ERR_CLSD: return "Connection closed"; + case ERR_ARG: return "Illegal argument"; + case -55: return "DNS failed"; + default: return "UNKNOWN"; + } +} +/* +const char * AsyncClient::stateToString(){ + switch(state()){ + case 0: return "Closed"; + case 1: return "Listen"; + case 2: return "SYN Sent"; + case 3: return "SYN Received"; + case 4: return "Established"; + case 5: return "FIN Wait 1"; + case 6: return "FIN Wait 2"; + case 7: return "Close Wait"; + case 8: return "Closing"; + case 9: return "Last ACK"; + case 10: return "Time Wait"; + default: return "UNKNOWN"; + } +} +*/ + + + +/* + Async TCP Server + */ + +AsyncServer::AsyncServer(IPAddress addr, uint16_t port) +: _port(port) +, _addr(addr) +, _noDelay(false) +, _connect_cb(0) +, _connect_cb_arg(0) +{} + +AsyncServer::AsyncServer(uint16_t port) +: _port(port) +, _addr((uint32_t) IPADDR_ANY) +, _noDelay(false) +, _connect_cb(0) +, _connect_cb_arg(0) +{} + +AsyncServer::~AsyncServer(){ + end(); +} + +void AsyncServer::onClient(AcConnectHandler cb, void* arg){ + _connect_cb = cb; + _connect_cb_arg = arg; +} + +void AsyncServer::begin() +{ + if (_socket != -1) return; + + if (!_start_asyncsock_task()) { + log_e("failed to start task"); + return; + } + + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) return; + + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_addr.s_addr = (uint32_t) _addr; + server.sin_port = htons(_port); + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) { + ::close(sockfd); + log_e("bind error: %d - %s", errno, strerror(errno)); + return; + } + + static uint8_t backlog = 5; + if (listen(sockfd , backlog) < 0) { + ::close(sockfd); + log_e("listen error: %d - %s", errno, strerror(errno)); + return; + } + fcntl(sockfd, F_SETFL, O_NONBLOCK); + + // Updating state visible to asyncTcpSock task + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + _socket = sockfd; + xSemaphoreGiveRecursive(_asyncsock_mutex); +} + +void AsyncServer::end() +{ + if (_socket == -1) return; + xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); + ::close(_socket); + _socket = -1; + xSemaphoreGiveRecursive(_asyncsock_mutex); +} + +void AsyncServer::_sockIsReadable(void) +{ + //Serial.print("AsyncServer::_sockIsReadable: "); Serial.println(_socket); + + if (_connect_cb) { + struct sockaddr_in client; + size_t cs = sizeof(struct sockaddr_in); + errno = 0; int accepted_sockfd = ::accept(_socket, (struct sockaddr *)&client, (socklen_t*)&cs); + //Serial.printf("\t new sockfd=%d errno=%d\r\n", accepted_sockfd, errno); + if (accepted_sockfd < 0) { + log_e("accept error: %d - %s", errno, strerror(errno)); + return; + } + + AsyncClient * c = new AsyncClient(accepted_sockfd); + if (c) { + c->setNoDelay(_noDelay); + _connect_cb(_connect_cb_arg, c); + } + } +} + diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h new file mode 100644 index 00000000..47a6f2d4 --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h @@ -0,0 +1,321 @@ +/* + Reimplementation of an asynchronous TCP library for Espressif MCUs, using + BSD sockets. + + Copyright (c) 2020 Alex Villacís Lasso. + + Original AsyncTCP API Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ASYNCTCP_H_ +#define ASYNCTCP_H_ + +#include "../../../system_settings.h" +#include "../../../devboard/hal/hal.h" + +#include "IPAddress.h" +#include "sdkconfig.h" +#include +#include +#include +#if ASYNC_TCP_SSL_ENABLED +#include +#include "AsyncTCP_TLS_Context.h" +#endif + +extern "C" { + #include "lwip/err.h" + #include "lwip/sockets.h" +} + +#define ASYNCTCP_VERSION "1.0.2-dev" +#define ASYNCTCP_VERSION_MAJOR 1 +#define ASYNCTCP_VERSION_MINOR 2 +#define ASYNCTCP_VERSION_REVISION 2 +#define ASYNCTCP_FORK_mathieucarbou + +//If core is not defined, then we are running in Arduino or PIO +#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE +#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE +#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event +#endif +#ifndef CONFIG_ASYNC_TCP_STACK_SIZE +#define CONFIG_ASYNC_TCP_STACK_SIZE 16384 // 8192 * 2 +#endif +#ifndef CONFIG_ASYNC_TCP_STACK +#define CONFIG_ASYNC_TCP_STACK CONFIG_ASYNC_TCP_STACK_SIZE +#endif +#ifndef CONFIG_ASYNC_TCP_PRIORITY +#define CONFIG_ASYNC_TCP_PRIORITY 3 +#endif +#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY +#define CONFIG_ASYNC_TCP_TASK_PRIORITY TASK_CONNECTIVITY_PRIO +#endif +#ifndef CONFIG_ASYNC_TCP_TASK_NAME +#define CONFIG_ASYNC_TCP_TASK_NAME "asyncTcpSock" +#endif + +class AsyncClient; + +#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME +#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000 +#endif +#ifndef ASYNC_MAX_ACK_TIME +#define ASYNC_MAX_ACK_TIME CONFIG_ASYNC_TCP_MAX_ACK_TIME +#endif +#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) +#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. +#define SSL_HANDSHAKE_TIMEOUT 5000 // timeout to complete SSL handshake + +typedef std::function AcConnectHandler; +typedef std::function AcAckHandler; +typedef std::function AcErrorHandler; +typedef std::function AcDataHandler; +//typedef std::function AcPacketHandler; +typedef std::function AcTimeoutHandler; + +class AsyncSocketBase +{ +private: + static std::list & _getSocketBaseList(void); + +protected: + int _socket = -1; + bool _selected = false; + bool _isdnsfinished = false; + uint32_t _sock_lastactivity = 0; + + virtual void _sockIsReadable(void) {} // Action to take on readable socket + virtual bool _sockIsWriteable(void) { return false; } // Action to take on writable socket + virtual void _sockPoll(void) {} // Action to take on idle socket activity poll + virtual void _sockDelayedConnect(void) {} // Action to take on DNS-resolve finished + + virtual bool _pendingWrite(void) { return false; } // Test if there is data pending to be written + virtual bool _isServer(void) { return false; } // Will a read from this socket result in one more client? + +public: + AsyncSocketBase(void); + virtual ~AsyncSocketBase(); + + friend void _asynctcpsock_task(void *); +}; + +class AsyncClient : public AsyncSocketBase +{ + public: + AsyncClient(int sockfd = -1); + ~AsyncClient(); + +#if ASYNC_TCP_SSL_ENABLED + bool connect(IPAddress ip, uint16_t port, bool secure = false); + bool connect(const char* host, uint16_t port, bool secure = false); + void setRootCa(const char* rootca, const size_t len); + void setClientCert(const char* cli_cert, const size_t len); + void setClientKey(const char* cli_key, const size_t len); + void setPsk(const char* psk_ident, const char* psk); +#else + bool connect(IPAddress ip, uint16_t port); + bool connect(const char* host, uint16_t port); +#endif // ASYNC_TCP_SSL_ENABLED + void close(bool now = false); + + int8_t abort(); + bool free(); + + bool canSend() { return space() > 0; } + size_t space(); + size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending + bool send(); + + //write equals add()+send() + size_t write(const char* data); + size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true + + uint8_t state() { return _conn_state; } + bool connected(); + bool freeable();//disconnected or disconnecting + + uint32_t getAckTimeout(); + void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds + + uint32_t getRxTimeout(); + void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds + void setNoDelay(bool nodelay); + bool getNoDelay(); + + uint32_t getRemoteAddress(); + uint16_t getRemotePort(); + uint32_t getLocalAddress(); + uint16_t getLocalPort(); + + //compatibility + IPAddress remoteIP(); + uint16_t remotePort(); + IPAddress localIP(); + uint16_t localPort(); + + void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect + void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected + void onAck(AcAckHandler cb, void* arg = 0); //ack received + void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error + void onData(AcDataHandler cb, void* arg = 0); //data received + void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout + void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected + + // The following functions are just for API compatibility and do nothing + size_t ack(size_t len) { return len; } + void ackLater() {} + + const char * errorToString(int8_t error); +// const char * stateToString(); + + protected: + bool _sockIsWriteable(void); + void _sockIsReadable(void); + void _sockPoll(void); + void _sockDelayedConnect(void); + bool _pendingWrite(void); + + private: + + AcConnectHandler _connect_cb; + void* _connect_cb_arg; + AcConnectHandler _discard_cb; + void* _discard_cb_arg; + AcAckHandler _sent_cb; + void* _sent_cb_arg; + AcErrorHandler _error_cb; + void* _error_cb_arg; + AcDataHandler _recv_cb; + void* _recv_cb_arg; + AcTimeoutHandler _timeout_cb; + void* _timeout_cb_arg; + AcConnectHandler _poll_cb; + void* _poll_cb_arg; + + uint32_t _rx_last_packet; + uint32_t _rx_since_timeout; + uint32_t _ack_timeout; + + // Used on asynchronous DNS resolving scenario - I do not want to connect() + // from the LWIP thread itself. + struct ip_addr _connect_addr; + uint16_t _connect_port = 0; + //const char * _connect_dnsname = NULL; + +#if ASYNC_TCP_SSL_ENABLED + size_t _root_ca_len; + char* _root_ca; + size_t _cli_cert_len; + char* _cli_cert; + size_t _cli_key_len; + char* _cli_key; + bool _secure; + bool _handshake_done; + const char* _psk_ident; + const char* _psk; + + String _hostname; + AsyncTCP_TLS_Context * _sslctx; +#endif // ASYNC_TCP_SSL_ENABLED + + // The following private struct represents a buffer enqueued with the add() + // method. Each of these buffers are flushed whenever the socket becomes + // writable + typedef struct { + uint8_t * data; // Pointer to data queued for write + uint32_t length; // Length of data queued for write + uint32_t written; // Length of data written to socket so far + uint32_t queued_at;// Timestamp at which this data buffer was queued + uint32_t written_at; // Timestamp at which this data buffer was completely written + int write_errno; // If != 0, errno value while writing this buffer + bool owned; // If true, we malloc'ed the data and should be freed after completely written. + // If false, app owns the memory and should ensure it remains valid until acked + } queued_writebuf; + + // Internal struct used to implement sent buffer notification + typedef struct { + uint32_t length; + uint32_t delay; + } notify_writebuf; + + // Queue of buffers to write to socket + SemaphoreHandle_t _write_mutex; + std::deque _writeQueue; + bool _ack_timeout_signaled = false; + + // Remaining space willing to queue for writing + uint32_t _writeSpaceRemaining; + + // Simulation of connection state + uint8_t _conn_state; + + void _error(int8_t err); + void _close(void); + void _removeAllCallbacks(void); + bool _flushWriteQueue(void); + void _clearWriteQueue(void); + void _collectNotifyWrittenBuffers(std::deque &, int &); + void _notifyWrittenBuffers(std::deque &, int); + +#if ASYNC_TCP_SSL_ENABLED + int _runSSLHandshakeLoop(void); +#endif + + friend void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg); +}; + +#if ASYNC_TCP_SSL_ENABLED +typedef std::function AcSSlFileHandler; +#endif + +class AsyncServer : public AsyncSocketBase +{ + public: + AsyncServer(IPAddress addr, uint16_t port); + AsyncServer(uint16_t port); + ~AsyncServer(); + void onClient(AcConnectHandler cb, void* arg); +#if ASYNC_TCP_SSL_ENABLED + // Dummy, so it compiles with ESP Async WebServer library enabled. + void onSslFileRequest(AcSSlFileHandler cb, void* arg) {}; + void beginSecure(const char *cert, const char *private_key_file, const char *password) {}; +#endif + void begin(); + void end(); + + void setNoDelay(bool nodelay) { _noDelay = nodelay; } + bool getNoDelay() { return _noDelay; } + uint8_t status(); + + protected: + uint16_t _port; + IPAddress _addr; + + bool _noDelay; + AcConnectHandler _connect_cb; + void* _connect_cb_arg; + + // Listening socket is readable on incoming connection + void _sockIsReadable(void); + + // Mark this class as a server + bool _isServer(void) { return true; } +}; + + +#endif /* ASYNCTCP_H_ */ diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h new file mode 100644 index 00000000..fc70073d --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h @@ -0,0 +1,6 @@ +#ifndef ASYNCTCP_SSL_H_ +#define ASYNCTCP_SSL_H_ + +#include "AsyncTCP_SSL.hpp" + +#endif /* ASYNCTCP_SSL_H_ */ \ No newline at end of file diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp new file mode 100644 index 00000000..8f088d0b --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp @@ -0,0 +1,17 @@ +#ifndef ASYNCTCP_SSL_HPP +#define ASYNCTCP_SSL_HPP + +#ifdef ASYNC_TCP_SSL_ENABLED + +#include + +#define AsyncSSLClient AsyncClient +#define AsyncSSLServer AsyncServer + +#define ASYNC_TCP_SSL_VERSION "AsyncTCPSock SSL shim v0.0.1" + +#else +#error Compatibility shim requires ASYNC_TCP_SSL_ENABLED to be defined! +#endif + +#endif \ No newline at end of file diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp new file mode 100644 index 00000000..677146a7 --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp @@ -0,0 +1,346 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "AsyncTCP_TLS_Context.h" + +#if ASYNC_TCP_SSL_ENABLED +#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED) +# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#else + +static const char *pers = "esp32-tls"; + +static int _handle_error(int err, const char * function, int line) +{ + if(err == -30848){ + return err; + } +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + mbedtls_strerror(err, error_buf, 100); + log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); +#else + log_e("[%s():%d]: code %d", function, line, err); +#endif + return err; +} + +#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + +AsyncTCP_TLS_Context::AsyncTCP_TLS_Context(void) +{ + mbedtls_ssl_init(&ssl_ctx); + mbedtls_ssl_config_init(&ssl_conf); + mbedtls_ctr_drbg_init(&drbg_ctx); + _socket = -1; + _have_ca_cert = false; + _have_client_cert = false; + _have_client_key = false; + handshake_timeout = 120000; +} + +int AsyncTCP_TLS_Context::startSSLClientInsecure(int sck, const char * host_or_ip) +{ + return _startSSLClient(sck, host_or_ip, + NULL, 0, + NULL, 0, + NULL, 0, + NULL, NULL, + true); +} + +int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, + const char *pskIdent, const char *psKey) +{ + return _startSSLClient(sck, host_or_ip, + NULL, 0, + NULL, 0, + NULL, 0, + pskIdent, psKey, + false); +} + +int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, + const char *rootCABuff, + const char *cli_cert, + const char *cli_key) +{ + return startSSLClient(sck, host_or_ip, + (const unsigned char *)rootCABuff, (rootCABuff != NULL) ? strlen(rootCABuff) + 1 : 0, + (const unsigned char *)cli_cert, (cli_cert != NULL) ? strlen(cli_cert) + 1 : 0, + (const unsigned char *)cli_key, (cli_key != NULL) ? strlen(cli_key) + 1 : 0); +} + +int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, + const unsigned char *rootCABuff, const size_t rootCABuff_len, + const unsigned char *cli_cert, const size_t cli_cert_len, + const unsigned char *cli_key, const size_t cli_key_len) +{ + return _startSSLClient(sck, host_or_ip, + rootCABuff, rootCABuff_len, + cli_cert, cli_cert_len, + cli_key, cli_key_len, + NULL, NULL, + false); +} + +int AsyncTCP_TLS_Context::_startSSLClient(int sck, const char * host_or_ip, + const unsigned char *rootCABuff, const size_t rootCABuff_len, + const unsigned char *cli_cert, const size_t cli_cert_len, + const unsigned char *cli_key, const size_t cli_key_len, + const char *pskIdent, const char *psKey, + bool insecure) +{ + int ret; + int enable = 1; + + // The insecure flag will skip server certificate validation. Otherwise some + // certificate is required. + if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { + return -1; + } + +#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} +// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO"); +// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO"); + + ROE(lwip_setsockopt(sck, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); + ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); + + log_v("Seeding the random number generator"); + mbedtls_entropy_init(&entropy_ctx); + + ret = mbedtls_ctr_drbg_seed(&drbg_ctx, mbedtls_entropy_func, + &entropy_ctx, (const unsigned char *) pers, strlen(pers)); + if (ret < 0) { + return handle_error(ret); + } + + log_v("Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + return handle_error(ret); + } + + if (insecure) { + mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE); + log_i("WARNING: Skipping SSL Verification. INSECURE!"); + } else if (rootCABuff != NULL) { + log_v("Loading CA cert"); + mbedtls_x509_crt_init(&ca_cert); + mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + ret = mbedtls_x509_crt_parse(&ca_cert, rootCABuff, rootCABuff_len); + _have_ca_cert = true; + mbedtls_ssl_conf_ca_chain(&ssl_conf, &ca_cert, NULL); + if (ret < 0) { + // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash. + _deleteHandshakeCerts(); + return handle_error(ret); + } + } else if (pskIdent != NULL && psKey != NULL) { + log_v("Setting up PSK"); + // convert PSK from hex to binary + if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) { + log_e("pre-shared key not valid hex or too long"); + return -1; + } + unsigned char psk[MBEDTLS_PSK_MAX_LEN]; + size_t psk_len = strlen(psKey)/2; + for (int j=0; j= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] = c<<4; + c = psKey[j+1]; + if (c >= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] |= c; + } + // set mbedtls config + ret = mbedtls_ssl_conf_psk(&ssl_conf, psk, psk_len, + (const unsigned char *)pskIdent, strlen(pskIdent)); + if (ret != 0) { + log_e("mbedtls_ssl_conf_psk returned %d", ret); + return handle_error(ret); + } + } else { + return -1; + } + + if (!insecure && cli_cert != NULL && cli_key != NULL) { + mbedtls_x509_crt_init(&client_cert); + mbedtls_pk_init(&client_key); + + log_v("Loading CRT cert"); + + ret = mbedtls_x509_crt_parse(&client_cert, cli_cert, cli_cert_len); + _have_client_cert = true; + if (ret < 0) { + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + _deleteHandshakeCerts(); + return handle_error(ret); + } + + log_v("Loading private key"); +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0); +#else + ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0, mbedtls_ctr_drbg_random, &drbg_ctx); +#endif + _have_client_key = true; + + if (ret != 0) { + _deleteHandshakeCerts(); + return handle_error(ret); + } + + mbedtls_ssl_conf_own_cert(&ssl_conf, &client_cert, &client_key); + } + + log_v("Setting hostname for TLS session..."); + + // Hostname set here should match CN in server certificate + if ((ret = mbedtls_ssl_set_hostname(&ssl_ctx, host_or_ip)) != 0){ + _deleteHandshakeCerts(); + return handle_error(ret); + } + + mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf)) != 0) { + _deleteHandshakeCerts(); + return handle_error(ret); + } + + _socket = sck; + mbedtls_ssl_set_bio(&ssl_ctx, &_socket, mbedtls_net_send, mbedtls_net_recv, NULL ); + handshake_start_time = 0; + + return 0; +} + +int AsyncTCP_TLS_Context::runSSLHandshake(void) +{ + int ret, flags; + if (_socket < 0) return -1; + + if (handshake_start_time == 0) handshake_start_time = millis(); + ret = mbedtls_ssl_handshake(&ssl_ctx); + if (ret != 0) { + // Something happened before SSL handshake could be completed + + // Negotiation error, other than socket not readable/writable when required + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + return handle_error(ret); + } + + // Handshake is taking too long + if ((millis()-handshake_start_time) > handshake_timeout) + return -1; + + // Either MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE + return ret; + } + + // Handshake completed, validate remote side if required... + + if (_have_client_cert && _have_client_key) { + log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_ctx)); + if ((ret = mbedtls_ssl_get_record_expansion(&ssl_ctx)) >= 0) { + log_d("Record expansion is %d", ret); + } else { + log_w("Record expansion is unknown (compression)"); + } + } + + log_v("Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(&ssl_ctx)) != 0) { + char buf[512]; + memset(buf, 0, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + log_e("Failed to verify peer certificate! verification info: %s", buf); + _deleteHandshakeCerts(); + return handle_error(ret); + } else { + log_v("Certificate verified."); + } + + _deleteHandshakeCerts(); + + log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); + + return 0; +} + +int AsyncTCP_TLS_Context::write(const uint8_t *data, size_t len) +{ + if (_socket < 0) return -1; + + log_v("Writing packet, %d bytes unencrypted...", len); + int ret = mbedtls_ssl_write(&ssl_ctx, data, len); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } + return ret; +} + +int AsyncTCP_TLS_Context::read(uint8_t * data, size_t len) +{ + int ret = mbedtls_ssl_read(&ssl_ctx, data, len); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } + if (ret > 0) log_v("Read packet, %d out of %d requested bytes...", ret, len); + return ret; +} + +void AsyncTCP_TLS_Context::_deleteHandshakeCerts(void) +{ + if (_have_ca_cert) { + log_v("Cleaning CA certificate."); + mbedtls_x509_crt_free(&ca_cert); + _have_ca_cert = false; + } + if (_have_client_cert) { + log_v("Cleaning client certificate."); + mbedtls_x509_crt_free(&client_cert); + _have_client_cert = false; + } + if (_have_client_key) { + log_v("Cleaning client certificate key."); + mbedtls_pk_free(&client_key); + _have_client_key = false; + } +} + +AsyncTCP_TLS_Context::~AsyncTCP_TLS_Context() +{ + _deleteHandshakeCerts(); + + log_v("Cleaning SSL connection."); + + mbedtls_ssl_free(&ssl_ctx); + mbedtls_ssl_config_free(&ssl_conf); + mbedtls_ctr_drbg_free(&drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); // <-- Is this OK to free if mbedtls_entropy_init() has not been called on it? +} + +#endif +#endif // ASYNC_TCP_SSL_ENABLED \ No newline at end of file diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h new file mode 100644 index 00000000..4ef03327 --- /dev/null +++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h @@ -0,0 +1,79 @@ +#pragma once + +#if ASYNC_TCP_SSL_ENABLED + +#include "mbedtls/version.h" +#include "mbedtls/platform.h" +#if MBEDTLS_VERSION_NUMBER < 0x03000000 +#include "mbedtls/net.h" +#else +#include "mbedtls/net_sockets.h" +#endif +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" + +#define ASYNCTCP_TLS_CAN_RETRY(r) (((r) == MBEDTLS_ERR_SSL_WANT_READ) || ((r) == MBEDTLS_ERR_SSL_WANT_WRITE)) +#define ASYNCTCP_TLS_EOF(r) (((r) == MBEDTLS_ERR_SSL_CONN_EOF) || ((r) == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) + +class AsyncTCP_TLS_Context +{ +private: + // These fields must persist for the life of the encrypted connection, destroyed on + // object destructor. + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + + // These allocate memory during handshake but must be freed on either success or failure + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + bool _have_ca_cert; + bool _have_client_cert; + bool _have_client_key; + + unsigned long handshake_timeout; + unsigned long handshake_start_time; + + int _socket; + + int _startSSLClient(int sck, const char * host_or_ip, + const unsigned char *rootCABuff, const size_t rootCABuff_len, + const unsigned char *cli_cert, const size_t cli_cert_len, + const unsigned char *cli_key, const size_t cli_key_len, + const char *pskIdent, const char *psKey, + bool insecure); + + // Delete certificates used in handshake + void _deleteHandshakeCerts(void); +public: + AsyncTCP_TLS_Context(void); + virtual ~AsyncTCP_TLS_Context(); + + int startSSLClientInsecure(int sck, const char * host_or_ip); + + int startSSLClient(int sck, const char * host_or_ip, + const char *pskIdent, const char *psKey); + + int startSSLClient(int sck, const char * host_or_ip, + const char *rootCABuff, + const char *cli_cert, + const char *cli_key); + + int startSSLClient(int sck, const char * host_or_ip, + const unsigned char *rootCABuff, const size_t rootCABuff_len, + const unsigned char *cli_cert, const size_t cli_cert_len, + const unsigned char *cli_key, const size_t cli_key_len); + + int runSSLHandshake(void); + + int write(const uint8_t *data, size_t len); + + int read(uint8_t * data, size_t len); +}; + +#endif // ASYNC_TCP_SSL_ENABLED \ No newline at end of file diff --git a/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncEventSource.h b/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncEventSource.h index 34e42be0..8c45bd73 100644 --- a/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncEventSource.h +++ b/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncEventSource.h @@ -23,7 +23,7 @@ #include -#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h" +#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h" #include #ifndef SSE_MAX_QUEUED_MESSAGES #define SSE_MAX_QUEUED_MESSAGES 32 diff --git a/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncWebSocket.h b/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncWebSocket.h index 778fbb8c..22fe18ad 100644 --- a/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncWebSocket.h +++ b/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/AsyncWebSocket.h @@ -22,7 +22,7 @@ #define ASYNCWEBSOCKET_H_ #include -#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h" +#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h" #include #ifndef WS_MAX_QUEUED_MESSAGES #define WS_MAX_QUEUED_MESSAGES 32 diff --git a/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/ESPAsyncWebServer.h b/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/ESPAsyncWebServer.h index 8f75c083..ec22806f 100644 --- a/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/ESPAsyncWebServer.h +++ b/Software/src/lib/mathieucarbou-ESPAsyncWebServer/src/ESPAsyncWebServer.h @@ -32,7 +32,7 @@ #include #ifdef ESP32 -#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h" +#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h" #include #elif defined(ESP8266) #include diff --git a/Software/src/lib/me-no-dev-AsyncTCP/README.md b/Software/src/lib/me-no-dev-AsyncTCP/README.md deleted file mode 100644 index 79ffa9ef..00000000 --- a/Software/src/lib/me-no-dev-AsyncTCP/README.md +++ /dev/null @@ -1,15 +0,0 @@ -This is commit ca8ac5f from https://github.com/me-no-dev/AsyncTCP - -# AsyncTCP -[![Build Status](https://travis-ci.org/me-no-dev/AsyncTCP.svg?branch=master)](https://travis-ci.org/me-no-dev/AsyncTCP) ![](https://github.com/me-no-dev/AsyncTCP/workflows/Async%20TCP%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2f7e4d1df8b446d192cbfec6dc174d2d)](https://www.codacy.com/manual/me-no-dev/AsyncTCP?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/AsyncTCP&utm_campaign=Badge_Grade) - -### Async TCP Library for ESP32 Arduino - -[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. - -This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) - -## AsyncClient and AsyncServer -The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. diff --git a/Software/src/lib/me-no-dev-AsyncTCP/library.json b/Software/src/lib/me-no-dev-AsyncTCP/library.json deleted file mode 100644 index 89f90e4e..00000000 --- a/Software/src/lib/me-no-dev-AsyncTCP/library.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name":"AsyncTCP", - "description":"Asynchronous TCP Library for ESP32", - "keywords":"async,tcp", - "authors": - { - "name": "Hristo Gochkov", - "maintainer": true - }, - "repository": - { - "type": "git", - "url": "https://github.com/me-no-dev/AsyncTCP.git" - }, - "version": "1.1.1", - "license": "LGPL-3.0", - "frameworks": "arduino", - "platforms": "espressif32", - "build": { - "libCompatMode": 2 - } -} diff --git a/Software/src/lib/me-no-dev-AsyncTCP/library.properties b/Software/src/lib/me-no-dev-AsyncTCP/library.properties deleted file mode 100644 index eb4e26e9..00000000 --- a/Software/src/lib/me-no-dev-AsyncTCP/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=AsyncTCP -version=1.1.1 -author=Me-No-Dev -maintainer=Me-No-Dev -sentence=Async TCP Library for ESP32 -paragraph=Async TCP Library for ESP32 -category=Other -url=https://github.com/me-no-dev/AsyncTCP -architectures=* diff --git a/Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.cpp b/Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.cpp deleted file mode 100644 index acd89639..00000000 --- a/Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.cpp +++ /dev/null @@ -1,1387 +0,0 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "Arduino.h" - -#include "AsyncTCP.h" -extern "C"{ -#include "lwip/opt.h" -#include "lwip/tcp.h" -#include "lwip/inet.h" -#include "lwip/dns.h" -#include "lwip/err.h" -} -#include "esp_task_wdt.h" - -/* - * TCP/IP Event Task - * */ - -#define TAG "AsyncTCP" - -// https://github.com/espressif/arduino-esp32/issues/10526 -#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING -#define TCP_MUTEX_LOCK() \ - if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ - LOCK_TCPIP_CORE(); \ - } - -#define TCP_MUTEX_UNLOCK() \ - if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ - UNLOCK_TCPIP_CORE(); \ - } -#else // CONFIG_LWIP_TCPIP_CORE_LOCKING -#define TCP_MUTEX_LOCK() -#define TCP_MUTEX_UNLOCK() -#endif // CONFIG_LWIP_TCPIP_CORE_LOCKING - -typedef enum { - LWIP_TCP_SENT, LWIP_TCP_RECV, LWIP_TCP_FIN, LWIP_TCP_ERROR, LWIP_TCP_POLL, LWIP_TCP_CLEAR, LWIP_TCP_ACCEPT, LWIP_TCP_CONNECTED, LWIP_TCP_DNS -} lwip_event_t; - -typedef struct { - lwip_event_t event; - void *arg; - union { - struct { - void * pcb; - int8_t err; - } connected; - struct { - int8_t err; - } error; - struct { - tcp_pcb * pcb; - uint16_t len; - } sent; - struct { - tcp_pcb * pcb; - pbuf * pb; - int8_t err; - } recv; - struct { - tcp_pcb * pcb; - int8_t err; - } fin; - struct { - tcp_pcb * pcb; - } poll; - struct { - AsyncClient * client; - } accept; - struct { - const char * name; - ip_addr_t addr; - } dns; - }; -} lwip_event_packet_t; - -static xQueueHandle _async_queue; -static TaskHandle_t _async_service_task_handle = NULL; - - -SemaphoreHandle_t _slots_lock; -const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; -static uint32_t _closed_slots[_number_of_closed_slots]; -static uint32_t _closed_index = []() { - _slots_lock = xSemaphoreCreateBinary(); - xSemaphoreGive(_slots_lock); - for (int i = 0; i < _number_of_closed_slots; ++ i) { - _closed_slots[i] = 1; - } - return 1; -}(); - - -static inline bool _init_async_event_queue(){ - if(!_async_queue){ - _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *)); - if(!_async_queue){ - return false; - } - } - return true; -} - -static inline bool _send_async_event(lwip_event_packet_t ** e){ - return _async_queue && xQueueSend(_async_queue, e, portMAX_DELAY) == pdPASS; -} - -static inline bool _prepend_async_event(lwip_event_packet_t ** e){ - return _async_queue && xQueueSendToFront(_async_queue, e, portMAX_DELAY) == pdPASS; -} - -static inline bool _get_async_event(lwip_event_packet_t ** e){ - return _async_queue && xQueueReceive(_async_queue, e, portMAX_DELAY) == pdPASS; -} - -static bool _remove_events_with_arg(void * arg){ - lwip_event_packet_t * first_packet = NULL; - lwip_event_packet_t * packet = NULL; - - if(!_async_queue){ - return false; - } - //figure out which is the first packet so we can keep the order - while(!first_packet){ - if(xQueueReceive(_async_queue, &first_packet, 0) != pdPASS){ - return false; - } - //discard packet if matching - if((int)first_packet->arg == (int)arg){ - free(first_packet); - first_packet = NULL; - //return first packet to the back of the queue - } else if(xQueueSend(_async_queue, &first_packet, portMAX_DELAY) != pdPASS){ - return false; - } - } - - while(xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet){ - if(xQueueReceive(_async_queue, &packet, 0) != pdPASS){ - return false; - } - if((int)packet->arg == (int)arg){ - free(packet); - packet = NULL; - } else if(xQueueSend(_async_queue, &packet, portMAX_DELAY) != pdPASS){ - return false; - } - } - return true; -} - -static void _handle_async_event(lwip_event_packet_t * e){ - if(e->arg == NULL){ - // do nothing when arg is NULL - //ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb); - } else if(e->event == LWIP_TCP_CLEAR){ - _remove_events_with_arg(e->arg); - } else if(e->event == LWIP_TCP_RECV){ - //ets_printf("-R: 0x%08x\n", e->recv.pcb); - AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); - } else if(e->event == LWIP_TCP_FIN){ - //ets_printf("-F: 0x%08x\n", e->fin.pcb); - AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); - } else if(e->event == LWIP_TCP_SENT){ - //ets_printf("-S: 0x%08x\n", e->sent.pcb); - AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); - } else if(e->event == LWIP_TCP_POLL){ - //ets_printf("-P: 0x%08x\n", e->poll.pcb); - AsyncClient::_s_poll(e->arg, e->poll.pcb); - } else if(e->event == LWIP_TCP_ERROR){ - //ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); - AsyncClient::_s_error(e->arg, e->error.err); - } else if(e->event == LWIP_TCP_CONNECTED){ - //ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); - AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); - } else if(e->event == LWIP_TCP_ACCEPT){ - //ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); - AsyncServer::_s_accepted(e->arg, e->accept.client); - } else if(e->event == LWIP_TCP_DNS){ - //ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); - AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); - } - free((void*)(e)); -} - -static void _async_service_task(void *pvParameters){ - lwip_event_packet_t * packet = NULL; - for (;;) { - if(_get_async_event(&packet)){ -#if CONFIG_ASYNC_TCP_USE_WDT - if(esp_task_wdt_add(NULL) != ESP_OK){ - log_e("Failed to add async task to WDT"); - } -#endif - _handle_async_event(packet); -#if CONFIG_ASYNC_TCP_USE_WDT - if(esp_task_wdt_delete(NULL) != ESP_OK){ - log_e("Failed to remove loop task from WDT"); - } -#endif - } - } - vTaskDelete(NULL); - _async_service_task_handle = NULL; -} -/* -static void _stop_async_task(){ - if(_async_service_task_handle){ - vTaskDelete(_async_service_task_handle); - _async_service_task_handle = NULL; - } -} -*/ -static bool _start_async_task(){ - if(!_init_async_event_queue()){ - return false; - } - if(!_async_service_task_handle){ - xTaskCreateUniversal(_async_service_task, "async_tcp", 8192 * 2, NULL, TASK_CONNECTIVITY_PRIO, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); - if(!_async_service_task_handle){ - return false; - } - } - return true; -} - -/* - * LwIP Callbacks - * */ - -static int8_t _tcp_clear_events(void * arg) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CLEAR; - e->arg = arg; - if (!_prepend_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) { - //ets_printf("+C: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CONNECTED; - e->arg = arg; - e->connected.pcb = pcb; - e->connected.err = err; - if (!_prepend_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) { - //ets_printf("+P: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_POLL; - e->arg = arg; - e->poll.pcb = pcb; - if (!_send_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->arg = arg; - if(pb){ - //ets_printf("+R: 0x%08x\n", pcb); - e->event = LWIP_TCP_RECV; - e->recv.pcb = pcb; - e->recv.pb = pb; - e->recv.err = err; - } else { - //ets_printf("+F: 0x%08x\n", pcb); - e->event = LWIP_TCP_FIN; - e->fin.pcb = pcb; - e->fin.err = err; - //close the PCB in LwIP thread - AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); - } - if (!_send_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; -} - -static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - //ets_printf("+S: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_SENT; - e->arg = arg; - e->sent.pcb = pcb; - e->sent.len = len; - if (!_send_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; -} - -static void _tcp_error(void * arg, int8_t err) { - //ets_printf("+E: 0x%08x\n", arg); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ERROR; - e->arg = arg; - e->error.err = err; - if (!_send_async_event(&e)) { - free((void*)(e)); - } -} - -static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - //ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); - e->event = LWIP_TCP_DNS; - e->arg = arg; - e->dns.name = name; - if (ipaddr) { - memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); - } else { - memset(&e->dns.addr, 0, sizeof(e->dns.addr)); - } - if (!_send_async_event(&e)) { - free((void*)(e)); - } -} - -//Used to switch out from LwIP thread -static int8_t _tcp_accept(void * arg, AsyncClient * client) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ACCEPT; - e->arg = arg; - e->accept.client = client; - if (!_prepend_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; -} - -/* - * TCP/IP API Calls - * */ - -#include "lwip/priv/tcpip_priv.h" - -typedef struct { - struct tcpip_api_call_data call; - tcp_pcb * pcb; - int8_t closed_slot; - int8_t err; - union { - struct { - const char* data; - size_t size; - uint8_t apiflags; - } write; - size_t received; - struct { - ip_addr_t * addr; - uint16_t port; - tcp_connected_fn cb; - } connect; - struct { - ip_addr_t * addr; - uint16_t port; - } bind; - uint8_t backlog; - }; -} tcp_api_call_t; - -static err_t _tcp_output_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_output(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_output(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_write_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); - } - return msg->err; -} - -static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.write.data = data; - msg.write.size = size; - msg.write.apiflags = apiflags; - tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_recved_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = 0; - tcp_recved(msg->pcb, msg->received); - } - return msg->err; -} - -static esp_err_t _tcp_recved(tcp_pcb * pcb, int8_t closed_slot, size_t len) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.received = len; - tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_close_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_close(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_close(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_abort_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - tcp_abort(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_abort(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_connect_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); - return msg->err; -} - -static esp_err_t _tcp_connect(tcp_pcb * pcb, int8_t closed_slot, ip_addr_t * addr, uint16_t port, tcp_connected_fn cb) { - if(!pcb){ - return ESP_FAIL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.connect.addr = addr; - msg.connect.port = port; - msg.connect.cb = cb; - tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_bind_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); - return msg->err; -} - -static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port) { - if(!pcb){ - return ESP_FAIL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = -1; - msg.bind.addr = addr; - msg.bind.port = port; - tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_listen_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = 0; - msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); - return msg->err; -} - -static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { - if(!pcb){ - return NULL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = -1; - msg.backlog = backlog?backlog:0xFF; - tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); - return msg.pcb; -} - - - -/* - Async TCP Client - */ - -AsyncClient::AsyncClient(tcp_pcb* pcb) -: _connect_cb(0) -, _connect_cb_arg(0) -, _discard_cb(0) -, _discard_cb_arg(0) -, _sent_cb(0) -, _sent_cb_arg(0) -, _error_cb(0) -, _error_cb_arg(0) -, _recv_cb(0) -, _recv_cb_arg(0) -, _pb_cb(0) -, _pb_cb_arg(0) -, _timeout_cb(0) -, _timeout_cb_arg(0) -, _pcb_busy(false) -, _pcb_sent_at(0) -, _ack_pcb(true) -, _rx_last_packet(0) -, _rx_since_timeout(0) -, _ack_timeout(ASYNC_MAX_ACK_TIME) -, _connect_port(0) -, prev(NULL) -, next(NULL) -{ - _pcb = pcb; - _closed_slot = -1; - if(_pcb){ - _allocate_closed_slot(); - _rx_last_packet = millis(); - tcp_arg(_pcb, this); - tcp_recv(_pcb, &_tcp_recv); - tcp_sent(_pcb, &_tcp_sent); - tcp_err(_pcb, &_tcp_error); - tcp_poll(_pcb, &_tcp_poll, 1); - } -} - -AsyncClient::~AsyncClient(){ - if(_pcb) { - _close(); - } - _free_closed_slot(); -} - -/* - * Operators - * */ - -AsyncClient& AsyncClient::operator=(const AsyncClient& other){ - if (_pcb) { - _close(); - } - - _pcb = other._pcb; - _closed_slot = other._closed_slot; - if (_pcb) { - _rx_last_packet = millis(); - tcp_arg(_pcb, this); - tcp_recv(_pcb, &_tcp_recv); - tcp_sent(_pcb, &_tcp_sent); - tcp_err(_pcb, &_tcp_error); - tcp_poll(_pcb, &_tcp_poll, 1); - } - return *this; -} - -bool AsyncClient::operator==(const AsyncClient &other) { - return _pcb == other._pcb; -} - -AsyncClient & AsyncClient::operator+=(const AsyncClient &other) { - if(next == NULL){ - next = (AsyncClient*)(&other); - next->prev = this; - } else { - AsyncClient *c = next; - while(c->next != NULL) { - c = c->next; - } - c->next =(AsyncClient*)(&other); - c->next->prev = c; - } - return *this; -} - -/* - * Callback Setters - * */ - -void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ - _connect_cb = cb; - _connect_cb_arg = arg; -} - -void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ - _discard_cb = cb; - _discard_cb_arg = arg; -} - -void AsyncClient::onAck(AcAckHandler cb, void* arg){ - _sent_cb = cb; - _sent_cb_arg = arg; -} - -void AsyncClient::onError(AcErrorHandler cb, void* arg){ - _error_cb = cb; - _error_cb_arg = arg; -} - -void AsyncClient::onData(AcDataHandler cb, void* arg){ - _recv_cb = cb; - _recv_cb_arg = arg; -} - -void AsyncClient::onPacket(AcPacketHandler cb, void* arg){ - _pb_cb = cb; - _pb_cb_arg = arg; -} - -void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ - _timeout_cb = cb; - _timeout_cb_arg = arg; -} - -void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ - _poll_cb = cb; - _poll_cb_arg = arg; -} - -/* - * Main Public Methods - * */ - -bool AsyncClient::connect(IPAddress ip, uint16_t port){ - if (_pcb){ - log_w("already connected, state %d", _pcb->state); - return false; - } - if(!_start_async_task()){ - log_e("failed to start task"); - return false; - } - - ip_addr_t addr; - addr.type = IPADDR_TYPE_V4; - addr.u_addr.ip4.addr = ip; - - TCP_MUTEX_LOCK(); - tcp_pcb* pcb = tcp_new_ip_type(IPADDR_TYPE_V4); - if (!pcb){ - TCP_MUTEX_UNLOCK(); - log_e("pcb == NULL"); - return false; - } - - tcp_arg(pcb, this); - tcp_err(pcb, &_tcp_error); - tcp_recv(pcb, &_tcp_recv); - tcp_sent(pcb, &_tcp_sent); - tcp_poll(pcb, &_tcp_poll, 1); - TCP_MUTEX_UNLOCK(); - //_tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected); - _tcp_connect(pcb, _closed_slot, &addr, port,(tcp_connected_fn)&_tcp_connected); - return true; -} - -bool AsyncClient::connect(const char* host, uint16_t port){ - ip_addr_t addr; - - if(!_start_async_task()){ - log_e("failed to start task"); - return false; - } - TCP_MUTEX_LOCK(); - err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); - TCP_MUTEX_UNLOCK(); - if(err == ERR_OK) { - return connect(IPAddress(addr.u_addr.ip4.addr), port); - } else if(err == ERR_INPROGRESS) { - _connect_port = port; - return true; - } - log_e("error: %d", err); - return false; -} - -void AsyncClient::close(bool now){ - if(_pcb){ - _tcp_recved(_pcb, _closed_slot, _rx_ack_len); - } - _close(); -} - -int8_t AsyncClient::abort(){ - if(_pcb) { - _tcp_abort(_pcb, _closed_slot ); - _pcb = NULL; - } - return ERR_ABRT; -} - -size_t AsyncClient::space(){ - if((_pcb != NULL) && (_pcb->state == 4)){ - return tcp_sndbuf(_pcb); - } - return 0; -} - -size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { - if(!_pcb || size == 0 || data == NULL) { - return 0; - } - size_t room = space(); - if(!room) { - return 0; - } - size_t will_send = (room < size) ? room : size; - int8_t err = ERR_OK; - err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); - if(err != ERR_OK) { - return 0; - } - return will_send; -} - -bool AsyncClient::send(){ - int8_t err = ERR_OK; - err = _tcp_output(_pcb, _closed_slot); - if(err == ERR_OK){ - _pcb_busy = true; - _pcb_sent_at = millis(); - return true; - } - return false; -} - -size_t AsyncClient::ack(size_t len){ - if(len > _rx_ack_len) - len = _rx_ack_len; - if(len){ - _tcp_recved(_pcb, _closed_slot, len); - } - _rx_ack_len -= len; - return len; -} - -void AsyncClient::ackPacket(struct pbuf * pb){ - if(!pb){ - return; - } - _tcp_recved(_pcb, _closed_slot, pb->len); - pbuf_free(pb); -} - -/* - * Main Private Methods - * */ - -int8_t AsyncClient::_close(){ - //ets_printf("X: 0x%08x\n", (uint32_t)this); - int8_t err = ERR_OK; - if(_pcb) { - //log_i(""); - TCP_MUTEX_LOCK(); - tcp_arg(_pcb, NULL); - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - TCP_MUTEX_UNLOCK(); - _tcp_clear_events(this); - err = _tcp_close(_pcb, _closed_slot); - if(err != ERR_OK) { - err = abort(); - } - _pcb = NULL; - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } - } - return err; -} - -void AsyncClient::_allocate_closed_slot(){ - xSemaphoreTake(_slots_lock, portMAX_DELAY); - uint32_t closed_slot_min_index = 0; - for (int i = 0; i < _number_of_closed_slots; ++ i) { - if ((_closed_slot == -1 || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) { - closed_slot_min_index = _closed_slots[i]; - _closed_slot = i; - } - } - if (_closed_slot != -1) { - _closed_slots[_closed_slot] = 0; - } - xSemaphoreGive(_slots_lock); -} - -void AsyncClient::_free_closed_slot(){ - if (_closed_slot != -1) { - _closed_slots[_closed_slot] = _closed_index; - _closed_slot = -1; - ++ _closed_index; - } -} - -/* - * Private Callbacks - * */ - -int8_t AsyncClient::_connected(void* pcb, int8_t err){ - _pcb = reinterpret_cast(pcb); - if(_pcb){ - _rx_last_packet = millis(); - _pcb_busy = false; -// tcp_recv(_pcb, &_tcp_recv); -// tcp_sent(_pcb, &_tcp_sent); -// tcp_poll(_pcb, &_tcp_poll, 1); - } - if(_connect_cb) { - _connect_cb(_connect_cb_arg, this); - } - return ERR_OK; -} - -void AsyncClient::_error(int8_t err) { - if(_pcb){ - tcp_arg(_pcb, NULL); - if(_pcb->state == LISTEN) { - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - } - _pcb = NULL; - } - if(_error_cb) { - _error_cb(_error_cb_arg, this, err); - } - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } -} - -//In LwIP Thread -int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { - if(!_pcb || pcb != _pcb){ - log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); - return ERR_OK; - } - tcp_arg(_pcb, NULL); - if(_pcb->state == LISTEN) { - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - } - if(tcp_close(_pcb) != ERR_OK) { - tcp_abort(_pcb); - } - _free_closed_slot(); - _pcb = NULL; - return ERR_OK; -} - -//In Async Thread -int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { - _tcp_clear_events(this); - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } - return ERR_OK; -} - -int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { - _rx_last_packet = millis(); - //log_i("%u", len); - _pcb_busy = false; - if(_sent_cb) { - _sent_cb(_sent_cb_arg, this, len, (millis() - _pcb_sent_at)); - } - return ERR_OK; -} - -int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { - while(pb != NULL) { - _rx_last_packet = millis(); - //we should not ack before we assimilate the data - _ack_pcb = true; - pbuf *b = pb; - pb = b->next; - b->next = NULL; - if(_pb_cb){ - _pb_cb(_pb_cb_arg, this, b); - } else { - if(_recv_cb) { - _recv_cb(_recv_cb_arg, this, b->payload, b->len); - } - if(!_ack_pcb) { - _rx_ack_len += b->len; - } else if(_pcb) { - _tcp_recved(_pcb, _closed_slot, b->len); - } - pbuf_free(b); - } - } - return ERR_OK; -} - -int8_t AsyncClient::_poll(tcp_pcb* pcb){ - if(!_pcb){ - log_w("pcb is NULL"); - return ERR_OK; - } - if(pcb != _pcb){ - log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); - return ERR_OK; - } - - uint32_t now = millis(); - - // ACK Timeout - if(_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout){ - _pcb_busy = false; - log_w("ack timeout %d", pcb->state); - if(_timeout_cb) - _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at)); - return ERR_OK; - } - // RX Timeout - if(_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)){ - log_w("rx timeout %d", pcb->state); - _close(); - return ERR_OK; - } - // Everything is fine - if(_poll_cb) { - _poll_cb(_poll_cb_arg, this); - } - return ERR_OK; -} - -void AsyncClient::_dns_found(struct ip_addr *ipaddr){ - if(ipaddr && ipaddr->u_addr.ip4.addr){ - connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port); - } else { - if(_error_cb) { - _error_cb(_error_cb_arg, this, -55); - } - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } - } -} - -/* - * Public Helper Methods - * */ - -void AsyncClient::stop() { - close(false); -} - -bool AsyncClient::free(){ - if(!_pcb) { - return true; - } - if(_pcb->state == 0 || _pcb->state > 4) { - return true; - } - return false; -} - -size_t AsyncClient::write(const char* data) { - if(data == NULL) { - return 0; - } - return write(data, strlen(data)); -} - -size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { - size_t will_send = add(data, size, apiflags); - if(!will_send || !send()) { - return 0; - } - return will_send; -} - -void AsyncClient::setRxTimeout(uint32_t timeout){ - _rx_since_timeout = timeout; -} - -uint32_t AsyncClient::getRxTimeout(){ - return _rx_since_timeout; -} - -uint32_t AsyncClient::getAckTimeout(){ - return _ack_timeout; -} - -void AsyncClient::setAckTimeout(uint32_t timeout){ - _ack_timeout = timeout; -} - -void AsyncClient::setNoDelay(bool nodelay){ - if(!_pcb) { - return; - } - if(nodelay) { - tcp_nagle_disable(_pcb); - } else { - tcp_nagle_enable(_pcb); - } -} - -bool AsyncClient::getNoDelay(){ - if(!_pcb) { - return false; - } - return tcp_nagle_disabled(_pcb); -} - -uint16_t AsyncClient::getMss(){ - if(!_pcb) { - return 0; - } - return tcp_mss(_pcb); -} - -uint32_t AsyncClient::getRemoteAddress() { - if(!_pcb) { - return 0; - } - return _pcb->remote_ip.u_addr.ip4.addr; -} - -uint16_t AsyncClient::getRemotePort() { - if(!_pcb) { - return 0; - } - return _pcb->remote_port; -} - -uint32_t AsyncClient::getLocalAddress() { - if(!_pcb) { - return 0; - } - return _pcb->local_ip.u_addr.ip4.addr; -} - -uint16_t AsyncClient::getLocalPort() { - if(!_pcb) { - return 0; - } - return _pcb->local_port; -} - -IPAddress AsyncClient::remoteIP() { - return IPAddress(getRemoteAddress()); -} - -uint16_t AsyncClient::remotePort() { - return getRemotePort(); -} - -IPAddress AsyncClient::localIP() { - return IPAddress(getLocalAddress()); -} - -uint16_t AsyncClient::localPort() { - return getLocalPort(); -} - -uint8_t AsyncClient::state() { - if(!_pcb) { - return 0; - } - return _pcb->state; -} - -bool AsyncClient::connected(){ - if (!_pcb) { - return false; - } - return _pcb->state == 4; -} - -bool AsyncClient::connecting(){ - if (!_pcb) { - return false; - } - return _pcb->state > 0 && _pcb->state < 4; -} - -bool AsyncClient::disconnecting(){ - if (!_pcb) { - return false; - } - return _pcb->state > 4 && _pcb->state < 10; -} - -bool AsyncClient::disconnected(){ - if (!_pcb) { - return true; - } - return _pcb->state == 0 || _pcb->state == 10; -} - -bool AsyncClient::freeable(){ - if (!_pcb) { - return true; - } - return _pcb->state == 0 || _pcb->state > 4; -} - -bool AsyncClient::canSend(){ - return space() > 0; -} - -const char * AsyncClient::errorToString(int8_t error){ - switch(error){ - case ERR_OK: return "OK"; - case ERR_MEM: return "Out of memory error"; - case ERR_BUF: return "Buffer error"; - case ERR_TIMEOUT: return "Timeout"; - case ERR_RTE: return "Routing problem"; - case ERR_INPROGRESS: return "Operation in progress"; - case ERR_VAL: return "Illegal value"; - case ERR_WOULDBLOCK: return "Operation would block"; - case ERR_USE: return "Address in use"; - case ERR_ALREADY: return "Already connected"; - case ERR_CONN: return "Not connected"; - case ERR_IF: return "Low-level netif error"; - case ERR_ABRT: return "Connection aborted"; - case ERR_RST: return "Connection reset"; - case ERR_CLSD: return "Connection closed"; - case ERR_ARG: return "Illegal argument"; - case -55: return "DNS failed"; - default: return "UNKNOWN"; - } -} - -const char * AsyncClient::stateToString(){ - switch(state()){ - case 0: return "Closed"; - case 1: return "Listen"; - case 2: return "SYN Sent"; - case 3: return "SYN Received"; - case 4: return "Established"; - case 5: return "FIN Wait 1"; - case 6: return "FIN Wait 2"; - case 7: return "Close Wait"; - case 8: return "Closing"; - case 9: return "Last ACK"; - case 10: return "Time Wait"; - default: return "UNKNOWN"; - } -} - -/* - * Static Callbacks (LwIP C2C++ interconnect) - * */ - -void AsyncClient::_s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg){ - reinterpret_cast(arg)->_dns_found(ipaddr); -} - -int8_t AsyncClient::_s_poll(void * arg, struct tcp_pcb * pcb) { - return reinterpret_cast(arg)->_poll(pcb); -} - -int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { - return reinterpret_cast(arg)->_recv(pcb, pb, err); -} - -int8_t AsyncClient::_s_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_fin(pcb, err); -} - -int8_t AsyncClient::_s_lwip_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_lwip_fin(pcb, err); -} - -int8_t AsyncClient::_s_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - return reinterpret_cast(arg)->_sent(pcb, len); -} - -void AsyncClient::_s_error(void * arg, int8_t err) { - reinterpret_cast(arg)->_error(err); -} - -int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ - return reinterpret_cast(arg)->_connected(pcb, err); -} - -/* - Async TCP Server - */ - -AsyncServer::AsyncServer(IPAddress addr, uint16_t port) -: _port(port) -, _addr(addr) -, _noDelay(false) -, _pcb(0) -, _connect_cb(0) -, _connect_cb_arg(0) -{} - -AsyncServer::AsyncServer(uint16_t port) -: _port(port) -, _addr((uint32_t) IPADDR_ANY) -, _noDelay(false) -, _pcb(0) -, _connect_cb(0) -, _connect_cb_arg(0) -{} - -AsyncServer::~AsyncServer(){ - end(); -} - -void AsyncServer::onClient(AcConnectHandler cb, void* arg){ - _connect_cb = cb; - _connect_cb_arg = arg; -} - -void AsyncServer::begin(){ - if(_pcb) { - return; - } - - if(!_start_async_task()){ - log_e("failed to start task"); - return; - } - int8_t err; - TCP_MUTEX_LOCK(); - _pcb = tcp_new_ip_type(IPADDR_TYPE_V4); - TCP_MUTEX_UNLOCK(); - if (!_pcb){ - log_e("_pcb == NULL"); - return; - } - - ip_addr_t local_addr; - local_addr.type = IPADDR_TYPE_V4; - local_addr.u_addr.ip4.addr = (uint32_t) _addr; - err = _tcp_bind(_pcb, &local_addr, _port); - - if (err != ERR_OK) { - _tcp_close(_pcb, -1); - log_e("bind error: %d", err); - return; - } - - static uint8_t backlog = 5; - _pcb = _tcp_listen_with_backlog(_pcb, backlog); - if (!_pcb) { - log_e("listen_pcb == NULL"); - return; - } - TCP_MUTEX_LOCK(); - tcp_arg(_pcb, (void*) this); - tcp_accept(_pcb, &_s_accept); - TCP_MUTEX_UNLOCK(); -} - -void AsyncServer::end(){ - if(_pcb){ - TCP_MUTEX_LOCK(); - tcp_arg(_pcb, NULL); - tcp_accept(_pcb, NULL); - TCP_MUTEX_UNLOCK(); - if(tcp_close(_pcb) != ERR_OK){ - _tcp_abort(_pcb, -1); - } - _pcb = NULL; - } -} - -//runs on LwIP thread -int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err){ - //ets_printf("+A: 0x%08x\n", pcb); - if(_connect_cb){ - AsyncClient *c = new AsyncClient(pcb); - if(c){ - c->setNoDelay(_noDelay); - return _tcp_accept(this, c); - } - } - if(tcp_close(pcb) != ERR_OK){ - tcp_abort(pcb); - } - log_e("FAIL"); - return ERR_OK; -} - -int8_t AsyncServer::_accepted(AsyncClient* client){ - if(_connect_cb){ - _connect_cb(_connect_cb_arg, client); - } - return ERR_OK; -} - -void AsyncServer::setNoDelay(bool nodelay){ - _noDelay = nodelay; -} - -bool AsyncServer::getNoDelay(){ - return _noDelay; -} - -uint8_t AsyncServer::status(){ - if (!_pcb) { - return 0; - } - return _pcb->state; -} - -int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err){ - return reinterpret_cast(arg)->_accept(pcb, err); -} - -int8_t AsyncServer::_s_accepted(void *arg, AsyncClient* client){ - return reinterpret_cast(arg)->_accepted(client); -} diff --git a/Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.h b/Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.h deleted file mode 100644 index b89fb122..00000000 --- a/Software/src/lib/me-no-dev-AsyncTCP/src/AsyncTCP.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef ASYNCTCP_H_ -#define ASYNCTCP_H_ - -#include "IPAddress.h" -#include "sdkconfig.h" -#include -extern "C" { - #include "freertos/semphr.h" - #include "lwip/pbuf.h" -} - -#include "../../../system_settings.h" -#include "../../../devboard/hal/hal.h" - -//If core is not defined, then we are running in Arduino or PIO -#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE -#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE //any available core -#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event -#endif - -class AsyncClient; - -#define ASYNC_MAX_ACK_TIME 5000 -#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) -#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. - -typedef std::function AcConnectHandler; -typedef std::function AcAckHandler; -typedef std::function AcErrorHandler; -typedef std::function AcDataHandler; -typedef std::function AcPacketHandler; -typedef std::function AcTimeoutHandler; - -struct tcp_pcb; -struct ip_addr; - -class AsyncClient { - public: - AsyncClient(tcp_pcb* pcb = 0); - ~AsyncClient(); - - AsyncClient & operator=(const AsyncClient &other); - AsyncClient & operator+=(const AsyncClient &other); - - bool operator==(const AsyncClient &other); - - bool operator!=(const AsyncClient &other) { - return !(*this == other); - } - bool connect(IPAddress ip, uint16_t port); - bool connect(const char* host, uint16_t port); - void close(bool now = false); - void stop(); - int8_t abort(); - bool free(); - - bool canSend();//ack is not pending - size_t space();//space available in the TCP window - size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending - bool send();//send all data added with the method above - - //write equals add()+send() - size_t write(const char* data); - size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true - - uint8_t state(); - bool connecting(); - bool connected(); - bool disconnecting(); - bool disconnected(); - bool freeable();//disconnected or disconnecting - - uint16_t getMss(); - - uint32_t getRxTimeout(); - void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds - - uint32_t getAckTimeout(); - void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds - - void setNoDelay(bool nodelay); - bool getNoDelay(); - - uint32_t getRemoteAddress(); - uint16_t getRemotePort(); - uint32_t getLocalAddress(); - uint16_t getLocalPort(); - - //compatibility - IPAddress remoteIP(); - uint16_t remotePort(); - IPAddress localIP(); - uint16_t localPort(); - - void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect - void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected - void onAck(AcAckHandler cb, void* arg = 0); //ack received - void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error - void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used) - void onPacket(AcPacketHandler cb, void* arg = 0); //data received - void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout - void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected - - void ackPacket(struct pbuf * pb);//ack pbuf from onPacket - size_t ack(size_t len); //ack data that you have not acked using the method below - void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData - - const char * errorToString(int8_t error); - const char * stateToString(); - - //Do not use any of the functions below! - static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb); - static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err); - static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); - static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); - static void _s_error(void *arg, int8_t err); - static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); - static int8_t _s_connected(void* arg, void* tpcb, int8_t err); - static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); - - int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); - tcp_pcb * pcb(){ return _pcb; } - - protected: - tcp_pcb* _pcb; - int8_t _closed_slot; - - AcConnectHandler _connect_cb; - void* _connect_cb_arg; - AcConnectHandler _discard_cb; - void* _discard_cb_arg; - AcAckHandler _sent_cb; - void* _sent_cb_arg; - AcErrorHandler _error_cb; - void* _error_cb_arg; - AcDataHandler _recv_cb; - void* _recv_cb_arg; - AcPacketHandler _pb_cb; - void* _pb_cb_arg; - AcTimeoutHandler _timeout_cb; - void* _timeout_cb_arg; - AcConnectHandler _poll_cb; - void* _poll_cb_arg; - - bool _pcb_busy; - uint32_t _pcb_sent_at; - bool _ack_pcb; - uint32_t _rx_ack_len; - uint32_t _rx_last_packet; - uint32_t _rx_since_timeout; - uint32_t _ack_timeout; - uint16_t _connect_port; - - int8_t _close(); - void _free_closed_slot(); - void _allocate_closed_slot(); - int8_t _connected(void* pcb, int8_t err); - void _error(int8_t err); - int8_t _poll(tcp_pcb* pcb); - int8_t _sent(tcp_pcb* pcb, uint16_t len); - int8_t _fin(tcp_pcb* pcb, int8_t err); - int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); - void _dns_found(struct ip_addr *ipaddr); - - public: - AsyncClient* prev; - AsyncClient* next; -}; - -class AsyncServer { - public: - AsyncServer(IPAddress addr, uint16_t port); - AsyncServer(uint16_t port); - ~AsyncServer(); - void onClient(AcConnectHandler cb, void* arg); - void begin(); - void end(); - void setNoDelay(bool nodelay); - bool getNoDelay(); - uint8_t status(); - - //Do not use any of the functions below! - static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err); - static int8_t _s_accepted(void *arg, AsyncClient* client); - - protected: - uint16_t _port; - IPAddress _addr; - bool _noDelay; - tcp_pcb* _pcb; - AcConnectHandler _connect_cb; - void* _connect_cb_arg; - - int8_t _accept(tcp_pcb* newpcb, int8_t err); - int8_t _accepted(AsyncClient* client); -}; - - -#endif /* ASYNCTCP_H_ */