Now with Websockets and Web (HMTL) based GUI

This commit is contained in:
enny 2019-12-08 16:16:33 +01:00
parent fa4542a53b
commit c94ba558ec
24 changed files with 3487 additions and 202 deletions

View File

@ -1,53 +1,124 @@
#include <Arduino.h>
#include <Ethernet.h>
#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);
}
}
}

244
WebGUI.html Normal file
View File

@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang=de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LED Board</title>
<style>
body {background-color: #ccc; margin: 0; padding: 0; text-align: center;}
input {background-color: #ccc; border-radius: 2px;}
.menu { background-color: rgb(4, 0, 59); color: #ccc; padding: 1.2em; width: 100%; box-sizing: border-box;}
.panelRow{
display: inline-block;
font-size: 0;
margin-top: 0px;}
.pixel{
float: left;
width: 20px;
height: 20px;
margin-right:0px;
background-color: #000;
}
</style>
</head>
<body>
<div class="menu">
IP: <input type="text" id="ip" value="10.23.42.24">
<div style="width: 2em; display: inline-block;" ></div>
Port: <input type="text" id="port" value="81">
<div style="width: 2em; display: inline-block;" ></div>
Panel width: <input type="text" id="width" value="32" style="width: 2.5em;" onchange="creatGUI()">
height: <input type="text" id="height" value="40" style="width: 2.5em;" onchange="creatGUI()">
<div style="width: 1em; display: inline-block;" ></div>
Connection state: <div id="connectionState" onclick="toggleConnection()" style="background-color: red; display: inline-block; width: 1em; height: 1em; border-radius: 0.5em;"></div>
</div>
<input type="button" value="clear" onclick="clearCanvas()"><br />
<canvas id="can" style="background-color:#000; margin: 1em;"></canvas>
<script type="text/javascript">
var socket;
document.onload = openWebSocket();
var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0));
var canvas, ctx = false,
prevX = 0,
currX = 0,
prevY = 0,
currY = 0,
dragMode = false;
var onColor = "#fd0";
var offColor = "#310";
var pixelBuffer = new Array(0);
var scaling;
var xMax, yMax;
var changeFlag = false;
var frameRate = 3; //frames per second
creatGUI();
setTimeout(frameRefresh, 1000 / frameRate);
function openWebSocket()
{
ip = document.getElementById('ip').value;
port = document.getElementById('port').value;
panelWidthElement = document.getElementById('width');
panelHeightElement = document.getElementById('height');
displayView = document.getElementById("can");
console.log("connect to " + ip + ":" + port);
socket = new WebSocket('ws://' + ip + ':'+port+'', ['arduino']);
socket.onopen = function ()
{
console.log('connected');
document.getElementById('connectionState').style.backgroundColor = "#3d3";
};
socket.onerror = function (error)
{
console.log('WebSocket Error ', error);
document.getElementById('connectionState').style.backgroundColor = "#dd3";
};
socket.onclose = function ()
{
console.log('WebSocket connection closed');
document.getElementById('connectionState').style.backgroundColor = "#d33";
};
}
function frameRefresh()
{
if(changeFlag==true && socket.readyState === WebSocket.OPEN)
{
changeFlag = false;
sendPixelBuffer();
console.log("refreshed");
}
setTimeout(frameRefresh, 1000 / frameRate);
}
function toggleConnection()
{
if(socket.readyState === WebSocket.CLOSED)
{
openWebSocket();
}
else
{
socket.close();
}
}
function sendPixelBuffer()
{
header = new Uint8Array(6);
header[0] = Math.floor(xMax / 255);
header[1] = xMax % 255;
header[2] = Math.floor(yMax / 255);
header[3] = yMax % 255;
header[4] = 0;
header[5] = 0;
socket.send(header);
socket.send(pixelBuffer);
}
function creatGUI()
{
scaling = 10;//(document.width / 2) / panelWidthElement.value;
xMax = panelWidthElement.value;
yMax = panelHeightElement.value;
displayView.width = panelWidthElement.value*scaling;// + "px";
displayView.height = panelHeightElement.value*scaling;// + "px";
pixelBuffer = new Uint8Array(xMax*yMax);
initCanvas();
}
function clearCanvas()
{
ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, displayView.width, displayView.height);
for(x = 0; x < xMax; x++)
{
for(y = 0; y < yMax; y++)
{
pixelBuffer[x, xMax*y] = 0;
drawDot(x*scaling+1, y*scaling +1, true);
}
}
}
function initCanvas() {
canvas = document.getElementById('can');
ctx = canvas.getContext("2d");
w = canvas.width;
h = canvas.height;
canvas.addEventListener("mousemove", function (e) {
findxy('move', e)
}, false);
canvas.addEventListener("mousedown", function (e) {
findxy('down', e)
}, false);
canvas.addEventListener("mouseup", function (e) {
findxy('up', e)
}, false);
canvas.addEventListener("mouseout", function (e) {
findxy('out', e)
}, false);
clearCanvas();
}
function findxy(res, e) {
if (res == 'down') {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;
drawDot(currX, currY);
dragMode = true;
}
if (res == 'up' || res == "out") {
dragMode = false;
}
if (res == 'move') {
if (dragMode) {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;
drawDot(currX, currY);
}
}
}
function drawDot(x, y, forceOff=false)
{
x = Math.floor(x / scaling);
y = Math.floor(y / scaling);
if((pixelBuffer[x+xMax*y] == 0 || dragMode==true) && forceOff==false)
{
ctx.fillStyle = onColor;
if(pixelBuffer[x+xMax*y] != 1)
{
changeFlag = true;
}
pixelBuffer[x+xMax*y] = 1;
}
else
{
ctx.fillStyle = offColor;
if(pixelBuffer[x+xMax*y] != 0)
{
changeFlag = true;
}
pixelBuffer[x+xMax*y] = 0;
}
ctx.beginPath();
ctx.arc(x * scaling + scaling/2, y * scaling + scaling/2, scaling/2, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
</script>
</body>
</html>

View File

@ -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);
}

20
image.h
View File

@ -1,20 +0,0 @@
#include <Arduino.h>
#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);

77
src/ProtocolDL.cpp Normal file
View File

@ -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;
}

35
src/ProtocolDL.hpp Normal file
View File

@ -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

592
src/WebSockets.cpp Normal file
View File

@ -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 <core_esp8266_features.h>
#endif
extern "C" {
#ifdef CORE_HAS_LIBB64
#include <libb64/cencode.h>
#else
#include "libb64/cencode_inc.h"
#endif
}
#ifdef ESP8266
#include <Hash.h>
#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;
}

159
src/WebSockets.h Normal file
View File

@ -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 <Arduino.h>
//#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 <ESP8266WiFi.h>
#define WEBSOCKETS_NETWORK_CLASS WiFiClient
#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer
#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_W5100)
#include <Ethernet.h>
#include <SPI.h>
#define WEBSOCKETS_NETWORK_CLASS EthernetClient
#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer
#elif (WEBSOCKETS_NETWORK_TYPE == NETWORK_ENC28J60)
#include <UIPEthernet.h>
#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_ */

506
src/WebSocketsClient.cpp Normal file
View File

@ -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);
}
}
}

98
src/WebSocketsClient.h Normal file
View File

@ -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 <Arduino.h>
#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_ */

714
src/WebSocketsServer.cpp Normal file
View File

@ -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);
}
}
}

143
src/WebSocketsServer.h Normal file
View File

@ -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 <Arduino.h>
#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_ */

47
src/image.cpp Normal file
View File

@ -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;
}

28
src/image.hpp Normal file
View File

@ -0,0 +1,28 @@
#ifndef IMAGE_HPP
#define IMAGE_HPP
#include <Arduino.h>
#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

7
src/libb64/AUTHORS Normal file
View File

@ -0,0 +1,7 @@
libb64: Base64 Encoding/Decoding Routines
======================================
Authors:
-------
Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com

29
src/libb64/LICENSE Normal file
View File

@ -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.

94
src/libb64/cdecode.c Normal file
View File

@ -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 <core_esp8266_features.h>
#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

28
src/libb64/cdecode_inc.h Normal file
View File

@ -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 */

115
src/libb64/cencode.c Normal file
View File

@ -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 <core_esp8266_features.h>
#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

31
src/libb64/cencode_inc.h Normal file
View File

@ -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 */

202
src/libsha1/libsha1.c Normal file
View File

@ -0,0 +1,202 @@
/* from valgrind tests */
/* ================ sha1.c ================ */
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
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 <stdio.h>
#include <string.h>
#include <stdint.h>
#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

21
src/libsha1/libsha1.h Normal file
View File

@ -0,0 +1,21 @@
/* ================ sha1.h ================ */
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
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

162
src/sha1/sha1.cpp Normal file
View File

@ -0,0 +1,162 @@
#include <string.h>
//#include <avr/io.h>
#ifdef ARDUINO_AVR_UNO
#include <avr/pgmspace.h>
#else
#include <avr/pgmspace.h>
#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<BLOCK_LENGTH; i++) {
write(keyBuffer[i] ^ HMAC_IPAD);
}
}
uint8_t* Sha1Class::resultHmac(void) {
uint8_t i;
// Complete inner hash
memcpy(innerHash,result(),HASH_LENGTH);
// Calculate outer hash
init();
for (i=0; i<BLOCK_LENGTH; i++) write(keyBuffer[i] ^ HMAC_OPAD);
for (i=0; i<HASH_LENGTH; i++) write(innerHash[i]);
return result();
}
Sha1Class Sha1;

47
src/sha1/sha1.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef Sha1_h
#define Sha1_h
#include <inttypes.h>
#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