├── 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
--------------------------------------------------------------------------------