Skip to content

Commit 0cb4f90

Browse files
committed
Add initial WebSocketClient
1 parent 32895db commit 0cb4f90

File tree

3 files changed

+386
-0
lines changed

3 files changed

+386
-0
lines changed

ArduinoHttpClient.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
#define ArduinoHttpClient_h
77

88
#include "HttpClient.h"
9+
#include "WebSocketClient.h"
910

1011
#endif

WebSocketClient.cpp

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// (c) Copyright Arduino. 2016
2+
// Released under Apache License, version 2.0
3+
4+
#include "b64.h"
5+
6+
#include "WebSocketClient.h"
7+
8+
WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
9+
: HttpClient(aClient, aServerName, aServerPort)
10+
{
11+
}
12+
13+
WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort)
14+
: HttpClient(aClient, aServerName, aServerPort)
15+
{
16+
}
17+
18+
WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
19+
: HttpClient(aClient, aServerAddress, aServerPort)
20+
{
21+
}
22+
23+
int WebSocketClient::begin(const char* aPath)
24+
{
25+
// start the GET request
26+
beginRequest();
27+
connectionKeepAlive();
28+
int status = get(aPath);
29+
30+
if (status == 0)
31+
{
32+
uint8_t randomKey[13];
33+
char base64RandomKey[21];
34+
35+
// create a random key for the connection upgrade
36+
for (int i = 0; i < (int)sizeof(randomKey); i++)
37+
{
38+
randomKey[i] = random(0x01, 0xff);
39+
}
40+
memset(base64RandomKey, 0x00, sizeof(base64RandomKey));
41+
b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey));
42+
43+
// start the connection upgrade sequence
44+
sendHeader("Upgrade", "websocket");
45+
sendHeader("Connection", "Upgrade");
46+
sendHeader("Sec-WebSocket-Key", base64RandomKey);
47+
sendHeader("Sec-WebSocket-Version", "13");
48+
endRequest();
49+
50+
status = responseStatusCode();
51+
52+
if (status > 0)
53+
{
54+
skipResponseHeaders();
55+
}
56+
}
57+
58+
// status code of 101 means success
59+
return (status == 101) ? 0 : status;
60+
}
61+
62+
int WebSocketClient::begin(const String& aPath)
63+
{
64+
return begin(aPath.c_str());
65+
}
66+
67+
int WebSocketClient::beginMessage(int aType)
68+
{
69+
iTxMessageType = (aType & 0xf);
70+
iTxSize = 0;
71+
72+
return 0;
73+
}
74+
75+
int WebSocketClient::endMessage()
76+
{
77+
// send FIN + the message type (opcode)
78+
HttpClient::write(0x80 | iTxMessageType);
79+
80+
// the message is masked (0x80)
81+
// send the length
82+
if (iTxSize < 126)
83+
{
84+
HttpClient::write(0x80 | (uint8_t)iTxSize);
85+
}
86+
else if (iTxSize < 0xffff)
87+
{
88+
HttpClient::write(0x80 | 126);
89+
HttpClient::write((iTxSize >> 8) & 0xff);
90+
HttpClient::write((iTxSize >> 0) & 0xff);
91+
}
92+
else
93+
{
94+
HttpClient::write(0x80 | 127);
95+
HttpClient::write((iTxSize >> 56) & 0xff);
96+
HttpClient::write((iTxSize >> 48) & 0xff);
97+
HttpClient::write((iTxSize >> 40) & 0xff);
98+
HttpClient::write((iTxSize >> 32) & 0xff);
99+
HttpClient::write((iTxSize >> 24) & 0xff);
100+
HttpClient::write((iTxSize >> 16) & 0xff);
101+
HttpClient::write((iTxSize >> 8) & 0xff);
102+
HttpClient::write((iTxSize >> 0) & 0xff);
103+
}
104+
105+
uint8_t maskKey[4];
106+
107+
// create a random mask for the data and send
108+
for (int i = 0; i < (int)sizeof(maskKey); i++)
109+
{
110+
maskKey[i] = random(0xff);
111+
}
112+
HttpClient::write(maskKey, sizeof(maskKey));
113+
114+
// mask the data and send
115+
for (int i = 0; i < (int)iTxSize; i++) {
116+
iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)];
117+
}
118+
119+
int txSize = iTxSize;
120+
121+
iTxSize = 0;
122+
123+
return HttpClient::write(iTxBuffer, txSize);
124+
}
125+
126+
size_t WebSocketClient::write(uint8_t aByte)
127+
{
128+
return write(&aByte, sizeof(aByte));
129+
}
130+
131+
size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize)
132+
{
133+
if (iState < eReadingBody)
134+
{
135+
// have not upgraded the connection yet
136+
return HttpClient::write(aBuffer, aSize);
137+
}
138+
139+
// check if the write size, fits in the buffer
140+
if ((iTxSize + aSize) > sizeof(iTxBuffer))
141+
{
142+
aSize = sizeof(iTxSize) - iTxSize;
143+
}
144+
145+
// copy data into the buffer
146+
memcpy(iTxBuffer + iTxSize, aBuffer, aSize);
147+
148+
iTxSize += aSize;
149+
150+
return aSize;
151+
}
152+
153+
int WebSocketClient::parseMessage()
154+
{
155+
// make sure 2 bytes (opcode + length)
156+
// are available
157+
if (HttpClient::available() < 2)
158+
{
159+
return 0;
160+
}
161+
162+
// read open code and length
163+
uint8_t opcode = HttpClient::read();
164+
int length = HttpClient::read();
165+
166+
if ((opcode & 0x0f) == 0)
167+
{
168+
// continuation, use previous opcode and update flags
169+
iRxOpCode |= opcode;
170+
}
171+
else
172+
{
173+
iRxOpCode = opcode;
174+
}
175+
176+
iRxMasked = (length & 0x80);
177+
length &= 0x7f;
178+
179+
// read the RX size
180+
if (length < 126)
181+
{
182+
iRxSize = length;
183+
}
184+
else if (length == 126)
185+
{
186+
iRxSize = (HttpClient::read() << 8) | HttpClient::read();
187+
}
188+
else
189+
{
190+
iRxSize = ((uint64_t)HttpClient::read() << 56) |
191+
((uint64_t)HttpClient::read() << 48) |
192+
((uint64_t)HttpClient::read() << 40) |
193+
((uint64_t)HttpClient::read() << 32) |
194+
((uint64_t)HttpClient::read() << 24) |
195+
((uint64_t)HttpClient::read() << 16) |
196+
((uint64_t)HttpClient::read() << 8) |
197+
(uint64_t)HttpClient::read();
198+
}
199+
200+
// read in the mask, if present
201+
if (iRxMasked)
202+
{
203+
for (int i = 0; i < (int)sizeof(iRxMaskKey); i++)
204+
{
205+
iRxMaskKey[i] = HttpClient::read();
206+
}
207+
}
208+
209+
iRxMaskIndex = 0;
210+
211+
return iRxSize;
212+
}
213+
214+
int WebSocketClient::messageType()
215+
{
216+
return (iRxOpCode & 0x0f);
217+
}
218+
219+
bool WebSocketClient::isFinal()
220+
{
221+
return ((iRxOpCode & 0x80) != 0);
222+
}
223+
224+
String WebSocketClient::readString()
225+
{
226+
int avail = available();
227+
String s;
228+
229+
if (avail > 0)
230+
{
231+
s.reserve(avail);
232+
233+
for (int i = 0; i < avail; i++)
234+
{
235+
s += (char)read();
236+
}
237+
}
238+
239+
return s;
240+
}
241+
242+
int WebSocketClient::available()
243+
{
244+
if (iState < eReadingBody)
245+
{
246+
return HttpClient::available();
247+
}
248+
249+
return iRxSize;
250+
}
251+
252+
int WebSocketClient::read()
253+
{
254+
byte b;
255+
256+
if (read(&b, sizeof(b)))
257+
{
258+
return b;
259+
}
260+
261+
return -1;
262+
}
263+
264+
int WebSocketClient::read(uint8_t *aBuffer, size_t aSize)
265+
{
266+
int readCount = HttpClient::read(aBuffer, aSize);
267+
268+
if (readCount > 0)
269+
{
270+
iRxSize -= readCount;
271+
272+
// unmask the RX data if needed
273+
if (iRxMasked)
274+
{
275+
for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) {
276+
aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
277+
}
278+
}
279+
}
280+
281+
return readCount;
282+
}
283+
284+
int WebSocketClient::peek()
285+
{
286+
int p = HttpClient::peek();
287+
288+
if (p != -1 && iRxMasked)
289+
{
290+
// unmask the RX data if needed
291+
p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
292+
}
293+
294+
return p;
295+
}

WebSocketClient.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// (c) Copyright Arduino. 2016
2+
// Released under Apache License, version 2.0
3+
4+
#ifndef WebSocketClient_h
5+
#define WebSocketClient_h
6+
7+
#include <Arduino.h>
8+
9+
#include "HttpClient.h"
10+
11+
static const int TYPE_CONTINUATION = 0x0;
12+
static const int TYPE_TEXT = 0x1;
13+
static const int TYPE_BINARY = 0x2;
14+
static const int TYPE_CONNECTION_CLOSE = 0x8;
15+
static const int TYPE_PING = 0x9;
16+
static const int TYPE_PONG = 0xa;
17+
18+
class WebSocketClient : public HttpClient
19+
{
20+
public:
21+
WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
22+
WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
23+
WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort);
24+
25+
/** Start the Web Socket connection to the specified path
26+
@param aURLPath Path to use in request (optional, "/" is used by default)
27+
@return 0 if successful, else error
28+
*/
29+
int begin(const char* aPath = "/");
30+
int begin(const String& aPath);
31+
32+
/** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY)
33+
Use the write or Stream API's to set message content, followed by endMessage
34+
to complete the message.
35+
@param aURLPath Path to use in request
36+
@return 0 if successful, else error
37+
*/
38+
int beginMessage(int aType);
39+
40+
/** Completes sending of a message started by beginMessage
41+
@return 0 if successful, else error
42+
*/
43+
int endMessage();
44+
45+
/** Try to parse an incoming messages
46+
@return 0 if no message available, else size of parsed message
47+
*/
48+
int parseMessage();
49+
50+
/** Returns type of current parsed message
51+
@return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY)
52+
*/
53+
int messageType();
54+
55+
/** Returns if the current message is the final chunk of a split
56+
message
57+
@return true for final message, false otherwise
58+
*/
59+
bool isFinal();
60+
61+
/** Read the current messages as a string
62+
@return current message as a string
63+
*/
64+
String readString();
65+
66+
// Inherited from Print
67+
virtual size_t write(uint8_t aByte);
68+
virtual size_t write(const uint8_t *aBuffer, size_t aSize);
69+
// Inherited from Stream
70+
virtual int available();
71+
/** Read the next byte from the server.
72+
@return Byte read or -1 if there are no bytes available.
73+
*/
74+
virtual int read();
75+
virtual int read(uint8_t *buf, size_t size);
76+
virtual int peek();
77+
78+
private:
79+
uint8_t iTxMessageType;
80+
uint8_t iTxBuffer[128];
81+
uint64_t iTxSize;
82+
83+
uint8_t iRxOpCode;
84+
uint64_t iRxSize;
85+
bool iRxMasked;
86+
int iRxMaskIndex;
87+
uint8_t iRxMaskKey[4];
88+
};
89+
90+
#endif

0 commit comments

Comments
 (0)