diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store 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/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 diff --git a/WebSocket.cpp b/WebSocket.cpp deleted file mode 100644 index a34e4f5..0000000 --- a/WebSocket.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "WebSocket.h" -#include "MD5.c" - -#define DEBUGGING - -WebSocket::WebSocket(const char *urlPrefix, int inPort) : - socket_server(inPort), - socket_client(255), - 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()) { - // Check request and look for websocket handshake - #ifdef DEBUGGING - Serial.println("Client connected"); - #endif - if (analyzeRequest(BUFFER_LENGTH)) { - #ifdef DEBUGGING - Serial.println("Websocket established"); - #endif - socketStream(BUFFER_LENGTH); - #ifdef DEBUGGING - Serial.println("Websocket dropped"); - #endif - } else { - // Might just need to break until out of socket_client loop. - #ifdef DEBUGGING - Serial.println("Disconnecting client"); - #endif - disconnectStream(); - } - } -} - -bool WebSocket::analyzeRequest(int bufferLength) { - // Use String library to do some sort of read() magic here. - String temp = String(60); - - char bite; - bool foundupgrade = false; - String key[2]; - unsigned long intkey[2]; - -#ifdef DEBUGGING - Serial.println("Analyzing request headers"); -#endif - - // TODO: More robust string extraction - while ((bite = socket_client.read()) != -1) { - temp += bite; - - if (bite == '\n') { - #ifdef DEBUGGING - Serial.print("Got Line: " + temp); - #endif - // TODO: Should ignore case when comparing and allow 0-n whitespace after ':'. See the spec: - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html - if (!foundupgrade && temp.startsWith("Upgrade: WebSocket")) { - // OK, it's a websockets handshake for sure - foundupgrade = true; - } 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 - } - 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++; - } - } - 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"); -#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; - } else { - // Nope, failed handshake. Disconnect -#ifdef DEBUGGING - Serial.println("Header mismatch"); -#endif - return false; - } -} - -void WebSocket::socketStream(int socketBufferLength) { - char bite; - int frameLength = 0; - // String to hold bytes sent by client to server. - String socketString = String(socketBufferLength); - - 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) { - // Frame end. Process what we got. - executeActions(socketString); - // Reset buffer - socketString = ""; - } - - 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"); -#endif - return; - } - } - } -} - -void WebSocket::addAction(Action *socketAction) { -#ifdef DEBUGGING - Serial.println("Adding actions"); -#endif - if (socket_actions_population <= SIZE(socket_actions)) { - socket_actions[socket_actions_population++].socketAction = socketAction; - } -} - -void WebSocket::disconnectStream() { -#ifdef DEBUGGING - Serial.println("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(1); - socket_client.stop(); -} - -void WebSocket::executeActions(String socketString) { - for (int i = 0; i < socket_actions_population; ++i) { -#ifdef DEBUGGING - Serial.print("Executing Action "); - Serial.println(i + 1); -#endif - socket_actions[i].socketAction(*this, socketString); - } -} - -void WebSocket::sendData(const char *str) { -#ifdef DEBUGGING - Serial.print("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 - } -} diff --git a/WebSocketClient.cpp b/WebSocketClient.cpp new file mode 100644 index 0000000..bad1b9f --- /dev/null +++ b/WebSocketClient.cpp @@ -0,0 +1,321 @@ +//#define DEBUGGING + +#include "global.h" +#include "WebSocketClient.h" + +#include "sha1.h" +#include "base64.h" + + +bool WebSocketClient::handshake(Client &client) { + + socket_client = &client; + + // If there is a connected client-> + if (socket_client->connected()) { + // Check request and look for websocket handshake +#ifdef DEBUGGING + Serial.println(F("Client connected")); +#endif + if (analyzeRequest()) { +#ifdef DEBUGGING + Serial.println(F("Websocket established")); +#endif + + return true; + + } else { + // Might just need to break until out of socket_client loop. +#ifdef DEBUGGING + Serial.println(F("Invalid handshake")); +#endif + disconnectStream(); + + return false; + } + } else { + return false; + } +} + +bool WebSocketClient::analyzeRequest() { + String temp; + + int bite; + bool foundupgrade = false; + unsigned long intkey[2]; + String serverKey; + char keyStart[17]; + char b64Key[25]; + String key = "------------------------"; + + randomSeed(analogRead(0)); + + for (int i=0; i<16; ++i) { + keyStart[i] = (char)random(1, 256); + } + + base64_encode(b64Key, keyStart, 16); + + for (int i=0; i<24; ++i) { + key[i] = b64Key[i]; + } + +#ifdef DEBUGGING + Serial.println(F("Sending websocket upgrade headers")); +#endif + + socket_client->print(F("GET ")); + socket_client->print(path); + socket_client->print(F(" HTTP/1.1\r\n")); + socket_client->print(F("Upgrade: websocket\r\n")); + socket_client->print(F("Connection: Upgrade\r\n")); + socket_client->print(F("Host: ")); + socket_client->print(host); + socket_client->print(CRLF); + socket_client->print(F("Sec-WebSocket-Key: ")); + socket_client->print(key); + socket_client->print(CRLF); + socket_client->print(F("Sec-WebSocket-Protocol: ")); + socket_client->print(protocol); + socket_client->print(CRLF); + socket_client->print(F("Sec-WebSocket-Version: 13\r\n")); + socket_client->print(CRLF); + +#ifdef DEBUGGING + Serial.println(F("Analyzing response headers")); +#endif + + while (socket_client->connected() && !socket_client->available()) { + delay(100); + Serial.println("Waiting..."); + } + + // TODO: More robust string extraction + while ((bite = socket_client->read()) != -1) { + + temp += (char)bite; + + if ((char)bite == '\n') { +#ifdef DEBUGGING + Serial.print("Got Header: " + temp); +#endif + if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { + foundupgrade = true; + } else if (temp.startsWith("Sec-WebSocket-Accept: ")) { + serverKey = temp.substring(22,temp.length() - 2); // Don't save last CR+LF + } + temp = ""; + } + + if (!socket_client->available()) { + delay(20); + } + } + + key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + uint8_t *hash; + char result[21]; + char b64Result[30]; + + Sha1.init(); + Sha1.print(key); + hash = Sha1.result(); + + for (int i=0; i<20; ++i) { + result[i] = (char)hash[i]; + } + result[20] = '\0'; + + base64_encode(b64Result, result, 20); + + // if the keys match, good to go + return serverKey.equals(String(b64Result)); +} + + +bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { + uint8_t msgtype; + uint8_t bite; + unsigned int length; + uint8_t mask[4]; + uint8_t index; + unsigned int i; + bool hasMask = false; + + if (!socket_client->connected() || !socket_client->available()) + { + return false; + } + + msgtype = timedRead(); + if (!socket_client->connected()) { + return false; + } + + length = timedRead(); + + if (length & WS_MASK) { + hasMask = true; + length = length & ~WS_MASK; + } + + + if (!socket_client->connected()) { + return false; + } + + index = 6; + + if (length == WS_SIZE16) { + length = timedRead() << 8; + if (!socket_client->connected()) { + return false; + } + + length |= timedRead(); + if (!socket_client->connected()) { + return false; + } + + } else if (length == WS_SIZE64) { +#ifdef DEBUGGING + Serial.println(F("No support for over 16 bit sized messages")); +#endif + return false; + } + + if (hasMask) { + // get the mask + mask[0] = timedRead(); + if (!socket_client->connected()) { + return false; + } + + mask[1] = timedRead(); + if (!socket_client->connected()) { + + return false; + } + + mask[2] = timedRead(); + if (!socket_client->connected()) { + return false; + } + + mask[3] = timedRead(); + if (!socket_client->connected()) { + return false; + } + } + + data = ""; + + if (opcode != NULL) + { + *opcode = msgtype & ~WS_FIN; + } + + if (hasMask) { + for (i=0; iconnected()) { + return false; + } + } + } else { + for (i=0; iconnected()) { + return false; + } + } + } + + return true; +} + +void WebSocketClient::disconnectStream() { +#ifdef DEBUGGING + Serial.println(F("Terminating socket")); +#endif + // Should send 0x8700 to server to tell it I'm quitting here. + socket_client->write((uint8_t) 0x87); + socket_client->write((uint8_t) 0x00); + + socket_client->flush(); + delay(10); + socket_client->stop(); +} + +bool WebSocketClient::getData(String& data, uint8_t *opcode) { + return handleStream(data, opcode); +} + +void WebSocketClient::sendData(const char *str, uint8_t opcode) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + sendEncodedData(str, opcode); + } +} + +void WebSocketClient::sendData(String str, uint8_t opcode) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + sendEncodedData(str, opcode); + } +} + +int WebSocketClient::timedRead() { + while (!socket_client->available()) { + delay(20); + } + + return socket_client->read(); +} + +void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) { + uint8_t mask[4]; + int size = strlen(str); + + // Opcode; final fragment + socket_client->write(opcode | WS_FIN); + + // NOTE: no support for > 16-bit sized messages + if (size > 125) { + socket_client->write(WS_SIZE16 | WS_MASK); + socket_client->write((uint8_t) (size >> 8)); + socket_client->write((uint8_t) (size & 0xFF)); + } else { + socket_client->write((uint8_t) size | WS_MASK); + } + + mask[0] = random(0, 256); + mask[1] = random(0, 256); + mask[2] = random(0, 256); + mask[3] = random(0, 256); + + socket_client->write(mask[0]); + socket_client->write(mask[1]); + socket_client->write(mask[2]); + socket_client->write(mask[3]); + + for (int i=0; iwrite(str[i] ^ mask[i % 4]); + } +} + +void WebSocketClient::sendEncodedData(String str, uint8_t opcode) { + int size = str.length() + 1; + char cstr[size]; + + str.toCharArray(cstr, size); + + sendEncodedData(cstr, opcode); +} diff --git a/WebSocketClient.h b/WebSocketClient.h new file mode 100644 index 0000000..89b7c23 --- /dev/null +++ b/WebSocketClient.h @@ -0,0 +1,127 @@ +/* +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)) + +// 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: + + // Handle connection requests to validate and process/refuse + // connections. + bool handshake(Client &client); + + // Get data off of the stream + bool getData(String& data, uint8_t *opcode = NULL); + + // Write data to the stream + void sendData(const char *str, uint8_t opcode = WS_OPCODE_TEXT); + void sendData(String str, uint8_t opcode = WS_OPCODE_TEXT); + + char *path; + char *host; + char *protocol; + +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(); + + bool handleStream(String& data, uint8_t *opcode); + + // Disconnect user gracefully. + void disconnectStream(); + + int timedRead(); + + void sendEncodedData(char *str, uint8_t opcode); + void sendEncodedData(String str, uint8_t opcode); +}; + + + +#endif \ No newline at end of file diff --git a/WebSocketServer.cpp b/WebSocketServer.cpp new file mode 100644 index 0000000..bee55c5 --- /dev/null +++ b/WebSocketServer.cpp @@ -0,0 +1,434 @@ +//#define DEBUGGING +//#define SUPPORT_HIXIE_76 + +#include "global.h" +#include "WebSocketServer.h" + +#ifdef SUPPORT_HIXIE_76 +#include "MD5.c" +#endif + +#include "sha1.h" +#include "base64.h" + + +bool WebSocketServer::handshake(Client &client) { + + socket_client = &client; + + // If there is a connected client-> + if (socket_client->connected()) { + // Check request and look for websocket handshake +#ifdef DEBUGGING + Serial.println(F("Client connected")); +#endif + if (analyzeRequest(BUFFER_LENGTH)) { +#ifdef DEBUGGING + Serial.println(F("Websocket established")); +#endif + + return true; + + } else { + // Might just need to break until out of socket_client loop. +#ifdef DEBUGGING + Serial.println(F("Disconnecting client")); +#endif + disconnectStream(); + + return false; + } + } else { + return false; + } +} + +bool WebSocketServer::analyzeRequest(int bufferLength) { + // Use String library to do some sort of read() magic here. + String temp; + + int bite; + bool foundupgrade = false; + String oldkey[2]; + unsigned long intkey[2]; + String newkey; + + hixie76style = false; + +#ifdef DEBUGGING + Serial.println(F("Analyzing request headers")); +#endif + + // TODO: More robust string extraction + while ((bite = socket_client->read()) != -1) { + + temp += (char)bite; + + if ((char)bite == '\n') { +#ifdef DEBUGGING + Serial.print("Got Line: " + temp); +#endif + // TODO: Should ignore case when comparing and allow 0-n whitespace after ':'. See the spec: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html + if (!foundupgrade && temp.startsWith("Upgrade: WebSocket")) { + // OK, it's a websockets handshake for sure + foundupgrade = true; + hixie76style = true; + } else if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { + foundupgrade = true; + hixie76style = false; + } else if (temp.startsWith("Origin: ")) { + origin = temp.substring(8,temp.length() - 2); // Don't save last CR+LF + } else if (temp.startsWith("Host: ")) { + host = temp.substring(6,temp.length() - 2); // Don't save last CR+LF + } else if (temp.startsWith("Sec-WebSocket-Key1: ")) { + oldkey[0]=temp.substring(20,temp.length() - 2); // Don't save last CR+LF + } else if (temp.startsWith("Sec-WebSocket-Key2: ")) { + oldkey[1]=temp.substring(20,temp.length() - 2); // Don't save last CR+LF + } else if (temp.startsWith("Sec-WebSocket-Key: ")) { + newkey=temp.substring(19,temp.length() - 2); // Don't save last CR+LF + } + temp = ""; + } + + if (!socket_client->available()) { + delay(20); + } + } + + if (!socket_client->connected()) { + return false; + } + + temp += 0; // Terminate string + + // Assert that we have all headers that are needed. If so, go ahead and + // send response headers. + if (foundupgrade == true) { + +#ifdef SUPPORT_HIXIE_76 + if (hixie76style && host.length() > 0 && oldkey[0].length() > 0 && oldkey[1].length() > 0) { + // All ok, proceed with challenge and MD5 digest + char key3[9] = {0}; + // What now is in temp should be the third key + temp.toCharArray(key3, 9); + + // Process keys + for (int i = 0; i <= 1; i++) { + unsigned int spaces =0; + String numbers; + + for (int c = 0; c < oldkey[i].length(); c++) { + char ac = oldkey[i].charAt(c); + if (ac >= '0' && ac <= '9') { + numbers += ac; + } + if (ac == ' ') { + spaces++; + } + } + char numberschar[numbers.length() + 1]; + numbers.toCharArray(numberschar, numbers.length()+1); + intkey[i] = strtoul(numberschar, NULL, 10) / spaces; + } + + unsigned char challenge[16] = {0}; + challenge[0] = (unsigned char) ((intkey[0] >> 24) & 0xFF); + challenge[1] = (unsigned char) ((intkey[0] >> 16) & 0xFF); + challenge[2] = (unsigned char) ((intkey[0] >> 8) & 0xFF); + challenge[3] = (unsigned char) ((intkey[0] ) & 0xFF); + challenge[4] = (unsigned char) ((intkey[1] >> 24) & 0xFF); + challenge[5] = (unsigned char) ((intkey[1] >> 16) & 0xFF); + challenge[6] = (unsigned char) ((intkey[1] >> 8) & 0xFF); + challenge[7] = (unsigned char) ((intkey[1] ) & 0xFF); + + memcpy(challenge + 8, key3, 8); + + unsigned char md5Digest[16]; + MD5(challenge, md5Digest, 16); + + socket_client->print(F("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")); + socket_client->print(F("Upgrade: WebSocket\r\n")); + socket_client->print(F("Connection: Upgrade\r\n")); + socket_client->print(F("Sec-WebSocket-Origin: ")); + socket_client->print(origin); + socket_client->print(CRLF); + + // The "Host:" value should be used as location + socket_client->print(F("Sec-WebSocket-Location: ws://")); + socket_client->print(host); + socket_client->print(socket_urlPrefix); + socket_client->print(CRLF); + socket_client->print(CRLF); + + socket_client->write(md5Digest, 16); + + return true; + } +#endif + + if (!hixie76style && newkey.length() > 0) { + + // add the magic string + newkey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + uint8_t *hash; + char result[21]; + char b64Result[30]; + + Sha1.init(); + Sha1.print(newkey); + hash = Sha1.result(); + + for (int i=0; i<20; ++i) { + result[i] = (char)hash[i]; + } + result[20] = '\0'; + + base64_encode(b64Result, result, 20); + + socket_client->print(F("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")); + socket_client->print(F("Upgrade: websocket\r\n")); + socket_client->print(F("Connection: Upgrade\r\n")); + socket_client->print(F("Sec-WebSocket-Accept: ")); + socket_client->print(b64Result); + socket_client->print(CRLF); + socket_client->print(CRLF); + + return true; + } else { + // something went horribly wrong + return false; + } + } else { + // Nope, failed handshake. Disconnect +#ifdef DEBUGGING + Serial.println(F("Header mismatch")); +#endif + return false; + } +} + +#ifdef SUPPORT_HIXIE_76 +String WebSocketServer::handleHixie76Stream() { + int bite; + int frameLength = 0; + // String to hold bytes sent by client to server. + String socketString; + + if (socket_client->connected() && socket_client->available()) { + bite = timedRead(); + + if (bite != -1) { + if (bite == 0) + continue; // Frame start, don't save + + if ((uint8_t) bite == 0xFF) { + // Frame end. Process what we got. + return socketString; + + } else { + socketString += (char)bite; + frameLength++; + + if (frameLength > MAX_FRAME_LENGTH) { + // Too big to handle! +#ifdef DEBUGGING + Serial.print("Client send frame exceeding "); + Serial.print(MAX_FRAME_LENGTH); + Serial.println(" bytes"); +#endif + return; + } + } + } + } + + return socketString; +} + +#endif + +String WebSocketServer::handleStream() { + uint8_t msgtype; + uint8_t bite; + unsigned int length; + uint8_t mask[4]; + uint8_t index; + unsigned int i; + + // String to hold bytes sent by client to server. + String socketString; + + if (socket_client->connected() && socket_client->available()) { + + msgtype = timedRead(); + if (!socket_client->connected()) { + return socketString; + } + + length = timedRead() & 127; + if (!socket_client->connected()) { + return socketString; + } + + index = 6; + + if (length == 126) { + length = timedRead() << 8; + if (!socket_client->connected()) { + return socketString; + } + + length |= timedRead(); + if (!socket_client->connected()) { + return socketString; + } + + } else if (length == 127) { +#ifdef DEBUGGING + Serial.println(F("No support for over 16 bit sized messages")); +#endif + while(1) { + // halt, can't handle this case + } + } + + // get the mask + mask[0] = timedRead(); + if (!socket_client->connected()) { + return socketString; + } + + mask[1] = timedRead(); + if (!socket_client->connected()) { + + return socketString; + } + + mask[2] = timedRead(); + if (!socket_client->connected()) { + return socketString; + } + + mask[3] = timedRead(); + if (!socket_client->connected()) { + return socketString; + } + + for (i=0; iconnected()) { + return socketString; + } + } + } + + return socketString; +} + +void WebSocketServer::disconnectStream() { +#ifdef DEBUGGING + Serial.println(F("Terminating socket")); +#endif + + if (hixie76style) { +#ifdef SUPPORT_HIXIE_76 + // Should send 0xFF00 to server to tell it I'm quitting here. + socket_client->write((uint8_t) 0xFF); + socket_client->write((uint8_t) 0x00); +#endif + } else { + + // Should send 0x8700 to server to tell it I'm quitting here. + socket_client->write((uint8_t) 0x87); + socket_client->write((uint8_t) 0x00); + } + + socket_client->flush(); + delay(10); + socket_client->stop(); +} + +String WebSocketServer::getData() { + String data; + + if (hixie76style) { +#ifdef SUPPORT_HIXIE_76 + data = handleHixie76Stream(); +#endif + } else { + data = handleStream(); + } + + return data; +} + +void WebSocketServer::sendData(const char *str) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + if (hixie76style) { + socket_client->write(0x00); // Frame start + socket_client->print(str); + socket_client->write(0xFF); // Frame end + } else { + sendEncodedData(str); + } + } +} + +void WebSocketServer::sendData(String str) { +#ifdef DEBUGGING + Serial.print(F("Sending data: ")); + Serial.println(str); +#endif + if (socket_client->connected()) { + if (hixie76style) { + socket_client->write(0x00); // Frame start + socket_client->print(str); + socket_client->write(0xFF); // Frame end + } else { + sendEncodedData(str); + } + } +} + +int WebSocketServer::timedRead() { + while (!socket_client->available()) { + delay(20); + } + + return socket_client->read(); +} + +void WebSocketServer::sendEncodedData(char *str) { + int size = strlen(str); + + // string type + socket_client->write(0x81); + + // NOTE: no support for > 16-bit sized messages + if (size > 125) { + socket_client->write(126); + socket_client->write((uint8_t) (size >> 8)); + socket_client->write((uint8_t) (size && 0xFF)); + } else { + socket_client->write((uint8_t) size); + } + + for (int i=0; iwrite(str[i]); + } +} + +void WebSocketServer::sendEncodedData(String str) { + int size = str.length() + 1; + char cstr[size]; + + str.toCharArray(cstr, size); + + sendEncodedData(cstr); +} diff --git a/WebSocket.h b/WebSocketServer.h similarity index 67% rename from WebSocket.h rename to WebSocketServer.h index 8cef828..6dcf052 100644 --- a/WebSocket.h +++ b/WebSocketServer.h @@ -39,14 +39,15 @@ 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 WEBSOCKETSERVER_H_ +#define WEBSOCKETSERVER_H_ -#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" @@ -70,58 +71,46 @@ 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 = "/", int inPort = 80); - - // 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(); - - // Loop to read information from the user. Returns false if user - // disconnects, server must disconnect, or an error occurs. - void socketStream(int socketBufferLink); - - // Adds each action to the list of actions for the program to run. - void addAction(Action *socketAction); + bool handshake(Client &client); - // Custom write for actions. + // Get data off of the stream + String getData(); + + // Write data to the stream void sendData(const char *str); + void sendData(String str); private: - Server socket_server; - Client socket_client; + Client *socket_client; + unsigned long _startMillis; const char *socket_urlPrefix; String origin; String host; - - struct ActionPack { - Action *socketAction; - // String *socketString; - } socket_actions[CALLBACK_FUNCTIONS]; - - int socket_actions_population; + bool hixie76style; // Discovers if the client's header is requesting an upgrade to a // websocket connection. bool analyzeRequest(int bufferLength); + +#ifdef SUPPORT_HIXIE_76 + String handleHixie76Stream(); +#endif + String handleStream(); // Disconnect user gracefully. void disconnectStream(); - // 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); + void sendEncodedData(String 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/WebSocketServer_Demo/WebSocketServer_Demo.ino b/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino new file mode 100644 index 0000000..2a83c09 --- /dev/null +++ b/examples/WebSocketServer_Demo/WebSocketServer_Demo.ino @@ -0,0 +1,123 @@ +#include +#include +#include + +// Enabe debug tracing to Serial port. +#define DEBUGGING + +// Here we define a maximum framelength to 64 bytes. Default is 256. +#define MAX_FRAME_LENGTH 64 + +// Define how many callback functions you have. Default is 1. +#define CALLBACK_FUNCTIONS 1 + +#include + +WiFlyServer server(80); +WebSocketServer webSocketServer; + + +// Called when a new message from the WebSocket is received +// Looks for a message in this form: +// +// DPV +// +// Where: +// D is either 'd' or 'a' - digital or analog +// P is a pin # +// V is the value to apply to the pin +// + +void handleClientData(String &dataString) { + bool isDigital = dataString[0] == 'd'; + int pin = dataString[1] - '0'; + int value; + + value = dataString[2] - '0'; + + + pinMode(pin, OUTPUT); + + if (isDigital) { + digitalWrite(pin, value); + } else { + analogWrite(pin, value); + } + + Serial.println(dataString); +} + +// send the client the analog value of a pin +void sendClientData(int pin) { + String data = "a"; + + pinMode(pin, INPUT); + data += String(pin) + String(analogRead(pin)); + webSocketServer.sendData(data); +} + +void setup() { + + + Serial.begin(9600); + SC16IS750.begin(); + + WiFly.setUart(&SC16IS750); + + WiFly.begin(); + + // This is for an unsecured network + // For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD' + // For a WEP network use auth 2, and in another command send 'set wlan key KEY' + WiFly.sendCommand(F("set wlan auth 1")); + WiFly.sendCommand(F("set wlan channel 0")); + WiFly.sendCommand(F("set ip dhcp 1")); + + server.begin(); + Serial.println(F("Joining WiFi network...")); + + + // Here is where you set the network name to join + if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { + Serial.println(F("Association failed.")); + while (1) { + // Hang on failure. + } + } + + if (!WiFly.waitForResponse("DHCP in", 10000)) { + Serial.println(F("DHCP failed.")); + while (1) { + // Hang on failure. + } + } + + // This is how you get the local IP as an IPAddress object + Serial.println(WiFly.localIP()); + + // This delay is needed to let the WiFly respond properly + delay(100); +} + +void loop() { + String data; + WiFlyClient client = server.available(); + + if (client.connected() && webSocketServer.handshake(client)) { + + while (client.connected()) { + data = webSocketServer.getData(); + + if (data.length() > 0) { + handleClientData(data); + } + + sendClientData(1); + sendClientData(2); + sendClientData(3); + } + } + + // wait to fully let the client disconnect + delay(100); +} diff --git a/examples/Websocket_Actions/Websocket_Actions.pde b/examples/Websocket_Actions/Websocket_Actions.pde deleted file mode 100644 index fdc51cc..0000000 --- a/examples/Websocket_Actions/Websocket_Actions.pde +++ /dev/null @@ -1,53 +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 - -#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/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/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 deleted file mode 100644 index 7ce5e68..0000000 --- a/websocket.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - WebSockets test - - - - -
-
- - \ No newline at end of file