diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e43b0f9..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/MD5.c b/MD5.c deleted file mode 100644 index 5edaf5c..0000000 --- a/MD5.c +++ /dev/null @@ -1,303 +0,0 @@ -/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm - * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. - * All rights reserved. - * - * License to copy and use this software is granted provided that it - * is identified as the "RSA Data Security, Inc. MD5 Message-Digest - * Algorithm" in all material mentioning or referencing this software - * or this function. - * - * License is also granted to make and use derivative works provided - * that such works are identified as "derived from the RSA Data - * Security, Inc. MD5 Message-Digest Algorithm" in all material - * mentioning or referencing the derived work. - * - * RSA Data Security, Inc. makes no representations concerning either - * the merchantability of this software or the suitability of this - * software for any particular purpose. It is provided "as is" - * without express or implied warranty of any kind. - * - * These notices must be retained in any copies of any part of this - * documentation and/or software. - */ - -#include "global.h" -#include "MD5.h" - -/* Constants for MD5Transform routine. */ - -#define S11 7 -#define S12 12 -#define S13 17 -#define S14 22 -#define S21 5 -#define S22 9 -#define S23 14 -#define S24 20 -#define S31 4 -#define S32 11 -#define S33 16 -#define S34 23 -#define S41 6 -#define S42 10 -#define S43 15 -#define S44 21 - -static void MD5Transform(UINT4 [4], unsigned char [64]); -static void Encode(unsigned char *, UINT4 *, unsigned int); -static void Decode(UINT4 *, unsigned char *, unsigned int); -static void MD5_memcpy(POINTER, POINTER, unsigned int); -static void MD5_memset(POINTER, int, unsigned int); - -static unsigned char PADDING[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -/* F, G, H and I are basic MD5 functions. */ -#define MF(x, y, z) (((x) & (y)) | ((~x) & (z))) -#define MG(x, y, z) (((x) & (z)) | ((y) & (~z))) -#define MH(x, y, z) ((x) ^ (y) ^ (z)) -#define MI(x, y, z) ((y) ^ ((x) | (~z))) - -/* ROTATE_LEFT rotates x left n bits. */ -#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) - -/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. - * Rotation is separate from addition to prevent recomputation. - */ -#define FF(a, b, c, d, x, s, ac) { \ - (a) += MF ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } -#define GG(a, b, c, d, x, s, ac) { \ - (a) += MG ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } -#define HH(a, b, c, d, x, s, ac) { \ - (a) += MH ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } -#define II(a, b, c, d, x, s, ac) { \ - (a) += MI ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } - -/* MD5 initialization. Begins an MD5 operation, writing a new context. */ -void MD5Init (MD5_CTX *context) /* context */ -{ - context->count[0] = context->count[1] = 0; - /* Load magic initialization constants. */ - context->state[0] = 0x67452301; - context->state[1] = 0xefcdab89; - context->state[2] = 0x98badcfe; - context->state[3] = 0x10325476; -} - -/* MD5 block update operation. Continues an MD5 message-digest - * operation, processing another message block, and updating the - * context. - */ -void MD5Update (MD5_CTX *context, unsigned char *input,unsigned int inputLen) /* length of input block */ -{ - unsigned int i, index, partLen; - - /* Compute number of bytes mod 64 */ - index = (unsigned int)((context->count[0] >> 3) & 0x3F); - - /* Update number of bits */ - if ((context->count[0] += ((UINT4)inputLen << 3)) - < ((UINT4)inputLen << 3)) - context->count[1]++; - context->count[1] += ((UINT4)inputLen >> 29); - partLen = 64 - index; - - /* Transform as many times as possible. */ - if (inputLen >= partLen) { - MD5_memcpy ((POINTER)&context->buffer[index], (POINTER)input, partLen); - MD5Transform (context->state, context->buffer); - for (i = partLen; i + 63 < inputLen; i += 64) - MD5Transform (context->state, &input[i]); - index = 0; - } - else - i = 0; - - /* Buffer remaining input */ - MD5_memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], - inputLen-i); -} - -/* MD5 finalization. Ends an MD5 message-digest operation, writing the - * the message digest and zeroizing the context. - */ -void MD5Final (unsigned char digest[16], MD5_CTX *context) -{ - unsigned char bits[8]; - unsigned int index, padLen; - - /* Save number of bits */ - Encode (bits, context->count, 8); - - /* Pad out to 56 mod 64. */ - index = (unsigned int)((context->count[0] >> 3) & 0x3f); - padLen = (index < 56) ? (56 - index) : (120 - index); - MD5Update (context, PADDING, padLen); - - /* Append length (before padding) */ - MD5Update (context, bits, 8); - - /* Store state in digest */ - Encode (digest, context->state, 16); - - /* Zeroize sensitive information. */ - MD5_memset ((POINTER)context, 0, sizeof (*context)); -} - -/* MD5 basic transformation. Transforms state based on block. */ -static void MD5Transform (UINT4 state[4], unsigned char block[64]) -{ - UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; - - Decode (x, block, 64); - - /* Round 1 */ - FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ - FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ - FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ - FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ - FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ - FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ - FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ - FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ - FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ - FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ - FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ - FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ - FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ - FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ - FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ - FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ - - /* Round 2 */ - GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ - GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ - GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ - GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ - GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ - GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ - GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ - GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ - GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ - GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ - GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ - GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ - GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ - GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ - GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ - GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ - - /* Round 3 */ - HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ - HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ - HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ - HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ - HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ - HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ - HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ - HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ - HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ - HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ - HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ - HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ - HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ - HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ - HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ - HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ - - /* Round 4 */ - II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ - II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ - II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ - II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ - II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ - II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ - II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ - II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ - II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ - II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ - II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ - II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ - II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ - II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ - II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ - II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - /* Zeroize sensitive information. */ - MD5_memset ((POINTER)x, 0, sizeof (x)); -} - -/* Encodes input (UINT4) into output (unsigned char). Assumes len is - * a multiple of 4. - */ -static void Encode (unsigned char *output, UINT4 *input, unsigned int len) -{ - unsigned int i, j; - - for (i = 0, j = 0; j < len; i++, j += 4) { - output[j] = (unsigned char)(input[i] & 0xff); - output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); - output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); - output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); - } -} - -/* Decodes input (unsigned char) into output (UINT4). Assumes len is - * a multiple of 4. - */ -static void Decode (UINT4 *output, unsigned char *input, unsigned int len) -{ - unsigned int i, j; - - for (i = 0, j = 0; j < len; i++, j += 4) - output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | - (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); -} - -/* Note: Replace "for loop" with standard memcpy if possible. */ - -static void MD5_memcpy (POINTER output, POINTER input, unsigned int len) -{ - unsigned int i; - - for (i = 0; i < len; i++) - output[i] = input[i]; -} - -/* Note: Replace "for loop" with standard memset if possible. */ -static void MD5_memset (POINTER output, int value, unsigned int len) -{ - unsigned int i; - - for (i = 0; i < len; i++) - ((char *)output)[i] = (char)value; -} - -/* Calculate MD5 Digest into md5Digest */ -void MD5(unsigned char strInputString[], unsigned char md5Digest[], unsigned int len){ - MD5_CTX ctx; - MD5Init(&ctx); - - MD5Update(&ctx, strInputString, len); - MD5Final(md5Digest, &ctx); -} \ No newline at end of file diff --git a/MD5.h b/MD5.h deleted file mode 100644 index c434fca..0000000 --- a/MD5.h +++ /dev/null @@ -1,35 +0,0 @@ -/* MD5.H - header file for MD5C.C - * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All - * rights reserved. - * - * License to copy and use this software is granted provided that it - * is identified as the "RSA Data Security, Inc. MD5 Message-Digest - * Algorithm" in all material mentioning or referencing this software - * or this function. - * - * License is also granted to make and use derivative works provided - * that such works are identified as "derived from the RSA Data - * Security, Inc. MD5 Message-Digest Algorithm" in all material - * mentioning or referencing the derived work. - * - * RSA Data Security, Inc. makes no representations concerning either - * the merchantability of this software or the suitability of this - * software for any particular purpose. It is provided "as is" - * without express or implied warranty of any kind. - * These notices must be retained in any copies of any part of this - * documentation and/or software. - */ - -/* MD5 context. */ -typedef struct { - UINT4 state[4]; /* state (ABCD) */ - UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ - unsigned char buffer[64]; /* input buffer */ -} MD5_CTX; - -void MD5Init (MD5_CTX *); -void MD5Update (MD5_CTX *, unsigned char *, unsigned int); -void MD5Final (unsigned char [16], MD5_CTX *); - -/* Function used by Websockets implementation */ -void MD5 (unsigned char [], unsigned char [], unsigned int); \ No newline at end of file diff --git a/README.md b/README.md index 2eea2f0..11ffdbb 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,68 @@ -## Websocket Client and Server for Arduino +## Websocket client for Arduino, with fast data send -This is a simple library that implements a Websocket client and server running on an Arduino. +This is a simple library that implements a Websocket client running on an Arduino. -### Getting started +### Rationale -The example WebSocketServer.html file should be served from any web server you have access to. Remember to change the URL in it to your Arduino. The examples are based on using a WiFly wireless card to connect. If you're using ethernet instead you'll need to swap out the client class. +For our IoT prototype project based on Arduino, we needed a reliable data transmission protocol, and we thought of Websocket. We then searched for existing client implementations for Arduino, something that was able to handle any subclass of the `Client` one provided by Arduino, and that was essential in implementation but complete as well. We found the excellent code here . However, some modifications were needed for our purpose. In particular, we needed max throughput possible, approaching 100 messages/s. -Install the library to "libraries" folder in your Arduino sketchbook folder. For example, on a mac that's `~/Documents/Arduino/libraries`. +### Features +I added the following: -Try the examples to ensure that things work. +- Faster data send (`client.sendData(..., true)`, default behaviour): instead of sending a TCP packet per char, everything is sent + in one shot in a single TCP packet. This makes the implementation much faster. However, take into consideration max string length when using `WiFiClient.write()` method (around 90 bytes, from users experience when googled). Example: -Start playing with your own code! + ```C++ + webSocketClient.sendData("my string to send", WS_OPCODE_TEXT, true); + ``` -### Notes -Inside of the WebSocketServer class there is a compiler directive to turn on support for the older "Hixie76" standard. If you don't need it, leave it off as it greatly increases the memory required. +- For method `client.getData()`, I created a pure C string implementation, to avoid chances of heap fragmentation due to `String` + class. Example: -Because of limitations of the current Arduino platform (Uno at the time of this writing), this library does not support messages larger than 65535 characters. In addition, this library only supports single-frame text frames. It currently does not recognize continuation frames, binary frames, or ping/pong frames. + ```C++ + char msg_in[100]; // should be long enough to hold the longest arriving message + uint8_t opcode_in; + ... + webSocketClient.getData(msg_in, &opcode_in) + ``` -### Credits -Thank you to github user ejeklint for the excellent starting point for this library. From his original Hixie76-only code I was able to add support for RFC 6455 and create the WebSocket client. +### Tests +The optimized code was tested for: + +- `WiFiClient` (`` and ``) +- Arduino UNO and ZERO +- WiFi shield (retired) on Arduino UNO and WiFi shield 101 on Arduino ZERO +- `ws` as Node.js websocket server + +We were able to obtain to reach the target throughput indicated above, with a message length of around 70 bytes (\*): + +(\*) In order to reach that speed, we had to apply the following hack: + +1. + + We did not want to get the `loop()` stuck if the TCP message was not sent (via WiFi), and we could afford some data lost randomly; although, we wanted our data to be reliable and in time order on the server side, so we excluded UDP packets. + However, when the message rate you want to have is high, then some TCP packets could be lost (data or ACK); in this case, TCP fast-retransmit might not always be triggered, and this might increase the wait time for next packet to arrive. In this case, UDP protocol is strongly suggested. + +2. After point 1, we had to manually disable the mask flag for websocket messages, by replacing this line in src /WebSocketClient.h: -- Branden \ No newline at end of file + ```C++ + #define WS_MASK 0x80 + ``` + + with this one: + + ```C++ + #define WS_MASK 0x00 + ``` + + This modification disables the message mask, which normally is **compulsory**. `ws` tolerates it however. + +### MCU compatibility +- Tested: Arduino UNO, ZERO +- Not tested: Arduino DUE; howerer, by searching similar C++ repos on GitHub (`arduino websocket due in:readme,name,description fork:true`), it seems that the conditional inclusion (in src/sha1.cpp) of `#include ` and `#include ` needed for ZERO board, would also fix compilation for DUE board. Any good-soul tester is welcome to feedback. + +### Notes +See the original code from Branden for additional notes. + +### Credits +This is an optimized version of the client code from the excellent job in . Most of the credit goes to Branden. diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp deleted file mode 100644 index bad1b9f..0000000 --- a/WebSocketClient.cpp +++ /dev/null @@ -1,321 +0,0 @@ -//#define DEBUGGING - -#include "global.h" -#include "WebSocketClient.h" - -#include "sha1.h" -#include "base64.h" - - -bool WebSocketClient::handshake(Client &client) { - - socket_client = &client; - - // If there is a connected client-> - if (socket_client->connected()) { - // Check request and look for websocket handshake -#ifdef DEBUGGING - Serial.println(F("Client connected")); -#endif - if (analyzeRequest()) { -#ifdef DEBUGGING - Serial.println(F("Websocket established")); -#endif - - return true; - - } else { - // Might just need to break until out of socket_client loop. -#ifdef DEBUGGING - Serial.println(F("Invalid handshake")); -#endif - disconnectStream(); - - return false; - } - } else { - return false; - } -} - -bool WebSocketClient::analyzeRequest() { - String temp; - - int bite; - bool foundupgrade = false; - unsigned long intkey[2]; - String serverKey; - char keyStart[17]; - char b64Key[25]; - String key = "------------------------"; - - randomSeed(analogRead(0)); - - for (int i=0; i<16; ++i) { - keyStart[i] = (char)random(1, 256); - } - - base64_encode(b64Key, keyStart, 16); - - for (int i=0; i<24; ++i) { - key[i] = b64Key[i]; - } - -#ifdef DEBUGGING - Serial.println(F("Sending websocket upgrade headers")); -#endif - - socket_client->print(F("GET ")); - socket_client->print(path); - socket_client->print(F(" HTTP/1.1\r\n")); - socket_client->print(F("Upgrade: websocket\r\n")); - socket_client->print(F("Connection: Upgrade\r\n")); - socket_client->print(F("Host: ")); - socket_client->print(host); - socket_client->print(CRLF); - socket_client->print(F("Sec-WebSocket-Key: ")); - socket_client->print(key); - socket_client->print(CRLF); - socket_client->print(F("Sec-WebSocket-Protocol: ")); - socket_client->print(protocol); - socket_client->print(CRLF); - socket_client->print(F("Sec-WebSocket-Version: 13\r\n")); - socket_client->print(CRLF); - -#ifdef DEBUGGING - Serial.println(F("Analyzing response headers")); -#endif - - while (socket_client->connected() && !socket_client->available()) { - delay(100); - Serial.println("Waiting..."); - } - - // TODO: More robust string extraction - while ((bite = socket_client->read()) != -1) { - - temp += (char)bite; - - if ((char)bite == '\n') { -#ifdef DEBUGGING - Serial.print("Got Header: " + temp); -#endif - if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { - foundupgrade = true; - } else if (temp.startsWith("Sec-WebSocket-Accept: ")) { - serverKey = temp.substring(22,temp.length() - 2); // Don't save last CR+LF - } - temp = ""; - } - - if (!socket_client->available()) { - delay(20); - } - } - - key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - uint8_t *hash; - char result[21]; - char b64Result[30]; - - Sha1.init(); - Sha1.print(key); - hash = Sha1.result(); - - for (int i=0; i<20; ++i) { - result[i] = (char)hash[i]; - } - result[20] = '\0'; - - base64_encode(b64Result, result, 20); - - // if the keys match, good to go - return serverKey.equals(String(b64Result)); -} - - -bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { - uint8_t msgtype; - uint8_t bite; - unsigned int length; - uint8_t mask[4]; - uint8_t index; - unsigned int i; - bool hasMask = false; - - if (!socket_client->connected() || !socket_client->available()) - { - return false; - } - - msgtype = timedRead(); - if (!socket_client->connected()) { - return false; - } - - length = timedRead(); - - if (length & WS_MASK) { - hasMask = true; - length = length & ~WS_MASK; - } - - - if (!socket_client->connected()) { - return false; - } - - index = 6; - - if (length == WS_SIZE16) { - length = timedRead() << 8; - if (!socket_client->connected()) { - return false; - } - - length |= timedRead(); - if (!socket_client->connected()) { - return false; - } - - } else if (length == WS_SIZE64) { -#ifdef DEBUGGING - Serial.println(F("No support for over 16 bit sized messages")); -#endif - return false; - } - - if (hasMask) { - // get the mask - mask[0] = timedRead(); - if (!socket_client->connected()) { - return false; - } - - mask[1] = timedRead(); - if (!socket_client->connected()) { - - return false; - } - - mask[2] = timedRead(); - if (!socket_client->connected()) { - return false; - } - - mask[3] = timedRead(); - if (!socket_client->connected()) { - return false; - } - } - - data = ""; - - if (opcode != NULL) - { - *opcode = msgtype & ~WS_FIN; - } - - if (hasMask) { - for (i=0; iconnected()) { - return false; - } - } - } else { - for (i=0; iconnected()) { - return false; - } - } - } - - return true; -} - -void WebSocketClient::disconnectStream() { -#ifdef DEBUGGING - Serial.println(F("Terminating socket")); -#endif - // Should send 0x8700 to server to tell it I'm quitting here. - socket_client->write((uint8_t) 0x87); - socket_client->write((uint8_t) 0x00); - - socket_client->flush(); - delay(10); - socket_client->stop(); -} - -bool WebSocketClient::getData(String& data, uint8_t *opcode) { - return handleStream(data, opcode); -} - -void WebSocketClient::sendData(const char *str, uint8_t opcode) { -#ifdef DEBUGGING - Serial.print(F("Sending data: ")); - Serial.println(str); -#endif - if (socket_client->connected()) { - sendEncodedData(str, opcode); - } -} - -void WebSocketClient::sendData(String str, uint8_t opcode) { -#ifdef DEBUGGING - Serial.print(F("Sending data: ")); - Serial.println(str); -#endif - if (socket_client->connected()) { - sendEncodedData(str, opcode); - } -} - -int WebSocketClient::timedRead() { - while (!socket_client->available()) { - delay(20); - } - - return socket_client->read(); -} - -void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) { - uint8_t mask[4]; - int size = strlen(str); - - // Opcode; final fragment - socket_client->write(opcode | WS_FIN); - - // NOTE: no support for > 16-bit sized messages - if (size > 125) { - socket_client->write(WS_SIZE16 | WS_MASK); - socket_client->write((uint8_t) (size >> 8)); - socket_client->write((uint8_t) (size & 0xFF)); - } else { - socket_client->write((uint8_t) size | WS_MASK); - } - - mask[0] = random(0, 256); - mask[1] = random(0, 256); - mask[2] = random(0, 256); - mask[3] = random(0, 256); - - socket_client->write(mask[0]); - socket_client->write(mask[1]); - socket_client->write(mask[2]); - socket_client->write(mask[3]); - - for (int i=0; iwrite(str[i] ^ mask[i % 4]); - } -} - -void WebSocketClient::sendEncodedData(String str, uint8_t opcode) { - int size = str.length() + 1; - char cstr[size]; - - str.toCharArray(cstr, size); - - sendEncodedData(cstr, opcode); -} diff --git a/WebSocketServer.cpp b/WebSocketServer.cpp deleted file mode 100644 index bee55c5..0000000 --- a/WebSocketServer.cpp +++ /dev/null @@ -1,434 +0,0 @@ -//#define DEBUGGING -//#define SUPPORT_HIXIE_76 - -#include "global.h" -#include "WebSocketServer.h" - -#ifdef SUPPORT_HIXIE_76 -#include "MD5.c" -#endif - -#include "sha1.h" -#include "base64.h" - - -bool WebSocketServer::handshake(Client &client) { - - socket_client = &client; - - // If there is a connected client-> - if (socket_client->connected()) { - // Check request and look for websocket handshake -#ifdef DEBUGGING - Serial.println(F("Client connected")); -#endif - if (analyzeRequest(BUFFER_LENGTH)) { -#ifdef DEBUGGING - Serial.println(F("Websocket established")); -#endif - - return true; - - } else { - // Might just need to break until out of socket_client loop. -#ifdef DEBUGGING - Serial.println(F("Disconnecting client")); -#endif - disconnectStream(); - - return false; - } - } else { - return false; - } -} - -bool WebSocketServer::analyzeRequest(int bufferLength) { - // Use String library to do some sort of read() magic here. - String temp; - - int bite; - bool foundupgrade = false; - String oldkey[2]; - unsigned long intkey[2]; - String newkey; - - hixie76style = false; - -#ifdef DEBUGGING - Serial.println(F("Analyzing request headers")); -#endif - - // TODO: More robust string extraction - while ((bite = socket_client->read()) != -1) { - - temp += (char)bite; - - if ((char)bite == '\n') { -#ifdef DEBUGGING - Serial.print("Got Line: " + temp); -#endif - // TODO: Should ignore case when comparing and allow 0-n whitespace after ':'. See the spec: - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html - if (!foundupgrade && temp.startsWith("Upgrade: WebSocket")) { - // OK, it's a websockets handshake for sure - foundupgrade = true; - hixie76style = true; - } else if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { - foundupgrade = true; - hixie76style = false; - } else if (temp.startsWith("Origin: ")) { - origin = temp.substring(8,temp.length() - 2); // Don't save last CR+LF - } else if (temp.startsWith("Host: ")) { - host = temp.substring(6,temp.length() - 2); // Don't save last CR+LF - } else if (temp.startsWith("Sec-WebSocket-Key1: ")) { - oldkey[0]=temp.substring(20,temp.length() - 2); // Don't save last CR+LF - } else if (temp.startsWith("Sec-WebSocket-Key2: ")) { - oldkey[1]=temp.substring(20,temp.length() - 2); // Don't save last CR+LF - } else if (temp.startsWith("Sec-WebSocket-Key: ")) { - newkey=temp.substring(19,temp.length() - 2); // Don't save last CR+LF - } - temp = ""; - } - - if (!socket_client->available()) { - delay(20); - } - } - - if (!socket_client->connected()) { - return false; - } - - temp += 0; // Terminate string - - // Assert that we have all headers that are needed. If so, go ahead and - // send response headers. - if (foundupgrade == true) { - -#ifdef SUPPORT_HIXIE_76 - if (hixie76style && host.length() > 0 && oldkey[0].length() > 0 && oldkey[1].length() > 0) { - // All ok, proceed with challenge and MD5 digest - char key3[9] = {0}; - // What now is in temp should be the third key - temp.toCharArray(key3, 9); - - // Process keys - for (int i = 0; i <= 1; i++) { - unsigned int spaces =0; - String numbers; - - for (int c = 0; c < oldkey[i].length(); c++) { - char ac = oldkey[i].charAt(c); - if (ac >= '0' && ac <= '9') { - numbers += ac; - } - if (ac == ' ') { - spaces++; - } - } - char numberschar[numbers.length() + 1]; - numbers.toCharArray(numberschar, numbers.length()+1); - intkey[i] = strtoul(numberschar, NULL, 10) / spaces; - } - - unsigned char challenge[16] = {0}; - challenge[0] = (unsigned char) ((intkey[0] >> 24) & 0xFF); - challenge[1] = (unsigned char) ((intkey[0] >> 16) & 0xFF); - challenge[2] = (unsigned char) ((intkey[0] >> 8) & 0xFF); - challenge[3] = (unsigned char) ((intkey[0] ) & 0xFF); - challenge[4] = (unsigned char) ((intkey[1] >> 24) & 0xFF); - challenge[5] = (unsigned char) ((intkey[1] >> 16) & 0xFF); - challenge[6] = (unsigned char) ((intkey[1] >> 8) & 0xFF); - challenge[7] = (unsigned char) ((intkey[1] ) & 0xFF); - - memcpy(challenge + 8, key3, 8); - - unsigned char md5Digest[16]; - MD5(challenge, md5Digest, 16); - - socket_client->print(F("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")); - socket_client->print(F("Upgrade: WebSocket\r\n")); - socket_client->print(F("Connection: Upgrade\r\n")); - socket_client->print(F("Sec-WebSocket-Origin: ")); - socket_client->print(origin); - socket_client->print(CRLF); - - // The "Host:" value should be used as location - socket_client->print(F("Sec-WebSocket-Location: ws://")); - socket_client->print(host); - socket_client->print(socket_urlPrefix); - socket_client->print(CRLF); - socket_client->print(CRLF); - - socket_client->write(md5Digest, 16); - - return true; - } -#endif - - if (!hixie76style && newkey.length() > 0) { - - // add the magic string - newkey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - uint8_t *hash; - char result[21]; - char b64Result[30]; - - Sha1.init(); - Sha1.print(newkey); - hash = Sha1.result(); - - for (int i=0; i<20; ++i) { - result[i] = (char)hash[i]; - } - result[20] = '\0'; - - base64_encode(b64Result, result, 20); - - socket_client->print(F("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")); - socket_client->print(F("Upgrade: websocket\r\n")); - socket_client->print(F("Connection: Upgrade\r\n")); - socket_client->print(F("Sec-WebSocket-Accept: ")); - socket_client->print(b64Result); - socket_client->print(CRLF); - socket_client->print(CRLF); - - return true; - } else { - // something went horribly wrong - return false; - } - } else { - // Nope, failed handshake. Disconnect -#ifdef DEBUGGING - Serial.println(F("Header mismatch")); -#endif - return false; - } -} - -#ifdef SUPPORT_HIXIE_76 -String WebSocketServer::handleHixie76Stream() { - int bite; - int frameLength = 0; - // String to hold bytes sent by client to server. - String socketString; - - if (socket_client->connected() && socket_client->available()) { - bite = timedRead(); - - if (bite != -1) { - if (bite == 0) - continue; // Frame start, don't save - - if ((uint8_t) bite == 0xFF) { - // Frame end. Process what we got. - return socketString; - - } else { - socketString += (char)bite; - frameLength++; - - if (frameLength > MAX_FRAME_LENGTH) { - // Too big to handle! -#ifdef DEBUGGING - Serial.print("Client send frame exceeding "); - Serial.print(MAX_FRAME_LENGTH); - Serial.println(" bytes"); -#endif - return; - } - } - } - } - - return socketString; -} - -#endif - -String WebSocketServer::handleStream() { - uint8_t msgtype; - uint8_t bite; - unsigned int length; - uint8_t mask[4]; - uint8_t index; - unsigned int i; - - // String to hold bytes sent by client to server. - String socketString; - - if (socket_client->connected() && socket_client->available()) { - - msgtype = timedRead(); - if (!socket_client->connected()) { - return socketString; - } - - length = timedRead() & 127; - if (!socket_client->connected()) { - return socketString; - } - - index = 6; - - if (length == 126) { - length = timedRead() << 8; - if (!socket_client->connected()) { - return socketString; - } - - length |= timedRead(); - if (!socket_client->connected()) { - return socketString; - } - - } else if (length == 127) { -#ifdef DEBUGGING - Serial.println(F("No support for over 16 bit sized messages")); -#endif - while(1) { - // halt, can't handle this case - } - } - - // get the mask - mask[0] = timedRead(); - if (!socket_client->connected()) { - return socketString; - } - - mask[1] = timedRead(); - if (!socket_client->connected()) { - - return socketString; - } - - mask[2] = timedRead(); - if (!socket_client->connected()) { - return socketString; - } - - mask[3] = timedRead(); - if (!socket_client->connected()) { - return socketString; - } - - for (i=0; iconnected()) { - return socketString; - } - } - } - - return socketString; -} - -void WebSocketServer::disconnectStream() { -#ifdef DEBUGGING - Serial.println(F("Terminating socket")); -#endif - - if (hixie76style) { -#ifdef SUPPORT_HIXIE_76 - // Should send 0xFF00 to server to tell it I'm quitting here. - socket_client->write((uint8_t) 0xFF); - socket_client->write((uint8_t) 0x00); -#endif - } else { - - // Should send 0x8700 to server to tell it I'm quitting here. - socket_client->write((uint8_t) 0x87); - socket_client->write((uint8_t) 0x00); - } - - socket_client->flush(); - delay(10); - socket_client->stop(); -} - -String WebSocketServer::getData() { - String data; - - if (hixie76style) { -#ifdef SUPPORT_HIXIE_76 - data = handleHixie76Stream(); -#endif - } else { - data = handleStream(); - } - - return data; -} - -void WebSocketServer::sendData(const char *str) { -#ifdef DEBUGGING - Serial.print(F("Sending data: ")); - Serial.println(str); -#endif - if (socket_client->connected()) { - if (hixie76style) { - socket_client->write(0x00); // Frame start - socket_client->print(str); - socket_client->write(0xFF); // Frame end - } else { - sendEncodedData(str); - } - } -} - -void WebSocketServer::sendData(String str) { -#ifdef DEBUGGING - Serial.print(F("Sending data: ")); - Serial.println(str); -#endif - if (socket_client->connected()) { - if (hixie76style) { - socket_client->write(0x00); // Frame start - socket_client->print(str); - socket_client->write(0xFF); // Frame end - } else { - sendEncodedData(str); - } - } -} - -int WebSocketServer::timedRead() { - while (!socket_client->available()) { - delay(20); - } - - return socket_client->read(); -} - -void WebSocketServer::sendEncodedData(char *str) { - int size = strlen(str); - - // string type - socket_client->write(0x81); - - // NOTE: no support for > 16-bit sized messages - if (size > 125) { - socket_client->write(126); - socket_client->write((uint8_t) (size >> 8)); - socket_client->write((uint8_t) (size && 0xFF)); - } else { - socket_client->write((uint8_t) size); - } - - for (int i=0; iwrite(str[i]); - } -} - -void WebSocketServer::sendEncodedData(String str) { - int size = str.length() + 1; - char cstr[size]; - - str.toCharArray(cstr, size); - - sendEncodedData(cstr); -} diff --git a/WebSocketServer.h b/WebSocketServer.h deleted file mode 100644 index 6dcf052..0000000 --- a/WebSocketServer.h +++ /dev/null @@ -1,118 +0,0 @@ -/* -Websocket-Arduino, a websocket implementation for Arduino -Copyright 2011 Per Ejeklint - -Based on previous implementations by -Copyright 2010 Ben Swanson -and -Copyright 2010 Randall Brewer -and -Copyright 2010 Oliver Smith - -Some code and concept based off of Webduino library -Copyright 2009 Ben Combee, Ran Talbott - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -------------- -Now based off -http://www.whatwg.org/specs/web-socket-protocol/ - -- OLD - -Currently based off of "The Web Socket protocol" draft (v 75): -http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 -*/ - - -#ifndef WEBSOCKETSERVER_H_ -#define WEBSOCKETSERVER_H_ - -#include -#include -#include "String.h" -#include "Server.h" -#include "Client.h" - -// CRLF characters to terminate lines/handshakes in headers. -#define CRLF "\r\n" - -// Amount of time (in ms) a user may be connected before getting disconnected -// for timing out (i.e. not sending any data to the server). -#define TIMEOUT_IN_MS 10000 -#define BUFFER_LENGTH 32 - -// ACTION_SPACE is how many actions are allowed in a program. Defaults to -// 5 unless overwritten by user. -#ifndef CALLBACK_FUNCTIONS -#define CALLBACK_FUNCTIONS 1 -#endif - -// Don't allow the client to send big frames of data. This will flood the Arduinos -// memory and might even crash it. -#ifndef MAX_FRAME_LENGTH -#define MAX_FRAME_LENGTH 256 -#endif - -#define SIZE(array) (sizeof(array) / sizeof(*array)) - -class WebSocketServer { -public: - - // Handle connection requests to validate and process/refuse - // connections. - bool handshake(Client &client); - - // Get data off of the stream - String getData(); - - // Write data to the stream - void sendData(const char *str); - void sendData(String str); - -private: - Client *socket_client; - unsigned long _startMillis; - - const char *socket_urlPrefix; - - String origin; - String host; - bool hixie76style; - - // Discovers if the client's header is requesting an upgrade to a - // websocket connection. - bool analyzeRequest(int bufferLength); - -#ifdef SUPPORT_HIXIE_76 - String handleHixie76Stream(); -#endif - String handleStream(); - - // Disconnect user gracefully. - void disconnectStream(); - - int timedRead(); - - void sendEncodedData(char *str); - void sendEncodedData(String str); -}; - - - -#endif \ No newline at end of file diff --git a/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino new file mode 100644 index 0000000..c813be0 --- /dev/null +++ b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino @@ -0,0 +1,130 @@ +#include +#include +#include + +// Here we define a maximum framelength to 64 bytes. Default is 256. +#define MAX_FRAME_LENGTH 64 + +// Define how many callback functions you have. Default is 1. +#define CALLBACK_FUNCTIONS 1 + +#define MESSAGE_INTERVAL 30000 +#define HEARTBEAT_INTERVAL 25000 + +#include + +WiFlyClient client = WiFlyClient(); +WebSocketClient webSocketClient; + +uint64_t messageTimestamp = 0; +uint64_t heartbeatTimestamp = 0; + +void setup() { + + + Serial.begin(9600); + SC16IS750.begin(); + + WiFly.setUart(&SC16IS750); + + WiFly.begin(); + + // This is for an unsecured network + // For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD' + // For a WEP network use auth 2, and in another command send 'set wlan key KEY' + WiFly.sendCommand(F("set wlan auth 1")); + WiFly.sendCommand(F("set wlan channel 0")); + WiFly.sendCommand(F("set ip dhcp 1")); + + Serial.println(F("Joining WiFi network...")); + + + // Here is where you set the network name to join + if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { + Serial.println(F("Association failed.")); + while (1) { + // Hang on failure. + } + } + + if (!WiFly.waitForResponse("DHCP in", 10000)) { + Serial.println(F("DHCP failed.")); + while (1) { + // Hang on failure. + } + } + + // This is how you get the local IP as an IPAddress object + Serial.println(WiFly.localIP()); + + // This delay is needed to let the WiFly respond properly + delay(100); + + // Connect to the websocket server + if (client.connect("echo.websocket.org", 80)) { + Serial.println("Connected"); + } else { + Serial.println("Connection failed."); + while(1) { + // Hang on failure + } + } + + // Handshake with the server + webSocketClient.path = "/"; + webSocketClient.host = "echo.websocket.org"; + + if (webSocketClient.handshake(client, true)) { + Serial.println("Handshake successful"); + // socket.io upgrade confirmation message (required) + webSocketClient.sendData("5"); + } else { + Serial.println("Handshake failed."); + while(1) { + // Hang on failure + } + } +} + +void loop() { + String data; + + if (client.connected()) { + + webSocketClient.getData(data); + + if (data.length() > 0) { + Serial.print("Received data: "); + Serial.println(data); + } + + uint64_t now = millis(); + + if(now - messageTimestamp > MESSAGE_INTERVAL) { + messageTimestamp = now; + // capture the value of analog 1, send it along + pinMode(1, INPUT); + data = String(analogRead(1)); + + // example socket.io message with type "messageType" and JSON payload + char message[128]; + sprintf(message, "42[\"messageType\",{\"data\":\"%s\"}]", data.c_str()); + webSocket.sendData(message); + } + if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) { + heartbeatTimestamp = now; + // socket.io heartbeat message + webSocket.sendData("2"); + } + + } else { + + Serial.println("Client disconnected."); + while (1) { + // Hang on disconnect. + } + } + + // wait to fully let the client disconnect + delay(3000); +} diff --git a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino new file mode 100644 index 0000000..413eb13 --- /dev/null +++ b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino @@ -0,0 +1,104 @@ +#include + +// Here we define a maximum framelength to 64 bytes. Default is 256. +#define MAX_FRAME_LENGTH 64 + +// Define how many callback functions you have. Default is 1. +#define CALLBACK_FUNCTIONS 1 + +#define MESSAGE_INTERVAL 30000 +#define HEARTBEAT_INTERVAL 25000 + +#include + +WiFiClient client; +WebSocketClient webSocketClient; + +uint64_t messageTimestamp = 0; +uint64_t heartbeatTimestamp = 0; + +void setup() { + + Serial.begin(115200); + + Serial.print("Connecting to Wifi"); + WiFi.begin("ssid", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(100); + Serial.print("."); + } + Serial.println("WiFi connected"); + + // This is how you get the local IP as an IPAddress object + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + // Connect to the websocket server + if (client.connect("echo.websocket.org", 80)) { + Serial.println("Connected"); + } else { + Serial.println("Connection failed."); + while(1) { + // Hang on failure + } + } + + // Handshake with the server + webSocketClient.path = "/"; + webSocketClient.host = "echo.websocket.org"; + + if (webSocketClient.handshake(client, true)) { + Serial.println("Handshake successful"); + // socket.io upgrade confirmation message (required) + webSocketClient.sendData("5"); + } else { + Serial.println("Handshake failed."); + while(1) { + // Hang on failure + } + } +} + +void loop() { + String data; + + if (client.connected()) { + + webSocketClient.getData(data); + + if (data.length() > 0) { + Serial.print("Received data: "); + Serial.println(data); + } + + uint64_t now = millis(); + + if(now - messageTimestamp > MESSAGE_INTERVAL) { + messageTimestamp = now; + // capture the value of analog 1, send it along + pinMode(1, INPUT); + data = String(analogRead(1)); + + // example socket.io message with type "messageType" and JSON payload + char message[128]; + sprintf(message, "42[\"messageType\",{\"data\":\"%s\"}]", data.c_str()); + webSocket.sendData(message); + } + if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) { + heartbeatTimestamp = now; + // socket.io heartbeat message + webSocket.sendData("2"); + } + + } else { + + Serial.println("Client disconnected."); + while (1) { + // Hang on disconnect. + } + } + + // wait to fully let the client disconnect + delay(3000); +} diff --git a/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino b/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino index 74f3203..c318137 100644 --- a/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino +++ b/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino @@ -14,34 +14,34 @@ WiFlyClient client = WiFlyClient(); WebSocketClient webSocketClient; void setup() { - + Serial.begin(9600); SC16IS750.begin(); - + WiFly.setUart(&SC16IS750); - + WiFly.begin(); - + // This is for an unsecured network // For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD' // For a WEP network use auth 2, and in another command send 'set wlan key KEY' WiFly.sendCommand(F("set wlan auth 1")); WiFly.sendCommand(F("set wlan channel 0")); WiFly.sendCommand(F("set ip dhcp 1")); - + Serial.println(F("Joining WiFi network...")); - + // Here is where you set the network name to join - if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { + if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { Serial.println(F("Association failed.")); while (1) { // Hang on failure. } } - - if (!WiFly.waitForResponse("DHCP in", 10000)) { + + if (!WiFly.waitForResponse("DHCP in", 10000)) { Serial.println(F("DHCP failed.")); while (1) { // Hang on failure. @@ -50,7 +50,7 @@ void setup() { // This is how you get the local IP as an IPAddress object Serial.println(WiFly.localIP()); - + // This delay is needed to let the WiFly respond properly delay(100); @@ -67,43 +67,43 @@ void setup() { // Handshake with the server webSocketClient.path = "/"; webSocketClient.host = "echo.websocket.org"; - + if (webSocketClient.handshake(client)) { Serial.println("Handshake successful"); } else { Serial.println("Handshake failed."); while(1) { // Hang on failure - } + } } } void loop() { String data; - + if (client.connected()) { - - data = webSocketClient.getData(); + + webSocketClient.getData(data); if (data.length() > 0) { Serial.print("Received data: "); Serial.println(data); } - + // capture the value of analog 1, send it along pinMode(1, INPUT); data = String(analogRead(1)); - + webSocketClient.sendData(data); - + } else { - + Serial.println("Client disconnected."); while (1) { // Hang on disconnect. } } - + // wait to fully let the client disconnect delay(3000); } diff --git a/examples/WebSocketServer.html b/examples/WebSocketServer.html deleted file mode 100644 index 71e2a60..0000000 --- a/examples/WebSocketServer.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - WebSocket Test - - - - - - -

- WebSocket Test -

- Pin 8 - Pin 9 - -
Pin 1
-
Pin 2
-
Pin 3
- - diff --git a/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino b/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino deleted file mode 100644 index 2a83c09..0000000 --- a/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino +++ /dev/null @@ -1,123 +0,0 @@ -#include -#include -#include - -// Enabe debug tracing to Serial port. -#define DEBUGGING - -// Here we define a maximum framelength to 64 bytes. Default is 256. -#define MAX_FRAME_LENGTH 64 - -// Define how many callback functions you have. Default is 1. -#define CALLBACK_FUNCTIONS 1 - -#include - -WiFlyServer server(80); -WebSocketServer webSocketServer; - - -// Called when a new message from the WebSocket is received -// Looks for a message in this form: -// -// DPV -// -// Where: -// D is either 'd' or 'a' - digital or analog -// P is a pin # -// V is the value to apply to the pin -// - -void handleClientData(String &dataString) { - bool isDigital = dataString[0] == 'd'; - int pin = dataString[1] - '0'; - int value; - - value = dataString[2] - '0'; - - - pinMode(pin, OUTPUT); - - if (isDigital) { - digitalWrite(pin, value); - } else { - analogWrite(pin, value); - } - - Serial.println(dataString); -} - -// send the client the analog value of a pin -void sendClientData(int pin) { - String data = "a"; - - pinMode(pin, INPUT); - data += String(pin) + String(analogRead(pin)); - webSocketServer.sendData(data); -} - -void setup() { - - - Serial.begin(9600); - SC16IS750.begin(); - - WiFly.setUart(&SC16IS750); - - WiFly.begin(); - - // This is for an unsecured network - // For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD' - // For a WEP network use auth 2, and in another command send 'set wlan key KEY' - WiFly.sendCommand(F("set wlan auth 1")); - WiFly.sendCommand(F("set wlan channel 0")); - WiFly.sendCommand(F("set ip dhcp 1")); - - server.begin(); - Serial.println(F("Joining WiFi network...")); - - - // Here is where you set the network name to join - if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { - Serial.println(F("Association failed.")); - while (1) { - // Hang on failure. - } - } - - if (!WiFly.waitForResponse("DHCP in", 10000)) { - Serial.println(F("DHCP failed.")); - while (1) { - // Hang on failure. - } - } - - // This is how you get the local IP as an IPAddress object - Serial.println(WiFly.localIP()); - - // This delay is needed to let the WiFly respond properly - delay(100); -} - -void loop() { - String data; - WiFlyClient client = server.available(); - - if (client.connected() && webSocketServer.handshake(client)) { - - while (client.connected()) { - data = webSocketServer.getData(); - - if (data.length() > 0) { - handleClientData(data); - } - - sendClientData(1); - sendClientData(2); - sendClientData(3); - } - } - - // wait to fully let the client disconnect - delay(100); -} diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..ad41d3f --- /dev/null +++ b/keywords.txt @@ -0,0 +1,24 @@ +####################################### +# Syntax Coloring Map WebsocketFast +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +WebSocketClient KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +handshake KEYWORD2 +getData KEYWORD2 +sendData KEYWORD2 +path KEYWORD2 +host KEYWORD2 +protocol KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..384b82a --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=Arduino-Websocket-Fast +version=1.0.0 +author=Davide Monari (KULeuven) +maintainer=Davide Monari +sentence=Websocket client library (fast data sending). +paragraph=The library can wrap around a generic Arduino Client() class or similar interface (e.g. EthernetClient(), WiFiClient(), WiflyClient(), ...) and is optimized in speed for data sending. +category=Communication +url=https://github.com/u0078867/Arduino-Websocket-Fast +architectures=avr,samd diff --git a/Base64.cpp b/src/Base64.cpp old mode 100755 new mode 100644 similarity index 100% rename from Base64.cpp rename to src/Base64.cpp diff --git a/Base64.h b/src/Base64.h old mode 100755 new mode 100644 similarity index 100% rename from Base64.h rename to src/Base64.h diff --git a/src/WebSocketClient.cpp b/src/WebSocketClient.cpp new file mode 100644 index 0000000..9d14d93 --- /dev/null +++ b/src/WebSocketClient.cpp @@ -0,0 +1,546 @@ +//#define DEBUGGING + +#include "global.h" +#include "WebSocketClient.h" + +#include "sha1.h" +#include "base64.h" + + +bool WebSocketClient::handshake(Client &client, bool socketio) { + + socket_client = &client; + issocketio = socketio; + strcpy(sid, ""); + + // If there is a connected client-> + if (socket_client->connected()) { + // Check request and look for websocket handshake +#ifdef DEBUGGING + Serial.println(F("Client connected")); +#endif + if (issocketio && strlen(sid) == 0) { + analyzeRequest(); + } + + if (analyzeRequest()) { +#ifdef DEBUGGING + Serial.println(F("Websocket established")); +#endif + + return true; + + } else { + // Might just need to break until out of socket_client loop. +#ifdef DEBUGGING + Serial.println(F("Invalid handshake")); +#endif + disconnectStream(); + + return false; + } + } else { + return false; + } +} + +bool WebSocketClient::analyzeRequest() { + String temp; + + int bite; + bool foundupgrade = false; + bool foundsid = false; + unsigned long intkey[2]; + String serverKey; + char keyStart[17]; + char b64Key[25]; + String key = "------------------------"; + + if (!issocketio || (issocketio && strlen(sid) > 0)) { + +#ifdef DEBUGGING + Serial.println(F("Sending websocket upgrade headers")); +#endif + + randomSeed(analogRead(0)); + + for (int i=0; i<16; ++i) { + keyStart[i] = (char)random(1, 256); + } + + base64_encode(b64Key, keyStart, 16); + + for (int i=0; i<24; ++i) { + key[i] = b64Key[i]; + } + + socket_client->print(F("GET ")); + socket_client->print(path); + if (issocketio) { + socket_client->print(F("socket.io/?EIO=3&transport=websocket&sid=")); + socket_client->print(sid); + } + socket_client->print(F(" HTTP/1.1\r\n")); + socket_client->print(F("Upgrade: websocket\r\n")); + socket_client->print(F("Connection: Upgrade\r\n")); + socket_client->print(F("Sec-WebSocket-Key: ")); + socket_client->print(key); + socket_client->print(CRLF); + socket_client->print(F("Sec-WebSocket-Protocol: ")); + socket_client->print(protocol); + socket_client->print(CRLF); + socket_client->print(F("Sec-WebSocket-Version: 13\r\n")); + + } else { + +#ifdef DEBUGGING + Serial.println(F("Sending socket.io session request headers")); +#endif + + socket_client->print(F("GET ")); + socket_client->print(path); + socket_client->print(F("socket.io/?EIO=3&transport=polling HTTP/1.1\r\n")); + socket_client->print(F("Connection: keep-alive\r\n")); + } + + socket_client->print(F("Host: ")); + socket_client->print(host); + socket_client->print(CRLF); + socket_client->print(CRLF); + +#ifdef DEBUGGING + Serial.println(F("Analyzing response headers")); +#endif + + while (socket_client->connected() && !socket_client->available()) { + delay(100); + Serial.println("Waiting..."); + } + + // TODO: More robust string extraction + while ((bite = socket_client->read()) != -1) { + + temp += (char)bite; + + if ((char)bite == '\n') { +#ifdef DEBUGGING + Serial.print("Got Header: " + temp); +#endif + if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { + foundupgrade = true; + } else if (temp.startsWith("Sec-WebSocket-Accept: ")) { + serverKey = temp.substring(22,temp.length() - 2); // Don't save last CR+LF + } else if (!foundsid && temp.startsWith("Set-Cookie: ")) { + foundsid = true; + String tempsid = temp.substring(temp.indexOf("=") + 1, temp.length() - 2); // Don't save last CR+LF + strcpy(sid, tempsid.c_str()); + } + temp = ""; + } + + if (!socket_client->available()) { + delay(20); + } + } + + if (issocketio && foundsid && !foundupgrade) { + return true; + } + + key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + uint8_t *hash; + char result[21]; + char b64Result[30]; + + Sha1.init(); + Sha1.print(key); + hash = Sha1.result(); + + for (int i=0; i<20; ++i) { + result[i] = (char)hash[i]; + } + result[20] = '\0'; + + base64_encode(b64Result, result, 20); + + // if the keys match, good to go + return serverKey.equals(String(b64Result)); +} + + +bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { + uint8_t msgtype; + uint8_t bite; + unsigned int length; + uint8_t mask[4]; + uint8_t index; + unsigned int i; + bool hasMask = false; + + if (!socket_client->connected() || !socket_client->available()) + { + return false; + } + + msgtype = timedRead(); + if (!socket_client->connected()) { + return false; + } + + length = timedRead(); + + if (length & WS_MASK) { + hasMask = true; + length = length & ~WS_MASK; + } + + + if (!socket_client->connected()) { + return false; + } + + index = 6; + + if (length == WS_SIZE16) { + length = timedRead() << 8; + if (!socket_client->connected()) { + return false; + } + + length |= timedRead(); + if (!socket_client->connected()) { + return false; + } + + } else if (length == WS_SIZE64) { +#ifdef DEBUGGING + Serial.println(F("No support for over 16 bit sized messages")); +#endif + return false; + } + + if (hasMask) { + // get the mask + mask[0] = timedRead(); + if (!socket_client->connected()) { + return false; + } + + mask[1] = timedRead(); + if (!socket_client->connected()) { + + return false; + } + + mask[2] = timedRead(); + if (!socket_client->connected()) { + return false; + } + + mask[3] = timedRead(); + if (!socket_client->connected()) { + return false; + } + } + + data = ""; + + if (opcode != NULL) + { + *opcode = msgtype & ~WS_FIN; + } + + if (hasMask) { + for (i=0; iconnected()) { + return false; + } + } + } else { + for (i=0; iconnected()) { + return false; + } + } + } + + return true; +} + +bool WebSocketClient::handleStream(char *data, uint8_t *opcode) { + uint8_t msgtype; + uint8_t bite; + unsigned int length; + uint8_t mask[4]; + uint8_t index; + unsigned int i; + bool hasMask = false; + + if (!socket_client->connected() || !socket_client->available()) + { + return false; + } + + msgtype = timedRead(); + if (!socket_client->connected()) { + return false; + } + + length = timedRead(); + + if (length & WS_MASK) { + hasMask = true; + length = length & ~WS_MASK; + } + + + if (!socket_client->connected()) { + return false; + } + + index = 6; + + if (length == WS_SIZE16) { + length = timedRead() << 8; + if (!socket_client->connected()) { + return false; + } + + length |= timedRead(); + if (!socket_client->connected()) { + return false; + } + + } else if (length == WS_SIZE64) { +#ifdef DEBUGGING + Serial.println(F("No support for over 16 bit sized messages")); +#endif + return false; + } + + if (hasMask) { + // get the mask + mask[0] = timedRead(); + if (!socket_client->connected()) { + return false; + } + + mask[1] = timedRead(); + if (!socket_client->connected()) { + + return false; + } + + mask[2] = timedRead(); + if (!socket_client->connected()) { + return false; + } + + mask[3] = timedRead(); + if (!socket_client->connected()) { + return false; + } + } + + strcpy(data, ""); + + if (opcode != NULL) + { + *opcode = msgtype & ~WS_FIN; + } + + if (hasMask) { + for (i=0; iconnected()) { + return false; + } + } + } else { + for (i=0; iconnected()) { + return false; + } + } + } + + return true; +} + +void WebSocketClient::disconnectStream() { +#ifdef DEBUGGING + Serial.println(F("Terminating socket")); +#endif + // Should send 0x8700 to server to tell it I'm quitting here. + socket_client->write((uint8_t) 0x87); + socket_client->write((uint8_t) 0x00); + + socket_client->flush(); + delay(10); + socket_client->stop(); + strcpy(sid, ""); +} + +bool WebSocketClient::getData(String& data, uint8_t *opcode) { + return handleStream(data, opcode); +} + +bool WebSocketClient::getData(char *data, uint8_t *opcode) { + return handleStream(data, opcode); +} + +void WebSocketClient::sendData(const char *str, uint8_t opcode, bool fast) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + if (fast) { + sendEncodedDataFast(str, opcode); + } else { + sendEncodedData(str, opcode); + } + } +} + +void WebSocketClient::sendData(String str, uint8_t opcode, bool fast) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + if (fast) { + sendEncodedDataFast(str, opcode); + } else { + sendEncodedData(str, opcode); + } + } +} + +int WebSocketClient::timedRead() { + while (!socket_client->available()) { + //delay(20); + } + + return socket_client->read(); +} + +void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) { + uint8_t mask[4]; + int size = strlen(str); + + // Opcode; final fragment + socket_client->write(opcode | WS_FIN); + + // NOTE: no support for > 16-bit sized messages + if (size > 125) { + socket_client->write(WS_SIZE16 | WS_MASK); + socket_client->write((uint8_t) (size >> 8)); + socket_client->write((uint8_t) (size & 0xFF)); + } else { + socket_client->write((uint8_t) size | WS_MASK); + } + + if (WS_MASK > 0) { + //Serial.println("MASK"); + mask[0] = random(0, 256); + mask[1] = random(0, 256); + mask[2] = random(0, 256); + mask[3] = random(0, 256); + + socket_client->write(mask[0]); + socket_client->write(mask[1]); + socket_client->write(mask[2]); + socket_client->write(mask[3]); + } + + for (int i=0; i 0) { + //Serial.println("send with MASK"); + //delay(20); + socket_client->write(str[i] ^ mask[i % 4]); + } else { + socket_client->write(str[i]); + } + } +} + +void WebSocketClient::sendEncodedDataFast(char *str, uint8_t opcode) { + uint8_t mask[4]; + int size = strlen(str); + int size_buf = size + 1; + if (size > 125) { + size_buf += 3; + } else { + size_buf += 1; + } + if (WS_MASK > 0) { + size_buf += 4; + } + + char buf[size_buf]; + char tmp[2]; + + // Opcode; final fragment + sprintf(tmp, "%c", (char)(opcode | WS_FIN)); + strcpy(buf, tmp); + + // NOTE: no support for > 16-bit sized messages + if (size > 125) { + sprintf(tmp, "%c", (char)(WS_SIZE16 | WS_MASK)); + strcat(buf, tmp); + sprintf(tmp, "%c", (char) (size >> 8)); + strcat(buf, tmp); + sprintf(tmp, "%c", (char) (size & 0xFF)); + strcat(buf, tmp); + } else { + sprintf(tmp, "%c", (char) size | WS_MASK); + strcat(buf, tmp); + } + + if (WS_MASK > 0) { + mask[0] = random(0, 256); + mask[1] = random(0, 256); + mask[2] = random(0, 256); + mask[3] = random(0, 256); + + sprintf(tmp, "%c", (char) mask[0]); + strcat(buf, tmp); + sprintf(tmp, "%c", (char) mask[1]); + strcat(buf, tmp); + sprintf(tmp, "%c", (char) mask[2]); + strcat(buf, tmp); + sprintf(tmp, "%c", (char) mask[3]); + strcat(buf, tmp); + + for (int i=0; iwrite((uint8_t*)buf, size_buf); +} + + +void WebSocketClient::sendEncodedData(String str, uint8_t opcode) { + int size = str.length() + 1; + char cstr[size]; + + str.toCharArray(cstr, size); + + sendEncodedData(cstr, opcode); +} + + +void WebSocketClient::sendEncodedDataFast(String str, uint8_t opcode) { + int size = str.length() + 1; + char cstr[size]; + + str.toCharArray(cstr, size); + + sendEncodedDataFast(cstr, opcode); +} diff --git a/WebSocketClient.h b/src/WebSocketClient.h similarity index 84% rename from WebSocketClient.h rename to src/WebSocketClient.h index 89b7c23..600f57e 100644 --- a/WebSocketClient.h +++ b/src/WebSocketClient.h @@ -1,8 +1,10 @@ /* Websocket-Arduino, a websocket implementation for Arduino -Copyright 2011 Per Ejeklint +Copyright 2016 Brendan Hall Based on previous implementations by +Copyright 2011 Brendan Hall +and Copyright 2010 Ben Swanson and Copyright 2010 Randall Brewer @@ -51,11 +53,11 @@ Currently based off of "The Web Socket protocol" draft (v 75): // CRLF characters to terminate lines/handshakes in headers. #define CRLF "\r\n" -// Amount of time (in ms) a user may be connected before getting disconnected +// Amount of time (in ms) a user may be connected before getting disconnected // for timing out (i.e. not sending any data to the server). #define TIMEOUT_IN_MS 10000 -// ACTION_SPACE is how many actions are allowed in a program. Defaults to +// ACTION_SPACE is how many actions are allowed in a program. Defaults to // 5 unless overwritten by user. #ifndef CALLBACK_FUNCTIONS #define CALLBACK_FUNCTIONS 1 @@ -79,30 +81,35 @@ Currently based off of "The Web Socket protocol" draft (v 75): #define WS_OPCODE_PONG 0x0a // Second byte #define WS_MASK 0x80 +//#define WS_MASK 0x00 #define WS_SIZE16 126 #define WS_SIZE64 127 - + class WebSocketClient { public: // Handle connection requests to validate and process/refuse // connections. - bool handshake(Client &client); - + bool handshake(Client &client, bool socketio = false); + // Get data off of the stream bool getData(String& data, uint8_t *opcode = NULL); + bool getData(char *data, uint8_t *opcode = NULL); // Write data to the stream - void sendData(const char *str, uint8_t opcode = WS_OPCODE_TEXT); - void sendData(String str, uint8_t opcode = WS_OPCODE_TEXT); + void sendData(const char *str, uint8_t opcode = WS_OPCODE_TEXT, bool fast = true); + void sendData(String str, uint8_t opcode = WS_OPCODE_TEXT, bool fast = true); + bool issocketio; char *path; char *host; char *protocol; private: Client *socket_client; + // socket.io session id + char sid[32]; unsigned long _startMillis; const char *socket_urlPrefix; @@ -111,17 +118,21 @@ class WebSocketClient { // websocket connection. bool analyzeRequest(); - bool handleStream(String& data, uint8_t *opcode); - + bool handleStream(String& data, uint8_t *opcode); + bool handleStream(char *data, uint8_t *opcode); + // Disconnect user gracefully. void disconnectStream(); - + int timedRead(); void sendEncodedData(char *str, uint8_t opcode); void sendEncodedData(String str, uint8_t opcode); + + void sendEncodedDataFast(char *str, uint8_t opcode); + void sendEncodedDataFast(String str, uint8_t opcode); }; -#endif \ No newline at end of file +#endif diff --git a/global.h b/src/global.h similarity index 100% rename from global.h rename to src/global.h diff --git a/sha1.cpp b/src/sha1.cpp old mode 100755 new mode 100644 similarity index 97% rename from sha1.cpp rename to src/sha1.cpp index 770f6f5..33b631c --- a/sha1.cpp +++ b/src/sha1.cpp @@ -1,6 +1,10 @@ #include +//#ifdef ARDUINO_SAMD_ZERO +//#else +#ifdef __AVR__ #include #include +#endif #include "sha1.h" #define SHA1_K0 0x5a827999 @@ -8,7 +12,7 @@ #define SHA1_K40 0x8f1bbcdc #define SHA1_K60 0xca62c1d6 -uint8_t sha1InitState[] PROGMEM = { +const uint8_t sha1InitState[] PROGMEM = { 0x01,0x23,0x45,0x67, // H0 0x89,0xab,0xcd,0xef, // H1 0xfe,0xdc,0xba,0x98, // H2 @@ -101,7 +105,7 @@ void Sha1Class::pad() { uint8_t* Sha1Class::result(void) { // Pad to complete the last block pad(); - + // Swap byte order back for (int i=0; i<5; i++) { uint32_t a,b; @@ -112,7 +116,7 @@ uint8_t* Sha1Class::result(void) { b|=a>>24; state.w[i]=b; } - + // Return pointer to hash (20 characters) return state.b; } diff --git a/sha1.h b/src/sha1.h old mode 100755 new mode 100644 similarity index 100% rename from sha1.h rename to src/sha1.h