Skip to content

Commit e9eece5

Browse files
committed
Rework WiFiClientSecure into SSLClient which works with any Client-derived class. Tested with ESP32 WiFiClient and TinyGsmClient.
1 parent 44fbde0 commit e9eece5

File tree

12 files changed

+1574
-0
lines changed

12 files changed

+1574
-0
lines changed

Diff for: libraries/SSLClient/README.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
SSLClient
2+
=========
3+
4+
The SSLClient class implements support for secure connections using TLS (SSL).
5+
It inherits from Client and thus implements a superset of that class' interface.
6+
There are three ways to establish a secure connection using the SSLClient class:
7+
using a root certificate authority (CA) cert, using a root CA cert plus a client cert and key,
8+
and using a pre-shared key (PSK).
9+
10+
Using a root certificate authority cert
11+
---------------------------------------
12+
This method authenticates the server and negotiates an encrypted connection.
13+
It is the same functionality as implemented in your web browser when you connect to HTTPS sites.
14+
15+
If you are accessing your own server:
16+
- Generate a root certificate for your own certificate authority
17+
- Generate a cert & private key using your root certificate ("self-signed cert") for your server
18+
19+
If you are accessing a public server:
20+
- Obtain the cert of the public CA that signed that server's cert
21+
Then:
22+
- In SSLClient use setCACert (or the appropriate connect method) to set the root cert of your
23+
CA or of the public CA
24+
- When SSLClient connects to the target server it uses the CA cert to verify the certificate
25+
presented by the server, and then negotiates encryption for the connection
26+
27+
Please see the SSLClient example.
28+
29+
Using a root CA cert and client cert/keys
30+
-----------------------------------------
31+
This method authenticates the server and additionally also authenticates
32+
the client to the server, then negotiates an encrypted connection.
33+
34+
- Follow steps above
35+
- Using your root CA generate cert/key for your client
36+
- Register the keys with the server you will be accessing so the server can authenticate your client
37+
- In SSLClient use setCACert (or the appropriate connect method) to set the root cert of your
38+
CA or of the public CA, this is used to authenticate the server
39+
- In SSLClient use setCertificate, and setPrivateKey (or the appropriate connect method) to
40+
set your client's cert & key, this will be used to authenticate your client to the server
41+
- When SSLClient connects to the target server it uses the CA cert to verify the certificate
42+
presented by the server, it will use the cert/key to authenticate your client to the server, and
43+
it will then negotiate encryption for the connection
44+
45+
Using Pre-Shared Keys (PSK)
46+
---------------------------
47+
48+
TLS supports authentication and encryption using a pre-shared key (i.e. a key that both client and
49+
server know) as an alternative to the public key cryptography commonly used on the web for HTTPS.
50+
PSK is starting to be used for MQTT, e.g. in mosquitto, to simplify the set-up and avoid having to
51+
go through the whole CA, cert, and private key process.
52+
53+
A pre-shared key is a binary string of up to 32 bytes and is commonly represented in hex form. In
54+
addition to the key, clients can also present an id and typically the server allows a different key
55+
to be associated with each client id. In effect this is very similar to username and password pairs,
56+
except that unlike a password the key is not directly transmitted to the server, thus a connection to a
57+
malicious server does not divulge the password. Plus the server is also authenticated to the client.
58+
59+
To use PSK:
60+
- Generate a random hex string (generating an MD5 or SHA for some file is one way to do this)
61+
- Come up with a string id for your client and configure your server to accept the id/key pair
62+
- In SSLClient use setPreSharedKey (or the appropriate connect method) to
63+
set the id/key combo
64+
- When SSLClient connects to the target server it uses the id/key combo to authenticate the
65+
server (it must prove that it has the key too), authenticate the client and then negotiate
66+
encryption for the connection
67+
68+
Please see the SSLClientPSK example.
69+
70+
Specifying the ALPN Protocol
71+
----------------------------
72+
73+
Application-Layer Protocol Negotiation (ALPN) is a Transport Layer Security (TLS) extension that allows
74+
the application layer to negotiate which protocol should be performed over a secure connection in a manner
75+
that avoids additional round trips and which is independent of the application-layer protocols.
76+
77+
For example, this is used with AWS IoT Custom Authorizers where an MQTT client must set the ALPN protocol to ```mqtt```:
78+
79+
```
80+
const char *aws_protos[] = {"mqtt", NULL};
81+
...
82+
sslClient.setAlpnProtocols(aws_protos);
83+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include <WiFi.h>
2+
#include <SSLClient.h>
3+
4+
const char* ssid = "your-ssid"; // your network SSID (name of wifi network)
5+
const char* password = "your-password"; // your network password
6+
7+
const char* server = "www.howsmyssl.com"; // Server URL
8+
9+
WiFiClient wificlient;
10+
SSLClient client(wificlient);
11+
12+
void setup() {
13+
//Initialize serial and wait for port to open:
14+
Serial.begin(115200);
15+
delay(100);
16+
17+
Serial.print("Attempting to connect to SSID: ");
18+
Serial.println(ssid);
19+
WiFi.begin(ssid, password);
20+
21+
// attempt to connect to Wifi network:
22+
while (WiFi.status() != WL_CONNECTED) {
23+
Serial.print(".");
24+
// wait 1 second for re-trying
25+
delay(1000);
26+
}
27+
28+
Serial.print("Connected to ");
29+
Serial.println(ssid);
30+
31+
Serial.println("\nStarting connection to server...");
32+
client.setInsecure();//skip verification
33+
if (!client.connect(server, 443))
34+
Serial.println("Connection failed!");
35+
else {
36+
Serial.println("Connected to server!");
37+
// Make a HTTP request:
38+
client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0");
39+
client.println("Host: www.howsmyssl.com");
40+
client.println("Connection: close");
41+
client.println();
42+
43+
while (client.connected()) {
44+
String line = client.readStringUntil('\n');
45+
if (line == "\r") {
46+
Serial.println("headers received");
47+
break;
48+
}
49+
}
50+
// if there are incoming bytes available
51+
// from the server, read them and print them:
52+
while (client.available()) {
53+
char c = client.read();
54+
Serial.write(c);
55+
}
56+
57+
client.stop();
58+
}
59+
}
60+
61+
void loop() {
62+
// do nothing
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Wifi secure connection example for ESP32 using a pre-shared key (PSK)
3+
This is useful with MQTT servers instead of using a self-signed cert, tested with mosquitto.
4+
Running on TLS 1.2 using mbedTLS
5+
6+
To test run a test server using: openssl s_server -accept 8443 -psk 1a2b3c4d -nocert
7+
It will show the http request made, but there's no easy way to send a reply back...
8+
9+
2017 - Evandro Copercini - Apache 2.0 License.
10+
2018 - Adapted for PSK by Thorsten von Eicken
11+
*/
12+
13+
#include <WiFi.h>
14+
#include <SSLClient.h>
15+
16+
#if 0
17+
const char* ssid = "your-ssid"; // your network SSID (name of wifi network)
18+
const char* password = "your-password"; // your network password
19+
#else
20+
const char* ssid = "test"; // your network SSID (name of wifi network)
21+
const char* password = "securetest"; // your network password
22+
#endif
23+
24+
//const char* server = "server.local"; // Server hostname
25+
const IPAddress server = IPAddress(192, 168, 0, 14); // Server IP address
26+
const int port = 8443; // server's port (8883 for MQTT)
27+
28+
const char* pskIdent = "Client_identity"; // PSK identity (sometimes called key hint)
29+
const char* psKey = "1a2b3c4d"; // PSK Key (must be hex string without 0x)
30+
31+
WiFiClient wificlient;
32+
SSLClient client(wificlient);
33+
34+
void setup() {
35+
//Initialize serial and wait for port to open:
36+
Serial.begin(115200);
37+
delay(100);
38+
39+
Serial.print("Attempting to connect to SSID: ");
40+
Serial.println(ssid);
41+
WiFi.begin(ssid, password);
42+
43+
// attempt to connect to Wifi network:
44+
while (WiFi.status() != WL_CONNECTED) {
45+
Serial.print(".");
46+
// wait 1 second for re-trying
47+
delay(1000);
48+
}
49+
50+
Serial.print("Connected to ");
51+
Serial.println(ssid);
52+
53+
client.setPreSharedKey(pskIdent, psKey);
54+
55+
Serial.println("\nStarting connection to server...");
56+
if (!client.connect(server, port))
57+
Serial.println("Connection failed!");
58+
else {
59+
Serial.println("Connected to server!");
60+
// Make a HTTP request:
61+
client.println("GET /a/check HTTP/1.0");
62+
client.print("Host: ");
63+
client.println(server);
64+
client.println("Connection: close");
65+
client.println();
66+
67+
while (client.connected()) {
68+
String line = client.readStringUntil('\n');
69+
if (line == "\r") {
70+
Serial.println("headers received");
71+
break;
72+
}
73+
}
74+
// if there are incoming bytes available
75+
// from the server, read them and print them:
76+
while (client.available()) {
77+
char c = client.read();
78+
Serial.write(c);
79+
}
80+
81+
client.stop();
82+
}
83+
}
84+
85+
void loop() {
86+
// do nothing
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Wifi secure connection example for ESP32
3+
Running on TLS 1.2 using mbedTLS
4+
Suporting the following chipersuites:
5+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_CCM","TLS_DHE_RSA_WITH_AES_256_CCM","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8","TLS_DHE_RSA_WITH_AES_256_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CCM","TLS_DHE_RSA_WITH_AES_128_CCM","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8","TLS_DHE_RSA_WITH_AES_128_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_GCM_SHA384","TLS_DHE_PSK_WITH_AES_256_CCM","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384","TLS_DHE_PSK_WITH_AES_256_CBC_SHA384","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_DHE_WITH_AES_256_CCM_8","TLS_DHE_PSK_WITH_AES_128_GCM_SHA256","TLS_DHE_PSK_WITH_AES_128_CCM","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256","TLS_DHE_PSK_WITH_AES_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_DHE_WITH_AES_128_CCM_8","TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_CCM","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_CCM_8","TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_128_CCM","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_128_CCM_8","TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_RSA_PSK_WITH_AES_256_GCM_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_128_GCM_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA","TLS_PSK_WITH_AES_256_GCM_SHA384","TLS_PSK_WITH_AES_256_CCM","TLS_PSK_WITH_AES_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CBC_SHA","TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CCM_8","TLS_PSK_WITH_AES_128_GCM_SHA256","TLS_PSK_WITH_AES_128_CCM","TLS_PSK_WITH_AES_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CBC_SHA","TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CCM_8","TLS_PSK_WITH_3DES_EDE_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"]
6+
2017 - Evandro Copercini - Apache 2.0 License.
7+
*/
8+
9+
#include <WiFi.h>
10+
#include <SSLClient.h>
11+
12+
const char* ssid = "your-ssid"; // your network SSID (name of wifi network)
13+
const char* password = "your-password"; // your network password
14+
15+
const char* server = "www.howsmyssl.com"; // Server URL
16+
17+
// www.howsmyssl.com root certificate authority, to verify the server
18+
// change it to your server root CA
19+
// SHA1 fingerprint is broken now!
20+
21+
const char* test_root_ca= \
22+
"-----BEGIN CERTIFICATE-----\n" \
23+
"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" \
24+
"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
25+
"DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" \
26+
"PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" \
27+
"Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" \
28+
"AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" \
29+
"rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" \
30+
"OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" \
31+
"xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" \
32+
"7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" \
33+
"aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \
34+
"HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" \
35+
"SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" \
36+
"ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" \
37+
"AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" \
38+
"R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" \
39+
"JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" \
40+
"Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" \
41+
"-----END CERTIFICATE-----\n";
42+
43+
// You can use x.509 client certificates if you want
44+
//const char* test_client_key = ""; //to verify the client
45+
//const char* test_client_cert = ""; //to verify the client
46+
47+
48+
WiFiClient wificlient;
49+
SSLClient client(wificlient);
50+
51+
void setup() {
52+
//Initialize serial and wait for port to open:
53+
Serial.begin(115200);
54+
delay(100);
55+
56+
Serial.print("Attempting to connect to SSID: ");
57+
Serial.println(ssid);
58+
WiFi.begin(ssid, password);
59+
60+
// attempt to connect to Wifi network:
61+
while (WiFi.status() != WL_CONNECTED) {
62+
Serial.print(".");
63+
// wait 1 second for re-trying
64+
delay(1000);
65+
}
66+
67+
Serial.print("Connected to ");
68+
Serial.println(ssid);
69+
70+
client.setCACert(test_root_ca);
71+
//client.setCertificate(test_client_cert); // for client verification
72+
//client.setPrivateKey(test_client_key); // for client verification
73+
74+
Serial.println("\nStarting connection to server...");
75+
if (!client.connect(server, 443))
76+
Serial.println("Connection failed!");
77+
else {
78+
Serial.println("Connected to server!");
79+
// Make a HTTP request:
80+
client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0");
81+
client.println("Host: www.howsmyssl.com");
82+
client.println("Connection: close");
83+
client.println();
84+
85+
while (client.connected()) {
86+
String line = client.readStringUntil('\n');
87+
if (line == "\r") {
88+
Serial.println("headers received");
89+
break;
90+
}
91+
}
92+
// if there are incoming bytes available
93+
// from the server, read them and print them:
94+
while (client.available()) {
95+
char c = client.read();
96+
Serial.write(c);
97+
}
98+
99+
client.stop();
100+
}
101+
}
102+
103+
void loop() {
104+
// do nothing
105+
}

0 commit comments

Comments
 (0)