Skip to content

Commit a19197c

Browse files
authored
Merge pull request #5 from sandeepmistry/websocket
Add initial WebSocket client
2 parents 6c39432 + f99517c commit a19197c

File tree

9 files changed

+603
-20
lines changed

9 files changed

+603
-20
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

HttpClient.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,15 +439,15 @@ int HttpClient::responseStatusCode()
439439
delay(kHttpWaitForDataDelay);
440440
}
441441
}
442-
if ( (c == '\n') && (iStatusCode < 200) )
442+
if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) )
443443
{
444444
// We've reached the end of an informational status line
445445
c = '\0'; // Clear c so we'll go back into the data reading loop
446446
}
447447
}
448448
// If we've read a status code successfully but it's informational (1xx)
449449
// loop back to the start
450-
while ( (iState == eStatusCodeRead) && (iStatusCode < 200) );
450+
while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) );
451451

452452
if ( (c == '\n') && (iState == eStatusCodeRead) )
453453
{

WebSocketClient.cpp

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

0 commit comments

Comments
 (0)