From c94ba558ec56fe2a6480229c016e1546840fd879 Mon Sep 17 00:00:00 2001 From: enny Date: Sun, 8 Dec 2019 16:16:33 +0100 Subject: [PATCH] Now with Websockets and Web (HMTL) based GUI --- LED-Board.ino | 247 ++++++-------- WebGUI.html | 244 +++++++++++++ image.cpp | 43 --- image.h | 20 -- src/ProtocolDL.cpp | 77 +++++ src/ProtocolDL.hpp | 35 ++ src/WebSockets.cpp | 592 ++++++++++++++++++++++++++++++++ src/WebSockets.h | 159 +++++++++ src/WebSocketsClient.cpp | 506 +++++++++++++++++++++++++++ src/WebSocketsClient.h | 98 ++++++ src/WebSocketsServer.cpp | 714 +++++++++++++++++++++++++++++++++++++++ src/WebSocketsServer.h | 143 ++++++++ src/image.cpp | 47 +++ src/image.hpp | 28 ++ src/libb64/AUTHORS | 7 + src/libb64/LICENSE | 29 ++ src/libb64/cdecode.c | 94 ++++++ src/libb64/cdecode_inc.h | 28 ++ src/libb64/cencode.c | 115 +++++++ src/libb64/cencode_inc.h | 31 ++ src/libsha1/libsha1.c | 202 +++++++++++ src/libsha1/libsha1.h | 21 ++ src/sha1/sha1.cpp | 162 +++++++++ src/sha1/sha1.h | 47 +++ 24 files changed, 3487 insertions(+), 202 deletions(-) create mode 100644 WebGUI.html delete mode 100644 image.cpp delete mode 100644 image.h create mode 100644 src/ProtocolDL.cpp create mode 100644 src/ProtocolDL.hpp create mode 100644 src/WebSockets.cpp create mode 100644 src/WebSockets.h create mode 100644 src/WebSocketsClient.cpp create mode 100644 src/WebSocketsClient.h create mode 100644 src/WebSocketsServer.cpp create mode 100644 src/WebSocketsServer.h create mode 100644 src/image.cpp create mode 100644 src/image.hpp create mode 100644 src/libb64/AUTHORS create mode 100644 src/libb64/LICENSE create mode 100644 src/libb64/cdecode.c create mode 100644 src/libb64/cdecode_inc.h create mode 100644 src/libb64/cencode.c create mode 100644 src/libb64/cencode_inc.h create mode 100644 src/libsha1/libsha1.c create mode 100644 src/libsha1/libsha1.h create mode 100644 src/sha1/sha1.cpp create mode 100644 src/sha1/sha1.h diff --git a/LED-Board.ino b/LED-Board.ino index bca5f2b..521206b 100644 --- a/LED-Board.ino +++ b/LED-Board.ino @@ -1,53 +1,124 @@ +#include #include -#include "image.h" + +#include "src/WebSocketsServer.h" +#include "src/image.hpp" +#include "src/ProtocolDL.hpp" + +#define USE_SERIAL Serial + #define LOAD 7 #define DATA 8 #define CLOCK 9 -struct _source_t { - // width and height of current frame - unsigned int width; - unsigned int height; - - // delay until next frame is shown - unsigned int delay; - - // position of current pixel - int x; - int y; -}; - -typedef struct _source_t source_t; byte mac[] = { 0xBE, 0xB7, 0x5C, 0x30, 0xC3, 0x04 }; IPAddress ip(10, 23, 42, 24); IPAddress router(10, 23, 42, 1); IPAddress subnet(255, 255, 254, 0); -EthernetServer server(9000); +WebSocketsServer webSocket = WebSocketsServer(81); +Image image; +ProtocolDL protocol = ProtocolDL(image); -image_t image; +unsigned long last_activity = 0; -source_t source; +bool someOneIsConnected = false; + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + static bool in_header = true; + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.print("["); + USE_SERIAL.print(num); + USE_SERIAL.println("] Disconnected!"); + someOneIsConnected = false; + break; + case WStype_CONNECTED: + { + //IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.print("["); + USE_SERIAL.print(num); + USE_SERIAL.print("] Connected "); + USE_SERIAL.print(" url: "); + //USE_SERIAL.println(payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + someOneIsConnected = true; + } + break; + case WStype_TEXT: + USE_SERIAL.print("["); + USE_SERIAL.print(num); + USE_SERIAL.print("] get Text: "); + //USE_SERIAL.println(payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.print("["); + USE_SERIAL.print(num); + USE_SERIAL.print("] get binary length: "); + USE_SERIAL.println(length); + + for(uint16_t i = 0; i < length; i++) + { + protocol.newByte(payload[i]); + } + + if(protocol.isComplete()) + { + Serial.println("complete"); + send_image(&image); + } + + break; + } + +} void setup() { - // setup network - Ethernet.init(10); - Ethernet.begin(mac, ip, router, router, subnet); - server.begin(); + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); - Serial.begin(115200); + //Serial.setDebugOutput(true); + //USE_SERIAL.setDebugOutput(true); - pinMode(DATA, OUTPUT); + Ethernet.init(10); + Ethernet.begin(mac, ip, router, router, subnet); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.print("[SETUP] BOOT WAIT "); + USE_SERIAL.print(t); + USE_SERIAL.println("..."); + USE_SERIAL.flush(); + delay(1000); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + + pinMode(DATA, OUTPUT); pinMode(CLOCK, OUTPUT); pinMode(LOAD, OUTPUT); Serial.println("setup done"); } -void send_block(image_t* p, int x, int y) { + +void send_block(Image* p, int x, int y) { int order[32][2] = { { 1, 1 }, // 1 { 1, 0 }, // 2 @@ -87,7 +158,7 @@ void send_block(image_t* p, int x, int y) { int x_offset = order[n][0]; int y_offset = order[n][1]; - byte pixel = get_pixel(p, x + x_offset, y + y_offset); + byte pixel = p->get_pixel(x + x_offset, y + y_offset); digitalWrite(DATA, pixel); clock(); } @@ -96,7 +167,7 @@ void send_block(image_t* p, int x, int y) { clock(); } -void send_image(image_t* img) { +void send_image(Image* img) { for (int y = 0; y < MAX_HEIGHT; y += 8) { for (int x = 0; x < MAX_WIDTH; x += 4) { send_block(img, x, y); @@ -119,131 +190,29 @@ void load() { // 0x00 0x00 0x00 0x00 0x00 0x00 0x00... // Width Height Delay Pixel -void default_image(image_t* p) { +void default_image(Image* p) { static int offset = 0; // reset image to maximum size - set_size(p, 32767, 32767); + p->set_size(32767, 32767); // toggle all pixels in tilted bars - int dim = max(p->width, p->height); + int dim = max(p->getWidth(), p->getHeight()); for (int n = 0; n < dim; n++) { - int x = (n + offset) % p->width; - int y = n % p->height; + int x = (n + offset) % p->getWidth(); + int y = n % p->getHeight(); - byte pixel = get_pixel(p, x, y); - set_pixel(p, x, y, !pixel); + byte pixel = p->get_pixel(x, y); + p->set_pixel(x, y, !pixel); } offset++; } -bool read_header(EthernetClient cli, source_t* src) { - // number of bytes already read from header - static int offset = 0; - // flag set, if header is complete - bool complete = false; - - while (offset < 6) { - int value = cli.read(); - if (value == -1) { - break; - } - - switch (offset) { - case 0: - src->width = (value << 8); - break; - case 1: - src->width |= value; - break; - case 2: - src->height = (value << 8); - break; - case 3: - src->height |= value; - break; - case 4: - src->delay = (value << 8); - break; - case 5: - src->delay |= value; - break; - } - - offset++; - } - - if (offset > 5) { - src->x = 0; - src->y = 0; - - offset = 0; - complete = true; - } - - return complete; -} - -bool read_pixels(EthernetClient cli, source_t* src, image_t* img) { - // copy dimension from header - set_size(img, src->width, src->height); - - while (true) { - int value = cli.read(); - if (value == -1) { - return false; - } - - set_pixel(img, src->x, src->y, value); - - src->x++; - if (src->x >= src->width) { - src->x = 0; - src->y++; - if (src->y >= src->height) { - src->y = 0; - return true; - } - } - } -} - void loop() { - static unsigned long last_activity = 0; - static bool in_header = true; - - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client) { - if (in_header == true) { - if (read_header(client, &source) == true) { - Serial.print("width="); - Serial.print(source.width); - Serial.print(" height="); - Serial.print(source.height); - Serial.print(" delay="); - Serial.println(source.delay); - - if ((source.width == 0) || (source.height == 0)) { - Serial.println("invalid dimension"); - client.stop(); - } else { - in_header = false; - } - } - } else { - if (read_pixels(client, &source, &image) == true) { - Serial.println("pixels complete"); - in_header = true; + webSocket.loop(); + if (someOneIsConnected == false) { + default_image(&image); send_image(&image); - } } - last_activity = millis(); - } else { - if ((millis() - last_activity) > 60000) { - default_image(&image); - send_image(&image); - } - } } diff --git a/WebGUI.html b/WebGUI.html new file mode 100644 index 0000000..09c46e6 --- /dev/null +++ b/WebGUI.html @@ -0,0 +1,244 @@ + + + + + + LED Board + + + + + +
+ + + + + diff --git a/image.cpp b/image.cpp deleted file mode 100644 index 8ea4c73..0000000 --- a/image.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "image.h" - -bool check_bounds(image_t* p, int x, int y) { - if (p == NULL) { - return false; - } - - if ((x < 0) || (y < 0)) { - return false; - } - - if ((x >= p->width) || (y >= p->height)) { - return false; - } - - return true; -} - -byte get_pixel(image_t* p, int x, int y) { - if (check_bounds(p, x, y) == false) { - return 0; - } - return p->data[y * p->width + x]; -} - -void set_pixel(image_t* p, int x, int y, byte value) { - if (check_bounds(p, x, y) == false) { - return; - } - p->data[y * p->width + x] = value; -} - -void clear_pixels(image_t* p) { - if (p == NULL) { - return; - } - memset(p->data, 0, sizeof(p->data)); -} - -void set_size(image_t* p, int width, int height) { - p->width = min(width, MAX_WIDTH); - p->height = min(height, MAX_HEIGHT); -} diff --git a/image.h b/image.h deleted file mode 100644 index 1db167c..0000000 --- a/image.h +++ /dev/null @@ -1,20 +0,0 @@ -#include - -#define MAX_WIDTH 32 -#define MAX_HEIGHT 40 - -struct _image_t { - int width; - int height; - byte data[MAX_WIDTH * MAX_HEIGHT]; -}; - -typedef struct _image_t image_t; - -byte get_pixel(image_t* p, int x, int y); - -void set_pixel(image_t* p, int x, int y, byte value); - -void clear_pixels(image_t* p); - -void set_size(image_t* p, int width, int height); diff --git a/src/ProtocolDL.cpp b/src/ProtocolDL.cpp new file mode 100644 index 0000000..30a03b7 --- /dev/null +++ b/src/ProtocolDL.cpp @@ -0,0 +1,77 @@ +#include "ProtocolDL.hpp" + +ProtocolDL::ProtocolDL(Image& img): + image(&img) +{ + +} + +void ProtocolDL::newByte(uint8_t data) +{ + switch(cnt) + { + case 0: + complete = false; + source.width = (data << 8); + cnt = 1; + break; + + case 1: + source.width |= data; + cnt = 2; + break; + + case 2: + source.height = (data << 8); + cnt = 3; + break; + + case 3: + source.height |= data; + cnt = 4; + break; + + case 4: + source.delay = (data << 8); + cnt = 5; + break; + + case 5: + source.delay |= data; + image->set_size(source.width, source.height); + source.x = 0; + source.y = 0; + cnt = 6; + break; + + default: + image->set_pixel(source.x, source.y, data); + + if(source.x == (source.width - 1) && source.y == (source.height - 1)) + { + //this was the last pixel + complete = true; + cnt = 0; + } + else + { + source.x++; + if (source.x >= source.width) { + source.x = 0; + source.y++; + if (source.y >= source.height) { + source.y = 0; + } + } + } + + break; + } + + +} + +bool ProtocolDL::isComplete() +{ + return complete; +} \ No newline at end of file diff --git a/src/ProtocolDL.hpp b/src/ProtocolDL.hpp new file mode 100644 index 0000000..950201d --- /dev/null +++ b/src/ProtocolDL.hpp @@ -0,0 +1,35 @@ +#ifndef PROTOCOL_DL_HPP +#define PROTOCOL_DL_HPP + +#include "image.hpp" + +class ProtocolDL +{ + public: + ProtocolDL(Image& img); + void newByte(uint8_t data); + bool isComplete(); + + private: + + + struct source_t { + // width and height of current frame + unsigned int width; + unsigned int height; + + // delay until next frame is shown + unsigned int delay; + + // position of current pixel + int x; + int y; + }; + + source_t source; + uint16_t cnt = 0; + bool complete = true; + Image* image; +}; + +#endif \ No newline at end of file diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp new file mode 100644 index 0000000..9b782f1 --- /dev/null +++ b/src/WebSockets.cpp @@ -0,0 +1,592 @@ +/** + * @file WebSockets.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" + +#define LEGACY_SHA1 + +#ifdef ESP8266 +#include +#endif + +extern "C" { +#ifdef CORE_HAS_LIBB64 + #include +#else + #include "libb64/cencode_inc.h" +#endif +} + +#ifdef ESP8266 +#include +#else + + +#ifdef LEGACY_SHA1 + #include "sha1/sha1.h" +#else + extern "C" { + #include "libsha1/libsha1.h" + } +#endif + +#endif + +#define WEBSOCKETS_MAX_HEADER_SIZE (14) + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param code uint16_t see RFC + * @param reason + * @param reasonLen + */ +void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] clientDisconnect code: "); + DEBUG_WEBSOCKETS(code); + DEBUG_WEBSOCKETS("\n"); + if(client->status == WSC_CONNECTED && code) { + if(reason) { + sendFrame(client, WSop_close, (uint8_t *) reason, reasonLen); + } else { + uint8_t buffer[2]; + buffer[0] = ((code >> 8) & 0xFF); + buffer[1] = (code & 0xFF); + sendFrame(client, WSop_close, &buffer[0], 2); + } + } + clientDisconnect(client); +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param length size_t + * @param mask bool add dummy mask to the frame (needed for web browser) + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) + */ +bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool mask, bool fin, bool headerToPayload) { + + if(client->tcp && !client->tcp->connected()) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] not Connected!?\n"); + return false; + } + + if(client->status != WSC_CONNECTED) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] not in WSC_CONNECTED state!?\n"); + return false; + } + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] ------- send massage frame -------\n"); + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] fin: "); + DEBUG_WEBSOCKETS(fin); + DEBUG_WEBSOCKETS(" opCode: "); + DEBUG_WEBSOCKETS(opcode); + DEBUG_WEBSOCKETS(" mask: "); + DEBUG_WEBSOCKETS(mask); + DEBUG_WEBSOCKETS(" length: "); + DEBUG_WEBSOCKETS(length); + DEBUG_WEBSOCKETS(" headerToPayload: "); + DEBUG_WEBSOCKETS(headerToPayload); + DEBUG_WEBSOCKETS("\n"); + + if(opcode == WSop_text) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] text: "); + DEBUG_WEBSOCKETS((char*) (payload + (headerToPayload ? 14 : 0))); + DEBUG_WEBSOCKETS("\n"); + } + + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize; + uint8_t * headerPtr; + uint8_t * payloadPtr = payload; + bool useInternBuffer = false; + bool ret = true; + + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(mask) { + headerSize += 4; + } + + +#ifdef WEBSOCKETS_USE_BIG_MEM + // only for ESP since AVR has less HEAP + // try to send data in one TCP package (only if some free Heap is there) + if(!headerToPayload && ((length > 0) && (length < 1400)) && (ESP.getFreeHeap() > 6000)) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] pack to one TCP package...\n"); + + uint8_t * dataPtr = (uint8_t *) malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); + if(dataPtr) { + memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); + headerToPayload = true; + useInternBuffer = true; + payloadPtr = dataPtr; + } + } +#endif + + // set Header Pointer + if(headerToPayload) { + // calculate offset in payload + headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); + } else { + headerPtr = &buffer[0]; + } + + // create header + + // byte 0 + *headerPtr = 0x00; + if(fin) { + *headerPtr |= bit(7); ///< set Fin + } + *headerPtr |= opcode; ///< set opcode + headerPtr++; + + // byte 1 + *headerPtr = 0x00; + if(mask) { + *headerPtr |= bit(7); ///< set mask + } + + if(length < 126) { + *headerPtr |= length; headerPtr++; + } else if(length < 0xFFFF) { + *headerPtr |= 126; headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); headerPtr++; + *headerPtr = (length & 0xFF); headerPtr++; + } else { + // Normally we never get here (to less memory) + *headerPtr |= 127; headerPtr++; + *headerPtr = 0x00; headerPtr++; + *headerPtr = 0x00; headerPtr++; + *headerPtr = 0x00; headerPtr++; + *headerPtr = 0x00; headerPtr++; + *headerPtr = ((length >> 24) & 0xFF); headerPtr++; + *headerPtr = ((length >> 16) & 0xFF); headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); headerPtr++; + *headerPtr = (length & 0xFF); headerPtr++; + } + + if(mask) { + if(useInternBuffer) { + // if we use a Intern Buffer we can modify the data + // by this fact its possible the do the masking + for(uint8_t x = 0; x < sizeof(maskKey); x++) { + maskKey[x] = random(0xFF); + *headerPtr = maskKey[x]; headerPtr++; + } + + uint8_t * dataMaskPtr; + + if(headerToPayload) { + dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); + } else { + dataMaskPtr = payloadPtr; + } + + for(size_t x = 0; x < length; x++) { + dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); + } + + } else { + *headerPtr = maskKey[0]; headerPtr++; + *headerPtr = maskKey[1]; headerPtr++; + *headerPtr = maskKey[2]; headerPtr++; + *headerPtr = maskKey[3]; headerPtr++; + } + } + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + if(headerToPayload) { + // header has be added to payload + // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings + // offset in payload is calculatetd 14 - headerSize + if (client->tcp->write(&payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { + ret = false; + } + } else { + // send header + if (client->tcp->write(&buffer[0], headerSize) != headerSize) { + ret = false; + } + + if(payloadPtr && length > 0) { + // send payload + if(client->tcp->write(&payloadPtr[0], length) != length) { + ret = false; + } + } + } + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][sendFrame] sending Frame Done ("); + DEBUG_WEBSOCKETS((micros() - start)); + DEBUG_WEBSOCKETS("us).\n"); + +#ifdef WEBSOCKETS_USE_BIG_MEM + if(useInternBuffer && payloadPtr) { + free(payloadPtr); + } +#endif + + return ret; +} + +/** + * handle the WebSocket stream + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::handleWebsocket(WSclient_t * client) { + + uint8_t buffer[8] = { 0 }; + + bool fin; + bool rsv1; + bool rsv2; + bool rsv3; + WSopcode_t opCode; + bool mask; + size_t payloadLen; + + uint8_t maskKey[4]; + + uint8_t * payload = NULL; + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] ------- read massage frame -------\n"); + + if(!readWait(client, buffer, 2)) { + //timeout + clientDisconnect(client, 1002); + return; + } + + // split first 2 bytes in the data + fin = ((buffer[0] >> 7) & 0x01); + rsv1 = ((buffer[0] >> 6) & 0x01); + rsv2 = ((buffer[0] >> 5) & 0x01); + rsv3 = ((buffer[0] >> 4) & 0x01); + opCode = (WSopcode_t) (buffer[0] & 0x0F); + + mask = ((buffer[1] >> 7) & 0x01); + payloadLen = (WSopcode_t) (buffer[1] & 0x7F); + + if(payloadLen == 126) { + if(!readWait(client, buffer, 2)) { + //timeout + clientDisconnect(client, 1002); + return; + } + payloadLen = buffer[0] << 8 | buffer[1]; + } else if(payloadLen == 127) { + // read 64bit integer as length + if(!readWait(client, buffer, 8)) { + //timeout + clientDisconnect(client, 1002); + return; + } + + if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { + // really to big! + payloadLen = 0xFFFFFFFF; + } else { + payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; + } + } + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] fin: "); + DEBUG_WEBSOCKETS(fin); + DEBUG_WEBSOCKETS(" rsv1: "); + DEBUG_WEBSOCKETS(rsv1); + DEBUG_WEBSOCKETS(" rsv2: "); + DEBUG_WEBSOCKETS(rsv2); + DEBUG_WEBSOCKETS(" rsv3: "); + DEBUG_WEBSOCKETS(rsv3); + DEBUG_WEBSOCKETS(" opCode: "); + DEBUG_WEBSOCKETS(opCode); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] mask: "); + DEBUG_WEBSOCKETS(mask); + DEBUG_WEBSOCKETS(" payloadLen: "); + DEBUG_WEBSOCKETS(payloadLen); + DEBUG_WEBSOCKETS("\n"); + + if(payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] payload to big! ("); + DEBUG_WEBSOCKETS(payloadLen); + DEBUG_WEBSOCKETS(")\n"); + clientDisconnect(client, 1009); + return; + } + + if(mask) { + if(!readWait(client, maskKey, 4)) { + //timeout + clientDisconnect(client, 1002); + return; + } + } + + if(payloadLen > 0) { + // if text data we need one more + payload = (uint8_t *) malloc(payloadLen + 1); + + if(!payload) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] to less memory to handle payload "); + DEBUG_WEBSOCKETS(payloadLen); + DEBUG_WEBSOCKETS("!\n"); + clientDisconnect(client, 1011); + return; + } + + if(!readWait(client, payload, payloadLen)) { + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] missing data!\n"); + free(payload); + clientDisconnect(client, 1002); + return; + } + + payload[payloadLen] = 0x00; + + if(mask) { + //decode XOR + for(size_t i = 0; i < payloadLen; i++) { + payload[i] = (payload[i] ^ maskKey[i % 4]); + } + } + } + + switch(opCode) { + case WSop_text: + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] text: "); + DEBUG_WEBSOCKETS((char*) payload); + DEBUG_WEBSOCKETS("\n"); + // no break here! + case WSop_binary: + messageRecived(client, opCode, payload, payloadLen); + break; + case WSop_ping: + // send pong back + sendFrame(client, WSop_pong, payload, payloadLen); + break; + case WSop_pong: + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] get pong ("); + DEBUG_WEBSOCKETS((char*) payload); + DEBUG_WEBSOCKETS(")\n"); + break; + case WSop_close: + { + uint16_t reasonCode = 1000; + if(payloadLen >= 2) { + reasonCode = payload[0] << 8 | payload[1]; + } + + DEBUG_WEBSOCKETS("[WS]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleWebsocket] get ask for close. Code: "); + DEBUG_WEBSOCKETS(reasonCode); + if(payloadLen > 2) { + DEBUG_WEBSOCKETS(" ("); + DEBUG_WEBSOCKETS((char*) (payload+2)); + DEBUG_WEBSOCKETS(")\n"); + } else { + DEBUG_WEBSOCKETS("\n"); + } + clientDisconnect(client, 1000); + } + break; + case WSop_continuation: + // continuation is not supported + clientDisconnect(client, 1003); + break; + default: + clientDisconnect(client, 1002); + break; + } + + if(payload) { + free(payload); + } + +} + +/** + * generate the key for Sec-WebSocket-Accept + * @param clientKey String + * @return String Accept Key + */ +String WebSockets::acceptKey(String clientKey) { + uint8_t sha1HashBin[20] = { 0 }; +#ifdef ESP8266 + sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); +#else + clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + #ifdef LEGACY_SHA1 + // Miha Nahtigal + char cbuff[clientKey.length()+1]; + clientKey.toCharArray(cbuff, clientKey.length()+1);//Converts String into character array + Sha1.init(); + Sha1.print(cbuff); + strcpy(sha1HashBin, Sha1.result()); + Serial.println(cbuff); + //old:uint8_t *sha1HashBin = Sha1.result(); + #else + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char*)clientKey.c_str(), clientKey.length()); + SHA1Final(&sha1HashBin[0], &ctx); + #endif +#endif + + String key = base64_encode(sha1HashBin, 20); + key.trim(); + + return key; +} + +/** + * base64_encode + * @param data uint8_t * + * @param length size_t + * @return base64 encoded String + */ +String WebSockets::base64_encode(uint8_t * data, size_t length) { + size_t size = ((length*1.6f)+1); + char * buffer = (char *) malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + + String base64 = String(buffer); + free(buffer); + return base64; + } + return String("-FAIL-"); +} + +/** + * read x byte from tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return true if ok + */ +bool WebSockets::readWait(WSclient_t * client, uint8_t *out, size_t n) { + unsigned long t = millis(); + size_t len; + + while(n > 0) { + if(!client->tcp) { + DEBUG_WEBSOCKETS("[readWait] tcp is null!\n"); + return false; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[readWait] not connected!\n"); + return false; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readWait] receive TIMEOUT!\n"); + return false; + } + + if(!client->tcp->available()) { +#ifdef ESP8266 + delay(0); +#endif + continue; + } + + len = client->tcp->read((uint8_t*) out, n); + if(len) { + t = millis(); + out += len; + n -= len; + //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } else { + //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } + //DEBUG_WEBSOCKETS("Receive "); + //DEBUG_WEBSOCKETS(len); + //DEBUG_WEBSOCKETS("left "); + //DEBUG_WEBSOCKETS(n); + //DEBUG_WEBSOCKETS("!\n"); +#ifdef ESP8266 + delay(0); +#endif + } + return true; +} diff --git a/src/WebSockets.h b/src/WebSockets.h new file mode 100644 index 0000000..6fc1ac5 --- /dev/null +++ b/src/WebSockets.h @@ -0,0 +1,159 @@ +/** + * @file WebSockets.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETS_H_ +#define WEBSOCKETS_H_ + +#include + +//#define DEBUG_WEBSOCKETS(...) Serial.print( __VA_ARGS__ ) + +#ifndef DEBUG_WEBSOCKETS +#define DEBUG_WEBSOCKETS(...) +#define NODEBUG_WEBSOCKETS +#endif + +#ifdef ESP8266 +#define WEBSOCKETS_MAX_DATA_SIZE (15*1024) +#define WEBSOCKETS_USE_BIG_MEM +#else +//atmega328p has only 2KB ram! +#define WEBSOCKETS_MAX_DATA_SIZE (2048) +#endif + +#define WEBSOCKETS_TCP_TIMEOUT (1500) + +#define NETWORK_ESP8266 (1) +#define NETWORK_W5100 (2) +#define NETWORK_ENC28J60 (3) + + +// select Network type based +#ifdef ESP8266 +#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266 +#else +#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100 +#endif + + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + +#ifndef ESP8266 +#error "network type ESP8266 only possible on the ESP mcu!" +#endif + +#include +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer + +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_W5100) + +#include +#include +#define WEBSOCKETS_NETWORK_CLASS EthernetClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer + +#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_ENC28J60) + +#include +#define WEBSOCKETS_NETWORK_CLASS UIPClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS UIPServer + +#else +#error "no network type selected!" +#endif + + +typedef enum { + WSC_NOT_CONNECTED, + WSC_HEADER, + WSC_CONNECTED +} WSclientsStatus_t; + +typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN +} WStype_t; + +typedef enum { + WSop_continuation = 0x00, ///< %x0 denotes a continuation frame + WSop_text = 0x01, ///< %x1 denotes a text frame + WSop_binary = 0x02, ///< %x2 denotes a binary frame + ///< %x3-7 are reserved for further non-control frames + WSop_close = 0x08, ///< %x8 denotes a connection close + WSop_ping = 0x09, ///< %x9 denotes a ping + WSop_pong = 0x0A ///< %xA denotes a pong + ///< %xB-F are reserved for further control frames +} WSopcode_t; + +typedef struct { + uint8_t num; ///< connection number + + WSclientsStatus_t status; + + WEBSOCKETS_NETWORK_CLASS * tcp; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + bool isSSL; ///< run in ssl mode + WiFiClientSecure * ssl; +#endif + + String cUrl; ///< http url + uint16_t cCode; ///< http code + + bool cIsUpgrade; ///< Connection == Upgrade + bool cIsWebsocket; ///< Upgrade == websocket + + String cKey; ///< client Sec-WebSocket-Key + String cAccept; ///< client Sec-WebSocket-Accept + String cProtocol; ///< client Sec-WebSocket-Protocol + String cExtensions; ///< client Sec-WebSocket-Extensions + uint16_t cVersion; ///< client Sec-WebSocket-Version + +} WSclient_t; + +class WebSockets { + protected: + virtual void clientDisconnect(WSclient_t * client); + virtual bool clientIsConnected(WSclient_t * client); + + virtual void messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length); + + void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); + bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool mask = false, bool fin = true, bool headerToPayload = false); + + + void handleWebsocket(WSclient_t * client); + + bool readWait(WSclient_t * client, uint8_t *out, size_t n); + + String acceptKey(String clientKey); + String base64_encode(uint8_t * data, size_t length); + +}; + +#endif /* WEBSOCKETS_H_ */ diff --git a/src/WebSocketsClient.cpp b/src/WebSocketsClient.cpp new file mode 100644 index 0000000..928874b --- /dev/null +++ b/src/WebSocketsClient.cpp @@ -0,0 +1,506 @@ +/** + * @file WebSocketsClient.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsClient.h" + +WebSocketsClient::WebSocketsClient() { + _cbEvent = NULL; + _client.num = 0; +} + +WebSocketsClient::~WebSocketsClient() { + disconnect(); +} + +/** + * calles to init the Websockets server + */ +void WebSocketsClient::begin(const char *host, uint16_t port, const char * url) { + _host = host; + _port = port; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + _fingerprint = ""; +#endif + + _client.num = 0; + _client.status = WSC_NOT_CONNECTED; + _client.tcp = NULL; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + _client.isSSL = false; + _client.ssl = NULL; +#endif + _client.cUrl = url; + _client.cCode = 0; + _client.cIsUpgrade = false; + _client.cIsWebsocket = true; + _client.cKey = ""; + _client.cAccept = ""; + _client.cProtocol = ""; + _client.cExtensions = ""; + _client.cVersion = 0; + +#ifdef ESP8266 + randomSeed(RANDOM_REG32); +#else + // todo find better seed + randomSeed(millis()); +#endif +} + +void WebSocketsClient::begin(String host, uint16_t port, String url) { + begin(host.c_str(), port, url.c_str()); +} + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) +void WebSocketsClient::beginSSL(const char *host, uint16_t port, const char * url, const char * fingerprint) { + begin(host, port, url); + _client.isSSL = true; + _fingerprint = fingerprint; +} + +void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint) { + beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str()); +} +#endif + +/** + * called in arduino loop + */ +void WebSocketsClient::loop(void) { + if(!clientIsConnected(&_client)) { + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + if(_client.isSSL) { + DEBUG_WEBSOCKETS("[WS-Client] connect wss...\n"); + if(_client.ssl) { + delete _client.ssl; + _client.ssl = NULL; + _client.tcp = NULL; + } + _client.ssl = new WiFiClientSecure(); + _client.tcp = _client.ssl; + } else { + DEBUG_WEBSOCKETS("[WS-Client] connect ws...\n"); + if(_client.tcp) { + delete _client.tcp; + _client.tcp = NULL; + } + _client.tcp = new WiFiClient(); + } +#else + _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); +#endif + + if(!_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); + return; + } + + if(_client.tcp->connect(_host.c_str(), _port)) { + DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n"); + //DEBUG_WEBSOCKETS(_host.c_str()); + //DEBUG_WEBSOCKETS(_port); + + _client.status = WSC_HEADER; + + // set Timeout for readBytesUntil and readStringUntil + _client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + _client.tcp->setNoDelay(true); + + if(_client.isSSL && _fingerprint.length()) { + if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) { + DEBUG_WEBSOCKETS("[WS-Client] certificate mismatch\n"); + WebSockets::clientDisconnect(&_client, 1000); + return; + } + } +#endif + + // send Header to Server + sendHeader(&_client); + + } else { + DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Faild\n"); + ///EBUG_WEBSOCKETS(_host.c_str()); + //DEBUG_WEBSOCKETS(_port); + delay(10); //some litle delay to not flood the server + } + } else { + handleClientData(); + } +} + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) { + _cbEvent = cbEvent; +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + */ +void WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) { + if(length == 0) { + length = strlen((const char *) payload); + } + if(clientIsConnected(&_client)) { + sendFrame(&_client, WSop_text, payload, length, true, true, headerToPayload); + } +} + +void WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) { + sendTXT((uint8_t *) payload, length); +} + +void WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) { + sendTXT((uint8_t *) payload, length, headerToPayload); +} + +void WebSocketsClient::sendTXT(const char * payload, size_t length) { + sendTXT((uint8_t *) payload, length); +} + +void WebSocketsClient::sendTXT(String payload) { + sendTXT((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + */ +void WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) { + if(clientIsConnected(&_client)) { + sendFrame(&_client, WSop_binary, payload, length, true, true, headerToPayload); + } +} + +void WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { + sendBIN((uint8_t *) payload, length); +} + + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsClient::disconnect(void) { + if(clientIsConnected(&_client)) { + WebSockets::clientDisconnect(&_client, 1000); + } +} + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param lenght size_t + */ +void WebSocketsClient::messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t lenght) { + WStype_t type = WStype_ERROR; + + switch(opcode) { + case WSop_text: + type = WStype_TEXT; + break; + case WSop_binary: + type = WStype_BIN; + break; + } + + runCbEvent(type, payload, lenght); + +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::clientDisconnect(WSclient_t * client) { + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif + + if(client->tcp) { + if(client->tcp->connected()) { + client->tcp->flush(); + client->tcp->stop(); + } + delete client->tcp; + client->tcp = NULL; + } + + client->cCode = 0; + client->cKey = ""; + client->cAccept = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + + client->status = WSC_NOT_CONNECTED; + + DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n"); + + runCbEvent(WStype_DISCONNECTED, NULL, 0); + +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = conneted + */ +bool WebSocketsClient::clientIsConnected(WSclient_t * client) { + + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Client] connection lost.\n"); + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + clientDisconnect(client); + } + + return false; +} + +/** + * Handel incomming data from Client + */ +void WebSocketsClient::handleClientData(void) { + int len = _client.tcp->available(); + if(len > 0) { + switch(_client.status) { + case WSC_HEADER: + handleHeader(&_client); + break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(&_client); + break; + default: + WebSockets::clientDisconnect(&_client, 1002); + break; + } + } +#ifdef ESP8266 + delay(0); +#endif +} + +/** + * send the WebSocket header to Server + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::sendHeader(WSclient_t * client) { + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n"); + + uint8_t randomKey[16] = { 0 }; + + for(uint8_t i = 0; i < sizeof(randomKey); i++) { + randomKey[i] = random(0xFF); + } + + client->cKey = base64_encode(&randomKey[0], 16); + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + String handshake = "GET " + client->cUrl + " HTTP/1.1\r\n" + "Host: " + _host + "\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "User-Agent: arduino-WebSocket-Client\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Protocol: arduino\r\n" + "Sec-WebSocket-Key: " + client->cKey + "\r\n"; + + if(client->cExtensions.length() > 0) { + handshake += "Sec-WebSocket-Extensions: " + client->cExtensions + "\r\n"; + } + + handshake += "\r\n"; + + client->tcp->write(handshake.c_str(), handshake.length()); + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%uus).\n"); + //DEBUG_WEBSOCKETS((micros() - start)); + +} + +/** + * handle the WebSocket header reading + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::handleHeader(WSclient_t * client) { + + String headerLine = client->tcp->readStringUntil('\n'); + headerLine.trim(); // remove \r + + if(headerLine.length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n"); + DEBUG_WEBSOCKETS(headerLine.c_str()); + + if(headerLine.startsWith("HTTP/1.")) { + // "HTTP/1.1 101 Switching Protocols" + client->cCode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt(); + } else if(headerLine.indexOf(':')) { + String headerName = headerLine.substring(0, headerLine.indexOf(':')); + String headerValue = headerLine.substring(headerLine.indexOf(':') + 2); + + if(headerName.equalsIgnoreCase("Connection")) { + if(headerValue.indexOf("Upgrade") >= 0) { + client->cIsUpgrade = true; + } + } else if(headerName.equalsIgnoreCase("Upgrade")) { + if(headerValue.equalsIgnoreCase("websocket")) { + client->cIsWebsocket = true; + } + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Accept")) { + client->cAccept = headerValue; + client->cAccept.trim(); // see rfc6455 + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Protocol")) { + client->cProtocol = headerValue; + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Extensions")) { + client->cExtensions = headerValue; + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Version")) { + client->cVersion = headerValue.toInt(); + } + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n"); + DEBUG_WEBSOCKETS(headerLine.c_str()); + } + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n"); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n"); + //DEBUG_WEBSOCKETS(client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n"); + //DEBUG_WEBSOCKETS(client->cKey.c_str()); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cCode: %d\n"); + //DEBUG_WEBSOCKETS(client->cCode); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n"); + //DEBUG_WEBSOCKETS(client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n"); + //DEBUG_WEBSOCKETS(client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cAccept: %s\n"); + //DEBUG_WEBSOCKETS(client->cAccept.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n"); + //DEBUG_WEBSOCKETS(client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n"); + //DEBUG_WEBSOCKETS(client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n"); + //DEBUG_WEBSOCKETS(client->cVersion); + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + switch(client->cCode) { + case 101: ///< Switching Protocols + + break; + case 403: ///< Forbidden + // todo handle login + default: ///< Server dont unterstand requrst + ok = false; + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n"); + //DEBUG_WEBSOCKETS(client->cCode); + clientDisconnect(client); + break; + } + } + + if(ok) { + if(client->cAccept.length() == 0) { + ok = false; + } else { + // generate Sec-WebSocket-Accept key for check + String sKey = acceptKey(client->cKey); + if(sKey != client->cAccept) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n"); + ok = false; + } + } + } + + if(ok) { + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n"); + + client->status = WSC_CONNECTED; + + runCbEvent(WStype_CONNECTED, (uint8_t *) client->cUrl.c_str(), client->cUrl.length()); + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); + client->tcp->write("This is a webSocket client!"); + clientDisconnect(client); + } + } +} + diff --git a/src/WebSocketsClient.h b/src/WebSocketsClient.h new file mode 100644 index 0000000..b5a88f3 --- /dev/null +++ b/src/WebSocketsClient.h @@ -0,0 +1,98 @@ +/** + * @file WebSocketsClient.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSCLIENT_H_ +#define WEBSOCKETSCLIENT_H_ + +#include +#include "WebSockets.h" + +class WebSocketsClient: private WebSockets { + public: + + typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); + + WebSocketsClient(void); + ~WebSocketsClient(void); + + void begin(const char *host, uint16_t port, const char * url = "/"); + void begin(String host, uint16_t port, String url = "/"); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + void beginSSL(const char *host, uint16_t port, const char * url = "/", const char * = ""); + void beginSSL(String host, uint16_t port, String url = "/", String fingerprint = ""); +#endif + + void loop(void); + + void onEvent(WebSocketClientEvent cbEvent); + + void sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + void sendTXT(const uint8_t * payload, size_t length = 0); + void sendTXT(char * payload, size_t length = 0, bool headerToPayload = false); + void sendTXT(const char * payload, size_t length = 0); + void sendTXT(String payload); + + void sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false); + void sendBIN(const uint8_t * payload, size_t length); + + void disconnect(void); + + protected: + String _host; + uint16_t _port; + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + String _fingerprint; +#endif + WSclient_t _client; + + WebSocketClientEvent _cbEvent; + + void messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + + void handleNewClients(void); + void handleClientData(void); + + void sendHeader(WSclient_t * client); + void handleHeader(WSclient_t * client); + + /** + * called for sending a Event to the app + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(type, payload, length); + } + } + +}; + +#endif /* WEBSOCKETSCLIENT_H_ */ diff --git a/src/WebSocketsServer.cpp b/src/WebSocketsServer.cpp new file mode 100644 index 0000000..ab1a408 --- /dev/null +++ b/src/WebSocketsServer.cpp @@ -0,0 +1,714 @@ +/** + * @file WebSocketsServer.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsServer.h" + +WebSocketsServer::WebSocketsServer(uint16_t port) { + _port = port; + _server = new WEBSOCKETS_NETWORK_SERVER_CLASS(port); + + _cbEvent = NULL; + +} + +WebSocketsServer::~WebSocketsServer() { + // disconnect all clients + disconnect(); + + // TODO how to close server? +} + +/** + * calles to init the Websockets server + */ +void WebSocketsServer::begin(void) { + WSclient_t * client; + + // init client storage + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + + client->num = i; + client->status = WSC_NOT_CONNECTED; + client->tcp = NULL; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + client->isSSL = false; + client->ssl = NULL; +#endif + client->cUrl = ""; + client->cCode = 0; + client->cKey = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + } + +#ifdef ESP8266 + randomSeed(RANDOM_REG32); +#else + // TODO find better seed + randomSeed(millis()); +#endif + + _server->begin(); + + DEBUG_WEBSOCKETS("[WS-Server] Server Started.\n"); +} + +/** + * called in arduino loop + */ +void WebSocketsServer::loop(void) { + handleNewClients(); + handleClientData(); +} + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsServer::onEvent(WebSocketServerEvent cbEvent) { + _cbEvent = cbEvent; +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + */ +void WebSocketsServer::sendTXT(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return; + } + if(length == 0) { + length = strlen((const char *) payload); + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + sendFrame(client, WSop_text, payload, length, false, true, headerToPayload); + } +} + +void WebSocketsServer::sendTXT(uint8_t num, const uint8_t * payload, size_t length) { + sendTXT(num, (uint8_t *) payload, length); +} + +void WebSocketsServer::sendTXT(uint8_t num, char * payload, size_t length, bool headerToPayload) { + sendTXT(num, (uint8_t *) payload, length, headerToPayload); +} + +void WebSocketsServer::sendTXT(uint8_t num, const char * payload, size_t length) { + sendTXT(num, (uint8_t *) payload, length); +} + +void WebSocketsServer::sendTXT(uint8_t num, String payload) { + sendTXT(num, (uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send text data to client all + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + */ +void WebSocketsServer::broadcastTXT(uint8_t * payload, size_t length, bool headerToPayload) { + WSclient_t * client; + if(length == 0) { + length = strlen((const char *) payload); + } + + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + sendFrame(client, WSop_text, payload, length, false, true, headerToPayload); + } +#ifdef ESP8266 + delay(0); +#endif + } +} + +void WebSocketsServer::broadcastTXT(const uint8_t * payload, size_t length) { + broadcastTXT((uint8_t *) payload, length); +} + +void WebSocketsServer::broadcastTXT(char * payload, size_t length, bool headerToPayload) { + broadcastTXT((uint8_t *) payload, length, headerToPayload); +} + +void WebSocketsServer::broadcastTXT(const char * payload, size_t length) { + broadcastTXT((uint8_t *) payload, length); +} + +void WebSocketsServer::broadcastTXT(String payload) { + broadcastTXT((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + */ +void WebSocketsServer::sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + sendFrame(client, WSop_binary, payload, length, false, true, headerToPayload); + } +} + +void WebSocketsServer::sendBIN(uint8_t num, const uint8_t * payload, size_t length) { + sendBIN(num, (uint8_t *) payload, length); +} + +/** + * send binary data to client all + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + */ +void WebSocketsServer::broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload) { + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + sendFrame(client, WSop_binary, payload, length, false, true, headerToPayload); + } +#ifdef ESP8266 + delay(0); +#endif + } +} + +void WebSocketsServer::broadcastBIN(const uint8_t * payload, size_t length) { + broadcastBIN((uint8_t *) payload, length); +} + +/** + * sends a WS ping to Client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsServer::sendPing(uint8_t num, uint8_t * payload, size_t length) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + WSclient_t * client; + client = &_clients[num]; + if(clientIsConnected(client)) { + return sendFrame(client, WSop_ping, payload, length); + } + return false; +} + +bool WebSocketsServer::sendPing(uint8_t num, String & payload) { + return sendPing(num, (uint8_t *) payload.c_str(), payload.length()); +} + +/** + * sends a WS ping to all Client + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsServer::broadcastPing(uint8_t * payload, size_t length) { + WSclient_t * client; + bool ret = true; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(!sendFrame(client, WSop_ping, payload, length)) { + ret = false; + } + } +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + delay(0); +#endif + } + return ret; +} + +bool WebSocketsServer::broadcastPing(String & payload) { + return broadcastPing((uint8_t *) payload.c_str(), payload.length()); +} + +bool WebSocketsServer::clientConnected(uint8_t num) { + WSclient_t * client; + client = &_clients[num]; + return clientIsConnected(client); +} + +/** + * disconnect all clients + */ +void WebSocketsServer::disconnect(void) { + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + WebSockets::clientDisconnect(client, 1000); + } + } +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsServer::disconnect(uint8_t num) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + WebSockets::clientDisconnect(client, 1000); + } +} + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) +/** + * get an IP for a client + * @param num uint8_t client id + * @return IPAddress + */ +IPAddress WebSocketsServer::remoteIP(uint8_t num) { + if(num < WEBSOCKETS_SERVER_CLIENT_MAX) { + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return client->tcp->remoteIP(); + } + } + + return IPAddress(); +} +#endif + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param lenght size_t + */ +void WebSocketsServer::messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t lenght) { + WStype_t type = WStype_ERROR; + + switch(opcode) { + case WSop_text: + type = WStype_TEXT; + break; + case WSop_binary: + type = WStype_BIN; + break; + } + + runCbEvent(client->num, type, payload, lenght); + +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsServer::clientDisconnect(WSclient_t * client) { + + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif + + if(client->tcp) { + if(client->tcp->connected()) { + client->tcp->flush(); + client->tcp->stop(); + } + delete client->tcp; + client->tcp = NULL; + } + + client->cUrl = ""; + client->cKey = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + + client->status = WSC_NOT_CONNECTED; + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS(" client disconnected.\n"); + + + runCbEvent(client->num, WStype_DISCONNECTED, NULL, 0); + +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = conneted + */ +bool WebSocketsServer::clientIsConnected(WSclient_t * client) { + + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + DEBUG_WEBSOCKETS(client->status); + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS(" client connection lost.\n"); + + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + clientDisconnect(client); + } + + return false; +} + +#if (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266) + bool WebSocketsServer::clientExists(const WEBSOCKETS_NETWORK_CLASS &c) + { + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if (clientIsConnected(client) && (*client->tcp == c)) + return true; + } + return false; + } +#endif + +/** + * Handle incomming Connection Request + */ +void WebSocketsServer::handleNewClients(void) { + WSclient_t * client; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + while(_server->hasClient()) { +#endif + bool ok = false; + // search free list entry for client + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + + // state is not connected or tcp connection is lost + if(!clientIsConnected(client)) { +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + // store new connection + client->tcp = new WEBSOCKETS_NETWORK_CLASS(_server->available()); +#else + WEBSOCKETS_NETWORK_CLASS newClient = _server->available(); + if (newClient && !clientExists(newClient)) { + client->tcp = new WEBSOCKETS_NETWORK_CLASS(newClient); + } else { + return; + } + //if (!client->tcp->connected() || !client->tcp->available()) { + // DEBUG_WEBSOCKETS("[WS-Client] Not Connected!\n"); + // return; + //} +#endif + if(!client->tcp) { + DEBUG_WEBSOCKETS("[WS-Server] creating Network class failed!"); + return; + } + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + client->isSSL = false; + client->tcp->setNoDelay(true); +#endif + // set Timeout for readBytesUntil and readStringUntil + client->tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); + client->status = WSC_HEADER; +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + IPAddress ip = client->tcp->remoteIP(); + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS(" new client from %d.%d.%d.%d\n"); + + //DEBUG_WEBSOCKETS(ip[0]); + //DEBUG_WEBSOCKETS(ip[1]); + //DEBUG_WEBSOCKETS(ip[2]); + //DEBUG_WEBSOCKETS(ip[3]); +#else + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS(" new client\n"); +#endif + ok = true; + break; + } + } + + if(!ok) { + // no free space to handle client + WEBSOCKETS_NETWORK_CLASS tcpClient = _server->available(); +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + IPAddress ip = client->tcp->remoteIP(); + DEBUG_WEBSOCKETS("[WS-Server] no free space new client from %d.%d.%d.%d\n"); + //DEBUG_WEBSOCKETS(ip[0]); + //DEBUG_WEBSOCKETS(ip[1]); + //DEBUG_WEBSOCKETS(ip[2]); + //DEBUG_WEBSOCKETS(ip[3]); +#else + DEBUG_WEBSOCKETS("[WS-Server] no free space new client\n"); +#endif + tcpClient.stop(); + } + +#ifdef ESP8266 + delay(0); +#endif +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + } +#endif +} + +/** + * Handel incomming data from Client + */ +void WebSocketsServer::handleClientData(void) { + + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + int len = client->tcp->available(); + if(len > 0) { + switch(client->status) { + case WSC_HEADER: + handleHeader(client); + break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(client); + break; + default: + WebSockets::clientDisconnect(client, 1002); + break; + } + } + } +#ifdef ESP8266 + delay(0); +#endif + } +} + +/** + * handle the WebSocket header reading + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsServer::handleHeader(WSclient_t * client) { + + String headerLine = client->tcp->readStringUntil('\n'); + headerLine.trim(); // remove \r + + if(headerLine.length() > 0) { + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] RX: "); + DEBUG_WEBSOCKETS(headerLine.c_str()); + DEBUG_WEBSOCKETS("\n"); + + // websocket request starts allways with GET see rfc6455 + if(headerLine.startsWith("GET ")) { + // cut URL out + client->cUrl = headerLine.substring(4, headerLine.indexOf(' ', 4)); + } else if(headerLine.indexOf(':')) { + String headerName = headerLine.substring(0, headerLine.indexOf(':')); + String headerValue = headerLine.substring(headerLine.indexOf(':') + 2); + //Serial.print(headerName); Serial.print(":"); Serial.println(headerValue); + + if(headerName.equalsIgnoreCase("Connection")) { + if(headerValue.indexOf("Upgrade") >= 0) { + client->cIsUpgrade = true; + } + } else if(headerName.equalsIgnoreCase("Upgrade")) { + if(headerValue.equalsIgnoreCase("websocket")) { + client->cIsWebsocket = true; + } + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Version")) { + client->cVersion = headerValue.toInt(); + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Key")) { + client->cKey = headerValue; + client->cKey.trim(); // see rfc6455 + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Protocol")) { + client->cProtocol = headerValue; + } else if(headerName.equalsIgnoreCase("Sec-WebSocket-Extensions")) { + client->cExtensions = headerValue; + } + } else { + Serial.print("[WS-Client][handleHeader] Header error (%s)\n"); + Serial.print(headerLine.c_str()); + } + + } else { + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] Header read fin.\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cURL: "); + DEBUG_WEBSOCKETS(client->cUrl.c_str()); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cIsUpgrade: "); + DEBUG_WEBSOCKETS(client->cIsUpgrade); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cIsWebsocket: "); + DEBUG_WEBSOCKETS(client->cIsWebsocket); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cKey: "); + DEBUG_WEBSOCKETS(client->cKey.c_str()); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cProtocol: "); + DEBUG_WEBSOCKETS(client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cExtensions: "); + DEBUG_WEBSOCKETS(client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("\n"); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - cVersion: "); + DEBUG_WEBSOCKETS(client->cVersion); + DEBUG_WEBSOCKETS("\n\n"); + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + Serial.println("here0"); + + if (!client->cIsUpgrade) { + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] Error! Not an Upgrade\n"); + } + + if (!client->cIsWebsocket) { + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] Error! Not a Websocket\n"); + } + + if(ok) { + Serial.println("here1"); + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] First ok test success.\n"); + + if(client->cUrl.length() == 0) { + Serial.println("here2"); + ok = false; + } + if(client->cKey.length() == 0) { + Serial.println("here3"); + ok = false; + } + if(client->cVersion != 13) { + Serial.println("here4"); + ok = false; + } + } + + if(ok) { + Serial.println("here5"); + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] Websocket connection incomming.\n"); + + + // generate Sec-WebSocket-Accept key + String sKey = acceptKey(client->cKey); + + DEBUG_WEBSOCKETS("[WS-Server][");DEBUG_WEBSOCKETS(client->num);DEBUG_WEBSOCKETS("]"); + DEBUG_WEBSOCKETS("[handleHeader] - sKey: "); + DEBUG_WEBSOCKETS(sKey.c_str()); + DEBUG_WEBSOCKETS("\n"); + + client->status = WSC_CONNECTED; + + client->tcp->write("HTTP/1.1 101 Switching Protocols\r\n" + "Server: arduino-WebSocketsServer\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Accept: "); + client->tcp->write(sKey.c_str(), sKey.length()); + client->tcp->write("\r\n"); + + if(client->cProtocol.length() > 0) { + // TODO add api to set Protocol of Server + client->tcp->write("Sec-WebSocket-Protocol: arduino\r\n"); + } + + // header end + client->tcp->write("\r\n"); + + // send ping + WebSockets::sendFrame(client, WSop_ping); + + runCbEvent(client->num, WStype_CONNECTED, (uint8_t *) client->cUrl.c_str(), client->cUrl.length()); + + } else { + handleNonWebsocketConnection(client); + } + } +} + + + diff --git a/src/WebSocketsServer.h b/src/WebSocketsServer.h new file mode 100644 index 0000000..c5627e0 --- /dev/null +++ b/src/WebSocketsServer.h @@ -0,0 +1,143 @@ +/** + * @file WebSocketsServer.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSSERVER_H_ +#define WEBSOCKETSSERVER_H_ + +#include +#include "WebSockets.h" + +#define WEBSOCKETS_SERVER_CLIENT_MAX (5) + + + + +class WebSocketsServer: private WebSockets { +public: + + typedef void (*WebSocketServerEvent)(uint8_t num, WStype_t type, uint8_t * payload, size_t length); + + WebSocketsServer(uint16_t port); + ~WebSocketsServer(void); + + void begin(void); + void loop(void); + + void onEvent(WebSocketServerEvent cbEvent); + + + void sendTXT(uint8_t num, uint8_t * payload, size_t length = 0, bool headerToPayload = false); + void sendTXT(uint8_t num, const uint8_t * payload, size_t length = 0); + void sendTXT(uint8_t num, char * payload, size_t length = 0, bool headerToPayload = false); + void sendTXT(uint8_t num, const char * payload, size_t length = 0); + void sendTXT(uint8_t num, String payload); + + void broadcastTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + void broadcastTXT(const uint8_t * payload, size_t length = 0); + void broadcastTXT(char * payload, size_t length = 0, bool headerToPayload = false); + void broadcastTXT(const char * payload, size_t length = 0); + void broadcastTXT(String payload); + + void sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload = false); + void sendBIN(uint8_t num, const uint8_t * payload, size_t length); + + bool sendPing(uint8_t num, uint8_t * payload = NULL, size_t length = 0); + bool sendPing(uint8_t num, String & payload); + + bool broadcastPing(uint8_t * payload = NULL, size_t length = 0); + bool broadcastPing(String & payload); + + void broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload = false); + void broadcastBIN(const uint8_t * payload, size_t length); + + void disconnect(void); + void disconnect(uint8_t num); + + bool clientConnected(uint8_t num); + +#if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + IPAddress remoteIP(uint8_t num); +#else + bool clientExists(const WEBSOCKETS_NETWORK_CLASS &c); + +#endif + +protected: + uint16_t _port; + + WEBSOCKETS_NETWORK_SERVER_CLASS * _server; + + WSclient_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX]; + + WebSocketServerEvent _cbEvent; + + void messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + + void handleNewClients(void); + void handleClientData(void); + + void handleHeader(WSclient_t * client); + + /** + * called if a non Websocket connection is comming in. + * Note: can be overrided + * @param client WSclient_t * ptr to the client struct + */ + virtual void handleNonWebsocketConnection(WSclient_t * client) { + DEBUG_WEBSOCKETS("[WS-Server]["); + DEBUG_WEBSOCKETS(client->num); + DEBUG_WEBSOCKETS("][handleHeader] no Websocket connection close.\n"); + + client->tcp->write("HTTP/1.1 400 Bad Request\r\n" + "Server: arduino-WebSocket-Server\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 32\r\n" + "Connection: close\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + "This is a Websocket server only!"); + clientDisconnect(client); + } + + /** + * called for sending a Event to the app + * @param num uint8_t + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(num, type, payload, length); + } + } + +}; + + + +#endif /* WEBSOCKETSSERVER_H_ */ diff --git a/src/image.cpp b/src/image.cpp new file mode 100644 index 0000000..7bcbbec --- /dev/null +++ b/src/image.cpp @@ -0,0 +1,47 @@ +#include "image.hpp" + + +bool Image::check_bounds(int x, int y) { + if ((x < 0) || (y < 0)) { + return false; + } + + if ((x >= width) || (y >= height)) { + return false; + } + + return true; +} + +byte Image::get_pixel(int x, int y) { + if (check_bounds(x, y) == false) { + return 0; + } + return data[y * width + x]; +} + +void Image::set_pixel(int x, int y, byte value) { + if (check_bounds(x, y) == false) { + return; + } + data[y * width + x] = value; +} + +void Image::clear_pixels() { + memset(data, 0, sizeof(data)); +} + +void Image::set_size(int w, int h) { + width = min(w, MAX_WIDTH); + height = min(h, MAX_HEIGHT); +} + +uint16_t Image::getWidth() +{ + return width; +} + +uint16_t Image::getHeight() +{ + return height; +} \ No newline at end of file diff --git a/src/image.hpp b/src/image.hpp new file mode 100644 index 0000000..d758727 --- /dev/null +++ b/src/image.hpp @@ -0,0 +1,28 @@ +#ifndef IMAGE_HPP +#define IMAGE_HPP + +#include + +#define MAX_WIDTH 32 +#define MAX_HEIGHT 40 + +class Image +{ + public: + byte get_pixel(int x, int y); + void set_pixel(int x, int y, byte value); + void clear_pixels(); + void set_size(int w, int h); + uint16_t getWidth(); + uint16_t getHeight(); + + private: + bool check_bounds(int x, int y); + + int width; + int height; + byte data[MAX_WIDTH * MAX_HEIGHT]; + +}; + +#endif \ No newline at end of file diff --git a/src/libb64/AUTHORS b/src/libb64/AUTHORS new file mode 100644 index 0000000..af68737 --- /dev/null +++ b/src/libb64/AUTHORS @@ -0,0 +1,7 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +Authors: +------- + +Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/src/libb64/LICENSE b/src/libb64/LICENSE new file mode 100644 index 0000000..a6b5606 --- /dev/null +++ b/src/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. \ No newline at end of file diff --git a/src/libb64/cdecode.c b/src/libb64/cdecode.c new file mode 100644 index 0000000..0d86d0e --- /dev/null +++ b/src/libb64/cdecode.c @@ -0,0 +1,94 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cdecode_inc.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +#endif diff --git a/src/libb64/cdecode_inc.h b/src/libb64/cdecode_inc.h new file mode 100644 index 0000000..d0d7f48 --- /dev/null +++ b/src/libb64/cdecode_inc.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/src/libb64/cencode.c b/src/libb64/cencode.c new file mode 100644 index 0000000..7367135 --- /dev/null +++ b/src/libb64/cencode.c @@ -0,0 +1,115 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cencode_inc.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = 0x00; + + return codechar - code_out; +} + +#endif diff --git a/src/libb64/cencode_inc.h b/src/libb64/cencode_inc.h new file mode 100644 index 0000000..c1e3464 --- /dev/null +++ b/src/libb64/cencode_inc.h @@ -0,0 +1,31 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ diff --git a/src/libsha1/libsha1.c b/src/libsha1/libsha1.c new file mode 100644 index 0000000..e6c1f3a --- /dev/null +++ b/src/libsha1/libsha1.c @@ -0,0 +1,202 @@ +/* from valgrind tests */ + +/* ================ sha1.c ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#ifndef ESP8266 + +#define SHA1HANDSOFF + +#include +#include +#include + +#include "libsha1.h" + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) +{ + uint32_t i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ + unsigned i; + unsigned char finalcount[8]; + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t; + } +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} +/* ================ end of sha1.c ================ */ + + +#endif diff --git a/src/libsha1/libsha1.h b/src/libsha1/libsha1.h new file mode 100644 index 0000000..81a953f --- /dev/null +++ b/src/libsha1/libsha1.h @@ -0,0 +1,21 @@ +/* ================ sha1.h ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain +*/ + +#ifndef ESP8266 + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); + +#endif \ No newline at end of file diff --git a/src/sha1/sha1.cpp b/src/sha1/sha1.cpp new file mode 100644 index 0000000..fdf07b2 --- /dev/null +++ b/src/sha1/sha1.cpp @@ -0,0 +1,162 @@ +#include +//#include +#ifdef ARDUINO_AVR_UNO + #include +#else + #include +#endif +#include "sha1.h" + +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +const 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; + } +} + +#if defined(ARDUINO) && ARDUINO >= 100 +size_t +#else +void +#endif +Sha1Class::write(uint8_t data) { + ++byteCount; + addUncounted(data); +#if defined(ARDUINO) && ARDUINO >= 100 + return 1; +#endif +} + +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); +#if defined(ARDUINO) && ARDUINO >= 100 + virtual size_t write(uint8_t); +#else + virtual void write(uint8_t); +#endif + 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