From 870804176b42bb15d9cbe82bee26a0d01d32e3c1 Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Wed, 4 Apr 2012 17:07:21 -0400 Subject: [PATCH 1/9] Working with hixie-76 --- Base64.cpp | 133 ++++++++++++++++++ Base64.h | 75 ++++++++++ MD5.c | 16 +-- WebSocket.cpp | 361 +++++++++++++++++++++++++++++++++---------------- WebSocket.h | 29 ++-- sha1.cpp | 152 +++++++++++++++++++++ sha1.h | 43 ++++++ websocket.html | 6 +- 8 files changed, 677 insertions(+), 138 deletions(-) create mode 100755 Base64.cpp create mode 100755 Base64.h create mode 100755 sha1.cpp create mode 100755 sha1.h diff --git a/Base64.cpp b/Base64.cpp new file mode 100755 index 0000000..62f517f --- /dev/null +++ b/Base64.cpp @@ -0,0 +1,133 @@ +#include "Base64.h" + +const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* 'Private' declarations */ +inline void a3_to_a4(unsigned char * a4, unsigned char * a3); +inline void a4_to_a3(unsigned char * a3, unsigned char * a4); +inline unsigned char b64_lookup(char c); + +int base64_encode(char *output, char *input, int inputLen) { + int i = 0, j = 0; + int encLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + while(inputLen--) { + a3[i++] = *(input++); + if(i == 3) { + a3_to_a4(a4, a3); + + for(i = 0; i < 4; i++) { + output[encLen++] = b64_alphabet[a4[i]]; + } + + i = 0; + } + } + + if(i) { + for(j = i; j < 3; j++) { + a3[j] = '\0'; + } + + a3_to_a4(a4, a3); + + for(j = 0; j < i + 1; j++) { + output[encLen++] = b64_alphabet[a4[j]]; + } + + while((i++ < 3)) { + output[encLen++] = '='; + } + } + output[encLen] = '\0'; + return encLen; +} + +int base64_decode(char * output, char * input, int inputLen) { + int i = 0, j = 0; + int decLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + + while (inputLen--) { + if(*input == '=') { + break; + } + + a4[i++] = *(input++); + if (i == 4) { + for (i = 0; i <4; i++) { + a4[i] = b64_lookup(a4[i]); + } + + a4_to_a3(a3,a4); + + for (i = 0; i < 3; i++) { + output[decLen++] = a3[i]; + } + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) { + a4[j] = '\0'; + } + + for (j = 0; j <4; j++) { + a4[j] = b64_lookup(a4[j]); + } + + a4_to_a3(a3,a4); + + for (j = 0; j < i - 1; j++) { + output[decLen++] = a3[j]; + } + } + output[decLen] = '\0'; + return decLen; +} + +int base64_enc_len(int plainLen) { + int n = plainLen; + return (n + 2 - ((n + 2) % 3)) / 3 * 4; +} + +int base64_dec_len(char * input, int inputLen) { + int i = 0; + int numEq = 0; + for(i = inputLen - 1; input[i] == '='; i--) { + numEq++; + } + + return ((6 * inputLen) / 8) - numEq; +} + +inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + a4[3] = (a3[2] & 0x3f); +} + +inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; +} + +inline unsigned char b64_lookup(char c) { + int i; + for(i = 0; i < 64; i++) { + if(b64_alphabet[i] == c) { + return i; + } + } + + return -1; +} diff --git a/Base64.h b/Base64.h new file mode 100755 index 0000000..cd17c53 --- /dev/null +++ b/Base64.h @@ -0,0 +1,75 @@ +#ifndef _BASE64_H +#define _BASE64_H + +/* b64_alphabet: + * Description: Base64 alphabet table, a mapping between integers + * and base64 digits + * Notes: This is an extern here but is defined in Base64.c + */ +extern const char b64_alphabet[]; + +/* base64_encode: + * Description: + * Encode a string of characters as base64 + * Parameters: + * output: the output buffer for the encoding, stores the encoded string + * input: the input buffer for the encoding, stores the binary to be encoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the encoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_encode(char *output, char *input, int inputLen); + +/* base64_decode: + * Description: + * Decode a base64 encoded string into bytes + * Parameters: + * output: the output buffer for the decoding, + * stores the decoded binary + * input: the input buffer for the decoding, + * stores the base64 string to be decoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the decoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_decode(char *output, char *input, int inputLen); + +/* base64_enc_len: + * Description: + * Returns the length of a base64 encoded string whose decoded + * form is inputLen bytes long + * Parameters: + * inputLen: the length of the decoded string + * Return value: + * The length of a base64 encoded string whose decoded form + * is inputLen bytes long + * Requirements: + * None + */ +int base64_enc_len(int inputLen); + +/* base64_dec_len: + * Description: + * Returns the length of the decoded form of a + * base64 encoded string + * Parameters: + * input: the base64 encoded string to be measured + * inputLen: the length of the base64 encoded string + * Return value: + * Returns the length of the decoded form of a + * base64 encoded string + * Requirements: + * 1. input must not be null + * 2. input must be greater than or equal to zero + */ +int base64_dec_len(char *input, int inputLen); + +#endif // _BASE64_H diff --git a/MD5.c b/MD5.c index c282b20..5edaf5c 100644 --- a/MD5.c +++ b/MD5.c @@ -56,10 +56,10 @@ static unsigned char PADDING[64] = { }; /* F, G, H and I are basic MD5 functions. */ -#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) -#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) -#define I(x, y, z) ((y) ^ ((x) | (~z))) +#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)))) @@ -68,22 +68,22 @@ static unsigned char PADDING[64] = { * Rotation is separate from addition to prevent recomputation. */ #define FF(a, b, c, d, x, s, ac) { \ - (a) += F ((b), (c), (d)) + (x) + (UINT4)(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) += G ((b), (c), (d)) + (x) + (UINT4)(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) += H ((b), (c), (d)) + (x) + (UINT4)(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) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) += MI ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } diff --git a/WebSocket.cpp b/WebSocket.cpp index a34e4f5..e3bc97e 100644 --- a/WebSocket.cpp +++ b/WebSocket.cpp @@ -1,42 +1,48 @@ +#include "global.h" #include "WebSocket.h" #include "MD5.c" +#include "sha1.h" +#include "base64.h" #define DEBUGGING -WebSocket::WebSocket(const char *urlPrefix, int inPort) : - socket_server(inPort), - socket_client(255), +WebSocket::WebSocket(const char *urlPrefix) : socket_actions_population(0), socket_urlPrefix(urlPrefix) { -} -void WebSocket::begin() { - socket_server.begin(); } -void WebSocket::connectionRequest() { - // This pulls any connected client into an active stream. - socket_client = socket_server.available(); - // If there is a connected client. - if (socket_client.connected()) { +void WebSocket::connectionRequest(Client &client) { + + socket_client = &client; + + // If there is a connected client-> + if (socket_client->connected()) { + Serial.println(F("Connection!")); // Check request and look for websocket handshake #ifdef DEBUGGING - Serial.println("Client connected"); + Serial.println(F("Client connected")); #endif if (analyzeRequest(BUFFER_LENGTH)) { #ifdef DEBUGGING - Serial.println("Websocket established"); + Serial.println(F("Websocket established")); #endif - socketStream(BUFFER_LENGTH); + + if (oldstyle) { + handleOldStream(BUFFER_LENGTH); + } else { + handleNewStream(BUFFER_LENGTH); + } + #ifdef DEBUGGING - Serial.println("Websocket dropped"); + Serial.println(F("Websocket dropped")); #endif } else { // Might just need to break until out of socket_client loop. #ifdef DEBUGGING - Serial.println("Disconnecting client"); + Serial.println(F("Disconnecting client")); #endif disconnectStream(); } @@ -45,20 +51,24 @@ void WebSocket::connectionRequest() { bool WebSocket::analyzeRequest(int bufferLength) { // Use String library to do some sort of read() magic here. - String temp = String(60); + String temp; char bite; bool foundupgrade = false; - String key[2]; + String oldkey[2]; unsigned long intkey[2]; + String newkey; #ifdef DEBUGGING - Serial.println("Analyzing request headers"); + Serial.println(F("Analyzing request headers")); #endif - + + temp.reserve(60); + // TODO: More robust string extraction - while ((bite = socket_client.read()) != -1) { + while ((bite = socket_client->read()) != -1) { temp += bite; + delay(10); if (bite == '\n') { #ifdef DEBUGGING @@ -68,102 +78,187 @@ bool WebSocket::analyzeRequest(int bufferLength) { // 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; + foundupgrade = true; + oldstyle = true; + } else if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { + foundupgrade = true; + oldstyle = 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")) { - key[0]=temp.substring(20,temp.length() - 2); // Don't save last CR+LF - } else if (temp.startsWith("Sec-WebSocket-Key2")) { - key[1]=temp.substring(20,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 = ""; } } temp += 0; // Terminate string - + // Assert that we have all headers that are needed. If so, go ahead and // send response headers. - if (foundupgrade == true && host.length() > 0 && key[0].length() > 0 && key[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 < key[i].length(); c++) { - char ac = key[i].charAt(c); - if (ac >= '0' && ac <= '9') { - numbers += ac; - } - if (ac == ' ') { - spaces++; + if (foundupgrade == true) { + + if (oldstyle && 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; } - 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); - -#ifdef DEBUGGING - Serial.println("Sending response header"); + + 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); + +#ifdef DEBUGGING + Serial.println(F("Sending old-style response header")); +#endif + 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; + } else if (!oldstyle && newkey.length() > 0) { + + +#ifdef DEBUGGING + Serial.println(F("Sending new-style response header")); #endif - socket_client.print("HTTP/1.1 101 Web Socket Protocol Handshake\r\n"); - socket_client.print("Upgrade: WebSocket\r\n"); - socket_client.print("Connection: Upgrade\r\n"); - socket_client.print("Sec-WebSocket-Origin: "); - socket_client.print(origin); - socket_client.print(CRLF); - - // The "Host:" value should be used as location - socket_client.print("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; + + // 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); + + Serial.println(b64Result); + + 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 { + return false; + } } else { // Nope, failed handshake. Disconnect #ifdef DEBUGGING - Serial.println("Header mismatch"); + Serial.println(F("Header mismatch")); #endif return false; } } -void WebSocket::socketStream(int socketBufferLength) { +void WebSocket::handleOldStream(int socketBufferLength) { + int bite; + int frameLength = 0; + // String to hold bytes sent by client to server. + String socketString; + + while (socket_client->connected()) { + if (socket_client->available()) { + bite = socket_client->read(); + + if (bite != -1) { + + if (bite == 0) + continue; // Frame start, don't save + if ((uint8_t) bite == 0xFF) { + // Frame end. Process what we got. + executeActions(socketString); + // Reset buffer + socketString = ""; + } else { + socketString += (char)bite; + frameLength++; + + if (frameLength > MAX_FRAME_LENGTH) { + // Too big to handle! Abort and disconnect. +#ifdef DEBUGGING + Serial.print("Client send frame exceeding "); + Serial.print(MAX_FRAME_LENGTH); + Serial.println(" bytes"); +#endif + return; + } + } + } + } + } +} + +void WebSocket::handleNewStream(int socketBufferLength) { char bite; int frameLength = 0; // String to hold bytes sent by client to server. - String socketString = String(socketBufferLength); + String socketString; - while (socket_client.connected()) { - if (socket_client.available()) { - bite = socket_client.read(); + while (socket_client->connected()) { + if (socket_client->available()) { + bite = socket_client->read(); if (bite == 0) continue; // Frame start, don't save if ((uint8_t) bite == 0xFF) { @@ -171,27 +266,29 @@ void WebSocket::socketStream(int socketBufferLength) { executeActions(socketString); // Reset buffer socketString = ""; - } - - socketString += bite; - frameLength++; - - if (frameLength > MAX_FRAME_LENGTH) { - // Too big to handle! Abort and disconnect. + } else { + socketString += bite; + frameLength++; + + if (frameLength > MAX_FRAME_LENGTH) { + // Too big to handle! Abort and disconnect. #ifdef DEBUGGING - Serial.print("Client send frame exceeding "); - Serial.print(MAX_FRAME_LENGTH); - Serial.println(" bytes"); + Serial.print("Client send frame exceeding "); + Serial.print(MAX_FRAME_LENGTH); + Serial.println(" bytes"); #endif - return; + return; + } } + + } } } void WebSocket::addAction(Action *socketAction) { #ifdef DEBUGGING - Serial.println("Adding actions"); + Serial.println(F("Adding actions")); #endif if (socket_actions_population <= SIZE(socket_actions)) { socket_actions[socket_actions_population++].socketAction = socketAction; @@ -200,22 +297,22 @@ void WebSocket::addAction(Action *socketAction) { void WebSocket::disconnectStream() { #ifdef DEBUGGING - Serial.println("Terminating socket"); + Serial.println(F("Terminating socket")); #endif // Should send 0xFF00 to client to tell it I'm quitting here. // TODO: Check if I understood this properly - socket_client.write((uint8_t) 0xFF); - socket_client.write((uint8_t) 0x00); + socket_client->write((uint8_t) 0xFF); + socket_client->write((uint8_t) 0x00); - socket_client.flush(); - delay(1); - socket_client.stop(); + socket_client->flush(); + delay(10); + socket_client->stop(); } void WebSocket::executeActions(String socketString) { for (int i = 0; i < socket_actions_population; ++i) { #ifdef DEBUGGING - Serial.print("Executing Action "); + Serial.print(F("Executing Action ")); Serial.println(i + 1); #endif socket_actions[i].socketAction(*this, socketString); @@ -224,12 +321,48 @@ void WebSocket::executeActions(String socketString) { void WebSocket::sendData(const char *str) { #ifdef DEBUGGING - Serial.print("Sending data: "); + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + if (oldstyle) { + socket_client->write(0x00); // Frame start + socket_client->print(str); + socket_client->write(0xFF); // Frame end + } else { + socket_client->print(wsEncode(str)); + } + socket_client->flush(); + + delay(10); + } +} + +void WebSocket::sendData(String str) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); Serial.println(str); #endif - if (socket_client.connected()) { - socket_client.print((uint8_t) 0x00); // Frame start - socket_client.print(str); - socket_client.print((uint8_t) 0xFF); // Frame end + if (socket_client->connected()) { + if (oldstyle) { + socket_client->write(0x00); // Frame start + socket_client->print(str); + socket_client->write(0xFF); // Frame end + } else { + socket_client->print(wsEncode(str)); + } + socket_client->flush(); + + delay(10); } } + +String WebSocket::wsEncode(char *str) { + String result; + return result; +} + +String WebSocket::wsEncode(String str) { + String result; + return result; +} diff --git a/WebSocket.h b/WebSocket.h index 8cef828..4a93490 100644 --- a/WebSocket.h +++ b/WebSocket.h @@ -39,15 +39,16 @@ Currently based off of "The Web Socket protocol" draft (v 75): http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 */ -#include "WProgram.h" -#include - -#include -#include #ifndef WEBSOCKET_H_ #define WEBSOCKET_H_ +#include +#include +#include "String.h" +#include "Server.h" +#include "Client.h" + // CRLF characters to terminate lines/handshakes in headers. #define CRLF "\r\n" @@ -73,37 +74,36 @@ Currently based off of "The Web Socket protocol" draft (v 75): class WebSocket { public: // Constructor for websocket class. - WebSocket(const char *urlPrefix = "/", int inPort = 80); + WebSocket(const char *urlPrefix = "/"); // Processor prototype. Processors allow the websocket server to // respond to input from client based on what the client supplies. typedef void Action(WebSocket &socket, String &socketString); - // Start the socket listening for connections. - void begin(); - // Handle connection requests to validate and process/refuse // connections. - void connectionRequest(); + void connectionRequest(Client &client); // Loop to read information from the user. Returns false if user // disconnects, server must disconnect, or an error occurs. - void socketStream(int socketBufferLink); + void handleOldStream(int socketBufferLink); + void handleNewStream(int socketBufferLink); // Adds each action to the list of actions for the program to run. void addAction(Action *socketAction); // Custom write for actions. void sendData(const char *str); + void sendData(String str); private: - Server socket_server; - Client socket_client; + Client *socket_client; const char *socket_urlPrefix; String origin; String host; + bool oldstyle; struct ActionPack { Action *socketAction; @@ -122,6 +122,9 @@ class WebSocket { // Returns true if the action was executed. It is up to the user to // write the logic of the action. void executeActions(String socketString); + + String wsEncode(char *str); + String wsEncode(String str); }; diff --git a/sha1.cpp b/sha1.cpp new file mode 100755 index 0000000..770f6f5 --- /dev/null +++ b/sha1.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include "sha1.h" + +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +uint8_t sha1InitState[] PROGMEM = { + 0x01,0x23,0x45,0x67, // H0 + 0x89,0xab,0xcd,0xef, // H1 + 0xfe,0xdc,0xba,0x98, // H2 + 0x76,0x54,0x32,0x10, // H3 + 0xf0,0xe1,0xd2,0xc3 // H4 +}; + +void Sha1Class::init(void) { + memcpy_P(state.b,sha1InitState,HASH_LENGTH); + byteCount = 0; + bufferOffset = 0; +} + +uint32_t Sha1Class::rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void Sha1Class::hashBlock() { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=state.w[0]; + b=state.w[1]; + c=state.w[2]; + d=state.w[3]; + e=state.w[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = buffer.w[(i+13)&15] ^ buffer.w[(i+8)&15] ^ buffer.w[(i+2)&15] ^ buffer.w[i&15]; + buffer.w[i&15] = rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=rol32(a,5) + e + buffer.w[i&15]; + e=d; + d=c; + c=rol32(b,30); + b=a; + a=t; + } + state.w[0] += a; + state.w[1] += b; + state.w[2] += c; + state.w[3] += d; + state.w[4] += e; +} + +void Sha1Class::addUncounted(uint8_t data) { + buffer.b[bufferOffset ^ 3] = data; + bufferOffset++; + if (bufferOffset == BLOCK_LENGTH) { + hashBlock(); + bufferOffset = 0; + } +} + +size_t Sha1Class::write(uint8_t data) { + ++byteCount; + addUncounted(data); + + return sizeof(data); +} + +void Sha1Class::pad() { + // Implement SHA-1 padding (fips180-2 ยง5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + addUncounted(0x80); + while (bufferOffset != 56) addUncounted(0x00); + + // Append length in the last 8 bytes + addUncounted(0); // We're only using 32 bit lengths + addUncounted(0); // But SHA-1 supports 64 bit lengths + addUncounted(0); // So zero pad the top bits + addUncounted(byteCount >> 29); // Shifting to multiply by 8 + addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as + addUncounted(byteCount >> 13); // byte. + addUncounted(byteCount >> 5); + addUncounted(byteCount << 3); +} + + +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; + a=state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + state.w[i]=b; + } + + // Return pointer to hash (20 characters) + return state.b; +} + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +void Sha1Class::initHmac(const uint8_t* key, int keyLength) { + uint8_t i; + memset(keyBuffer,0,BLOCK_LENGTH); + if (keyLength > BLOCK_LENGTH) { + // Hash long keys + init(); + for (;keyLength--;) write(*key++); + memcpy(keyBuffer,result(),HASH_LENGTH); + } else { + // Block length keys are used as is + memcpy(keyBuffer,key,keyLength); + } + // Start inner hash + init(); + for (i=0; i +#include "Print.h" + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +}; +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +}; + +class Sha1Class : public Print +{ + public: + void init(void); + void initHmac(const uint8_t* secret, int secretLength); + uint8_t* result(void); + uint8_t* resultHmac(void); + virtual size_t write(uint8_t); + using Print::write; + private: + void pad(); + void addUncounted(uint8_t data); + void hashBlock(); + uint32_t rol32(uint32_t number, uint8_t bits); + _buffer buffer; + uint8_t bufferOffset; + _state state; + uint32_t byteCount; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; + +}; +extern Sha1Class Sha1; + +#endif diff --git a/websocket.html b/websocket.html index 7ce5e68..c4a4244 100644 --- a/websocket.html +++ b/websocket.html @@ -15,13 +15,13 @@ try { window.console.log("Setting up socket"); //ws = new WebSocket("ws://109.228.139.253:8383/"); - ws = new WebSocket("ws://10.0.1.83:8383/"); + ws = new WebSocket("ws://10.1.1.119:80/"); ws.onmessage = function(evt) { window.console.log(evt.data); $("#msg").append("

"+evt.data+"

"); }; ws.onerror = function(evt) { - window.console.log(evt.data); + window.console.log(evt); $("#msg").append("

ERROR: "+evt.data+"

"); }; ws.onclose = function() { @@ -31,7 +31,7 @@ ws.onopen = function() { window.console.log("onopen called"); debug("connected..."); - ws.send("Hello, Ardunio"); + ws.send("Hello, Arduino"); }; } catch(exception) { window.console.log('

Error'+exception); From b883bedc77d201cb7f37c9a3ba39827801bed779 Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Fri, 27 Apr 2012 14:07:14 -0400 Subject: [PATCH 2/9] updated --- .DS_Store | Bin 0 -> 6148 bytes WebSocket.cpp | 287 ++++++++++++------ WebSocket.h | 17 +- examples/.DS_Store | Bin 0 -> 6148 bytes .../Websocket_Actions/Websocket_Actions.pde | 53 ---- examples/Websocket_Demo/.DS_Store | Bin 0 -> 6148 bytes examples/Websocket_Demo/Websocket_Demo.ino | 156 ++++++++++ examples/Websocket_Demo/Websocket_Demo.pde | 43 --- examples/websocket_test.html | 63 ++++ websocket.html | 54 ---- 10 files changed, 419 insertions(+), 254 deletions(-) create mode 100644 .DS_Store create mode 100644 examples/.DS_Store delete mode 100644 examples/Websocket_Actions/Websocket_Actions.pde create mode 100644 examples/Websocket_Demo/.DS_Store create mode 100644 examples/Websocket_Demo/Websocket_Demo.ino delete mode 100644 examples/Websocket_Demo/Websocket_Demo.pde create mode 100644 examples/websocket_test.html delete mode 100644 websocket.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 if (socket_client->connected()) { - Serial.println(F("Connection!")); // Check request and look for websocket handshake - #ifdef DEBUGGING +#ifdef DEBUGGING Serial.println(F("Client connected")); - #endif +#endif if (analyzeRequest(BUFFER_LENGTH)) { - #ifdef DEBUGGING +#ifdef DEBUGGING Serial.println(F("Websocket established")); - #endif +#endif - if (oldstyle) { - handleOldStream(BUFFER_LENGTH); + if (hixie76style) { + +#ifdef SUPPORT_HIXIE_76 + handleHixie76Stream(BUFFER_LENGTH); +#endif } else { - handleNewStream(BUFFER_LENGTH); + handleStream(BUFFER_LENGTH); + delay(20); + } - #ifdef DEBUGGING - Serial.println(F("Websocket dropped")); - #endif +#ifdef DEBUGGING + Serial.println(F("Websocket dropped")); +#endif } else { // Might just need to break until out of socket_client loop. - #ifdef DEBUGGING - Serial.println(F("Disconnecting client")); - #endif +#ifdef DEBUGGING + Serial.println(F("Disconnecting client")); +#endif disconnectStream(); } } @@ -53,36 +62,36 @@ bool WebSocket::analyzeRequest(int bufferLength) { // Use String library to do some sort of read() magic here. String temp; - char bite; + 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 - temp.reserve(60); - // TODO: More robust string extraction while ((bite = socket_client->read()) != -1) { - temp += bite; - delay(10); - if (bite == '\n') { - #ifdef DEBUGGING + temp += (char)bite; + + if ((char)bite == '\n') { +#ifdef DEBUGGING Serial.print("Got Line: " + temp); - #endif +#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; - oldstyle = true; + hixie76style = true; } else if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { foundupgrade = true; - oldstyle = false; + 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: ")) { @@ -96,6 +105,16 @@ bool WebSocket::analyzeRequest(int bufferLength) { } temp = ""; } + + if (!socket_client->available()) { + delay(20); + } + } + + Serial.println("DONE WITH HEADERS!"); + + if (!socket_client->connected()) { + return false; } temp += 0; // Terminate string @@ -104,7 +123,10 @@ bool WebSocket::analyzeRequest(int bufferLength) { // send response headers. if (foundupgrade == true) { - if (oldstyle && host.length() > 0 && oldkey[0].length() > 0 && oldkey[1].length() > 0) { + Serial.println("FOUND UPGRADE!"); + +#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 @@ -144,9 +166,6 @@ bool WebSocket::analyzeRequest(int bufferLength) { unsigned char md5Digest[16]; MD5(challenge, md5Digest, 16); -#ifdef DEBUGGING - Serial.println(F("Sending old-style response header")); -#endif 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")); @@ -164,13 +183,11 @@ bool WebSocket::analyzeRequest(int bufferLength) { socket_client->write(md5Digest, 16); return true; - } else if (!oldstyle && newkey.length() > 0) { - - -#ifdef DEBUGGING - Serial.println(F("Sending new-style response header")); + } #endif + if (!hixie76style && newkey.length() > 0) { + // add the magic string newkey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -189,8 +206,6 @@ bool WebSocket::analyzeRequest(int bufferLength) { base64_encode(b64Result, result, 20); - Serial.println(b64Result); - 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")); @@ -201,6 +216,10 @@ bool WebSocket::analyzeRequest(int bufferLength) { return true; } else { + Serial.println("WHOA!"); + Serial.println(hixie76style); + Serial.println(newkey.length()); + return false; } } else { @@ -212,77 +231,126 @@ bool WebSocket::analyzeRequest(int bufferLength) { } } -void WebSocket::handleOldStream(int socketBufferLength) { +#ifdef SUPPORT_HIXIE_76 +void WebSocket::handleHixie76Stream(int socketBufferLength) { int bite; int frameLength = 0; // String to hold bytes sent by client to server. String socketString; while (socket_client->connected()) { - if (socket_client->available()) { - bite = socket_client->read(); - - if (bite != -1) { - - if (bite == 0) - continue; // Frame start, don't save - if ((uint8_t) bite == 0xFF) { - // Frame end. Process what we got. - executeActions(socketString); - // Reset buffer - socketString = ""; - } else { - socketString += (char)bite; - frameLength++; - - if (frameLength > MAX_FRAME_LENGTH) { - // Too big to handle! Abort and disconnect. -#ifdef DEBUGGING - Serial.print("Client send frame exceeding "); - Serial.print(MAX_FRAME_LENGTH); - Serial.println(" bytes"); -#endif - return; - } - } - } - } - } -} - -void WebSocket::handleNewStream(int socketBufferLength) { - char bite; - int frameLength = 0; - // String to hold bytes sent by client to server. - String socketString; + bite = timedRead(); - while (socket_client->connected()) { - if (socket_client->available()) { - bite = socket_client->read(); + if (bite != -1) { if (bite == 0) continue; // Frame start, don't save + if ((uint8_t) bite == 0xFF) { // Frame end. Process what we got. executeActions(socketString); // Reset buffer socketString = ""; } else { - socketString += bite; + socketString += (char)bite; frameLength++; - + if (frameLength > MAX_FRAME_LENGTH) { // Too big to handle! Abort and disconnect. -#ifdef DEBUGGING + #ifdef DEBUGGING Serial.print("Client send frame exceeding "); Serial.print(MAX_FRAME_LENGTH); Serial.println(" bytes"); + #endif + return; + } + } + } + } +} + #endif + +void WebSocket::handleStream(int socketBufferLength) { + 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; + + while (socket_client->connected()) { + + if (socket_client->available()) { + + msgtype = timedRead(); + if (!socket_client->connected()) { + return; + } + + length = timedRead() & 127; + if (!socket_client->connected()) { + return; + } + + index = 6; + + if (length == 126) { + length = timedRead() << 8; + if (!socket_client->connected()) { + return; + } + + length |= timedRead(); + if (!socket_client->connected()) { return; - } + } + + } 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; + } + + mask[1] = timedRead(); + if (!socket_client->connected()) { + + return; + } + + mask[2] = timedRead(); + if (!socket_client->connected()) { + return; + } + + mask[3] = timedRead(); + if (!socket_client->connected()) { + return; + } + + for (i=0; iconnected()) { + return; + } + } + + executeActions(socketString); + socketString = ""; } + + // need this wait to prevent hanging } } @@ -325,16 +393,13 @@ void WebSocket::sendData(const char *str) { Serial.println(str); #endif if (socket_client->connected()) { - if (oldstyle) { + if (hixie76style) { socket_client->write(0x00); // Frame start socket_client->print(str); socket_client->write(0xFF); // Frame end } else { - socket_client->print(wsEncode(str)); - } - socket_client->flush(); - - delay(10); + sendEncodedData(str); + } } } @@ -344,25 +409,49 @@ void WebSocket::sendData(String str) { Serial.println(str); #endif if (socket_client->connected()) { - if (oldstyle) { + if (hixie76style) { socket_client->write(0x00); // Frame start socket_client->print(str); socket_client->write(0xFF); // Frame end } else { - socket_client->print(wsEncode(str)); + sendEncodedData(str); } - socket_client->flush(); - - delay(10); } } -String WebSocket::wsEncode(char *str) { - String result; - return result; +int WebSocket::timedRead() { + while (!socket_client->available()) { + delay(20); + } + + return socket_client->read(); } -String WebSocket::wsEncode(String str) { - String result; - return result; +void WebSocket::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 WebSocket::sendEncodedData(String str) { + int size = str.length() + 1; + char cstr[size]; + + str.toCharArray(cstr, size); + + sendEncodedData(cstr); } diff --git a/WebSocket.h b/WebSocket.h index 4a93490..521bf33 100644 --- a/WebSocket.h +++ b/WebSocket.h @@ -86,8 +86,12 @@ class WebSocket { // Loop to read information from the user. Returns false if user // disconnects, server must disconnect, or an error occurs. - void handleOldStream(int socketBufferLink); - void handleNewStream(int socketBufferLink); + +#ifdef SUPPORT_HIXIE_76 + void handleHixie76Stream(int socketBufferLink); +#endif + + void handleStream(int socketBufferLink); // Adds each action to the list of actions for the program to run. void addAction(Action *socketAction); @@ -98,12 +102,13 @@ class WebSocket { private: Client *socket_client; + unsigned long _startMillis; const char *socket_urlPrefix; String origin; String host; - bool oldstyle; + bool hixie76style; struct ActionPack { Action *socketAction; @@ -123,8 +128,10 @@ class WebSocket { // write the logic of the action. void executeActions(String socketString); - String wsEncode(char *str); - String wsEncode(String str); + int timedRead(); + + void sendEncodedData(char *str); + void sendEncodedData(String str); }; diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 -#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 - -#define PREFIX "/ws" -#define PORT 8080 - -byte mac[] = { 0x52, 0x4F, 0x43, 0x4B, 0x45, 0x54 }; -byte ip[] = { 192, 168, 4, 2 }; - -// Create a Websocket server listening to http://192.168.4.2:8080/ws/ -WebSocket websocketServer(PREFIX, PORT); - -// You must have at least one function with the following signature. -// It will be called by the server when a data frame is received. -void dataReceivedAction(WebSocket &socket, String &dataString) { - // I just had a LED on pin 9 - if (dataString == "1") { - digitalWrite(9, HIGH); - } else { - digitalWrite(9, LOW); - } - - socket.sendData("Ok"); -} - -void setup() { -#ifdef DEBUGGING - Serial.begin(57600); -#endif - pinMode(9, OUTPUT); - Ethernet.begin(mac, ip); - websocketServer.begin(); - // Add the callback function to the server. You can have several callback functions - // if you like, they will be called with the same data and in the same order as you - // add them to the server. If you have more than one, define CALLBACK_FUNCTIONS before including - // WebSocket.h - websocketServer.addAction(&dataReceivedAction); - delay(1000); // Give Ethernet time to get ready -} - -void loop() { - // Don't add any code after next line. It will never be called. - websocketServer.connectionRequest(); -} diff --git a/examples/Websocket_Demo/.DS_Store b/examples/Websocket_Demo/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 +#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); +WebSocket websocketServer; + + +// Called when a new message from the WebSocket is received +// Looks for a message in this form: +// +// WDPV +// +// Where: W is either 'w' or 'r' - write or read +// D is either 'd' or 'a' - digital or analog +// P is a pin # +// V is optionally the value to apply to the pin +// +// If the message is a read message then the client will be sent +// back a message in the following form: +// +// P:V +// +// Where: P is a pin # +// V is the value of that pin +void dataReceivedAction(WebSocket &socket, String &dataString) { + bool isWrite = dataString[0] == 'w'; + bool isDigital = dataString[1] == 'd'; + int pin = dataString[2] - '0'; + int value; + +#ifdef DEBUGGING + Serial.print(isWrite); + Serial.print(" "); + Serial.print(isDigital); + Serial.print(" "); + Serial.print(pin); +#endif + + if (isWrite) { + value = dataString[3] - '0'; + +#ifdef DEBUGGING + Serial.print(" "); + Serial.println(value); +#endif + + pinMode(pin, OUTPUT); + + if (isDigital) { + digitalWrite(pin, value); + } else { + analogWrite(pin, value); + } + + } else { + String result = String(pin); + + result += ":"; + + pinMode(pin, INPUT); + + if (isDigital) { + value = digitalRead(pin); + } else { + value = analogRead(pin); + } + +#ifdef DEBUGGING + Serial.print(" -> "); + Serial.println(value); +#endif + + result += String(value); + + socket.sendData(result); + } + + +#ifdef DEBUGGING + Serial.println(dataString); +#endif +} + +void setup() { + +#ifdef DEBUGGING + Serial.begin(9600); +#endif + + 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 0")); + WiFly.sendCommand(F("set wlan channel 0")); + WiFly.sendCommand(F("set ip dhcp 1")); + + server.begin(); + +#ifdef DEBUGGING + Serial.println(F("Joining WiFi network...")); +#endif + + // Here is where you set the network name to join + if (!WiFly.sendCommand(F("join automata_arduino"), "Associated!", 20000, false)) { +#ifdef DEBUGGING + Serial.println(F("Association failed.")); +#endif + while (1) { + // Hang on failure. + } + } + + if (!WiFly.waitForResponse("DHCP in", 10000)) { +#ifdef DEBUGGING + Serial.println(F("DHCP failed.")); +#endif + while (1) { + // Hang on failure. + } + } + + // This is how you get the local IP as an IPAddress object +#ifdef DEBUGGING + Serial.println(WiFly.localIP()); +#endif + + websocketServer.addAction(&dataReceivedAction); +} + +void loop() { + + WiFlyClient client = server.available(); + + // This delay is needed to let the WiFly respond properly + delay(100); + + websocketServer.connectionRequest(client); +} diff --git a/examples/Websocket_Demo/Websocket_Demo.pde b/examples/Websocket_Demo/Websocket_Demo.pde deleted file mode 100644 index 77c4498..0000000 --- a/examples/Websocket_Demo/Websocket_Demo.pde +++ /dev/null @@ -1,43 +0,0 @@ -#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 - -byte mac[] = { 0x52, 0x4F, 0x43, 0x4B, 0x45, 0x54 }; -byte ip[] = { 192, 168, 1, 170 }; - -// Create a Websocket server listening to http://192.168.1.170/ -WebSocket websocketServer(); - -// You must have at least one function with the following signature. -// It will be called by the server when a data frame is received. -void dataReceivedAction(WebSocket &socket, String &dataString) { - // Just echo back data for fun. - socket.sendData(&dataString[0]); -} - -void setup() { -#ifdef DEBUGGING - Serial.begin(57600); -#endif - Ethernet.begin(mac, ip); - websocketServer.begin(); - // Add the callback function to the server. You can have several callback functions - // if you like, they will be called with the same data and in the same order as you - // add them to the server. If you have more than one, define CALLBACK_FUNCTIONS before including - // WebSocket.h - websocketServer.addAction(&dataReceivedAction); - delay(1000); // Give Ethernet time to get ready -} - -void loop() { - // Don't add any code after next line. It will never be called. - websocketServer.connectionRequest(); -} diff --git a/examples/websocket_test.html b/examples/websocket_test.html new file mode 100644 index 0000000..feae332 --- /dev/null +++ b/examples/websocket_test.html @@ -0,0 +1,63 @@ + + + + + + WebSocket Test + + + + +

+ WebSocket Test +

+ Red + Green + + diff --git a/websocket.html b/websocket.html deleted file mode 100644 index c4a4244..0000000 --- a/websocket.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - WebSockets test - - - - -
-
- - \ No newline at end of file From dd8c8c7d3a83a63c69b9a1e93a3e4e6db1483496 Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Fri, 27 Apr 2012 14:14:50 -0400 Subject: [PATCH 3/9] cleanup --- WebSocket.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/WebSocket.cpp b/WebSocket.cpp index ad24509..c85a6da 100644 --- a/WebSocket.cpp +++ b/WebSocket.cpp @@ -111,8 +111,6 @@ bool WebSocket::analyzeRequest(int bufferLength) { } } - Serial.println("DONE WITH HEADERS!"); - if (!socket_client->connected()) { return false; } @@ -123,8 +121,6 @@ bool WebSocket::analyzeRequest(int bufferLength) { // send response headers. if (foundupgrade == true) { - Serial.println("FOUND UPGRADE!"); - #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 @@ -216,10 +212,7 @@ bool WebSocket::analyzeRequest(int bufferLength) { return true; } else { - Serial.println("WHOA!"); - Serial.println(hixie76style); - Serial.println(newkey.length()); - + // something went horribly wrong return false; } } else { From d58b87b1dba089a95b73984e5aff3cccc57d4bab Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Fri, 27 Apr 2012 14:42:43 -0400 Subject: [PATCH 4/9] extracted loop --- WebSocket.cpp | 65 ++++++++------------ WebSocket.h | 25 +++----- examples/Websocket_Demo/Websocket_Demo.ino | 69 +++++++++------------- 3 files changed, 62 insertions(+), 97 deletions(-) diff --git a/WebSocket.cpp b/WebSocket.cpp index c85a6da..96d1946 100644 --- a/WebSocket.cpp +++ b/WebSocket.cpp @@ -19,7 +19,7 @@ WebSocket::WebSocket(const char *urlPrefix) : } -void WebSocket::connectionRequest(Client &client) { +bool WebSocket::connectionRequest(Client &client) { socket_client = &client; @@ -34,27 +34,19 @@ void WebSocket::connectionRequest(Client &client) { Serial.println(F("Websocket established")); #endif - if (hixie76style) { + return true; -#ifdef SUPPORT_HIXIE_76 - handleHixie76Stream(BUFFER_LENGTH); -#endif - } else { - handleStream(BUFFER_LENGTH); - delay(20); - - } - -#ifdef DEBUGGING - Serial.println(F("Websocket dropped")); -#endif } 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; } } @@ -225,13 +217,13 @@ bool WebSocket::analyzeRequest(int bufferLength) { } #ifdef SUPPORT_HIXIE_76 -void WebSocket::handleHixie76Stream(int socketBufferLength) { +void WebSocket::handleHixie76Stream() { int bite; int frameLength = 0; // String to hold bytes sent by client to server. String socketString; - while (socket_client->connected()) { + if (socket_client->connected() && socket_client->available()) { bite = timedRead(); if (bite != -1) { @@ -248,22 +240,24 @@ void WebSocket::handleHixie76Stream(int socketBufferLength) { frameLength++; if (frameLength > MAX_FRAME_LENGTH) { - // Too big to handle! Abort and disconnect. - #ifdef DEBUGGING + // Too big to handle! +#ifdef DEBUGGING Serial.print("Client send frame exceeding "); Serial.print(MAX_FRAME_LENGTH); Serial.println(" bytes"); - #endif +#endif return; } } } } + + return socketString; } #endif -void WebSocket::handleStream(int socketBufferLength) { +void WebSocket::handleStream() { uint8_t msgtype; uint8_t bite; unsigned int length; @@ -274,9 +268,7 @@ void WebSocket::handleStream(int socketBufferLength) { // String to hold bytes sent by client to server. String socketString; - while (socket_client->connected()) { - - if (socket_client->available()) { + if (socket_client->connected() && socket_client->available()) { msgtype = timedRead(); if (!socket_client->connected()) { @@ -338,22 +330,11 @@ void WebSocket::handleStream(int socketBufferLength) { return; } } - - executeActions(socketString); - socketString = ""; } - // need this wait to prevent hanging } -} -void WebSocket::addAction(Action *socketAction) { -#ifdef DEBUGGING - Serial.println(F("Adding actions")); -#endif - if (socket_actions_population <= SIZE(socket_actions)) { - socket_actions[socket_actions_population++].socketAction = socketAction; - } + return socketString; } void WebSocket::disconnectStream() { @@ -370,14 +351,18 @@ void WebSocket::disconnectStream() { socket_client->stop(); } -void WebSocket::executeActions(String socketString) { - for (int i = 0; i < socket_actions_population; ++i) { +void WebSocket::getData(String str) { + String data; + + if (hixie76style) { #ifdef DEBUGGING - Serial.print(F("Executing Action ")); - Serial.println(i + 1); + data = handleHixie76Stream(); #endif - socket_actions[i].socketAction(*this, socketString); + } else { + data = handleStream(); } + + return data; } void WebSocket::sendData(const char *str) { diff --git a/WebSocket.h b/WebSocket.h index 521bf33..a807076 100644 --- a/WebSocket.h +++ b/WebSocket.h @@ -82,20 +82,13 @@ class WebSocket { // Handle connection requests to validate and process/refuse // connections. - void connectionRequest(Client &client); + bool connectionRequest(Client &client); // Loop to read information from the user. Returns false if user // disconnects, server must disconnect, or an error occurs. - -#ifdef SUPPORT_HIXIE_76 - void handleHixie76Stream(int socketBufferLink); -#endif - - void handleStream(int socketBufferLink); - - // Adds each action to the list of actions for the program to run. - void addAction(Action *socketAction); + String getData(); + // Custom write for actions. void sendData(const char *str); void sendData(String str); @@ -110,16 +103,14 @@ class WebSocket { String host; bool hixie76style; - struct ActionPack { - Action *socketAction; - // String *socketString; - } socket_actions[CALLBACK_FUNCTIONS]; - - int socket_actions_population; - // Discovers if the client's header is requesting an upgrade to a // websocket connection. bool analyzeRequest(int bufferLength); + +#ifdef SUPPORT_HIXIE_76 + void handleHixie76Stream(); +#endif + void handleStream(); // Disconnect user gracefully. void disconnectStream(); diff --git a/examples/Websocket_Demo/Websocket_Demo.ino b/examples/Websocket_Demo/Websocket_Demo.ino index 3bd3681..61de4fb 100644 --- a/examples/Websocket_Demo/Websocket_Demo.ino +++ b/examples/Websocket_Demo/Websocket_Demo.ino @@ -14,15 +14,16 @@ #include WiFlyServer server(80); +WiFlyClient client; WebSocket websocketServer; // Called when a new message from the WebSocket is received // Looks for a message in this form: // -// WDPV +// DPV // -// Where: W is either 'w' or 'r' - write or read +// Where: // D is either 'd' or 'a' - digital or analog // P is a pin # // V is optionally the value to apply to the pin @@ -34,22 +35,18 @@ WebSocket websocketServer; // // Where: P is a pin # // V is the value of that pin -void dataReceivedAction(WebSocket &socket, String &dataString) { - bool isWrite = dataString[0] == 'w'; - bool isDigital = dataString[1] == 'd'; - int pin = dataString[2] - '0'; +void handleClientData(String &dataString) { + bool isDigital = dataString[0] == 'd'; + int pin = dataString[1] - '0'; int value; #ifdef DEBUGGING - Serial.print(isWrite); - Serial.print(" "); Serial.print(isDigital); Serial.print(" "); Serial.print(pin); #endif - if (isWrite) { - value = dataString[3] - '0'; + value = dataString[2] - '0'; #ifdef DEBUGGING Serial.print(" "); @@ -64,28 +61,6 @@ void dataReceivedAction(WebSocket &socket, String &dataString) { analogWrite(pin, value); } - } else { - String result = String(pin); - - result += ":"; - - pinMode(pin, INPUT); - - if (isDigital) { - value = digitalRead(pin); - } else { - value = analogRead(pin); - } - -#ifdef DEBUGGING - Serial.print(" -> "); - Serial.println(value); -#endif - - result += String(value); - - socket.sendData(result); - } #ifdef DEBUGGING @@ -142,15 +117,29 @@ void setup() { Serial.println(WiFly.localIP()); #endif - websocketServer.addAction(&dataReceivedAction); -} - -void loop() { - - WiFlyClient client = server.available(); + client = server.available(); // This delay is needed to let the WiFly respond properly delay(100); - - websocketServer.connectionRequest(client); +} + +void loop() { + String data; + + if (websocketServer.connectionRequest(client)) { + + while (client->connected()) { + data = websocketServer.getData(); + + if (data.length() > 0) { + handleClientData(data); + } + + data = ""; + data = ":::::"; + + websocketServer.sendData(data); + + } + } } From 0d8564efade7388f7f0150cf9e84e8e48808bcb0 Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Thu, 2 Aug 2012 22:40:19 -0400 Subject: [PATCH 5/9] Added support for WebSocket client, renamed server --- WebSocketClient.cpp | 309 ++++++++++++++++++ WebSocketClient.h | 112 +++++++ WebSocket.cpp => WebSocketServer.cpp | 130 ++++---- WebSocket.h => WebSocketServer.h | 30 +- .../WebSocketClient_Demo.ino | 109 ++++++ examples/WebSocketServer.html | 101 ++++++ .../WebSocketServer_Demo.ino} | 98 +++--- examples/Websocket_Demo/.DS_Store | Bin 6148 -> 0 bytes examples/websocket_test.html | 63 ---- 9 files changed, 738 insertions(+), 214 deletions(-) create mode 100644 WebSocketClient.cpp create mode 100644 WebSocketClient.h rename WebSocket.cpp => WebSocketServer.cpp (82%) rename WebSocket.h => WebSocketServer.h (79%) create mode 100644 examples/WebSocketClient_Demo/WebSocketClient_Demo.ino create mode 100644 examples/WebSocketServer.html rename examples/{Websocket_Demo/Websocket_Demo.ino => WebSocketServer_Demo/WebSocketServer_Demo.ino} (58%) delete mode 100644 examples/Websocket_Demo/.DS_Store delete mode 100644 examples/websocket_test.html diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp new file mode 100644 index 0000000..1d256f8 --- /dev/null +++ b/WebSocketClient.cpp @@ -0,0 +1,309 @@ +//#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-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)); +} + + +String WebSocketClient::handleStream() { + uint8_t msgtype; + uint8_t bite; + unsigned int length; + uint8_t mask[4]; + uint8_t index; + unsigned int i; + bool hasMask = false; + + // String to hold bytes sent by server to client + String socketString; + + if (socket_client->connected() && socket_client->available()) { + + msgtype = timedRead(); + if (!socket_client->connected()) { + return socketString; + } + + length = timedRead(); + + if (length > 127) { + hasMask = true; + length = length & 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 + } + } + + if (hasMask) { + // 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; + } + } + + if (hasMask) { + for (i=0; iconnected()) { + return socketString; + } + } + } else { + for (i=0; iconnected()) { + return socketString; + } + } + } + + } + + return socketString; +} + +void WebSocketClient::disconnectStream() { +#ifdef DEBUGGING + Serial.println(F("Terminating socket")); +#endif + // Should send 0xFF00 to client to tell it I'm quitting here. + // TODO: Check if I understood this properly + socket_client->write((uint8_t) 0xFF); + socket_client->write((uint8_t) 0x00); + + socket_client->flush(); + delay(10); + socket_client->stop(); +} + +String WebSocketClient::getData() { + String data; + + data = handleStream(); + + return data; +} + +void WebSocketClient::sendData(const char *str) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + sendEncodedData(str); + } +} + +void WebSocketClient::sendData(String str) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + sendEncodedData(str); + } +} + +int WebSocketClient::timedRead() { + while (!socket_client->available()) { + delay(20); + } + + return socket_client->read(); +} + +void WebSocketClient::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 WebSocketClient::sendEncodedData(String str) { + int size = str.length() + 1; + char cstr[size]; + + str.toCharArray(cstr, size); + + sendEncodedData(cstr); +} diff --git a/WebSocketClient.h b/WebSocketClient.h new file mode 100644 index 0000000..ba98783 --- /dev/null +++ b/WebSocketClient.h @@ -0,0 +1,112 @@ +/* +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 WEBSOCKETCLIENT_H_ +#define WEBSOCKETCLIENT_H_ + +#include +#include +#include "String.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 + +// 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 WebSocketClient { +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); + + char *path; + char *host; + +private: + Client *socket_client; + unsigned long _startMillis; + + const char *socket_urlPrefix; + + // Discovers if the client's header is requesting an upgrade to a + // websocket connection. + bool analyzeRequest(); + + 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/WebSocket.cpp b/WebSocketServer.cpp similarity index 82% rename from WebSocket.cpp rename to WebSocketServer.cpp index 96d1946..3ee0a98 100644 --- a/WebSocket.cpp +++ b/WebSocketServer.cpp @@ -2,7 +2,7 @@ //#define SUPPORT_HIXIE_76 #include "global.h" -#include "WebSocket.h" +#include "WebSocketServer.h" #ifdef SUPPORT_HIXIE_76 #include "MD5.c" @@ -11,15 +11,8 @@ #include "sha1.h" #include "base64.h" -WebSocket::WebSocket(const char *urlPrefix) : - socket_actions_population(0), - socket_urlPrefix(urlPrefix) -{ -} - - -bool WebSocket::connectionRequest(Client &client) { +bool WebSocketServer::handshake(Client &client) { socket_client = &client; @@ -50,7 +43,7 @@ bool WebSocket::connectionRequest(Client &client) { } } -bool WebSocket::analyzeRequest(int bufferLength) { +bool WebSocketServer::analyzeRequest(int bufferLength) { // Use String library to do some sort of read() magic here. String temp; @@ -217,7 +210,7 @@ bool WebSocket::analyzeRequest(int bufferLength) { } #ifdef SUPPORT_HIXIE_76 -void WebSocket::handleHixie76Stream() { +String WebSocketServer::handleHixie76Stream() { int bite; int frameLength = 0; // String to hold bytes sent by client to server. @@ -232,9 +225,8 @@ void WebSocket::handleHixie76Stream() { if ((uint8_t) bite == 0xFF) { // Frame end. Process what we got. - executeActions(socketString); - // Reset buffer - socketString = ""; + return socketString; + } else { socketString += (char)bite; frameLength++; @@ -257,7 +249,7 @@ void WebSocket::handleHixie76Stream() { #endif -void WebSocket::handleStream() { +String WebSocketServer::handleStream() { uint8_t msgtype; uint8_t bite; unsigned int length; @@ -270,74 +262,72 @@ void WebSocket::handleStream() { if (socket_client->connected() && socket_client->available()) { - msgtype = timedRead(); - if (!socket_client->connected()) { - return; - } + msgtype = timedRead(); + if (!socket_client->connected()) { + return socketString; + } - length = timedRead() & 127; - if (!socket_client->connected()) { - return; - } + length = timedRead() & 127; + if (!socket_client->connected()) { + return socketString; + } - index = 6; + index = 6; - if (length == 126) { - length = timedRead() << 8; - if (!socket_client->connected()) { - return; - } - - length |= timedRead(); - if (!socket_client->connected()) { - return; - } + if (length == 126) { + length = timedRead() << 8; + if (!socket_client->connected()) { + return socketString; + } + + length |= timedRead(); + if (!socket_client->connected()) { + return socketString; + } - } else if (length == 127) { + } else if (length == 127) { #ifdef DEBUGGING - Serial.println(F("No support for over 16 bit sized messages")); + Serial.println(F("No support for over 16 bit sized messages")); #endif - while(1) { - // halt, can't handle this case - } + while(1) { + // halt, can't handle this case } + } - // get the mask - mask[0] = timedRead(); - if (!socket_client->connected()) { - return; - } + // get the mask + mask[0] = timedRead(); + if (!socket_client->connected()) { + return socketString; + } - mask[1] = timedRead(); - if (!socket_client->connected()) { + mask[1] = timedRead(); + if (!socket_client->connected()) { - return; - } + return socketString; + } - mask[2] = timedRead(); - if (!socket_client->connected()) { - return; - } + mask[2] = timedRead(); + if (!socket_client->connected()) { + return socketString; + } - mask[3] = timedRead(); - if (!socket_client->connected()) { - return; - } + mask[3] = timedRead(); + if (!socket_client->connected()) { + return socketString; + } - for (i=0; iconnected()) { - return; - } + for (i=0; iconnected()) { + return socketString; } } - // need this wait to prevent hanging } return socketString; } -void WebSocket::disconnectStream() { +void WebSocketServer::disconnectStream() { #ifdef DEBUGGING Serial.println(F("Terminating socket")); #endif @@ -351,11 +341,11 @@ void WebSocket::disconnectStream() { socket_client->stop(); } -void WebSocket::getData(String str) { +String WebSocketServer::getData() { String data; if (hixie76style) { -#ifdef DEBUGGING +#ifdef SUPPORT_HIXIE_76 data = handleHixie76Stream(); #endif } else { @@ -365,7 +355,7 @@ void WebSocket::getData(String str) { return data; } -void WebSocket::sendData(const char *str) { +void WebSocketServer::sendData(const char *str) { #ifdef DEBUGGING Serial.print(F("Sending data: ")); Serial.println(str); @@ -381,7 +371,7 @@ void WebSocket::sendData(const char *str) { } } -void WebSocket::sendData(String str) { +void WebSocketServer::sendData(String str) { #ifdef DEBUGGING Serial.print(F("Sending data: ")); Serial.println(str); @@ -397,7 +387,7 @@ void WebSocket::sendData(String str) { } } -int WebSocket::timedRead() { +int WebSocketServer::timedRead() { while (!socket_client->available()) { delay(20); } @@ -405,7 +395,7 @@ int WebSocket::timedRead() { return socket_client->read(); } -void WebSocket::sendEncodedData(char *str) { +void WebSocketServer::sendEncodedData(char *str) { int size = strlen(str); // string type @@ -425,7 +415,7 @@ void WebSocket::sendEncodedData(char *str) { } } -void WebSocket::sendEncodedData(String str) { +void WebSocketServer::sendEncodedData(String str) { int size = str.length() + 1; char cstr[size]; diff --git a/WebSocket.h b/WebSocketServer.h similarity index 79% rename from WebSocket.h rename to WebSocketServer.h index a807076..6dcf052 100644 --- a/WebSocket.h +++ b/WebSocketServer.h @@ -40,8 +40,8 @@ Currently based off of "The Web Socket protocol" draft (v 75): */ -#ifndef WEBSOCKET_H_ -#define WEBSOCKET_H_ +#ifndef WEBSOCKETSERVER_H_ +#define WEBSOCKETSERVER_H_ #include #include @@ -71,25 +71,17 @@ Currently based off of "The Web Socket protocol" draft (v 75): #define SIZE(array) (sizeof(array) / sizeof(*array)) -class WebSocket { +class WebSocketServer { public: - // Constructor for websocket class. - WebSocket(const char *urlPrefix = "/"); - - // Processor prototype. Processors allow the websocket server to - // respond to input from client based on what the client supplies. - typedef void Action(WebSocket &socket, String &socketString); - + // Handle connection requests to validate and process/refuse // connections. - bool connectionRequest(Client &client); - - // Loop to read information from the user. Returns false if user - // disconnects, server must disconnect, or an error occurs. + bool handshake(Client &client); + // Get data off of the stream String getData(); - // Custom write for actions. + // Write data to the stream void sendData(const char *str); void sendData(String str); @@ -108,17 +100,13 @@ class WebSocket { bool analyzeRequest(int bufferLength); #ifdef SUPPORT_HIXIE_76 - void handleHixie76Stream(); + String handleHixie76Stream(); #endif - void handleStream(); + String handleStream(); // Disconnect user gracefully. void disconnectStream(); - // Returns true if the action was executed. It is up to the user to - // write the logic of the action. - void executeActions(String socketString); - int timedRead(); void sendEncodedData(char *str); diff --git a/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino b/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino new file mode 100644 index 0000000..74f3203 --- /dev/null +++ b/examples/WebSocketClient_Demo/WebSocketClient_Demo.ino @@ -0,0 +1,109 @@ +#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 + +#include + +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)) { + 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)) { + Serial.println("Handshake successful"); + } else { + Serial.println("Handshake failed."); + while(1) { + // Hang on failure + } + } +} + +void loop() { + String data; + + if (client.connected()) { + + data = webSocketClient.getData(); + + 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 new file mode 100644 index 0000000..71e2a60 --- /dev/null +++ b/examples/WebSocketServer.html @@ -0,0 +1,101 @@ + + + + + + WebSocket Test + + + + + + +

+ WebSocket Test +

+ Pin 8 + Pin 9 + +
Pin 1
+
Pin 2
+
Pin 3
+ + diff --git a/examples/Websocket_Demo/Websocket_Demo.ino b/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino similarity index 58% rename from examples/Websocket_Demo/Websocket_Demo.ino rename to examples/WebSocketServer_Demo/WebSocketServer_Demo.ino index 61de4fb..2a83c09 100644 --- a/examples/Websocket_Demo/Websocket_Demo.ino +++ b/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino @@ -11,11 +11,10 @@ // Define how many callback functions you have. Default is 1. #define CALLBACK_FUNCTIONS 1 -#include +#include WiFlyServer server(80); -WiFlyClient client; -WebSocket websocketServer; +WebSocketServer webSocketServer; // Called when a new message from the WebSocket is received @@ -26,54 +25,41 @@ WebSocket websocketServer; // Where: // D is either 'd' or 'a' - digital or analog // P is a pin # -// V is optionally the value to apply to the pin +// V is the value to apply to the pin // -// If the message is a read message then the client will be sent -// back a message in the following form: -// -// P:V -// -// Where: P is a pin # -// V is the value of that pin + void handleClientData(String &dataString) { bool isDigital = dataString[0] == 'd'; int pin = dataString[1] - '0'; int value; -#ifdef DEBUGGING - Serial.print(isDigital); - Serial.print(" "); - Serial.print(pin); -#endif + value = dataString[2] - '0'; - value = dataString[2] - '0'; - -#ifdef DEBUGGING - Serial.print(" "); - Serial.println(value); -#endif - pinMode(pin, OUTPUT); + pinMode(pin, OUTPUT); - if (isDigital) { - digitalWrite(pin, value); - } else { - analogWrite(pin, value); - } + if (isDigital) { + digitalWrite(pin, value); + } else { + analogWrite(pin, value); + } - - -#ifdef DEBUGGING Serial.println(dataString); -#endif +} + +// 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() { -#ifdef DEBUGGING - Serial.begin(9600); -#endif + Serial.begin(9600); SC16IS750.begin(); WiFly.setUart(&SC16IS750); @@ -83,63 +69,55 @@ void setup() { // 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 0")); + WiFly.sendCommand(F("set wlan auth 1")); WiFly.sendCommand(F("set wlan channel 0")); WiFly.sendCommand(F("set ip dhcp 1")); server.begin(); - -#ifdef DEBUGGING Serial.println(F("Joining WiFi network...")); -#endif + // Here is where you set the network name to join - if (!WiFly.sendCommand(F("join automata_arduino"), "Associated!", 20000, false)) { -#ifdef DEBUGGING + if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { Serial.println(F("Association failed.")); -#endif while (1) { // Hang on failure. } } - if (!WiFly.waitForResponse("DHCP in", 10000)) { -#ifdef DEBUGGING + if (!WiFly.waitForResponse("DHCP in", 10000)) { Serial.println(F("DHCP failed.")); -#endif while (1) { // Hang on failure. } } // This is how you get the local IP as an IPAddress object -#ifdef DEBUGGING Serial.println(WiFly.localIP()); -#endif - - client = server.available(); // This delay is needed to let the WiFly respond properly delay(100); } -void loop() { +void loop() { String data; - - if (websocketServer.connectionRequest(client)) { - - while (client->connected()) { - data = websocketServer.getData(); + WiFlyClient client = server.available(); + + if (client.connected() && webSocketServer.handshake(client)) { + + while (client.connected()) { + data = webSocketServer.getData(); if (data.length() > 0) { handleClientData(data); } - data = ""; - data = ":::::"; - - websocketServer.sendData(data); - + sendClientData(1); + sendClientData(2); + sendClientData(3); } } + + // wait to fully let the client disconnect + delay(100); } diff --git a/examples/Websocket_Demo/.DS_Store b/examples/Websocket_Demo/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 - - - - - WebSocket Test - - - - -

- WebSocket Test -

- Red - Green - - From df4301c118646f98a3aceb201e0a0ed93d6c3d22 Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Thu, 2 Aug 2012 22:45:43 -0400 Subject: [PATCH 6/9] Clean up of unneeded files --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 1 + examples/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 1 insertion(+) delete mode 100644 .DS_Store create mode 100644 .gitignore delete mode 100644 examples/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Thu, 2 Aug 2012 23:08:49 -0400 Subject: [PATCH 7/9] Fixed disconnect messages --- WebSocketClient.cpp | 5 ++--- WebSocketServer.cpp | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp index 1d256f8..f0d3db9 100644 --- a/WebSocketClient.cpp +++ b/WebSocketClient.cpp @@ -233,9 +233,8 @@ void WebSocketClient::disconnectStream() { #ifdef DEBUGGING Serial.println(F("Terminating socket")); #endif - // Should send 0xFF00 to client to tell it I'm quitting here. - // TODO: Check if I understood this properly - socket_client->write((uint8_t) 0xFF); + // 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(); diff --git a/WebSocketServer.cpp b/WebSocketServer.cpp index 3ee0a98..bee55c5 100644 --- a/WebSocketServer.cpp +++ b/WebSocketServer.cpp @@ -331,10 +331,19 @@ void WebSocketServer::disconnectStream() { #ifdef DEBUGGING Serial.println(F("Terminating socket")); #endif - // Should send 0xFF00 to client to tell it I'm quitting here. - // TODO: Check if I understood this properly - socket_client->write((uint8_t) 0xFF); - socket_client->write((uint8_t) 0x00); + + 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); From 0b21afac57195b9eef7b7ab0b9208120652475c2 Mon Sep 17 00:00:00 2001 From: Branden Hall Date: Thu, 2 Aug 2012 23:10:43 -0400 Subject: [PATCH 8/9] Updated readme --- README.md | 62 +++++++++---------------------------------------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index c9a7b8f..2eea2f0 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,23 @@ -## Websocket Server for Arduino +## Websocket Client and Server for Arduino -This is a simple library that implements a Websocket server running on an Arduino. The Websocket specification is a moving target and this implementation is based on a [draft specification][1] which expired i February 2011. But this is the version that has support by a few browsers and so is what's usable now. The protocol will change slightly as indicated by the [current draft][2] into something that looks nicer to your eye, if you're into staring at Request Headers. - -The implementation in this library has restrictions as the Arduino platform resources are quite limited. Most notably, the handshake headers are case sensitive while the specification state they should be case _insensitive_, and, after a header field name and it's trailing colon, there **must** be one and only one space before the header value while the specification only "prefers" one space but allows 0-n. - -This will most likely not be a problem as current implementations in Safari, Chrome and Firefox formats the request in that particular way. See below for what it looks like. - -_Header example:_ - - Upgrade: WebSocket - Connection: Upgrade - Host: example.com - Origin: http://example.com - Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 - Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 - - ^n:ds[4U - -The last line, the somewhat cryptic `^n:ds[4U` is the third key sent by the client, is always 8 bytes long and not terminated by a newline. - -_Response example:_ - - Upgrade: WebSocket - Connection: Upgrade - Sec-WebSocket-Origin: http://example.com - Sec-WebSocket-Location: ws://example.com/demo - Sec-WebSocket-Protocol: sample - - 8jKS'y:G*Co,Wxa- - -The last line is the MD5 Digest, always 16 bytes and not terminated by a newline. - -The next version of the specification will get rid of those butt-ugly keys, keeping everything readable. Or at least not looking broken like the keys above. - -Other limitations and assumptions in the current implementation: - -* The server requires all received data to be UTF-8 only. -* The server only accepts frames starting with 0x00 and ending with 0xFF. That is, the alternative frame definition in the websocket standard with 0xFF followed by specified length, is not supported. (If I got that part of the spec right.) -* The server assumes all data sent to client is UTF-8 only. No encoding is done by the server. -* The server handles incoming frames of limited lengths (default setting is 256). Remember, an Arduino is not an ocean of free memory cells. Don't even think about trying 2^32 - 1... +This is a simple library that implements a Websocket client and server running on an Arduino. ### Getting started -The example websockets.html file should be served from any web server you have access to. Remember to change the ws://... URL in it to your Arduino. +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. Install the library to "libraries" folder in your Arduino sketchbook folder. For example, on a mac that's `~/Documents/Arduino/libraries`. -Try one of the examples to ensure that things work. +Try the examples to ensure that things work. Start playing with your own code! -### The Future - -As the Websocket specification matures, the implementations in various browsers will follow and this library will need to change accordingly. I will try to keep up, but I'm not monitoring the Websocket World daily so do file an issue for attention. Or bugs! Or corrections on where I've failed to understand the specification! Or any wish! - -_Enjoy!_ - -Oh by the way, quoting myself: +### 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. -> Don't forget to place a big ***fat*** disclaimer in the README. There is most certainly bugs in the code and I may well have misunderstood some things in the specification which I have only skimmed through and not slept with. So _please_ do not use this code in appliancies where people or pets could get hurt, like space shuttles, dog tread mills and Large Hadron Colliders. +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. +### 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. -[1]: http://www.whatwg.org/specs/web-socket-protocol/ "Protol version implemented here" -[2]: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol "Latest specification" +- Branden \ No newline at end of file From cd1482ef14749dfc15caa45ca56ff1ad8a15ab5d Mon Sep 17 00:00:00 2001 From: Phil Wright Date: Wed, 23 Apr 2014 16:28:01 +0100 Subject: [PATCH 9/9] Fixed payload length bug; Added protocol header, ping/pong, data masking. 16-bit payload lengths were invalid due to a typo, && should be &. Added Sec-WebSocket-Protocol header. getData and sendData methods now take an opcode value. This is used to implement ping/pong. sendEncodedData now applies a mask to the data. WebSocket constants have been #defined. --- WebSocketClient.cpp | 185 ++++++++++++++++++++++++-------------------- WebSocketClient.h | 27 +++++-- 2 files changed, 120 insertions(+), 92 deletions(-) diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp index f0d3db9..bad1b9f 100644 --- a/WebSocketClient.cpp +++ b/WebSocketClient.cpp @@ -76,6 +76,9 @@ bool WebSocketClient::analyzeRequest() { 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); @@ -131,7 +134,7 @@ bool WebSocketClient::analyzeRequest() { } -String WebSocketClient::handleStream() { +bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { uint8_t msgtype; uint8_t bite; unsigned int length; @@ -140,93 +143,96 @@ String WebSocketClient::handleStream() { unsigned int i; bool hasMask = false; - // String to hold bytes sent by server to client - String socketString; + if (!socket_client->connected() || !socket_client->available()) + { + return false; + } - if (socket_client->connected() && socket_client->available()) { + msgtype = timedRead(); + if (!socket_client->connected()) { + return false; + } - msgtype = timedRead(); - if (!socket_client->connected()) { - return socketString; - } + length = timedRead(); + + if (length & WS_MASK) { + hasMask = true; + length = length & ~WS_MASK; + } - length = timedRead(); - if (length > 127) { - hasMask = true; - length = length & 127; - } + if (!socket_client->connected()) { + return false; + } + index = 6; + if (length == WS_SIZE16) { + length = timedRead() << 8; if (!socket_client->connected()) { - return socketString; + return false; } - - index = 6; - - if (length == 126) { - length = timedRead() << 8; - if (!socket_client->connected()) { - return socketString; - } - length |= timedRead(); - if (!socket_client->connected()) { - return socketString; - } + length |= timedRead(); + if (!socket_client->connected()) { + return false; + } - } else if (length == 127) { + } else if (length == WS_SIZE64) { #ifdef DEBUGGING - Serial.println(F("No support for over 16 bit sized messages")); + Serial.println(F("No support for over 16 bit sized messages")); #endif - while(1) { - // halt, can't handle this case - } - } + return false; + } - if (hasMask) { - // get the mask - mask[0] = timedRead(); - if (!socket_client->connected()) { - return socketString; - } + if (hasMask) { + // get the mask + mask[0] = timedRead(); + if (!socket_client->connected()) { + return false; + } - mask[1] = timedRead(); - if (!socket_client->connected()) { + mask[1] = timedRead(); + if (!socket_client->connected()) { - return socketString; - } + return false; + } - mask[2] = timedRead(); - if (!socket_client->connected()) { - return socketString; - } + mask[2] = timedRead(); + if (!socket_client->connected()) { + return false; + } - mask[3] = timedRead(); + mask[3] = timedRead(); + if (!socket_client->connected()) { + return false; + } + } + + data = ""; + + if (opcode != NULL) + { + *opcode = msgtype & ~WS_FIN; + } + + if (hasMask) { + for (i=0; iconnected()) { - return socketString; + return false; } } - - if (hasMask) { - for (i=0; iconnected()) { - return socketString; - } + } else { + for (i=0; iconnected()) { + return false; } - } else { - for (i=0; iconnected()) { - return socketString; - } - } - } - + } } - - return socketString; + + return true; } void WebSocketClient::disconnectStream() { @@ -242,31 +248,27 @@ void WebSocketClient::disconnectStream() { socket_client->stop(); } -String WebSocketClient::getData() { - String data; +bool WebSocketClient::getData(String& data, uint8_t *opcode) { + return handleStream(data, opcode); +} - data = handleStream(); - - return data; -} - -void WebSocketClient::sendData(const char *str) { +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); + sendEncodedData(str, opcode); } } -void WebSocketClient::sendData(String str) { +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); + sendEncodedData(str, opcode); } } @@ -278,31 +280,42 @@ int WebSocketClient::timedRead() { return socket_client->read(); } -void WebSocketClient::sendEncodedData(char *str) { +void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) { + uint8_t mask[4]; int size = strlen(str); - // string type - socket_client->write(0x81); + // Opcode; final fragment + socket_client->write(opcode | WS_FIN); // NOTE: no support for > 16-bit sized messages if (size > 125) { - socket_client->write(126); + socket_client->write(WS_SIZE16 | WS_MASK); socket_client->write((uint8_t) (size >> 8)); - socket_client->write((uint8_t) (size && 0xFF)); + socket_client->write((uint8_t) (size & 0xFF)); } else { - socket_client->write((uint8_t) size); + 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]); + socket_client->write(str[i] ^ mask[i % 4]); } } -void WebSocketClient::sendEncodedData(String str) { +void WebSocketClient::sendEncodedData(String str, uint8_t opcode) { int size = str.length() + 1; char cstr[size]; str.toCharArray(cstr, size); - sendEncodedData(cstr); + sendEncodedData(cstr, opcode); } diff --git a/WebSocketClient.h b/WebSocketClient.h index ba98783..89b7c23 100644 --- a/WebSocketClient.h +++ b/WebSocketClient.h @@ -69,6 +69,20 @@ Currently based off of "The Web Socket protocol" draft (v 75): #define SIZE(array) (sizeof(array) / sizeof(*array)) +// WebSocket protocol constants +// First byte +#define WS_FIN 0x80 +#define WS_OPCODE_TEXT 0x01 +#define WS_OPCODE_BINARY 0x02 +#define WS_OPCODE_CLOSE 0x08 +#define WS_OPCODE_PING 0x09 +#define WS_OPCODE_PONG 0x0a +// Second byte +#define WS_MASK 0x80 +#define WS_SIZE16 126 +#define WS_SIZE64 127 + + class WebSocketClient { public: @@ -77,14 +91,15 @@ class WebSocketClient { bool handshake(Client &client); // Get data off of the stream - String getData(); + bool getData(String& data, uint8_t *opcode = NULL); // Write data to the stream - void sendData(const char *str); - void sendData(String str); + void sendData(const char *str, uint8_t opcode = WS_OPCODE_TEXT); + void sendData(String str, uint8_t opcode = WS_OPCODE_TEXT); char *path; char *host; + char *protocol; private: Client *socket_client; @@ -96,15 +111,15 @@ class WebSocketClient { // websocket connection. bool analyzeRequest(); - String handleStream(); + bool handleStream(String& data, uint8_t *opcode); // Disconnect user gracefully. void disconnectStream(); int timedRead(); - void sendEncodedData(char *str); - void sendEncodedData(String str); + void sendEncodedData(char *str, uint8_t opcode); + void sendEncodedData(String str, uint8_t opcode); };