From 365cc8bd70720743928bd69c636bf5ffb2cee312 Mon Sep 17 00:00:00 2001 From: Davide Monari Date: Tue, 24 May 2016 10:59:27 +0200 Subject: [PATCH 01/10] First commit --- .gitignore | 1 - MD5.c | 303 ------------ MD5.h | 35 -- README.md | 36 +- WebSocketServer.cpp | 434 ------------------ WebSocketServer.h | 118 ----- examples/WebSocketServer.html | 101 ---- .../WebSocketServer_Demo.ino | 123 ----- keywords.txt | 24 + library.properties | 9 + Base64.cpp => src/Base64.cpp | 0 Base64.h => src/Base64.h | 0 .../WebSocketClient.cpp | 250 ++++++++-- WebSocketClient.h => src/WebSocketClient.h | 30 +- global.h => src/global.h | 0 sha1.cpp => src/sha1.cpp | 10 +- sha1.h => src/sha1.h | 0 17 files changed, 302 insertions(+), 1172 deletions(-) delete mode 100644 .gitignore delete mode 100644 MD5.c delete mode 100644 MD5.h delete mode 100644 WebSocketServer.cpp delete mode 100644 WebSocketServer.h delete mode 100644 examples/WebSocketServer.html delete mode 100644 examples/WebSocketServer_Demo/WebSocketServer_Demo.ino create mode 100644 keywords.txt create mode 100644 library.properties rename Base64.cpp => src/Base64.cpp (100%) mode change 100755 => 100644 rename Base64.h => src/Base64.h (100%) mode change 100755 => 100644 rename WebSocketClient.cpp => src/WebSocketClient.cpp (57%) rename WebSocketClient.h => src/WebSocketClient.h (86%) rename global.h => src/global.h (100%) rename sha1.cpp => src/sha1.cpp (97%) mode change 100755 => 100644 rename sha1.h => src/sha1.h (100%) mode change 100755 => 100644 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..88eb534 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,33 @@ -## 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 project prototype 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); +- for method `client.getData()`, I created a pure C string implementation, to avoid chances of heap fragmentation due to `String` class. -Start playing with your own code! +### Tests +The optimized code was tested for: -### 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. +- `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 -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. +We were able to obtain to reach the target throughput indicated above, with a message length of around 70 bytes (\*): -### 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. +(\*) In order to reeach that speed, we had to apply the following hack: + +We did not want to get the `loop()` stuck if the TCP message was not sent (via WiFi), and we could afford some data loss randomly; although, we wanted our data to be reliable on the server side, so we excluded UDP packets. -- Branden \ No newline at end of file +### 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/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/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..103bde9 --- /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 (e.g. EthernetClient(), WiFiClient(), ...) and is optimized in speed for data sending. +category=Communication +url=https://github.com/u0078867/Arduino-Websocket-Fast +architectures=* 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/WebSocketClient.cpp b/src/WebSocketClient.cpp similarity index 57% rename from WebSocketClient.cpp rename to src/WebSocketClient.cpp index bad1b9f..b635e4a 100644 --- a/WebSocketClient.cpp +++ b/src/WebSocketClient.cpp @@ -63,7 +63,7 @@ bool WebSocketClient::analyzeRequest() { #ifdef DEBUGGING Serial.println(F("Sending websocket upgrade headers")); -#endif +#endif socket_client->print(F("GET ")); socket_client->print(path); @@ -72,7 +72,7 @@ bool WebSocketClient::analyzeRequest() { socket_client->print(F("Connection: Upgrade\r\n")); socket_client->print(F("Host: ")); socket_client->print(host); - socket_client->print(CRLF); + socket_client->print(CRLF); socket_client->print(F("Sec-WebSocket-Key: ")); socket_client->print(key); socket_client->print(CRLF); @@ -84,7 +84,7 @@ bool WebSocketClient::analyzeRequest() { #ifdef DEBUGGING Serial.println(F("Analyzing response headers")); -#endif +#endif while (socket_client->connected() && !socket_client->available()) { delay(100); @@ -105,7 +105,7 @@ bool WebSocketClient::analyzeRequest() { } else if (temp.startsWith("Sec-WebSocket-Accept: ")) { serverKey = temp.substring(22,temp.length() - 2); // Don't save last CR+LF } - temp = ""; + temp = ""; } if (!socket_client->available()) { @@ -146,7 +146,7 @@ bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { if (!socket_client->connected() || !socket_client->available()) { return false; - } + } msgtype = timedRead(); if (!socket_client->connected()) { @@ -172,11 +172,11 @@ bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { if (!socket_client->connected()) { return false; } - + length |= timedRead(); if (!socket_client->connected()) { return false; - } + } } else if (length == WS_SIZE64) { #ifdef DEBUGGING @@ -208,14 +208,14 @@ bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { return false; } } - + data = ""; - + if (opcode != NULL) { *opcode = msgtype & ~WS_FIN; } - + if (hasMask) { 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; } @@ -242,7 +343,7 @@ void WebSocketClient::disconnectStream() { // 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(); @@ -250,31 +351,43 @@ void WebSocketClient::disconnectStream() { 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) { +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()) { - sendEncodedData(str, opcode); + if (fast) { + sendEncodedDataFast(str, opcode); + } else { + sendEncodedData(str, opcode); + } } } -void WebSocketClient::sendData(String str, uint8_t 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()) { - sendEncodedData(str, opcode); + if (fast) { + sendEncodedDataFast(str, opcode); + } else { + sendEncodedData(str, opcode); + } } } int WebSocketClient::timedRead() { while (!socket_client->available()) { - delay(20); + //delay(20); } return socket_client->read(); @@ -296,21 +409,88 @@ void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) { 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]); - + 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; iwrite(str[i] ^ mask[i % 4]); + if (WS_MASK > 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]; @@ -319,3 +499,13 @@ void WebSocketClient::sendEncodedData(String str, uint8_t opcode) { 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 86% rename from WebSocketClient.h rename to src/WebSocketClient.h index 89b7c23..e3b8108 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,23 +81,25 @@ 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 // if disabled (0x00) it might fasten sending #define WS_SIZE16 126 #define WS_SIZE64 127 - + class WebSocketClient { public: // Handle connection requests to validate and process/refuse // connections. bool handshake(Client &client); - + // 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); char *path; char *host; @@ -111,17 +115,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 From 19a3d0ca6d8692c82a3ad02f5c001eb643dfd752 Mon Sep 17 00:00:00 2001 From: Davide Monari Date: Tue, 24 May 2016 11:52:06 +0200 Subject: [PATCH 02/10] Updated README.md --- README.md | 40 +++++++++++++++++++++++++++++++++++----- src/WebSocketClient.h | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 88eb534..cbee600 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,27 @@ This is a simple library that implements a Websocket client running on an Arduin ### Rationale -For our IoT project prototype 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. +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. ### Features I added the following: -- 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); -- for method `client.getData()`, I created a pure C string implementation, to avoid chances of heap fragmentation due to `String` class. +- 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: + + ```C++ + webSocketClient.sendData("my string to send", WS_OPCODE_TEXT, true); + ``` + +- For method `client.getData()`, I created a pure C string implementation, to avoid chances of heap fragmentation due to `String` + class. Example: + + ```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) + ``` ### Tests The optimized code was tested for: @@ -23,8 +37,24 @@ The optimized code was tested for: We were able to obtain to reach the target throughput indicated above, with a message length of around 70 bytes (\*): (\*) In order to reeach that speed, we had to apply the following hack: - -We did not want to get the `loop()` stuck if the TCP message was not sent (via WiFi), and we could afford some data loss randomly; although, we wanted our data to be reliable on the server side, so we excluded UDP packets. + +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 on the server side, so we excluded UDP packets. + +2. After point 1, we had to manually disable the mask flag for websocket messages, by replacing this line in src /WebSocketClient.h: + + ```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. ### Notes See the original code from Branden for additional notes. diff --git a/src/WebSocketClient.h b/src/WebSocketClient.h index e3b8108..cb3091e 100644 --- a/src/WebSocketClient.h +++ b/src/WebSocketClient.h @@ -81,7 +81,7 @@ 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 // if disabled (0x00) it might fasten sending +//#define WS_MASK 0x00 #define WS_SIZE16 126 #define WS_SIZE64 127 From a24700cbb356cd0c8e8a8eb3eea0ba2d6648ebb0 Mon Sep 17 00:00:00 2001 From: Davide Monari Date: Tue, 24 May 2016 13:51:45 +0200 Subject: [PATCH 03/10] Updated README.md and library.properties --- README.md | 4 ++++ library.properties | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbee600..2ee4e94 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ We were able to obtain to reach the target throughput indicated above, with a me 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. diff --git a/library.properties b/library.properties index 103bde9..384b82a 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ 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 (e.g. EthernetClient(), WiFiClient(), ...) and is optimized in speed for 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=* +architectures=avr,samd From 0eb36e8bbad18b8777e297bf2a92b8582eb9eb4d Mon Sep 17 00:00:00 2001 From: Davide Monari Date: Tue, 24 May 2016 14:07:15 +0200 Subject: [PATCH 04/10] Updated README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ee4e94..e0850c0 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ The optimized code was tested for: We were able to obtain to reach the target throughput indicated above, with a message length of around 70 bytes (\*): -(\*) In order to reeach that speed, we had to apply the following hack: +(\*) 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 on the server side, so we excluded UDP packets. + 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. 2. After point 1, we had to manually disable the mask flag for websocket messages, by replacing this line in src /WebSocketClient.h: From d38db509d8e60fd06ef70514fcbc1dccee074495 Mon Sep 17 00:00:00 2001 From: Davide Monari Date: Tue, 24 May 2016 14:16:03 +0200 Subject: [PATCH 05/10] Updated example --- .../WebSocketClient_Demo.ino | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) 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); } From e4f75b68126c00679a3df2af317191115fd0bd57 Mon Sep 17 00:00:00 2001 From: Kenta Kusumoto Date: Mon, 6 Jun 2016 17:34:21 +0300 Subject: [PATCH 06/10] Add socket.io client --- .../SocketIOClient_Demo.ino | 132 ++++++++++++++++++ .../SocketIOClient_ISP8266_Demo.ino | 108 ++++++++++++++ src/WebSocketClient.cpp | 79 ++++++++--- src/WebSocketClient.h | 5 +- 4 files changed, 301 insertions(+), 23 deletions(-) create mode 100644 examples/SocketIOClient_Demo/SocketIOClient_Demo.ino create mode 100644 examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino diff --git a/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino new file mode 100644 index 0000000..cc2ace1 --- /dev/null +++ b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino @@ -0,0 +1,132 @@ +#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); + } + + // capture the value of analog 1, send it along + pinMode(1, INPUT); + data = String(analogRead(1)); + + webSocketClient.sendData(data); + + uint64_t now = millis(); + + if(now - messageTimestamp > INTERVAL) { + messageTimestamp = now; + // 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..0a6f6ff --- /dev/null +++ b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino @@ -0,0 +1,108 @@ +#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 "); + Serial.println(ssid); + + 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); + } + + // capture the value of analog 1, send it along + pinMode(1, INPUT); + data = String(analogRead(1)); + + webSocketClient.sendData(data); + + uint64_t now = millis(); + + if(now - messageTimestamp > INTERVAL) { + messageTimestamp = now; + // 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/src/WebSocketClient.cpp b/src/WebSocketClient.cpp index b635e4a..9d14d93 100644 --- a/src/WebSocketClient.cpp +++ b/src/WebSocketClient.cpp @@ -7,9 +7,11 @@ #include "base64.h" -bool WebSocketClient::handshake(Client &client) { +bool WebSocketClient::handshake(Client &client, bool socketio) { socket_client = &client; + issocketio = socketio; + strcpy(sid, ""); // If there is a connected client-> if (socket_client->connected()) { @@ -17,6 +19,10 @@ bool WebSocketClient::handshake(Client &client) { #ifdef DEBUGGING Serial.println(F("Client connected")); #endif + if (issocketio && strlen(sid) == 0) { + analyzeRequest(); + } + if (analyzeRequest()) { #ifdef DEBUGGING Serial.println(F("Websocket established")); @@ -43,43 +49,63 @@ bool WebSocketClient::analyzeRequest() { int bite; bool foundupgrade = false; + bool foundsid = false; unsigned long intkey[2]; String serverKey; char keyStart[17]; char b64Key[25]; String key = "------------------------"; - randomSeed(analogRead(0)); + if (!issocketio || (issocketio && strlen(sid) > 0)) { - for (int i=0; i<16; ++i) { - keyStart[i] = (char)random(1, 256); - } +#ifdef DEBUGGING + Serial.println(F("Sending websocket upgrade headers")); +#endif - base64_encode(b64Key, keyStart, 16); + randomSeed(analogRead(0)); - for (int i=0; i<24; ++i) { - key[i] = b64Key[i]; - } + 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 websocket upgrade headers")); + Serial.println(F("Sending socket.io session request 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("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(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 @@ -104,6 +130,10 @@ bool WebSocketClient::analyzeRequest() { 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 = ""; } @@ -113,6 +143,10 @@ bool WebSocketClient::analyzeRequest() { } } + if (issocketio && foundsid && !foundupgrade) { + return true; + } + key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; uint8_t *hash; char result[21]; @@ -347,6 +381,7 @@ void WebSocketClient::disconnectStream() { socket_client->flush(); delay(10); socket_client->stop(); + strcpy(sid, ""); } bool WebSocketClient::getData(String& data, uint8_t *opcode) { diff --git a/src/WebSocketClient.h b/src/WebSocketClient.h index cb3091e..600f57e 100644 --- a/src/WebSocketClient.h +++ b/src/WebSocketClient.h @@ -91,7 +91,7 @@ class WebSocketClient { // 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); @@ -101,12 +101,15 @@ class WebSocketClient { 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; From c2fd571aba7f90c53053b6230e40407065640411 Mon Sep 17 00:00:00 2001 From: Kenta Kusumoto Date: Mon, 6 Jun 2016 17:38:45 +0300 Subject: [PATCH 07/10] Remove extra sendData() in socket.io examples --- examples/SocketIOClient_Demo/SocketIOClient_Demo.ino | 10 ++++------ .../SocketIOClient_ISP8266_Demo.ino | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino index cc2ace1..245bcc2 100644 --- a/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino +++ b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino @@ -98,16 +98,14 @@ void loop() { Serial.println(data); } - // capture the value of analog 1, send it along - pinMode(1, INPUT); - data = String(analogRead(1)); - - webSocketClient.sendData(data); - uint64_t now = millis(); if(now - messageTimestamp > 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()); diff --git a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino index 0a6f6ff..0d55f8f 100644 --- a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino +++ b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino @@ -74,16 +74,14 @@ void loop() { Serial.println(data); } - // capture the value of analog 1, send it along - pinMode(1, INPUT); - data = String(analogRead(1)); - - webSocketClient.sendData(data); - uint64_t now = millis(); if(now - messageTimestamp > 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()); From 8fdaddb0d133eef0c0e3e6f26b2c09e35028d6c7 Mon Sep 17 00:00:00 2001 From: Kenta Kusumoto Date: Mon, 6 Jun 2016 17:46:15 +0300 Subject: [PATCH 08/10] Remove reference to variable ssid --- .../SocketIOClient_ISP8266_Demo.ino | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino index 0d55f8f..ac8db60 100644 --- a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino +++ b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino @@ -21,9 +21,7 @@ void setup() { Serial.begin(115200); - Serial.print("Connecting to "); - Serial.println(ssid); - + Serial.print("Connecting to Wifi"); WiFi.begin("ssid", "password"); while (WiFi.status() != WL_CONNECTED) { From cbac1aae0a23603fbd845bd7e72803c66c5a8998 Mon Sep 17 00:00:00 2001 From: Kenta Kusumoto Date: Mon, 6 Jun 2016 17:49:24 +0300 Subject: [PATCH 09/10] Fix reference to INTERVAL --- examples/SocketIOClient_Demo/SocketIOClient_Demo.ino | 2 +- .../SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino index 245bcc2..c813be0 100644 --- a/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino +++ b/examples/SocketIOClient_Demo/SocketIOClient_Demo.ino @@ -100,7 +100,7 @@ void loop() { uint64_t now = millis(); - if(now - messageTimestamp > INTERVAL) { + if(now - messageTimestamp > MESSAGE_INTERVAL) { messageTimestamp = now; // capture the value of analog 1, send it along pinMode(1, INPUT); diff --git a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino index ac8db60..413eb13 100644 --- a/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino +++ b/examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino @@ -74,7 +74,7 @@ void loop() { uint64_t now = millis(); - if(now - messageTimestamp > INTERVAL) { + if(now - messageTimestamp > MESSAGE_INTERVAL) { messageTimestamp = now; // capture the value of analog 1, send it along pinMode(1, INPUT); From 44bedfbdd217e0cc541cfc9248b2634205615a4d Mon Sep 17 00:00:00 2001 From: Davide Monari Date: Tue, 28 Jun 2016 09:55:11 +0200 Subject: [PATCH 10/10] Merged pull #1; Updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e0850c0..11ffdbb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ We were able to obtain to reach the target throughput indicated above, with a me 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: