first commit

This commit is contained in:
Jérôme Delacotte
2025-03-06 11:15:32 +01:00
commit 7b30d6e298
5276 changed files with 2108927 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
## Websocket client for Arduino, with fast data send
This is a simple library that implements a Websocket client running on an Arduino.
### Rationale
For our IoT prototype project based on Arduino, we needed a reliable data transmission protocol, and we thought of Websocket. We then searched for existing client implementations for Arduino, something that was able to handle any subclass of the `Client` one provided by Arduino, and that was essential in implementation but complete as well. We found the excellent code here <https://github.com/brandenhall/Arduino-Websocket>. However, some modifications were needed for our purpose. In particular, we needed max throughput possible, approaching 100 messages/s.
### Features
I added the following:
- Faster data send (`client.sendData(..., true)`, default behaviour): instead of sending a TCP packet per char, everything is sent
in one shot in a single TCP packet. This makes the implementation much faster. However, take into consideration max string length when using `WiFiClient.write()` method (around 90 bytes, from users experience when googled). Example:
```C++
webSocketClient.sendData("my string to send", WS_OPCODE_TEXT, true);
```
- For method `client.getData()`, I created a pure C string implementation, to avoid chances of heap fragmentation due to `String`
class. Example:
```C++
char msg_in[100]; // should be long enough to hold the longest arriving message
uint8_t opcode_in;
...
webSocketClient.getData(msg_in, &opcode_in)
```
### Tests
The optimized code was tested for:
- `WiFiClient` (`<WiFi.h>` and `<WiFi101.h>`)
- Arduino UNO and ZERO
- WiFi shield (retired) on Arduino UNO and WiFi shield 101 on Arduino ZERO
- `ws` as Node.js websocket server
We were able to obtain to reach the target throughput indicated above, with a message length of around 70 bytes (\*):
(\*) In order to reach that speed, we had to apply the following hack:
1. <https://gist.github.com/u0078867/9df30eb7da64d8f43422faa70b1a9e52>
We did not want to get the `loop()` stuck if the TCP message was not sent (via WiFi), and we could afford some data lost randomly; although, we wanted our data to be reliable and in time order on the server side, so we excluded UDP packets.
2. After point 1, we had to manually disable the mask flag for websocket messages, by replacing this line in src /WebSocketClient.h:
```C++
#define WS_MASK 0x80
```
with this one:
```C++
#define WS_MASK 0x00
```
This modification disables the message mask, which normally is **compulsory**. `ws` tolerates it however.
### MCU compatibility
- Tested: Arduino UNO, ZERO
- Not tested: Arduino DUE; howerer, by searching similar C++ repos on GitHub (`arduino websocket due in:readme,name,description fork:true`), it seems that the conditional inclusion (in src/sha1.cpp) of `#include <avr/io.h>` and `#include <avr/pgmspace.h>` needed for ZERO board, would also fix compilation for DUE board. Any good-soul tester is welcome to feedback.
### Notes
See the original code from Branden for additional notes.
### Credits
This is an optimized version of the client code from the excellent job in <https://github.com/brandenhall/Arduino-Websocket>. Most of the credit goes to Branden.

View File

@@ -0,0 +1,109 @@
#include <SPI.h>
#include <SC16IS750.h>
#include <WiFly.h>
// Here we define a maximum framelength to 64 bytes. Default is 256.
#define MAX_FRAME_LENGTH 64
// Define how many callback functions you have. Default is 1.
#define CALLBACK_FUNCTIONS 1
#include <WebSocketClient.h>
WiFlyClient client = WiFlyClient();
WebSocketClient webSocketClient;
void setup() {
Serial.begin(9600);
SC16IS750.begin();
WiFly.setUart(&SC16IS750);
WiFly.begin();
// This is for an unsecured network
// For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD'
// For a WEP network use auth 2, and in another command send 'set wlan key KEY'
WiFly.sendCommand(F("set wlan auth 1"));
WiFly.sendCommand(F("set wlan channel 0"));
WiFly.sendCommand(F("set ip dhcp 1"));
Serial.println(F("Joining WiFi network..."));
// Here is where you set the network name to join
if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) {
Serial.println(F("Association failed."));
while (1) {
// Hang on failure.
}
}
if (!WiFly.waitForResponse("DHCP in", 10000)) {
Serial.println(F("DHCP failed."));
while (1) {
// Hang on failure.
}
}
// This is how you get the local IP as an IPAddress object
Serial.println(WiFly.localIP());
// This delay is needed to let the WiFly respond properly
delay(100);
// Connect to the websocket server
if (client.connect("echo.websocket.org", 80)) {
Serial.println("Connected");
} else {
Serial.println("Connection failed.");
while(1) {
// Hang on failure
}
}
// Handshake with the server
webSocketClient.path = "/";
webSocketClient.host = "echo.websocket.org";
if (webSocketClient.handshake(client)) {
Serial.println("Handshake successful");
} else {
Serial.println("Handshake failed.");
while(1) {
// Hang on failure
}
}
}
void loop() {
String data;
if (client.connected()) {
webSocketClient.getData(data);
if (data.length() > 0) {
Serial.print("Received data: ");
Serial.println(data);
}
// capture the value of analog 1, send it along
pinMode(1, INPUT);
data = String(analogRead(1));
webSocketClient.sendData(data);
} else {
Serial.println("Client disconnected.");
while (1) {
// Hang on disconnect.
}
}
// wait to fully let the client disconnect
delay(3000);
}

View File

@@ -0,0 +1,24 @@
#######################################
# Syntax Coloring Map WebsocketFast
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
WebSocketClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
handshake KEYWORD2
getData KEYWORD2
sendData KEYWORD2
path KEYWORD2
host KEYWORD2
protocol KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@@ -0,0 +1,9 @@
name=Arduino-Websocket-Fast
version=1.0.0
author=Davide Monari (KULeuven)
maintainer=Davide Monari <dvidemnr@gmail.com>
sentence=Websocket client library (fast data sending).
paragraph=The library can wrap around a generic Arduino Client() class or similar interface (e.g. EthernetClient(), WiFiClient(), WiflyClient(), ...) and is optimized in speed for data sending.
category=Communication
url=https://github.com/u0078867/Arduino-Websocket-Fast
architectures=avr,samd

View File

@@ -0,0 +1,133 @@
#include "Base64.h"
const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
/* 'Private' declarations */
inline void a3_to_a4(unsigned char * a4, unsigned char * a3);
inline void a4_to_a3(unsigned char * a3, unsigned char * a4);
inline unsigned char b64_lookup(char c);
int base64_encode(char *output, char *input, int inputLen) {
int i = 0, j = 0;
int encLen = 0;
unsigned char a3[3];
unsigned char a4[4];
while(inputLen--) {
a3[i++] = *(input++);
if(i == 3) {
a3_to_a4(a4, a3);
for(i = 0; i < 4; i++) {
output[encLen++] = b64_alphabet[a4[i]];
}
i = 0;
}
}
if(i) {
for(j = i; j < 3; j++) {
a3[j] = '\0';
}
a3_to_a4(a4, a3);
for(j = 0; j < i + 1; j++) {
output[encLen++] = b64_alphabet[a4[j]];
}
while((i++ < 3)) {
output[encLen++] = '=';
}
}
output[encLen] = '\0';
return encLen;
}
int base64_decode(char * output, char * input, int inputLen) {
int i = 0, j = 0;
int decLen = 0;
unsigned char a3[3];
unsigned char a4[4];
while (inputLen--) {
if(*input == '=') {
break;
}
a4[i++] = *(input++);
if (i == 4) {
for (i = 0; i <4; i++) {
a4[i] = b64_lookup(a4[i]);
}
a4_to_a3(a3,a4);
for (i = 0; i < 3; i++) {
output[decLen++] = a3[i];
}
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++) {
a4[j] = '\0';
}
for (j = 0; j <4; j++) {
a4[j] = b64_lookup(a4[j]);
}
a4_to_a3(a3,a4);
for (j = 0; j < i - 1; j++) {
output[decLen++] = a3[j];
}
}
output[decLen] = '\0';
return decLen;
}
int base64_enc_len(int plainLen) {
int n = plainLen;
return (n + 2 - ((n + 2) % 3)) / 3 * 4;
}
int base64_dec_len(char * input, int inputLen) {
int i = 0;
int numEq = 0;
for(i = inputLen - 1; input[i] == '='; i--) {
numEq++;
}
return ((6 * inputLen) / 8) - numEq;
}
inline void a3_to_a4(unsigned char * a4, unsigned char * a3) {
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = (a3[2] & 0x3f);
}
inline void a4_to_a3(unsigned char * a3, unsigned char * a4) {
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
}
inline unsigned char b64_lookup(char c) {
int i;
for(i = 0; i < 64; i++) {
if(b64_alphabet[i] == c) {
return i;
}
}
return -1;
}

View File

@@ -0,0 +1,75 @@
#ifndef _BASE64_H
#define _BASE64_H
/* b64_alphabet:
* Description: Base64 alphabet table, a mapping between integers
* and base64 digits
* Notes: This is an extern here but is defined in Base64.c
*/
extern const char b64_alphabet[];
/* base64_encode:
* Description:
* Encode a string of characters as base64
* Parameters:
* output: the output buffer for the encoding, stores the encoded string
* input: the input buffer for the encoding, stores the binary to be encoded
* inputLen: the length of the input buffer, in bytes
* Return value:
* Returns the length of the encoded string
* Requirements:
* 1. output must not be null or empty
* 2. input must not be null
* 3. inputLen must be greater than or equal to 0
*/
int base64_encode(char *output, char *input, int inputLen);
/* base64_decode:
* Description:
* Decode a base64 encoded string into bytes
* Parameters:
* output: the output buffer for the decoding,
* stores the decoded binary
* input: the input buffer for the decoding,
* stores the base64 string to be decoded
* inputLen: the length of the input buffer, in bytes
* Return value:
* Returns the length of the decoded string
* Requirements:
* 1. output must not be null or empty
* 2. input must not be null
* 3. inputLen must be greater than or equal to 0
*/
int base64_decode(char *output, char *input, int inputLen);
/* base64_enc_len:
* Description:
* Returns the length of a base64 encoded string whose decoded
* form is inputLen bytes long
* Parameters:
* inputLen: the length of the decoded string
* Return value:
* The length of a base64 encoded string whose decoded form
* is inputLen bytes long
* Requirements:
* None
*/
int base64_enc_len(int inputLen);
/* base64_dec_len:
* Description:
* Returns the length of the decoded form of a
* base64 encoded string
* Parameters:
* input: the base64 encoded string to be measured
* inputLen: the length of the base64 encoded string
* Return value:
* Returns the length of the decoded form of a
* base64 encoded string
* Requirements:
* 1. input must not be null
* 2. input must be greater than or equal to zero
*/
int base64_dec_len(char *input, int inputLen);
#endif // _BASE64_H

View File

@@ -0,0 +1,511 @@
//#define DEBUGGING
#include "global.h"
#include "WebSocketClient.h"
#include "sha1.h"
#include "base64.h"
bool WebSocketClient::handshake(Client &client) {
socket_client = &client;
// If there is a connected client->
if (socket_client->connected()) {
// Check request and look for websocket handshake
#ifdef DEBUGGING
Serial.println(F("Client connected"));
#endif
if (analyzeRequest()) {
#ifdef DEBUGGING
Serial.println(F("Websocket established"));
#endif
return true;
} else {
// Might just need to break until out of socket_client loop.
#ifdef DEBUGGING
Serial.println(F("Invalid handshake"));
#endif
disconnectStream();
return false;
}
} else {
return false;
}
}
bool WebSocketClient::analyzeRequest() {
String temp;
int bite;
bool foundupgrade = false;
unsigned long intkey[2];
String serverKey;
char keyStart[17];
char b64Key[25];
String key = "------------------------";
randomSeed(analogRead(0));
for (int i=0; i<16; ++i) {
keyStart[i] = (char)random(1, 256);
}
base64_encode(b64Key, keyStart, 16);
for (int i=0; i<24; ++i) {
key[i] = b64Key[i];
}
#ifdef DEBUGGING
Serial.println(F("Sending websocket upgrade headers"));
#endif
socket_client->print(F("GET "));
socket_client->print(path);
socket_client->print(F(" HTTP/1.1\r\n"));
socket_client->print(F("Upgrade: websocket\r\n"));
socket_client->print(F("Connection: Upgrade\r\n"));
socket_client->print(F("Host: "));
socket_client->print(host);
socket_client->print(CRLF);
socket_client->print(F("Sec-WebSocket-Key: "));
socket_client->print(key);
socket_client->print(CRLF);
socket_client->print(F("Sec-WebSocket-Protocol: "));
socket_client->print(protocol);
socket_client->print(CRLF);
socket_client->print(F("Sec-WebSocket-Version: 13\r\n"));
socket_client->print(CRLF);
#ifdef DEBUGGING
Serial.println(F("Analyzing response headers"));
#endif
while (socket_client->connected() && !socket_client->available()) {
delay(100);
Serial.println("Waiting...");
}
// TODO: More robust string extraction
while ((bite = socket_client->read()) != -1) {
temp += (char)bite;
if ((char)bite == '\n') {
#ifdef DEBUGGING
Serial.print("Got Header: " + temp);
#endif
if (!foundupgrade && temp.startsWith("Upgrade: websocket")) {
foundupgrade = true;
} else if (temp.startsWith("Sec-WebSocket-Accept: ")) {
serverKey = temp.substring(22,temp.length() - 2); // Don't save last CR+LF
}
temp = "";
}
if (!socket_client->available()) {
delay(20);
}
}
key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
uint8_t *hash;
char result[21];
char b64Result[30];
Sha1.init();
Sha1.print(key);
hash = Sha1.result();
for (int i=0; i<20; ++i) {
result[i] = (char)hash[i];
}
result[20] = '\0';
base64_encode(b64Result, result, 20);
// if the keys match, good to go
return serverKey.equals(String(b64Result));
}
bool WebSocketClient::handleStream(String& data, uint8_t *opcode) {
uint8_t msgtype;
uint8_t bite;
unsigned int length;
uint8_t mask[4];
uint8_t index;
unsigned int i;
bool hasMask = false;
if (!socket_client->connected() || !socket_client->available())
{
return false;
}
msgtype = timedRead();
if (!socket_client->connected()) {
return false;
}
length = timedRead();
if (length & WS_MASK) {
hasMask = true;
length = length & ~WS_MASK;
}
if (!socket_client->connected()) {
return false;
}
index = 6;
if (length == WS_SIZE16) {
length = timedRead() << 8;
if (!socket_client->connected()) {
return false;
}
length |= timedRead();
if (!socket_client->connected()) {
return false;
}
} else if (length == WS_SIZE64) {
#ifdef DEBUGGING
Serial.println(F("No support for over 16 bit sized messages"));
#endif
return false;
}
if (hasMask) {
// get the mask
mask[0] = timedRead();
if (!socket_client->connected()) {
return false;
}
mask[1] = timedRead();
if (!socket_client->connected()) {
return false;
}
mask[2] = timedRead();
if (!socket_client->connected()) {
return false;
}
mask[3] = timedRead();
if (!socket_client->connected()) {
return false;
}
}
data = "";
if (opcode != NULL)
{
*opcode = msgtype & ~WS_FIN;
}
if (hasMask) {
for (i=0; i<length; ++i) {
data += (char) (timedRead() ^ mask[i % 4]);
if (!socket_client->connected()) {
return false;
}
}
} else {
for (i=0; i<length; ++i) {
data += (char) timedRead();
if (!socket_client->connected()) {
return false;
}
}
}
return true;
}
bool WebSocketClient::handleStream(char *data, uint8_t *opcode) {
uint8_t msgtype;
uint8_t bite;
unsigned int length;
uint8_t mask[4];
uint8_t index;
unsigned int i;
bool hasMask = false;
if (!socket_client->connected() || !socket_client->available())
{
return false;
}
msgtype = timedRead();
if (!socket_client->connected()) {
return false;
}
length = timedRead();
if (length & WS_MASK) {
hasMask = true;
length = length & ~WS_MASK;
}
if (!socket_client->connected()) {
return false;
}
index = 6;
if (length == WS_SIZE16) {
length = timedRead() << 8;
if (!socket_client->connected()) {
return false;
}
length |= timedRead();
if (!socket_client->connected()) {
return false;
}
} else if (length == WS_SIZE64) {
#ifdef DEBUGGING
Serial.println(F("No support for over 16 bit sized messages"));
#endif
return false;
}
if (hasMask) {
// get the mask
mask[0] = timedRead();
if (!socket_client->connected()) {
return false;
}
mask[1] = timedRead();
if (!socket_client->connected()) {
return false;
}
mask[2] = timedRead();
if (!socket_client->connected()) {
return false;
}
mask[3] = timedRead();
if (!socket_client->connected()) {
return false;
}
}
strcpy(data, "");
if (opcode != NULL)
{
*opcode = msgtype & ~WS_FIN;
}
if (hasMask) {
for (i=0; i<length; ++i) {
sprintf(data, "%s%c", data, (char) (timedRead() ^ mask[i % 4]));
if (!socket_client->connected()) {
return false;
}
}
} else {
for (i=0; i<length; ++i) {
sprintf(data, "%s%c", data, (char) timedRead());
if (!socket_client->connected()) {
return false;
}
}
}
return true;
}
void WebSocketClient::disconnectStream() {
#ifdef DEBUGGING
Serial.println(F("Terminating socket"));
#endif
// Should send 0x8700 to server to tell it I'm quitting here.
socket_client->write((uint8_t) 0x87);
socket_client->write((uint8_t) 0x00);
socket_client->flush();
delay(10);
socket_client->stop();
}
bool WebSocketClient::getData(String& data, uint8_t *opcode) {
return handleStream(data, opcode);
}
bool WebSocketClient::getData(char *data, uint8_t *opcode) {
return handleStream(data, opcode);
}
void WebSocketClient::sendData(const char *str, uint8_t opcode, bool fast) {
#ifdef DEBUGGING
Serial.print(F("Sending data: "));
Serial.println(str);
#endif
if (socket_client->connected()) {
if (fast) {
sendEncodedDataFast(str, opcode);
} else {
sendEncodedData(str, opcode);
}
}
}
void WebSocketClient::sendData(String str, uint8_t opcode, bool fast) {
#ifdef DEBUGGING
Serial.print(F("Sending data: "));
Serial.println(str);
#endif
if (socket_client->connected()) {
if (fast) {
sendEncodedDataFast(str, opcode);
} else {
sendEncodedData(str, opcode);
}
}
}
int WebSocketClient::timedRead() {
while (!socket_client->available()) {
//delay(20);
}
return socket_client->read();
}
void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) {
uint8_t mask[4];
int size = strlen(str);
// Opcode; final fragment
socket_client->write(opcode | WS_FIN);
// NOTE: no support for > 16-bit sized messages
if (size > 125) {
socket_client->write(WS_SIZE16 | WS_MASK);
socket_client->write((uint8_t) (size >> 8));
socket_client->write((uint8_t) (size & 0xFF));
} else {
socket_client->write((uint8_t) size | WS_MASK);
}
if (WS_MASK > 0) {
//Serial.println("MASK");
mask[0] = random(0, 256);
mask[1] = random(0, 256);
mask[2] = random(0, 256);
mask[3] = random(0, 256);
socket_client->write(mask[0]);
socket_client->write(mask[1]);
socket_client->write(mask[2]);
socket_client->write(mask[3]);
}
for (int i=0; i<size; ++i) {
if (WS_MASK > 0) {
//Serial.println("send with MASK");
//delay(20);
socket_client->write(str[i] ^ mask[i % 4]);
} else {
socket_client->write(str[i]);
}
}
}
void WebSocketClient::sendEncodedDataFast(char *str, uint8_t opcode) {
uint8_t mask[4];
int size = strlen(str);
int size_buf = size + 1;
if (size > 125) {
size_buf += 3;
} else {
size_buf += 1;
}
if (WS_MASK > 0) {
size_buf += 4;
}
char buf[size_buf];
char tmp[2];
// Opcode; final fragment
sprintf(tmp, "%c", (char)(opcode | WS_FIN));
strcpy(buf, tmp);
// NOTE: no support for > 16-bit sized messages
if (size > 125) {
sprintf(tmp, "%c", (char)(WS_SIZE16 | WS_MASK));
strcat(buf, tmp);
sprintf(tmp, "%c", (char) (size >> 8));
strcat(buf, tmp);
sprintf(tmp, "%c", (char) (size & 0xFF));
strcat(buf, tmp);
} else {
sprintf(tmp, "%c", (char) size | WS_MASK);
strcat(buf, tmp);
}
if (WS_MASK > 0) {
mask[0] = random(0, 256);
mask[1] = random(0, 256);
mask[2] = random(0, 256);
mask[3] = random(0, 256);
sprintf(tmp, "%c", (char) mask[0]);
strcat(buf, tmp);
sprintf(tmp, "%c", (char) mask[1]);
strcat(buf, tmp);
sprintf(tmp, "%c", (char) mask[2]);
strcat(buf, tmp);
sprintf(tmp, "%c", (char) mask[3]);
strcat(buf, tmp);
for (int i=0; i<size; ++i) {
str[i] = str[i] ^ mask[i % 4];
}
}
strcat(buf, str);
socket_client->write((uint8_t*)buf, size_buf);
}
void WebSocketClient::sendEncodedData(String str, uint8_t opcode) {
int size = str.length() + 1;
char cstr[size];
str.toCharArray(cstr, size);
sendEncodedData(cstr, opcode);
}
void WebSocketClient::sendEncodedDataFast(String str, uint8_t opcode) {
int size = str.length() + 1;
char cstr[size];
str.toCharArray(cstr, size);
sendEncodedDataFast(cstr, opcode);
}

View File

@@ -0,0 +1,135 @@
/*
Websocket-Arduino, a websocket implementation for Arduino
Copyright 2016 Brendan Hall
Based on previous implementations by
Copyright 2011 Brendan Hall
and
Copyright 2010 Ben Swanson
and
Copyright 2010 Randall Brewer
and
Copyright 2010 Oliver Smith
Some code and concept based off of Webduino library
Copyright 2009 Ben Combee, Ran Talbott
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-------------
Now based off
http://www.whatwg.org/specs/web-socket-protocol/
- OLD -
Currently based off of "The Web Socket protocol" draft (v 75):
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
*/
#ifndef WEBSOCKETCLIENT_H_
#define WEBSOCKETCLIENT_H_
#include <Arduino.h>
#include <Stream.h>
#include "String.h"
#include "Client.h"
// CRLF characters to terminate lines/handshakes in headers.
#define CRLF "\r\n"
// Amount of time (in ms) a user may be connected before getting disconnected
// for timing out (i.e. not sending any data to the server).
#define TIMEOUT_IN_MS 10000
// ACTION_SPACE is how many actions are allowed in a program. Defaults to
// 5 unless overwritten by user.
#ifndef CALLBACK_FUNCTIONS
#define CALLBACK_FUNCTIONS 1
#endif
// Don't allow the client to send big frames of data. This will flood the Arduinos
// memory and might even crash it.
#ifndef MAX_FRAME_LENGTH
#define MAX_FRAME_LENGTH 256
#endif
#define SIZE(array) (sizeof(array) / sizeof(*array))
// WebSocket protocol constants
// First byte
#define WS_FIN 0x80
#define WS_OPCODE_TEXT 0x01
#define WS_OPCODE_BINARY 0x02
#define WS_OPCODE_CLOSE 0x08
#define WS_OPCODE_PING 0x09
#define WS_OPCODE_PONG 0x0a
// Second byte
#define WS_MASK 0x80
//#define WS_MASK 0x00
#define WS_SIZE16 126
#define WS_SIZE64 127
class WebSocketClient {
public:
// Handle connection requests to validate and process/refuse
// connections.
bool handshake(Client &client);
// Get data off of the stream
bool getData(String& data, uint8_t *opcode = NULL);
bool getData(char *data, uint8_t *opcode = NULL);
// Write data to the stream
void sendData(const char *str, uint8_t opcode = WS_OPCODE_TEXT, bool fast = true);
void sendData(String str, uint8_t opcode = WS_OPCODE_TEXT, bool fast = true);
char *path;
char *host;
char *protocol;
private:
Client *socket_client;
unsigned long _startMillis;
const char *socket_urlPrefix;
// Discovers if the client's header is requesting an upgrade to a
// websocket connection.
bool analyzeRequest();
bool handleStream(String& data, uint8_t *opcode);
bool handleStream(char *data, uint8_t *opcode);
// Disconnect user gracefully.
void disconnectStream();
int timedRead();
void sendEncodedData(char *str, uint8_t opcode);
void sendEncodedData(String str, uint8_t opcode);
void sendEncodedDataFast(char *str, uint8_t opcode);
void sendEncodedDataFast(String str, uint8_t opcode);
};
#endif

View File

@@ -0,0 +1,33 @@
/* GLOBAL.H - RSAREF types and constants */
/* PROTOTYPES should be set to one if and only if the compiler
* supports function argument prototyping.
* The following makes PROTOTYPES default to 0 if it has not already
* been defined with C compiler flags.
*/
#ifndef PROTOTYPES
#define PROTOTYPES 0
#endif
/*Modified by MMoore http://mikestechspot.blogspot.com
Changed typedefs to be fully compatible w/ Arduino 08/09/2010 */
/* POINTER defines a generic pointer type */
typedef unsigned char *POINTER;
/* UINT2 defines a two byte word */
typedef unsigned int UINT2;
/* UINT4 defines a four byte word */
typedef unsigned long UINT4;
/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
* If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
* returns an empty list.
*/
#if PROTOTYPES
#define PROTO_LIST(list) list
#else
#define PROTO_LIST(list) ()
#endif

View File

@@ -0,0 +1,156 @@
#include <string.h>
//#ifdef ARDUINO_SAMD_ZERO
//#else
#ifdef __AVR__
#include <avr/io.h>
#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;
}
}
size_t Sha1Class::write(uint8_t data) {
++byteCount;
addUncounted(data);
return sizeof(data);
}
void Sha1Class::pad() {
// Implement SHA-1 padding (fips180-2 §5.1.1)
// Pad with 0x80 followed by 0x00 until the end of the block
addUncounted(0x80);
while (bufferOffset != 56) addUncounted(0x00);
// Append length in the last 8 bytes
addUncounted(0); // We're only using 32 bit lengths
addUncounted(0); // But SHA-1 supports 64 bit lengths
addUncounted(0); // So zero pad the top bits
addUncounted(byteCount >> 29); // Shifting to multiply by 8
addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as
addUncounted(byteCount >> 13); // byte.
addUncounted(byteCount >> 5);
addUncounted(byteCount << 3);
}
uint8_t* Sha1Class::result(void) {
// Pad to complete the last block
pad();
// Swap byte order back
for (int i=0; i<5; i++) {
uint32_t a,b;
a=state.w[i];
b=a<<24;
b|=(a<<8) & 0x00ff0000;
b|=(a>>8) & 0x0000ff00;
b|=a>>24;
state.w[i]=b;
}
// Return pointer to hash (20 characters)
return state.b;
}
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
void Sha1Class::initHmac(const uint8_t* key, int keyLength) {
uint8_t i;
memset(keyBuffer,0,BLOCK_LENGTH);
if (keyLength > BLOCK_LENGTH) {
// Hash long keys
init();
for (;keyLength--;) write(*key++);
memcpy(keyBuffer,result(),HASH_LENGTH);
} else {
// Block length keys are used as is
memcpy(keyBuffer,key,keyLength);
}
// Start inner hash
init();
for (i=0; i<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;

View File

@@ -0,0 +1,43 @@
#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);
virtual size_t write(uint8_t);
using Print::write;
private:
void pad();
void addUncounted(uint8_t data);
void hashBlock();
uint32_t rol32(uint32_t number, uint8_t bits);
_buffer buffer;
uint8_t bufferOffset;
_state state;
uint32_t byteCount;
uint8_t keyBuffer[BLOCK_LENGTH];
uint8_t innerHash[HASH_LENGTH];
};
extern Sha1Class Sha1;
#endif