├── LICENSE ├── examples ├── raw_async_http_request │ └── raw_async_http_request.ino └── raw_async_https_request │ ├── build_opt.h │ └── raw_async_https_request.ino ├── library.json ├── library.properties └── src ├── AsyncTCP.cpp ├── AsyncTCP.h ├── AsyncTCP_SSL.h ├── AsyncTCP_SSL.hpp ├── AsyncTCP_TLS_Context.cpp └── AsyncTCP_TLS_Context.h /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /examples/raw_async_http_request/raw_async_http_request.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const char* ssid = "YOUR-WIFI-SSID-HERE"; 5 | const char* password = "YOUR-WIFI-PASSWORD-HERE"; 6 | 7 | AsyncClient tcpClient; 8 | 9 | void setup() { 10 | // put your setup code here, to run once: 11 | 12 | Serial.begin(115200); 13 | while (!Serial); 14 | 15 | WiFi.mode(WIFI_STA); 16 | 17 | WiFi.begin(ssid, password); 18 | 19 | Serial.println("Connecting to WiFi SSID: " + String(ssid)); 20 | 21 | while (WiFi.status() != WL_CONNECTED) 22 | { 23 | delay(500); 24 | Serial.print("."); 25 | } 26 | 27 | Serial.print(F("Connected to WiFi @ IP : ")); 28 | Serial.println(WiFi.localIP()); 29 | 30 | // Callbacks for data handling, run in separate task to achieve concurrency 31 | 32 | // Callback on successful connection 33 | tcpClient.onConnect([](void * ctx_ptr, AsyncClient * client) { 34 | Serial.println("\n\nonConnect successful! sending data..."); 35 | 36 | // For the sake of this example, buffer will contain outgoing protocol data 37 | // not necessarily produced from static ASCII strings. 38 | char buffer[128]; 39 | 40 | // Simulating an HTTP request to http://worldtimeapi.org/api/timezone/Europe/London.txt 41 | 42 | // ASYNC_WRITE_FLAG_COPY is the default value of the apiflags parameter in AsyncTCPSock 43 | // and can be omitted. Only if the data to be sent is static or long-lived and guaranteed 44 | // to persist until all data has been written, should you consider passing 0 as apiflags, 45 | // which will instead store the passed pointer without performing a copy. 46 | strcpy(buffer, "GET /api/timezone/Europe/London.txt HTTP/1.1\r\n"); 47 | client->add(buffer, strlen(buffer), ASYNC_WRITE_FLAG_COPY); 48 | 49 | strcpy(buffer, "Host: worldtimeapi.org\r\n"); 50 | client->add(buffer, strlen(buffer), ASYNC_WRITE_FLAG_COPY); 51 | 52 | strcpy(buffer, "Connection: close\r\n\r\n"); 53 | client->add(buffer, strlen(buffer), ASYNC_WRITE_FLAG_COPY); 54 | 55 | client->send(); 56 | }, 57 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 58 | ); 59 | 60 | // Callback on data ready to be processed - MUST BE CONSUMED AT ONCE or will be discarded 61 | tcpClient.onData([](void * ctx_ptr, AsyncClient * client, void * buf, size_t len) { 62 | 63 | Serial.printf("\n\nonData received data (%u bytes), raw buffer follows:\r\n", len); 64 | Serial.write((const uint8_t *)buf, len); 65 | 66 | }, 67 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 68 | ); 69 | 70 | // Callback on written data being acknowledged as sent. If the data written so far fully covers 71 | // a buffer added WITHOUT the ASYNC_WRITE_FLAG_COPY flag, now is the first safe moment at 72 | // which such a buffer area may be discarded or reused. 73 | tcpClient.onAck([](void * ctx_ptr, AsyncClient * client, size_t len, uint32_t ms_delay) { 74 | 75 | Serial.printf("\n\nonAck acknowledged sending next %u bytes after %u ms\r\n", len, ms_delay); 76 | 77 | }, 78 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 79 | ); 80 | 81 | // Callback on socket disconnect, called: 82 | // - on any socket close event (local or remote) after being connected 83 | // - on failure to connect, right after the onError callback 84 | tcpClient.onDisconnect([](void * ctx_ptr, AsyncClient * client) { 85 | 86 | Serial.println("\n\nonDisconnect socket disconnected!"); 87 | 88 | }, 89 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 90 | ); 91 | 92 | // Callback on error event 93 | tcpClient.onError([](void * ctx_ptr, AsyncClient * client, int8_t error) { 94 | 95 | Serial.printf("\n\nonError socket reported error %d\r\n", error); 96 | 97 | }, 98 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 99 | ); 100 | } 101 | 102 | const char * hostname = "worldtimeapi.org"; 103 | 104 | uint32_t t_dot = 0; 105 | uint32_t t_req = 0; 106 | void loop() { 107 | uint32_t t = millis(); 108 | 109 | // This is to show that the main loop() is running while the AsyncClient 110 | // object processes data in the background. 111 | if (t - t_dot >= 200) { 112 | t_dot = t; 113 | Serial.print("*"); 114 | } 115 | 116 | // Try connecting to remote host if 10 seconds pass after last try 117 | if (t - t_req >= 20000 && tcpClient.state() == 0) { 118 | 119 | // NOTE: DNS resolving is also done asynchronously (in the LWIP thread) 120 | t_req = t; 121 | Serial.printf("\n\nStarting connection to %s port 80...\r\n", hostname); 122 | tcpClient.connect(hostname, 80); 123 | 124 | } 125 | 126 | delay(50); 127 | } 128 | -------------------------------------------------------------------------------- /examples/raw_async_https_request/build_opt.h: -------------------------------------------------------------------------------- 1 | -DASYNC_TCP_SSL_ENABLED=1 2 | -------------------------------------------------------------------------------- /examples/raw_async_https_request/raw_async_https_request.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const char* ssid = "YOUR-WIFI-SSID-HERE"; 6 | const char* password = "YOUR-WIFI-PASSWORD-HERE"; 7 | 8 | // On your Arduino projects, create the file build_opt.h if it does not exist 9 | // and add the following single line to enable AsyncTCP SSL support: 10 | // -DASYNC_TCP_SSL_ENABLED=1 11 | #ifndef ASYNC_TCP_SSL_ENABLED 12 | #error The macro ASYNC_TCP_SSL_ENABLED has not been correctly enabled in your environment! 13 | #endif 14 | 15 | // To extract root certificate for test: 16 | // openssl s_client -showcerts -connect SSLDOMAIN.ORG:443 < /dev/null 17 | 18 | // Root certificate test site (Let's Encrypt) 19 | 20 | const char * hostname = "www.eff.org"; 21 | 22 | const char * rootCACertificate = \ 23 | "-----BEGIN CERTIFICATE-----\n" \ 24 | "MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/\n" \ 25 | "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ 26 | "DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow\n" \ 27 | "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ 28 | "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB\n" \ 29 | "AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC\n" \ 30 | "ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL\n" \ 31 | "wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D\n" \ 32 | "LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK\n" \ 33 | "4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5\n" \ 34 | "bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y\n" \ 35 | "sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ\n" \ 36 | "Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4\n" \ 37 | "FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc\n" \ 38 | "SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql\n" \ 39 | "PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND\n" \ 40 | "TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw\n" \ 41 | "SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1\n" \ 42 | "c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx\n" \ 43 | "+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB\n" \ 44 | "ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu\n" \ 45 | "b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E\n" \ 46 | "U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu\n" \ 47 | "MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC\n" \ 48 | "5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW\n" \ 49 | "9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG\n" \ 50 | "WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O\n" \ 51 | "he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC\n" \ 52 | "Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5\n" \ 53 | "-----END CERTIFICATE-----\n"; 54 | 55 | AsyncClient sslClient; 56 | 57 | void setup() { 58 | // put your setup code here, to run once: 59 | 60 | Serial.begin(115200); 61 | while (!Serial); 62 | 63 | WiFi.mode(WIFI_STA); 64 | 65 | WiFi.begin(ssid, password); 66 | 67 | Serial.println("Connecting to WiFi SSID: " + String(ssid)); 68 | 69 | while (WiFi.status() != WL_CONNECTED) 70 | { 71 | delay(500); 72 | Serial.print("."); 73 | } 74 | 75 | Serial.print(F("Connected to WiFi @ IP : ")); 76 | Serial.println(WiFi.localIP()); 77 | 78 | // Callbacks for data handling, run in separate task to achieve concurrency 79 | 80 | // Callback on successful connection 81 | sslClient.onConnect([](void * ctx_ptr, AsyncClient * client) { 82 | Serial.println("\n\nonConnect successful! sending data..."); 83 | 84 | // For the sake of this example, buffer will contain outgoing protocol data 85 | // not necessarily produced from static ASCII strings. 86 | char buffer[128]; 87 | 88 | // Simulating an HTTP request 89 | 90 | // ASYNC_WRITE_FLAG_COPY is the default value of the apiflags parameter in AsyncTCPSock 91 | // and can be omitted. Only if the data to be sent is static or long-lived and guaranteed 92 | // to persist until all data has been written, should you consider passing 0 as apiflags, 93 | // which will instead store the passed pointer without performing a copy. 94 | strcpy(buffer, "GET / HTTP/1.1\r\n"); 95 | client->add(buffer, strlen(buffer), ASYNC_WRITE_FLAG_COPY); 96 | 97 | snprintf(buffer, sizeof(buffer), "Host: %s\r\n", hostname); 98 | client->add(buffer, strlen(buffer), ASYNC_WRITE_FLAG_COPY); 99 | 100 | strcpy(buffer, "Connection: close\r\n\r\n"); 101 | client->add(buffer, strlen(buffer), ASYNC_WRITE_FLAG_COPY); 102 | 103 | client->send(); 104 | }, 105 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 106 | ); 107 | 108 | // Callback on data ready to be processed - MUST BE CONSUMED AT ONCE or will be discarded 109 | sslClient.onData([](void * ctx_ptr, AsyncClient * client, void * buf, size_t len) { 110 | 111 | Serial.printf("\n\nonData received data (%u bytes), raw buffer follows:\r\n", len); 112 | Serial.write((const uint8_t *)buf, len); 113 | 114 | }, 115 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 116 | ); 117 | 118 | // Callback on written data being acknowledged as sent. If the data written so far fully covers 119 | // a buffer added WITHOUT the ASYNC_WRITE_FLAG_COPY flag, now is the first safe moment at 120 | // which such a buffer area may be discarded or reused. 121 | sslClient.onAck([](void * ctx_ptr, AsyncClient * client, size_t len, uint32_t ms_delay) { 122 | 123 | Serial.printf("\n\nonAck acknowledged sending next %u bytes after %u ms\r\n", len, ms_delay); 124 | 125 | }, 126 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 127 | ); 128 | 129 | // Callback on socket disconnect, called: 130 | // - on any socket close event (local or remote) after being connected 131 | // - on failure to connect, right after the onError callback 132 | sslClient.onDisconnect([](void * ctx_ptr, AsyncClient * client) { 133 | 134 | Serial.println("\n\nonDisconnect socket disconnected!"); 135 | 136 | }, 137 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 138 | ); 139 | 140 | // Callback on error event 141 | sslClient.onError([](void * ctx_ptr, AsyncClient * client, int8_t error) { 142 | 143 | Serial.printf("\n\nonError socket reported error %d\r\n", error); 144 | 145 | }, 146 | NULL // <-- Pointer to application data, accessible within callback through ctx_ptr 147 | ); 148 | 149 | sslClient.setRootCa(rootCACertificate, strlen(rootCACertificate) + 1); 150 | 151 | } 152 | 153 | uint32_t t_dot = 0; 154 | uint32_t t_req = 0; 155 | void loop() { 156 | uint32_t t = millis(); 157 | 158 | // This is to show that the main loop() is running while the AsyncClient 159 | // object processes data in the background. 160 | if (t - t_dot >= 200) { 161 | t_dot = t; 162 | Serial.print("*"); 163 | } 164 | 165 | // Try connecting to remote host if 10 seconds pass after last try 166 | if (t - t_req >= 20000 && sslClient.state() == 0) { 167 | 168 | // NOTE: DNS resolving is also done asynchronously (in the LWIP thread) 169 | t_req = t; 170 | Serial.printf("\n\nStarting connection to %s port 443...\r\n", hostname); 171 | sslClient.connect(hostname, 443, true); // <-- Enable encrypted connection on this instance 172 | 173 | } 174 | 175 | delay(50); 176 | } 177 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"AsyncTCPSock", 3 | "description":"Reimplementation of an Asynchronous TCP Library for ESP32, using BSD Sockets", 4 | "keywords":"async,tcp", 5 | "authors": 6 | { 7 | "name": "Alex Villacís Lasso", 8 | "maintainer": true 9 | }, 10 | "repository": 11 | { 12 | "type": "git", 13 | "url": "https://github.com/yubox-node-org/AsyncTCPSock.git" 14 | }, 15 | "version": "0.0.1", 16 | "license": "LGPL-3.0", 17 | "frameworks": "arduino", 18 | "platforms": "espressif32", 19 | "build": { 20 | "libCompatMode": 2 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=AsyncTCPSock 2 | version=0.0.1 3 | author=avillacis 4 | maintainer=avillacis 5 | sentence=Reimplemented Async TCP Library for ESP32 using BSD Sockets 6 | 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. 7 | category=Other 8 | url=https://github.com/yubox-node-org/AsyncTCPSock 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/AsyncTCP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Reimplementation of an asynchronous TCP library for Espressif MCUs, using 3 | BSD sockets. 4 | 5 | Copyright (c) 2020 Alex Villacís Lasso. 6 | 7 | Original AsyncTCP API Copyright (c) 2016 Hristo Gochkov. All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #include "Arduino.h" 25 | 26 | #include "AsyncTCP.h" 27 | #include "esp_task_wdt.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | #include 36 | 37 | #undef close 38 | #undef connect 39 | #undef write 40 | #undef read 41 | 42 | static TaskHandle_t _asyncsock_service_task_handle = NULL; 43 | static SemaphoreHandle_t _asyncsock_mutex = NULL; 44 | 45 | typedef std::list::iterator sockIterator; 46 | 47 | void _asynctcpsock_task(void *); 48 | 49 | #define ASYNCTCPSOCK_POLL_INTERVAL 125 50 | 51 | #define MAX_PAYLOAD_SIZE 1360 52 | 53 | // Since the only task reading from these sockets is the asyncTcpPSock task 54 | // and all socket clients are serviced sequentially, only one read buffer 55 | // is needed, and it can therefore be statically allocated 56 | static uint8_t _readBuffer[MAX_PAYLOAD_SIZE]; 57 | 58 | // Start async socket task 59 | static bool _start_asyncsock_task(void) 60 | { 61 | if (!_asyncsock_service_task_handle) { 62 | log_i("Creating asyncTcpSock task running in core %d (-1 for any available core)...", CONFIG_ASYNC_TCP_RUNNING_CORE); 63 | xTaskCreateUniversal( 64 | _asynctcpsock_task, 65 | "asyncTcpSock", 66 | CONFIG_ASYNC_TCP_STACK, 67 | NULL, 68 | CONFIG_ASYNC_TCP_TASK_PRIORITY, 69 | &_asyncsock_service_task_handle, 70 | CONFIG_ASYNC_TCP_RUNNING_CORE); 71 | if (!_asyncsock_service_task_handle) return false; 72 | } 73 | return true; 74 | } 75 | 76 | // Actual asynchronous socket task 77 | void _asynctcpsock_task(void *) 78 | { 79 | auto & _socketBaseList = AsyncSocketBase::_getSocketBaseList(); 80 | 81 | while (true) { 82 | sockIterator it; 83 | fd_set sockSet_r; 84 | fd_set sockSet_w; 85 | int max_sock = 0; 86 | 87 | std::list sockList; 88 | 89 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 90 | 91 | // Collect all of the active sockets into socket set. Half-destroyed 92 | // connections should have set _socket to -1 and therefore should not 93 | // end up in the sockList. 94 | FD_ZERO(&sockSet_r); FD_ZERO(&sockSet_w); 95 | for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { 96 | if ((*it)->_socket != -1) { 97 | #ifdef CONFIG_LWIP_MAX_SOCKETS 98 | if (!(*it)->_isServer() || _socketBaseList.size() < CONFIG_LWIP_MAX_SOCKETS) { 99 | #endif 100 | FD_SET((*it)->_socket, &sockSet_r); 101 | if (max_sock <= (*it)->_socket) max_sock = (*it)->_socket + 1; 102 | #ifdef CONFIG_LWIP_MAX_SOCKETS 103 | } 104 | #endif 105 | if ((*it)->_pendingWrite()) { 106 | FD_SET((*it)->_socket, &sockSet_w); 107 | if (max_sock <= (*it)->_socket) max_sock = (*it)->_socket + 1; 108 | } 109 | (*it)->_selected = true; 110 | } 111 | } 112 | 113 | // Wait for activity on all monitored sockets 114 | struct timeval tv; 115 | tv.tv_sec = 0; 116 | tv.tv_usec = ASYNCTCPSOCK_POLL_INTERVAL * 1000; 117 | 118 | xSemaphoreGiveRecursive(_asyncsock_mutex); 119 | 120 | int r = select(max_sock, &sockSet_r, &sockSet_w, NULL, &tv); 121 | 122 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 123 | 124 | // Check all sockets to see which ones are active 125 | uint32_t nActive = 0; 126 | if (r > 0) { 127 | // Collect and notify all writable sockets. Half-destroyed connections 128 | // should have set _socket to -1 and therefore should not end up in 129 | // the sockList. 130 | for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { 131 | if ((*it)->_selected && FD_ISSET((*it)->_socket, &sockSet_w)) { 132 | sockList.push_back(*it); 133 | } 134 | } 135 | for (it = sockList.begin(); it != sockList.end(); it++) { 136 | #if CONFIG_ASYNC_TCP_USE_WDT 137 | if (esp_task_wdt_add(NULL) != ESP_OK) { 138 | log_e("Failed to add async task to WDT"); 139 | } 140 | #endif 141 | if ((*it)->_sockIsWriteable()) { 142 | (*it)->_sock_lastactivity = millis(); 143 | nActive++; 144 | } 145 | #if CONFIG_ASYNC_TCP_USE_WDT 146 | if (esp_task_wdt_delete(NULL) != ESP_OK) { 147 | log_e("Failed to remove loop task from WDT"); 148 | } 149 | #endif 150 | } 151 | sockList.clear(); 152 | 153 | // Collect and notify all readable sockets. Half-destroyed connections 154 | // should have set _socket to -1 and therefore should not end up in 155 | // the sockList. 156 | for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { 157 | if ((*it)->_selected && FD_ISSET((*it)->_socket, &sockSet_r)) { 158 | sockList.push_back(*it); 159 | } 160 | } 161 | for (it = sockList.begin(); it != sockList.end(); it++) { 162 | #if CONFIG_ASYNC_TCP_USE_WDT 163 | if (esp_task_wdt_add(NULL) != ESP_OK) { 164 | log_e("Failed to add async task to WDT"); 165 | } 166 | #endif 167 | (*it)->_sock_lastactivity = millis(); 168 | (*it)->_sockIsReadable(); 169 | nActive++; 170 | #if CONFIG_ASYNC_TCP_USE_WDT 171 | if (esp_task_wdt_delete(NULL) != ESP_OK) { 172 | log_e("Failed to remove loop task from WDT"); 173 | } 174 | #endif 175 | } 176 | sockList.clear(); 177 | } 178 | 179 | // Collect and notify all sockets waiting for DNS completion 180 | for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { 181 | // Collect socket that has finished resolving DNS (with or without error) 182 | if ((*it)->_isdnsfinished) { 183 | sockList.push_back(*it); 184 | } 185 | } 186 | for (it = sockList.begin(); it != sockList.end(); it++) { 187 | #if CONFIG_ASYNC_TCP_USE_WDT 188 | if(esp_task_wdt_add(NULL) != ESP_OK){ 189 | log_e("Failed to add async task to WDT"); 190 | } 191 | #endif 192 | (*it)->_isdnsfinished = false; 193 | (*it)->_sockDelayedConnect(); 194 | #if CONFIG_ASYNC_TCP_USE_WDT 195 | if(esp_task_wdt_delete(NULL) != ESP_OK){ 196 | log_e("Failed to remove loop task from WDT"); 197 | } 198 | #endif 199 | } 200 | sockList.clear(); 201 | 202 | xSemaphoreGiveRecursive(_asyncsock_mutex); 203 | 204 | // Collect and run activity poll on all pollable sockets 205 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 206 | for (it = _socketBaseList.begin(); it != _socketBaseList.end(); it++) { 207 | (*it)->_selected = false; 208 | if (millis() - (*it)->_sock_lastactivity >= ASYNCTCPSOCK_POLL_INTERVAL) { 209 | (*it)->_sock_lastactivity = millis(); 210 | sockList.push_back(*it); 211 | } 212 | } 213 | 214 | // Run activity poll on all pollable sockets 215 | for (it = sockList.begin(); it != sockList.end(); it++) { 216 | #if CONFIG_ASYNC_TCP_USE_WDT 217 | if(esp_task_wdt_add(NULL) != ESP_OK){ 218 | log_e("Failed to add async task to WDT"); 219 | } 220 | #endif 221 | (*it)->_sockPoll(); 222 | #if CONFIG_ASYNC_TCP_USE_WDT 223 | if(esp_task_wdt_delete(NULL) != ESP_OK){ 224 | log_e("Failed to remove loop task from WDT"); 225 | } 226 | #endif 227 | } 228 | sockList.clear(); 229 | 230 | xSemaphoreGiveRecursive(_asyncsock_mutex); 231 | } 232 | 233 | vTaskDelete(NULL); 234 | _asyncsock_service_task_handle = NULL; 235 | } 236 | 237 | AsyncSocketBase::AsyncSocketBase() 238 | { 239 | if (_asyncsock_mutex == NULL) _asyncsock_mutex = xSemaphoreCreateRecursiveMutex(); 240 | 241 | _sock_lastactivity = millis(); 242 | _selected = false; 243 | 244 | // Add this base socket to the monitored list 245 | auto & _socketBaseList = AsyncSocketBase::_getSocketBaseList(); 246 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 247 | _socketBaseList.push_back(this); 248 | xSemaphoreGiveRecursive(_asyncsock_mutex); 249 | } 250 | 251 | std::list & AsyncSocketBase::_getSocketBaseList(void) 252 | { 253 | // List of monitored socket objects 254 | static std::list _socketBaseList; 255 | return _socketBaseList; 256 | } 257 | 258 | AsyncSocketBase::~AsyncSocketBase() 259 | { 260 | // Remove this base socket from the monitored list 261 | auto & _socketBaseList = AsyncSocketBase::_getSocketBaseList(); 262 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 263 | _socketBaseList.remove(this); 264 | xSemaphoreGiveRecursive(_asyncsock_mutex); 265 | } 266 | 267 | 268 | AsyncClient::AsyncClient(int sockfd) 269 | : _connect_cb(0) 270 | , _connect_cb_arg(0) 271 | , _discard_cb(0) 272 | , _discard_cb_arg(0) 273 | , _sent_cb(0) 274 | , _sent_cb_arg(0) 275 | , _error_cb(0) 276 | , _error_cb_arg(0) 277 | , _recv_cb(0) 278 | , _recv_cb_arg(0) 279 | , _timeout_cb(0) 280 | , _timeout_cb_arg(0) 281 | , _rx_last_packet(0) 282 | , _rx_since_timeout(0) 283 | , _ack_timeout(ASYNC_MAX_ACK_TIME) 284 | , _connect_port(0) 285 | #if ASYNC_TCP_SSL_ENABLED 286 | , _root_ca_len(0) 287 | , _root_ca(NULL) 288 | , _cli_cert_len(0) 289 | , _cli_cert(NULL) 290 | , _cli_key_len(0) 291 | , _cli_key(NULL) 292 | , _secure(false) 293 | , _handshake_done(true) 294 | , _psk_ident(0) 295 | , _psk(0) 296 | , _sslctx(NULL) 297 | #endif // ASYNC_TCP_SSL_ENABLED 298 | , _writeSpaceRemaining(TCP_SND_BUF) 299 | , _conn_state(0) 300 | { 301 | _write_mutex = xSemaphoreCreateMutex(); 302 | if (sockfd != -1) { 303 | fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); 304 | 305 | // Updating state visible to asyncTcpSock task 306 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 307 | _conn_state = 4; 308 | _socket = sockfd; 309 | _rx_last_packet = millis(); 310 | xSemaphoreGiveRecursive(_asyncsock_mutex); 311 | } 312 | } 313 | 314 | AsyncClient::~AsyncClient() 315 | { 316 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 317 | if (_socket != -1) _close(); 318 | _removeAllCallbacks(); 319 | vSemaphoreDelete(_write_mutex); 320 | _write_mutex = NULL; 321 | xSemaphoreGiveRecursive(_asyncsock_mutex); 322 | } 323 | 324 | void AsyncClient::setRxTimeout(uint32_t timeout){ 325 | _rx_since_timeout = timeout; 326 | } 327 | 328 | uint32_t AsyncClient::getRxTimeout(){ 329 | return _rx_since_timeout; 330 | } 331 | 332 | uint32_t AsyncClient::getAckTimeout(){ 333 | return _ack_timeout; 334 | } 335 | 336 | void AsyncClient::setAckTimeout(uint32_t timeout){ 337 | _ack_timeout = timeout; 338 | } 339 | 340 | void AsyncClient::setNoDelay(bool nodelay){ 341 | if (_socket == -1) return; 342 | 343 | int flag = nodelay; 344 | int res = setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); 345 | if(res < 0) { 346 | log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); 347 | } 348 | } 349 | 350 | bool AsyncClient::getNoDelay(){ 351 | if (_socket == -1) return false; 352 | 353 | int flag = 0; 354 | socklen_t size = sizeof(int); 355 | int res = getsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, &size); 356 | if(res < 0) { 357 | log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); 358 | } 359 | return flag; 360 | } 361 | 362 | /* 363 | * Callback Setters 364 | * */ 365 | 366 | void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ 367 | _connect_cb = cb; 368 | _connect_cb_arg = arg; 369 | } 370 | 371 | void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ 372 | _discard_cb = cb; 373 | _discard_cb_arg = arg; 374 | } 375 | 376 | void AsyncClient::onAck(AcAckHandler cb, void* arg){ 377 | _sent_cb = cb; 378 | _sent_cb_arg = arg; 379 | } 380 | 381 | void AsyncClient::onError(AcErrorHandler cb, void* arg){ 382 | _error_cb = cb; 383 | _error_cb_arg = arg; 384 | } 385 | 386 | void AsyncClient::onData(AcDataHandler cb, void* arg){ 387 | _recv_cb = cb; 388 | _recv_cb_arg = arg; 389 | } 390 | 391 | void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ 392 | _timeout_cb = cb; 393 | _timeout_cb_arg = arg; 394 | } 395 | 396 | void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ 397 | _poll_cb = cb; 398 | _poll_cb_arg = arg; 399 | } 400 | 401 | bool AsyncClient::connected(){ 402 | if (_socket == -1) { 403 | return false; 404 | } 405 | return _conn_state == 4; 406 | } 407 | 408 | bool AsyncClient::freeable(){ 409 | if (_socket == -1) { 410 | return true; 411 | } 412 | return _conn_state == 0 || _conn_state > 4; 413 | } 414 | 415 | uint32_t AsyncClient::getRemoteAddress() { 416 | if(_socket == -1) { 417 | return 0; 418 | } 419 | 420 | struct sockaddr_storage addr; 421 | socklen_t len = sizeof addr; 422 | getpeername(_socket, (struct sockaddr*)&addr, &len); 423 | struct sockaddr_in *s = (struct sockaddr_in *)&addr; 424 | 425 | return s->sin_addr.s_addr; 426 | } 427 | 428 | uint16_t AsyncClient::getRemotePort() { 429 | if(_socket == -1) { 430 | return 0; 431 | } 432 | 433 | struct sockaddr_storage addr; 434 | socklen_t len = sizeof addr; 435 | getpeername(_socket, (struct sockaddr*)&addr, &len); 436 | struct sockaddr_in *s = (struct sockaddr_in *)&addr; 437 | 438 | return ntohs(s->sin_port); 439 | } 440 | 441 | uint32_t AsyncClient::getLocalAddress() { 442 | if(_socket == -1) { 443 | return 0; 444 | } 445 | 446 | struct sockaddr_storage addr; 447 | socklen_t len = sizeof addr; 448 | getsockname(_socket, (struct sockaddr*)&addr, &len); 449 | struct sockaddr_in *s = (struct sockaddr_in *)&addr; 450 | 451 | return s->sin_addr.s_addr; 452 | } 453 | 454 | uint16_t AsyncClient::getLocalPort() { 455 | if(_socket == -1) { 456 | return 0; 457 | } 458 | 459 | struct sockaddr_storage addr; 460 | socklen_t len = sizeof addr; 461 | getsockname(_socket, (struct sockaddr*)&addr, &len); 462 | struct sockaddr_in *s = (struct sockaddr_in *)&addr; 463 | 464 | return ntohs(s->sin_port); 465 | } 466 | 467 | IPAddress AsyncClient::remoteIP() { 468 | return IPAddress(getRemoteAddress()); 469 | } 470 | 471 | uint16_t AsyncClient::remotePort() { 472 | return getRemotePort(); 473 | } 474 | 475 | IPAddress AsyncClient::localIP() { 476 | return IPAddress(getLocalAddress()); 477 | } 478 | 479 | uint16_t AsyncClient::localPort() { 480 | return getLocalPort(); 481 | } 482 | 483 | 484 | #if ASYNC_TCP_SSL_ENABLED 485 | bool AsyncClient::connect(IPAddress ip, uint16_t port, bool secure) 486 | #else 487 | bool AsyncClient::connect(IPAddress ip, uint16_t port) 488 | #endif // ASYNC_TCP_SSL_ENABLED 489 | { 490 | if (_socket != -1) { 491 | log_w("already connected, state %d", _conn_state); 492 | return false; 493 | } 494 | 495 | if(!_start_asyncsock_task()){ 496 | log_e("failed to start task"); 497 | return false; 498 | } 499 | 500 | #if ASYNC_TCP_SSL_ENABLED 501 | _secure = secure; 502 | _handshake_done = !secure; 503 | #endif // ASYNC_TCP_SSL_ENABLED 504 | 505 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 506 | if (sockfd < 0) { 507 | log_e("socket: %d", errno); 508 | return false; 509 | } 510 | int r = fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); 511 | 512 | uint32_t ip_addr = ip; 513 | struct sockaddr_in serveraddr; 514 | memset(&serveraddr, 0, sizeof(serveraddr)); 515 | serveraddr.sin_family = AF_INET; 516 | memcpy(&(serveraddr.sin_addr.s_addr), &ip_addr, 4); 517 | serveraddr.sin_port = htons(port); 518 | 519 | #ifdef EINPROGRESS 520 | #if EINPROGRESS != 119 521 | #error EINPROGRESS invalid 522 | #endif 523 | #endif 524 | 525 | //Serial.printf("DEBUG: connect to %08x port %d using IP... ", ip_addr, port); 526 | errno = 0; r = ::connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); 527 | //Serial.printf("r=%d errno=%d\r\n", r, errno); 528 | if (r < 0 && errno != EINPROGRESS) { 529 | //Serial.println("\t(connect failed)"); 530 | log_e("connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); 531 | ::close(sockfd); 532 | return false; 533 | } 534 | 535 | // Updating state visible to asyncTcpSock task 536 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 537 | _conn_state = 2; 538 | _socket = sockfd; 539 | _rx_last_packet = millis(); 540 | xSemaphoreGiveRecursive(_asyncsock_mutex); 541 | 542 | // Socket is now connecting. Should become writable in asyncTcpSock task 543 | //Serial.printf("\twaiting for connect finished on socket: %d\r\n", _socket); 544 | return true; 545 | } 546 | 547 | void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg); 548 | #if ASYNC_TCP_SSL_ENABLED 549 | bool AsyncClient::connect(const char* host, uint16_t port, bool secure){ 550 | #else 551 | bool AsyncClient::connect(const char* host, uint16_t port){ 552 | #endif // ASYNC_TCP_SSL_ENABLED 553 | ip_addr_t addr; 554 | 555 | if(!_start_asyncsock_task()){ 556 | log_e("failed to start task"); 557 | return false; 558 | } 559 | 560 | log_v("connect to %s port %d using DNS...", host, port); 561 | err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcpsock_dns_found, this); 562 | if(err == ERR_OK) { 563 | log_v("\taddr resolved as %08x, connecting...", addr.u_addr.ip4.addr); 564 | #if ASYNC_TCP_SSL_ENABLED 565 | _hostname = host; 566 | return connect(IPAddress(addr.u_addr.ip4.addr), port, secure); 567 | #else 568 | return connect(IPAddress(addr.u_addr.ip4.addr), port); 569 | #endif // ASYNC_TCP_SSL_ENABLED 570 | } else if(err == ERR_INPROGRESS) { 571 | log_v("\twaiting for DNS resolution"); 572 | _conn_state = 1; 573 | _connect_port = port; 574 | #if ASYNC_TCP_SSL_ENABLED 575 | _hostname = host; 576 | _secure = secure; 577 | _handshake_done = !secure; 578 | #endif // ASYNC_TCP_SSL_ENABLED 579 | return true; 580 | } 581 | log_e("error: %d", err); 582 | return false; 583 | } 584 | 585 | // This function runs in the LWIP thread 586 | void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) 587 | { 588 | AsyncClient * c = (AsyncClient *)arg; 589 | if (ipaddr) { 590 | memcpy(&(c->_connect_addr), ipaddr, sizeof(struct ip_addr)); 591 | } else { 592 | memset(&(c->_connect_addr), 0, sizeof(struct ip_addr)); 593 | } 594 | 595 | // Updating state visible to asyncTcpSock task 596 | // MUST NOT take _asyncsock_mutex lock, risks a deadlock if task holding lock 597 | // attempts a LWIP network call. 598 | c->_isdnsfinished = true; 599 | 600 | // TODO: actually use name 601 | } 602 | 603 | // DNS resolving has finished. Check for error or connect 604 | void AsyncClient::_sockDelayedConnect(void) 605 | { 606 | if (_connect_addr.u_addr.ip4.addr) { 607 | #if ASYNC_TCP_SSL_ENABLED 608 | connect(IPAddress(_connect_addr.u_addr.ip4.addr), _connect_port, _secure); 609 | #else 610 | connect(IPAddress(_connect_addr.u_addr.ip4.addr), _connect_port); 611 | #endif 612 | } else { 613 | _conn_state = 0; 614 | if(_error_cb) { 615 | _error_cb(_error_cb_arg, this, -55); 616 | } 617 | if(_discard_cb) { 618 | _discard_cb(_discard_cb_arg, this); 619 | } 620 | } 621 | } 622 | 623 | #if ASYNC_TCP_SSL_ENABLED 624 | int AsyncClient::_runSSLHandshakeLoop(void) 625 | { 626 | int res = 0; 627 | 628 | while (!_handshake_done) { 629 | res = _sslctx->runSSLHandshake(); 630 | if (res == 0) { 631 | // Handshake successful 632 | _handshake_done = true; 633 | } else if (ASYNCTCP_TLS_CAN_RETRY(res)) { 634 | // Ran out of readable data or writable space on socket, must continue later 635 | break; 636 | } else { 637 | // SSL handshake for AsyncTCP does not inform SSL errors 638 | log_e("TLS setup failed with error %d, closing socket...", res); 639 | _close(); 640 | // _sslctx should be NULL after this 641 | break; 642 | } 643 | } 644 | 645 | return res; 646 | } 647 | #endif 648 | 649 | bool AsyncClient::_sockIsWriteable(void) 650 | { 651 | int res; 652 | int sockerr; 653 | socklen_t len; 654 | bool activity = false; 655 | 656 | int sent_errno = 0; 657 | std::deque notifylist; 658 | 659 | // Socket is now writeable. What should we do? 660 | switch (_conn_state) { 661 | case 2: 662 | case 3: 663 | // Socket has finished connecting. What happened? 664 | len = (socklen_t)sizeof(int); 665 | res = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); 666 | if (res < 0) { 667 | _error(errno); 668 | } else if (sockerr != 0) { 669 | _error(sockerr); 670 | } else { 671 | #if ASYNC_TCP_SSL_ENABLED 672 | if (_secure) { 673 | int res = 0; 674 | 675 | if (_sslctx == NULL) { 676 | String remIP_str = remoteIP().toString(); 677 | const char * host_or_ip = _hostname.isEmpty() 678 | ? remIP_str.c_str() 679 | : _hostname.c_str(); 680 | 681 | _sslctx = new AsyncTCP_TLS_Context(); 682 | if (_root_ca != NULL) { 683 | res = _sslctx->startSSLClient(_socket, host_or_ip, 684 | (const unsigned char *)_root_ca, _root_ca_len, 685 | (const unsigned char *)_cli_cert, _cli_cert_len, 686 | (const unsigned char *)_cli_key, _cli_key_len); 687 | } else if (_psk_ident != NULL) { 688 | res = _sslctx->startSSLClient(_socket, host_or_ip, 689 | _psk_ident, _psk); 690 | } else { 691 | res = _sslctx->startSSLClientInsecure(_socket, host_or_ip); 692 | } 693 | 694 | if (res != 0) { 695 | // SSL setup for AsyncTCP does not inform SSL errors 696 | log_e("TLS setup failed with error %d, closing socket...", res); 697 | _close(); 698 | // _sslctx should be NULL after this 699 | } 700 | } 701 | 702 | // _handshake_done is set to FALSE on connect() if encrypted connection 703 | if (_sslctx != NULL && res == 0) res = _runSSLHandshakeLoop(); 704 | 705 | if (!_handshake_done) return ASYNCTCP_TLS_CAN_RETRY(res); 706 | 707 | // Fallthrough to ordinary successful connection 708 | } 709 | #endif 710 | 711 | // Socket is now fully connected 712 | _conn_state = 4; 713 | activity = true; 714 | _rx_last_packet = millis(); 715 | _ack_timeout_signaled = false; 716 | 717 | if(_connect_cb) { 718 | _connect_cb(_connect_cb_arg, this); 719 | } 720 | } 721 | break; 722 | case 4: 723 | default: 724 | // Socket can accept some new data... 725 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 726 | if (_writeQueue.size() > 0) { 727 | activity = _flushWriteQueue(); 728 | _collectNotifyWrittenBuffers(notifylist, sent_errno); 729 | } 730 | xSemaphoreGive(_write_mutex); 731 | 732 | _notifyWrittenBuffers(notifylist, sent_errno); 733 | 734 | break; 735 | } 736 | 737 | return activity; 738 | } 739 | 740 | bool AsyncClient::_flushWriteQueue(void) 741 | { 742 | bool activity = false; 743 | 744 | if (_socket == -1) return false; 745 | 746 | for (auto it = _writeQueue.begin(); it != _writeQueue.end(); it++) { 747 | // Abort iteration if error found while writing a buffer 748 | if (it->write_errno != 0) break; 749 | 750 | // Skip over head buffers already fully written 751 | if (it->written >= it->length) continue; 752 | 753 | bool keep_writing = true; 754 | do { 755 | uint8_t * p = it->data + it->written; 756 | size_t n = it->length - it->written; 757 | errno = 0; 758 | ssize_t r; 759 | 760 | #if ASYNC_TCP_SSL_ENABLED 761 | if (_sslctx != NULL) { 762 | r = _sslctx->write(p, n); 763 | if (ASYNCTCP_TLS_CAN_RETRY(r)) { 764 | r = -1; 765 | errno = EAGAIN; 766 | } else if (ASYNCTCP_TLS_EOF(r)) { 767 | r = -1; 768 | errno = EPIPE; 769 | } else if (r < 0) { 770 | if (errno == 0) errno = EIO; 771 | } 772 | } else { 773 | #endif 774 | r = lwip_write(_socket, p, n); 775 | #if ASYNC_TCP_SSL_ENABLED 776 | } 777 | #endif 778 | 779 | if (r >= 0) { 780 | // Written some data into the socket 781 | it->written += r; 782 | _writeSpaceRemaining += r; 783 | activity = true; 784 | 785 | if (it->written >= it->length) { 786 | it->written_at = millis(); 787 | if (it->owned) ::free(it->data); 788 | it->data = NULL; 789 | } 790 | } else if (errno == EAGAIN || errno == EWOULDBLOCK) { 791 | // Socket is full, could not write anything 792 | keep_writing = false; 793 | } else { 794 | // A write error happened that should be reported 795 | it->write_errno = errno; 796 | keep_writing = false; 797 | log_e("socket %d lwip_write() failed errno=%d", _socket, it->write_errno); 798 | } 799 | } while (keep_writing && it->written < it->length); 800 | } 801 | 802 | return activity; 803 | } 804 | 805 | // This method MUST be called with _write_mutex held 806 | void AsyncClient::_collectNotifyWrittenBuffers(std::deque & notifyqueue, int & write_errno) 807 | { 808 | write_errno = 0; 809 | notifyqueue.clear(); 810 | 811 | while (_writeQueue.size() > 0) { 812 | if (_writeQueue.front().write_errno != 0) { 813 | write_errno = _writeQueue.front().write_errno; 814 | return; 815 | } 816 | 817 | if (_writeQueue.front().written >= _writeQueue.front().length) { 818 | // Collect information on fully-written buffer, and stash it into notify queue 819 | if (_writeQueue.front().written_at > _rx_last_packet) { 820 | _rx_last_packet = _writeQueue.front().written_at; 821 | } 822 | if (_writeQueue.front().owned && _writeQueue.front().data != NULL) ::free(_writeQueue.front().data); 823 | 824 | notify_writebuf noti; 825 | noti.length = _writeQueue.front().length; 826 | noti.delay = _writeQueue.front().written_at - _writeQueue.front().queued_at; 827 | _writeQueue.pop_front(); 828 | notifyqueue.push_back(noti); 829 | } else { 830 | // Found first not-fully-written buffer, stop here 831 | return; 832 | } 833 | } 834 | } 835 | 836 | void AsyncClient::_notifyWrittenBuffers(std::deque & notifyqueue, int write_errno) 837 | { 838 | while (notifyqueue.size() > 0) { 839 | if (notifyqueue.front().length > 0 && _sent_cb) { 840 | _sent_cb(_sent_cb_arg, this, notifyqueue.front().length, notifyqueue.front().delay); 841 | } 842 | notifyqueue.pop_front(); 843 | } 844 | 845 | if (write_errno != 0) _error(write_errno); 846 | } 847 | 848 | void AsyncClient::_sockIsReadable(void) 849 | { 850 | _rx_last_packet = millis(); 851 | errno = 0; 852 | ssize_t r; 853 | 854 | #if ASYNC_TCP_SSL_ENABLED 855 | if (_sslctx != NULL) { 856 | if (!_handshake_done) { 857 | // Handshake process has stopped for want of data, must be 858 | // continued here for connection to complete. 859 | _runSSLHandshakeLoop(); 860 | 861 | // If handshake was successful, this will be recognized when the socket 862 | // next becomes writable. No other read operation should be done here. 863 | return; 864 | } else { 865 | r = _sslctx->read(_readBuffer, MAX_PAYLOAD_SIZE); 866 | if (ASYNCTCP_TLS_CAN_RETRY(r)) { 867 | r = -1; 868 | errno = EAGAIN; 869 | } else if (ASYNCTCP_TLS_EOF(r)) { 870 | // Simulate "successful" end-of-stream condition 871 | r = 0; 872 | } else if (r < 0) { 873 | if (errno == 0) errno = EIO; 874 | } 875 | } 876 | } else { 877 | #endif 878 | r = lwip_read(_socket, _readBuffer, MAX_PAYLOAD_SIZE); 879 | #if ASYNC_TCP_SSL_ENABLED 880 | } 881 | #endif 882 | 883 | if (r > 0) { 884 | if(_recv_cb) { 885 | _recv_cb(_recv_cb_arg, this, _readBuffer, r); 886 | } 887 | } else if (r == 0) { 888 | // A successful read of 0 bytes indicates remote side closed connection 889 | _close(); 890 | } else if (r < 0) { 891 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 892 | // Do nothing, will try later 893 | } else { 894 | _error(errno); 895 | } 896 | } 897 | } 898 | 899 | void AsyncClient::_sockPoll(void) 900 | { 901 | if (!connected()) return; 902 | 903 | // The AsyncClient::send() call may be invoked from tasks other than "asyncTcpSock" 904 | // and may have written buffers via _flushWriteQueue(), but the ack callbacks have 905 | // not been called yet, nor buffers removed from the write queue. For consistency, 906 | // written buffers are now acked here. 907 | std::deque notifylist; 908 | int sent_errno = 0; 909 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 910 | if (_writeQueue.size() > 0) { 911 | _collectNotifyWrittenBuffers(notifylist, sent_errno); 912 | } 913 | xSemaphoreGive(_write_mutex); 914 | 915 | _notifyWrittenBuffers(notifylist, sent_errno); 916 | 917 | /* Connection migh be closed after ACK notification. */ 918 | if (!connected()) return; 919 | 920 | uint32_t now = millis(); 921 | 922 | // ACK Timeout - simulated by write queue staleness 923 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 924 | if (_writeQueue.size() > 0 && !_ack_timeout_signaled && _ack_timeout) { 925 | uint32_t sent_delay = now - _writeQueue.front().queued_at; 926 | if (sent_delay >= _ack_timeout && _writeQueue.front().written_at == 0) { 927 | _ack_timeout_signaled = true; 928 | //log_w("ack timeout %d", pcb->state); 929 | xSemaphoreGive(_write_mutex); 930 | if(_timeout_cb) 931 | _timeout_cb(_timeout_cb_arg, this, sent_delay); 932 | return; 933 | } 934 | } 935 | xSemaphoreGive(_write_mutex); 936 | 937 | // RX Timeout? Check for readable socket before bailing out 938 | if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) { 939 | fd_set sockSet_r; 940 | struct timeval tv; 941 | 942 | FD_ZERO(&sockSet_r); 943 | FD_SET(_socket, &sockSet_r); 944 | tv.tv_sec = 0; 945 | tv.tv_usec = 0; 946 | 947 | int r = select(_socket + 1, &sockSet_r, NULL, NULL, &tv); 948 | if (r > 0) _rx_last_packet = now; 949 | } 950 | 951 | // RX Timeout 952 | if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) { 953 | //log_w("rx timeout %d", pcb->state); 954 | _close(); 955 | return; 956 | } 957 | // Everything is fine 958 | if(_poll_cb) { 959 | _poll_cb(_poll_cb_arg, this); 960 | } 961 | } 962 | 963 | void AsyncClient::_removeAllCallbacks(void) 964 | { 965 | _connect_cb = NULL; 966 | _connect_cb_arg = NULL; 967 | _discard_cb = NULL; 968 | _discard_cb_arg = NULL; 969 | _sent_cb = NULL; 970 | _sent_cb_arg = NULL; 971 | _error_cb = NULL; 972 | _error_cb_arg = NULL; 973 | _recv_cb = NULL; 974 | _recv_cb_arg = NULL; 975 | _timeout_cb = NULL; 976 | _timeout_cb_arg = NULL; 977 | _poll_cb = NULL; 978 | _poll_cb_arg = NULL; 979 | } 980 | 981 | void AsyncClient::_close(void) 982 | { 983 | //Serial.print("AsyncClient::_close: "); Serial.println(_socket); 984 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 985 | _conn_state = 0; 986 | ::close(_socket); 987 | _socket = -1; 988 | #if ASYNC_TCP_SSL_ENABLED 989 | if (_sslctx != NULL) { 990 | delete _sslctx; 991 | _sslctx = NULL; 992 | } 993 | #endif 994 | xSemaphoreGiveRecursive(_asyncsock_mutex); 995 | 996 | _clearWriteQueue(); 997 | if (_discard_cb) _discard_cb(_discard_cb_arg, this); 998 | } 999 | 1000 | void AsyncClient::_error(int8_t err) 1001 | { 1002 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 1003 | _conn_state = 0; 1004 | ::close(_socket); 1005 | _socket = -1; 1006 | #if ASYNC_TCP_SSL_ENABLED 1007 | if (_sslctx != NULL) { 1008 | delete _sslctx; 1009 | _sslctx = NULL; 1010 | } 1011 | #endif 1012 | xSemaphoreGiveRecursive(_asyncsock_mutex); 1013 | 1014 | _clearWriteQueue(); 1015 | if (_error_cb) _error_cb(_error_cb_arg, this, err); 1016 | if (_discard_cb) _discard_cb(_discard_cb_arg, this); 1017 | } 1018 | 1019 | size_t AsyncClient::space() 1020 | { 1021 | if (!connected()) return 0; 1022 | return _writeSpaceRemaining; 1023 | } 1024 | 1025 | size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) 1026 | { 1027 | queued_writebuf n_entry; 1028 | 1029 | if (!connected() || data == NULL || size <= 0) return 0; 1030 | 1031 | size_t room = space(); 1032 | if (!room) return 0; 1033 | 1034 | size_t will_send = (room < size) ? room : size; 1035 | if (apiflags & ASYNC_WRITE_FLAG_COPY) { 1036 | n_entry.data = (uint8_t *)malloc(will_send); 1037 | if (n_entry.data == NULL) { 1038 | return 0; 1039 | } 1040 | memcpy(n_entry.data, data, will_send); 1041 | n_entry.owned = true; 1042 | } else { 1043 | n_entry.data = (uint8_t *)data; 1044 | n_entry.owned = false; 1045 | } 1046 | n_entry.length = will_send; 1047 | n_entry.written = 0; 1048 | n_entry.queued_at = millis(); 1049 | n_entry.written_at = 0; 1050 | n_entry.write_errno = 0; 1051 | 1052 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 1053 | _writeQueue.push_back(n_entry); 1054 | _writeSpaceRemaining -= will_send; 1055 | _ack_timeout_signaled = false; 1056 | xSemaphoreGive(_write_mutex); 1057 | 1058 | return will_send; 1059 | } 1060 | 1061 | bool AsyncClient::send() 1062 | { 1063 | if (!connected()) return false; 1064 | 1065 | fd_set sockSet_w; 1066 | struct timeval tv; 1067 | 1068 | FD_ZERO(&sockSet_w); 1069 | FD_SET(_socket, &sockSet_w); 1070 | tv.tv_sec = 0; 1071 | tv.tv_usec = 0; 1072 | 1073 | // Write as much data as possible from queue if socket is writable 1074 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 1075 | int r = select(_socket + 1, NULL, &sockSet_w, NULL, &tv); 1076 | if (r > 0) _flushWriteQueue(); 1077 | xSemaphoreGive(_write_mutex); 1078 | return true; 1079 | } 1080 | 1081 | bool AsyncClient::_pendingWrite(void) 1082 | { 1083 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 1084 | bool pending = ((_conn_state > 0 && _conn_state < 4) || _writeQueue.size() > 0); 1085 | xSemaphoreGive(_write_mutex); 1086 | return pending; 1087 | } 1088 | 1089 | // In normal operation this should be a no-op. Will only free something in case 1090 | // of errors before all data was written. 1091 | void AsyncClient::_clearWriteQueue(void) 1092 | { 1093 | xSemaphoreTake(_write_mutex, (TickType_t)portMAX_DELAY); 1094 | while (_writeQueue.size() > 0) { 1095 | if (_writeQueue.front().owned) { 1096 | if (_writeQueue.front().data != NULL) ::free(_writeQueue.front().data); 1097 | } 1098 | _writeQueue.pop_front(); 1099 | } 1100 | xSemaphoreGive(_write_mutex); 1101 | } 1102 | 1103 | bool AsyncClient::free(){ 1104 | if (_socket == -1) return true; 1105 | return (_conn_state == 0 || _conn_state > 4); 1106 | } 1107 | 1108 | size_t AsyncClient::write(const char* data) { 1109 | if(data == NULL) { 1110 | return 0; 1111 | } 1112 | return write(data, strlen(data)); 1113 | } 1114 | 1115 | size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { 1116 | size_t will_send = add(data, size, apiflags); 1117 | if(!will_send || !send()) { 1118 | return 0; 1119 | } 1120 | return will_send; 1121 | } 1122 | 1123 | void AsyncClient::close(bool now) 1124 | { 1125 | if (_socket != -1) _close(); 1126 | } 1127 | 1128 | int8_t AsyncClient::abort(){ 1129 | if (_socket != -1) { 1130 | // Note: needs LWIP_SO_LINGER to be enabled in order to work, otherwise 1131 | // this call is equivalent to close(). 1132 | struct linger l; 1133 | l.l_onoff = 1; 1134 | l.l_linger = 0; 1135 | setsockopt(_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); 1136 | _close(); 1137 | } 1138 | return ERR_ABRT; 1139 | } 1140 | 1141 | #if ASYNC_TCP_SSL_ENABLED 1142 | void AsyncClient::setRootCa(const char* rootca, const size_t len) { 1143 | _root_ca = (char*)rootca; 1144 | _root_ca_len = len; 1145 | } 1146 | 1147 | void AsyncClient::setClientCert(const char* cli_cert, const size_t len) { 1148 | _cli_cert = (char*)cli_cert; 1149 | _cli_cert_len = len; 1150 | } 1151 | 1152 | void AsyncClient::setClientKey(const char* cli_key, const size_t len) { 1153 | _cli_key = (char*)cli_key; 1154 | _cli_key_len = len; 1155 | } 1156 | 1157 | void AsyncClient::setPsk(const char* psk_ident, const char* psk) { 1158 | _psk_ident = psk_ident; 1159 | _psk = psk; 1160 | } 1161 | #endif // ASYNC_TCP_SSL_ENABLED 1162 | 1163 | const char * AsyncClient::errorToString(int8_t error){ 1164 | switch(error){ 1165 | case ERR_OK: return "OK"; 1166 | case ERR_MEM: return "Out of memory error"; 1167 | case ERR_BUF: return "Buffer error"; 1168 | case ERR_TIMEOUT: return "Timeout"; 1169 | case ERR_RTE: return "Routing problem"; 1170 | case ERR_INPROGRESS: return "Operation in progress"; 1171 | case ERR_VAL: return "Illegal value"; 1172 | case ERR_WOULDBLOCK: return "Operation would block"; 1173 | case ERR_USE: return "Address in use"; 1174 | case ERR_ALREADY: return "Already connected"; 1175 | case ERR_CONN: return "Not connected"; 1176 | case ERR_IF: return "Low-level netif error"; 1177 | case ERR_ABRT: return "Connection aborted"; 1178 | case ERR_RST: return "Connection reset"; 1179 | case ERR_CLSD: return "Connection closed"; 1180 | case ERR_ARG: return "Illegal argument"; 1181 | case -55: return "DNS failed"; 1182 | default: return "UNKNOWN"; 1183 | } 1184 | } 1185 | /* 1186 | const char * AsyncClient::stateToString(){ 1187 | switch(state()){ 1188 | case 0: return "Closed"; 1189 | case 1: return "Listen"; 1190 | case 2: return "SYN Sent"; 1191 | case 3: return "SYN Received"; 1192 | case 4: return "Established"; 1193 | case 5: return "FIN Wait 1"; 1194 | case 6: return "FIN Wait 2"; 1195 | case 7: return "Close Wait"; 1196 | case 8: return "Closing"; 1197 | case 9: return "Last ACK"; 1198 | case 10: return "Time Wait"; 1199 | default: return "UNKNOWN"; 1200 | } 1201 | } 1202 | */ 1203 | 1204 | 1205 | 1206 | /* 1207 | Async TCP Server 1208 | */ 1209 | 1210 | AsyncServer::AsyncServer(IPAddress addr, uint16_t port) 1211 | : _port(port) 1212 | , _addr(addr) 1213 | , _noDelay(false) 1214 | , _connect_cb(0) 1215 | , _connect_cb_arg(0) 1216 | {} 1217 | 1218 | AsyncServer::AsyncServer(uint16_t port) 1219 | : _port(port) 1220 | , _addr((uint32_t) IPADDR_ANY) 1221 | , _noDelay(false) 1222 | , _connect_cb(0) 1223 | , _connect_cb_arg(0) 1224 | {} 1225 | 1226 | AsyncServer::~AsyncServer(){ 1227 | end(); 1228 | } 1229 | 1230 | void AsyncServer::onClient(AcConnectHandler cb, void* arg){ 1231 | _connect_cb = cb; 1232 | _connect_cb_arg = arg; 1233 | } 1234 | 1235 | void AsyncServer::begin() 1236 | { 1237 | if (_socket != -1) return; 1238 | 1239 | if (!_start_asyncsock_task()) { 1240 | log_e("failed to start task"); 1241 | return; 1242 | } 1243 | 1244 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 1245 | if (sockfd < 0) return; 1246 | 1247 | struct sockaddr_in server; 1248 | server.sin_family = AF_INET; 1249 | server.sin_addr.s_addr = (uint32_t) _addr; 1250 | server.sin_port = htons(_port); 1251 | if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) { 1252 | ::close(sockfd); 1253 | log_e("bind error: %d - %s", errno, strerror(errno)); 1254 | return; 1255 | } 1256 | 1257 | static uint8_t backlog = 5; 1258 | if (listen(sockfd , backlog) < 0) { 1259 | ::close(sockfd); 1260 | log_e("listen error: %d - %s", errno, strerror(errno)); 1261 | return; 1262 | } 1263 | fcntl(sockfd, F_SETFL, O_NONBLOCK); 1264 | 1265 | // Updating state visible to asyncTcpSock task 1266 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 1267 | _socket = sockfd; 1268 | xSemaphoreGiveRecursive(_asyncsock_mutex); 1269 | } 1270 | 1271 | void AsyncServer::end() 1272 | { 1273 | if (_socket == -1) return; 1274 | xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY); 1275 | ::close(_socket); 1276 | _socket = -1; 1277 | xSemaphoreGiveRecursive(_asyncsock_mutex); 1278 | } 1279 | 1280 | void AsyncServer::_sockIsReadable(void) 1281 | { 1282 | //Serial.print("AsyncServer::_sockIsReadable: "); Serial.println(_socket); 1283 | 1284 | if (_connect_cb) { 1285 | struct sockaddr_in client; 1286 | size_t cs = sizeof(struct sockaddr_in); 1287 | errno = 0; int accepted_sockfd = ::accept(_socket, (struct sockaddr *)&client, (socklen_t*)&cs); 1288 | //Serial.printf("\t new sockfd=%d errno=%d\r\n", accepted_sockfd, errno); 1289 | if (accepted_sockfd < 0) { 1290 | log_e("accept error: %d - %s", errno, strerror(errno)); 1291 | return; 1292 | } 1293 | 1294 | AsyncClient * c = new AsyncClient(accepted_sockfd); 1295 | if (c) { 1296 | c->setNoDelay(_noDelay); 1297 | _connect_cb(_connect_cb_arg, c); 1298 | } 1299 | } 1300 | } 1301 | 1302 | -------------------------------------------------------------------------------- /src/AsyncTCP.h: -------------------------------------------------------------------------------- 1 | /* 2 | Reimplementation of an asynchronous TCP library for Espressif MCUs, using 3 | BSD sockets. 4 | 5 | Copyright (c) 2020 Alex Villacís Lasso. 6 | 7 | Original AsyncTCP API Copyright (c) 2016 Hristo Gochkov. All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #ifndef ASYNCTCP_H_ 25 | #define ASYNCTCP_H_ 26 | 27 | #include "IPAddress.h" 28 | #include "sdkconfig.h" 29 | #include 30 | #include 31 | #include 32 | #if ASYNC_TCP_SSL_ENABLED 33 | #include "AsyncTCP_TLS_Context.h" 34 | #endif 35 | 36 | extern "C" { 37 | #include "lwip/err.h" 38 | #include "lwip/sockets.h" 39 | } 40 | 41 | //If core is not defined, then we are running in Arduino or PIO 42 | #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE 43 | #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core 44 | #define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event 45 | #endif 46 | #ifndef CONFIG_ASYNC_TCP_STACK 47 | #define CONFIG_ASYNC_TCP_STACK 16384 // 8192 * 2 48 | #endif 49 | #ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY 50 | #define CONFIG_ASYNC_TCP_TASK_PRIORITY 3 51 | #endif 52 | 53 | class AsyncClient; 54 | 55 | #define ASYNC_MAX_ACK_TIME 5000 56 | #define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) 57 | #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. 58 | #define SSL_HANDSHAKE_TIMEOUT 5000 // timeout to complete SSL handshake 59 | 60 | typedef std::function AcConnectHandler; 61 | typedef std::function AcAckHandler; 62 | typedef std::function AcErrorHandler; 63 | typedef std::function AcDataHandler; 64 | //typedef std::function AcPacketHandler; 65 | typedef std::function AcTimeoutHandler; 66 | 67 | class AsyncSocketBase 68 | { 69 | private: 70 | static std::list & _getSocketBaseList(void); 71 | 72 | protected: 73 | int _socket = -1; 74 | bool _selected = false; 75 | bool _isdnsfinished = false; 76 | uint32_t _sock_lastactivity = 0; 77 | 78 | virtual void _sockIsReadable(void) {} // Action to take on readable socket 79 | virtual bool _sockIsWriteable(void) { return false; } // Action to take on writable socket 80 | virtual void _sockPoll(void) {} // Action to take on idle socket activity poll 81 | virtual void _sockDelayedConnect(void) {} // Action to take on DNS-resolve finished 82 | 83 | virtual bool _pendingWrite(void) { return false; } // Test if there is data pending to be written 84 | virtual bool _isServer(void) { return false; } // Will a read from this socket result in one more client? 85 | 86 | public: 87 | AsyncSocketBase(void); 88 | virtual ~AsyncSocketBase(); 89 | 90 | friend void _asynctcpsock_task(void *); 91 | }; 92 | 93 | class AsyncClient : public AsyncSocketBase 94 | { 95 | public: 96 | AsyncClient(int sockfd = -1); 97 | ~AsyncClient(); 98 | 99 | #if ASYNC_TCP_SSL_ENABLED 100 | bool connect(IPAddress ip, uint16_t port, bool secure = false); 101 | bool connect(const char* host, uint16_t port, bool secure = false); 102 | void setRootCa(const char* rootca, const size_t len); 103 | void setClientCert(const char* cli_cert, const size_t len); 104 | void setClientKey(const char* cli_key, const size_t len); 105 | void setPsk(const char* psk_ident, const char* psk); 106 | #else 107 | bool connect(IPAddress ip, uint16_t port); 108 | bool connect(const char* host, uint16_t port); 109 | #endif // ASYNC_TCP_SSL_ENABLED 110 | void close(bool now = false); 111 | 112 | int8_t abort(); 113 | bool free(); 114 | 115 | bool canSend() { return space() > 0; } 116 | size_t space(); 117 | size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending 118 | bool send(); 119 | 120 | //write equals add()+send() 121 | size_t write(const char* data); 122 | size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true 123 | 124 | uint8_t state() { return _conn_state; } 125 | bool connected(); 126 | bool freeable();//disconnected or disconnecting 127 | 128 | uint32_t getAckTimeout(); 129 | void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds 130 | 131 | uint32_t getRxTimeout(); 132 | void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds 133 | void setNoDelay(bool nodelay); 134 | bool getNoDelay(); 135 | 136 | uint32_t getRemoteAddress(); 137 | uint16_t getRemotePort(); 138 | uint32_t getLocalAddress(); 139 | uint16_t getLocalPort(); 140 | 141 | //compatibility 142 | IPAddress remoteIP(); 143 | uint16_t remotePort(); 144 | IPAddress localIP(); 145 | uint16_t localPort(); 146 | 147 | void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect 148 | void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected 149 | void onAck(AcAckHandler cb, void* arg = 0); //ack received 150 | void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error 151 | void onData(AcDataHandler cb, void* arg = 0); //data received 152 | void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout 153 | void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected 154 | 155 | // The following functions are just for API compatibility and do nothing 156 | size_t ack(size_t len) { return len; } 157 | void ackLater() {} 158 | 159 | const char * errorToString(int8_t error); 160 | // const char * stateToString(); 161 | 162 | protected: 163 | bool _sockIsWriteable(void); 164 | void _sockIsReadable(void); 165 | void _sockPoll(void); 166 | void _sockDelayedConnect(void); 167 | bool _pendingWrite(void); 168 | 169 | private: 170 | 171 | AcConnectHandler _connect_cb; 172 | void* _connect_cb_arg; 173 | AcConnectHandler _discard_cb; 174 | void* _discard_cb_arg; 175 | AcAckHandler _sent_cb; 176 | void* _sent_cb_arg; 177 | AcErrorHandler _error_cb; 178 | void* _error_cb_arg; 179 | AcDataHandler _recv_cb; 180 | void* _recv_cb_arg; 181 | AcTimeoutHandler _timeout_cb; 182 | void* _timeout_cb_arg; 183 | AcConnectHandler _poll_cb; 184 | void* _poll_cb_arg; 185 | 186 | uint32_t _rx_last_packet; 187 | uint32_t _rx_since_timeout; 188 | uint32_t _ack_timeout; 189 | 190 | // Used on asynchronous DNS resolving scenario - I do not want to connect() 191 | // from the LWIP thread itself. 192 | struct ip_addr _connect_addr; 193 | uint16_t _connect_port = 0; 194 | //const char * _connect_dnsname = NULL; 195 | 196 | #if ASYNC_TCP_SSL_ENABLED 197 | size_t _root_ca_len; 198 | char* _root_ca; 199 | size_t _cli_cert_len; 200 | char* _cli_cert; 201 | size_t _cli_key_len; 202 | char* _cli_key; 203 | bool _secure; 204 | bool _handshake_done; 205 | const char* _psk_ident; 206 | const char* _psk; 207 | 208 | String _hostname; 209 | AsyncTCP_TLS_Context * _sslctx; 210 | #endif // ASYNC_TCP_SSL_ENABLED 211 | 212 | // The following private struct represents a buffer enqueued with the add() 213 | // method. Each of these buffers are flushed whenever the socket becomes 214 | // writable 215 | typedef struct { 216 | uint8_t * data; // Pointer to data queued for write 217 | uint32_t length; // Length of data queued for write 218 | uint32_t written; // Length of data written to socket so far 219 | uint32_t queued_at;// Timestamp at which this data buffer was queued 220 | uint32_t written_at; // Timestamp at which this data buffer was completely written 221 | int write_errno; // If != 0, errno value while writing this buffer 222 | bool owned; // If true, we malloc'ed the data and should be freed after completely written. 223 | // If false, app owns the memory and should ensure it remains valid until acked 224 | } queued_writebuf; 225 | 226 | // Internal struct used to implement sent buffer notification 227 | typedef struct { 228 | uint32_t length; 229 | uint32_t delay; 230 | } notify_writebuf; 231 | 232 | // Queue of buffers to write to socket 233 | SemaphoreHandle_t _write_mutex; 234 | std::deque _writeQueue; 235 | bool _ack_timeout_signaled = false; 236 | 237 | // Remaining space willing to queue for writing 238 | uint32_t _writeSpaceRemaining; 239 | 240 | // Simulation of connection state 241 | uint8_t _conn_state; 242 | 243 | void _error(int8_t err); 244 | void _close(void); 245 | void _removeAllCallbacks(void); 246 | bool _flushWriteQueue(void); 247 | void _clearWriteQueue(void); 248 | void _collectNotifyWrittenBuffers(std::deque &, int &); 249 | void _notifyWrittenBuffers(std::deque &, int); 250 | 251 | #if ASYNC_TCP_SSL_ENABLED 252 | int _runSSLHandshakeLoop(void); 253 | #endif 254 | 255 | friend void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg); 256 | }; 257 | 258 | #if ASYNC_TCP_SSL_ENABLED 259 | typedef std::function AcSSlFileHandler; 260 | #endif 261 | 262 | class AsyncServer : public AsyncSocketBase 263 | { 264 | public: 265 | AsyncServer(IPAddress addr, uint16_t port); 266 | AsyncServer(uint16_t port); 267 | ~AsyncServer(); 268 | void onClient(AcConnectHandler cb, void* arg); 269 | #if ASYNC_TCP_SSL_ENABLED 270 | // Dummy, so it compiles with ESP Async WebServer library enabled. 271 | void onSslFileRequest(AcSSlFileHandler cb, void* arg) {}; 272 | void beginSecure(const char *cert, const char *private_key_file, const char *password) {}; 273 | #endif 274 | void begin(); 275 | void end(); 276 | 277 | void setNoDelay(bool nodelay) { _noDelay = nodelay; } 278 | bool getNoDelay() { return _noDelay; } 279 | uint8_t status(); 280 | 281 | protected: 282 | uint16_t _port; 283 | IPAddress _addr; 284 | 285 | bool _noDelay; 286 | AcConnectHandler _connect_cb; 287 | void* _connect_cb_arg; 288 | 289 | // Listening socket is readable on incoming connection 290 | void _sockIsReadable(void); 291 | 292 | // Mark this class as a server 293 | bool _isServer(void) { return true; } 294 | }; 295 | 296 | 297 | #endif /* ASYNCTCP_H_ */ 298 | -------------------------------------------------------------------------------- /src/AsyncTCP_SSL.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNCTCP_SSL_H_ 2 | #define ASYNCTCP_SSL_H_ 3 | 4 | #include "AsyncTCP_SSL.hpp" 5 | 6 | #endif /* ASYNCTCP_SSL_H_ */ -------------------------------------------------------------------------------- /src/AsyncTCP_SSL.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ASYNCTCP_SSL_HPP 2 | #define ASYNCTCP_SSL_HPP 3 | 4 | #ifdef ASYNC_TCP_SSL_ENABLED 5 | 6 | #include 7 | 8 | #define AsyncSSLClient AsyncClient 9 | #define AsyncSSLServer AsyncServer 10 | 11 | #define ASYNC_TCP_SSL_VERSION "AsyncTCPSock SSL shim v0.0.1" 12 | 13 | #else 14 | #error Compatibility shim requires ASYNC_TCP_SSL_ENABLED to be defined! 15 | #endif 16 | 17 | #endif -------------------------------------------------------------------------------- /src/AsyncTCP_TLS_Context.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | #include "AsyncTCP_TLS_Context.h" 12 | 13 | #if ASYNC_TCP_SSL_ENABLED 14 | #if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED) 15 | # warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" 16 | #else 17 | 18 | static const char *pers = "esp32-tls"; 19 | 20 | static int _handle_error(int err, const char * function, int line) 21 | { 22 | if(err == -30848){ 23 | return err; 24 | } 25 | #ifdef MBEDTLS_ERROR_C 26 | char error_buf[100]; 27 | mbedtls_strerror(err, error_buf, 100); 28 | log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); 29 | #else 30 | log_e("[%s():%d]: code %d", function, line, err); 31 | #endif 32 | return err; 33 | } 34 | 35 | #define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) 36 | 37 | AsyncTCP_TLS_Context::AsyncTCP_TLS_Context(void) 38 | { 39 | mbedtls_ssl_init(&ssl_ctx); 40 | mbedtls_ssl_config_init(&ssl_conf); 41 | mbedtls_ctr_drbg_init(&drbg_ctx); 42 | _socket = -1; 43 | _have_ca_cert = false; 44 | _have_client_cert = false; 45 | _have_client_key = false; 46 | handshake_timeout = 120000; 47 | } 48 | 49 | int AsyncTCP_TLS_Context::startSSLClientInsecure(int sck, const char * host_or_ip) 50 | { 51 | return _startSSLClient(sck, host_or_ip, 52 | NULL, 0, 53 | NULL, 0, 54 | NULL, 0, 55 | NULL, NULL, 56 | true); 57 | } 58 | 59 | int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, 60 | const char *pskIdent, const char *psKey) 61 | { 62 | return _startSSLClient(sck, host_or_ip, 63 | NULL, 0, 64 | NULL, 0, 65 | NULL, 0, 66 | pskIdent, psKey, 67 | false); 68 | } 69 | 70 | int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, 71 | const char *rootCABuff, 72 | const char *cli_cert, 73 | const char *cli_key) 74 | { 75 | return startSSLClient(sck, host_or_ip, 76 | (const unsigned char *)rootCABuff, (rootCABuff != NULL) ? strlen(rootCABuff) + 1 : 0, 77 | (const unsigned char *)cli_cert, (cli_cert != NULL) ? strlen(cli_cert) + 1 : 0, 78 | (const unsigned char *)cli_key, (cli_key != NULL) ? strlen(cli_key) + 1 : 0); 79 | } 80 | 81 | int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, 82 | const unsigned char *rootCABuff, const size_t rootCABuff_len, 83 | const unsigned char *cli_cert, const size_t cli_cert_len, 84 | const unsigned char *cli_key, const size_t cli_key_len) 85 | { 86 | return _startSSLClient(sck, host_or_ip, 87 | rootCABuff, rootCABuff_len, 88 | cli_cert, cli_cert_len, 89 | cli_key, cli_key_len, 90 | NULL, NULL, 91 | false); 92 | } 93 | 94 | int AsyncTCP_TLS_Context::_startSSLClient(int sck, const char * host_or_ip, 95 | const unsigned char *rootCABuff, const size_t rootCABuff_len, 96 | const unsigned char *cli_cert, const size_t cli_cert_len, 97 | const unsigned char *cli_key, const size_t cli_key_len, 98 | const char *pskIdent, const char *psKey, 99 | bool insecure) 100 | { 101 | int ret; 102 | int enable = 1; 103 | 104 | // The insecure flag will skip server certificate validation. Otherwise some 105 | // certificate is required. 106 | if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { 107 | return -1; 108 | } 109 | 110 | #define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} 111 | // ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO"); 112 | // ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO"); 113 | 114 | ROE(lwip_setsockopt(sck, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); 115 | ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); 116 | 117 | log_v("Seeding the random number generator"); 118 | mbedtls_entropy_init(&entropy_ctx); 119 | 120 | ret = mbedtls_ctr_drbg_seed(&drbg_ctx, mbedtls_entropy_func, 121 | &entropy_ctx, (const unsigned char *) pers, strlen(pers)); 122 | if (ret < 0) { 123 | return handle_error(ret); 124 | } 125 | 126 | log_v("Setting up the SSL/TLS structure..."); 127 | 128 | if ((ret = mbedtls_ssl_config_defaults(&ssl_conf, 129 | MBEDTLS_SSL_IS_CLIENT, 130 | MBEDTLS_SSL_TRANSPORT_STREAM, 131 | MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { 132 | return handle_error(ret); 133 | } 134 | 135 | if (insecure) { 136 | mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE); 137 | log_i("WARNING: Skipping SSL Verification. INSECURE!"); 138 | } else if (rootCABuff != NULL) { 139 | log_v("Loading CA cert"); 140 | mbedtls_x509_crt_init(&ca_cert); 141 | mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); 142 | ret = mbedtls_x509_crt_parse(&ca_cert, rootCABuff, rootCABuff_len); 143 | _have_ca_cert = true; 144 | mbedtls_ssl_conf_ca_chain(&ssl_conf, &ca_cert, NULL); 145 | if (ret < 0) { 146 | // 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. 147 | _deleteHandshakeCerts(); 148 | return handle_error(ret); 149 | } 150 | } else if (pskIdent != NULL && psKey != NULL) { 151 | log_v("Setting up PSK"); 152 | // convert PSK from hex to binary 153 | if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) { 154 | log_e("pre-shared key not valid hex or too long"); 155 | return -1; 156 | } 157 | unsigned char psk[MBEDTLS_PSK_MAX_LEN]; 158 | size_t psk_len = strlen(psKey)/2; 159 | for (int j=0; j= '0' && c <= '9') c -= '0'; 162 | else if (c >= 'A' && c <= 'F') c -= 'A' - 10; 163 | else if (c >= 'a' && c <= 'f') c -= 'a' - 10; 164 | else return -1; 165 | psk[j/2] = c<<4; 166 | c = psKey[j+1]; 167 | if (c >= '0' && c <= '9') c -= '0'; 168 | else if (c >= 'A' && c <= 'F') c -= 'A' - 10; 169 | else if (c >= 'a' && c <= 'f') c -= 'a' - 10; 170 | else return -1; 171 | psk[j/2] |= c; 172 | } 173 | // set mbedtls config 174 | ret = mbedtls_ssl_conf_psk(&ssl_conf, psk, psk_len, 175 | (const unsigned char *)pskIdent, strlen(pskIdent)); 176 | if (ret != 0) { 177 | log_e("mbedtls_ssl_conf_psk returned %d", ret); 178 | return handle_error(ret); 179 | } 180 | } else { 181 | return -1; 182 | } 183 | 184 | if (!insecure && cli_cert != NULL && cli_key != NULL) { 185 | mbedtls_x509_crt_init(&client_cert); 186 | mbedtls_pk_init(&client_key); 187 | 188 | log_v("Loading CRT cert"); 189 | 190 | ret = mbedtls_x509_crt_parse(&client_cert, cli_cert, cli_cert_len); 191 | _have_client_cert = true; 192 | if (ret < 0) { 193 | // 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. 194 | _deleteHandshakeCerts(); 195 | return handle_error(ret); 196 | } 197 | 198 | log_v("Loading private key"); 199 | #if MBEDTLS_VERSION_NUMBER < 0x03000000 200 | ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0); 201 | #else 202 | ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0, mbedtls_ctr_drbg_random, &drbg_ctx); 203 | #endif 204 | _have_client_key = true; 205 | 206 | if (ret != 0) { 207 | _deleteHandshakeCerts(); 208 | return handle_error(ret); 209 | } 210 | 211 | mbedtls_ssl_conf_own_cert(&ssl_conf, &client_cert, &client_key); 212 | } 213 | 214 | log_v("Setting hostname for TLS session..."); 215 | 216 | // Hostname set here should match CN in server certificate 217 | if ((ret = mbedtls_ssl_set_hostname(&ssl_ctx, host_or_ip)) != 0){ 218 | _deleteHandshakeCerts(); 219 | return handle_error(ret); 220 | } 221 | 222 | mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &drbg_ctx); 223 | 224 | if ((ret = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf)) != 0) { 225 | _deleteHandshakeCerts(); 226 | return handle_error(ret); 227 | } 228 | 229 | _socket = sck; 230 | mbedtls_ssl_set_bio(&ssl_ctx, &_socket, mbedtls_net_send, mbedtls_net_recv, NULL ); 231 | handshake_start_time = 0; 232 | 233 | return 0; 234 | } 235 | 236 | int AsyncTCP_TLS_Context::runSSLHandshake(void) 237 | { 238 | int ret, flags; 239 | if (_socket < 0) return -1; 240 | 241 | if (handshake_start_time == 0) handshake_start_time = millis(); 242 | ret = mbedtls_ssl_handshake(&ssl_ctx); 243 | if (ret != 0) { 244 | // Something happened before SSL handshake could be completed 245 | 246 | // Negotiation error, other than socket not readable/writable when required 247 | if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { 248 | return handle_error(ret); 249 | } 250 | 251 | // Handshake is taking too long 252 | if ((millis()-handshake_start_time) > handshake_timeout) 253 | return -1; 254 | 255 | // Either MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE 256 | return ret; 257 | } 258 | 259 | // Handshake completed, validate remote side if required... 260 | 261 | if (_have_client_cert && _have_client_key) { 262 | log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_ctx)); 263 | if ((ret = mbedtls_ssl_get_record_expansion(&ssl_ctx)) >= 0) { 264 | log_d("Record expansion is %d", ret); 265 | } else { 266 | log_w("Record expansion is unknown (compression)"); 267 | } 268 | } 269 | 270 | log_v("Verifying peer X.509 certificate..."); 271 | 272 | if ((flags = mbedtls_ssl_get_verify_result(&ssl_ctx)) != 0) { 273 | char buf[512]; 274 | memset(buf, 0, sizeof(buf)); 275 | mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); 276 | log_e("Failed to verify peer certificate! verification info: %s", buf); 277 | _deleteHandshakeCerts(); 278 | return handle_error(ret); 279 | } else { 280 | log_v("Certificate verified."); 281 | } 282 | 283 | _deleteHandshakeCerts(); 284 | 285 | log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); 286 | 287 | return 0; 288 | } 289 | 290 | int AsyncTCP_TLS_Context::write(const uint8_t *data, size_t len) 291 | { 292 | if (_socket < 0) return -1; 293 | 294 | log_v("Writing packet, %d bytes unencrypted...", len); 295 | int ret = mbedtls_ssl_write(&ssl_ctx, data, len); 296 | if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { 297 | log_v("Handling error %d", ret); //for low level debug 298 | return handle_error(ret); 299 | } 300 | return ret; 301 | } 302 | 303 | int AsyncTCP_TLS_Context::read(uint8_t * data, size_t len) 304 | { 305 | int ret = mbedtls_ssl_read(&ssl_ctx, data, len); 306 | if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { 307 | log_v("Handling error %d", ret); //for low level debug 308 | return handle_error(ret); 309 | } 310 | if (ret > 0) log_v("Read packet, %d out of %d requested bytes...", ret, len); 311 | return ret; 312 | } 313 | 314 | void AsyncTCP_TLS_Context::_deleteHandshakeCerts(void) 315 | { 316 | if (_have_ca_cert) { 317 | log_v("Cleaning CA certificate."); 318 | mbedtls_x509_crt_free(&ca_cert); 319 | _have_ca_cert = false; 320 | } 321 | if (_have_client_cert) { 322 | log_v("Cleaning client certificate."); 323 | mbedtls_x509_crt_free(&client_cert); 324 | _have_client_cert = false; 325 | } 326 | if (_have_client_key) { 327 | log_v("Cleaning client certificate key."); 328 | mbedtls_pk_free(&client_key); 329 | _have_client_key = false; 330 | } 331 | } 332 | 333 | AsyncTCP_TLS_Context::~AsyncTCP_TLS_Context() 334 | { 335 | _deleteHandshakeCerts(); 336 | 337 | log_v("Cleaning SSL connection."); 338 | 339 | mbedtls_ssl_free(&ssl_ctx); 340 | mbedtls_ssl_config_free(&ssl_conf); 341 | mbedtls_ctr_drbg_free(&drbg_ctx); 342 | mbedtls_entropy_free(&entropy_ctx); // <-- Is this OK to free if mbedtls_entropy_init() has not been called on it? 343 | } 344 | 345 | #endif 346 | #endif // ASYNC_TCP_SSL_ENABLED -------------------------------------------------------------------------------- /src/AsyncTCP_TLS_Context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if ASYNC_TCP_SSL_ENABLED 4 | 5 | #include "mbedtls/version.h" 6 | #include "mbedtls/platform.h" 7 | #if MBEDTLS_VERSION_NUMBER < 0x03000000 8 | #include "mbedtls/net.h" 9 | #else 10 | #include "mbedtls/net_sockets.h" 11 | #endif 12 | #include "mbedtls/debug.h" 13 | #include "mbedtls/ssl.h" 14 | #include "mbedtls/entropy.h" 15 | #include "mbedtls/ctr_drbg.h" 16 | #include "mbedtls/error.h" 17 | 18 | #define ASYNCTCP_TLS_CAN_RETRY(r) (((r) == MBEDTLS_ERR_SSL_WANT_READ) || ((r) == MBEDTLS_ERR_SSL_WANT_WRITE)) 19 | #define ASYNCTCP_TLS_EOF(r) (((r) == MBEDTLS_ERR_SSL_CONN_EOF) || ((r) == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) 20 | 21 | class AsyncTCP_TLS_Context 22 | { 23 | private: 24 | // These fields must persist for the life of the encrypted connection, destroyed on 25 | // object destructor. 26 | mbedtls_ssl_context ssl_ctx; 27 | mbedtls_ssl_config ssl_conf; 28 | mbedtls_ctr_drbg_context drbg_ctx; 29 | mbedtls_entropy_context entropy_ctx; 30 | 31 | // These allocate memory during handshake but must be freed on either success or failure 32 | mbedtls_x509_crt ca_cert; 33 | mbedtls_x509_crt client_cert; 34 | mbedtls_pk_context client_key; 35 | bool _have_ca_cert; 36 | bool _have_client_cert; 37 | bool _have_client_key; 38 | 39 | unsigned long handshake_timeout; 40 | unsigned long handshake_start_time; 41 | 42 | int _socket; 43 | 44 | int _startSSLClient(int sck, const char * host_or_ip, 45 | const unsigned char *rootCABuff, const size_t rootCABuff_len, 46 | const unsigned char *cli_cert, const size_t cli_cert_len, 47 | const unsigned char *cli_key, const size_t cli_key_len, 48 | const char *pskIdent, const char *psKey, 49 | bool insecure); 50 | 51 | // Delete certificates used in handshake 52 | void _deleteHandshakeCerts(void); 53 | public: 54 | AsyncTCP_TLS_Context(void); 55 | virtual ~AsyncTCP_TLS_Context(); 56 | 57 | int startSSLClientInsecure(int sck, const char * host_or_ip); 58 | 59 | int startSSLClient(int sck, const char * host_or_ip, 60 | const char *pskIdent, const char *psKey); 61 | 62 | int startSSLClient(int sck, const char * host_or_ip, 63 | const char *rootCABuff, 64 | const char *cli_cert, 65 | const char *cli_key); 66 | 67 | int startSSLClient(int sck, const char * host_or_ip, 68 | const unsigned char *rootCABuff, const size_t rootCABuff_len, 69 | const unsigned char *cli_cert, const size_t cli_cert_len, 70 | const unsigned char *cli_key, const size_t cli_key_len); 71 | 72 | int runSSLHandshake(void); 73 | 74 | int write(const uint8_t *data, size_t len); 75 | 76 | int read(uint8_t * data, size_t len); 77 | }; 78 | 79 | #endif // ASYNC_TCP_SSL_ENABLED --------------------------------------------------------------------------------