├── README.md ├── examples ├── SocketIOClient_Demo │ └── SocketIOClient_Demo.ino ├── SocketIOClient_ISP8266_Demo │ └── SocketIOClient_ISP8266_Demo.ino └── WebSocketClient_Demo │ └── WebSocketClient_Demo.ino ├── keywords.txt ├── library.properties └── src ├── Base64.cpp ├── Base64.h ├── WebSocketClient.cpp ├── WebSocketClient.h ├── global.h ├── sha1.cpp └── sha1.h /README.md: -------------------------------------------------------------------------------- 1 | ## Websocket client for Arduino, with fast data send 2 | 3 | This is a simple library that implements a Websocket client running on an Arduino. 4 | 5 | ### Rationale 6 | 7 | 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 . However, some modifications were needed for our purpose. In particular, we needed max throughput possible, approaching 100 messages/s. 8 | 9 | ### Features 10 | I added the following: 11 | 12 | - Faster data send (`client.sendData(..., true)`, default behaviour): instead of sending a TCP packet per char, everything is sent 13 | 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: 14 | 15 | ```C++ 16 | webSocketClient.sendData("my string to send", WS_OPCODE_TEXT, true); 17 | ``` 18 | 19 | - For method `client.getData()`, I created a pure C string implementation, to avoid chances of heap fragmentation due to `String` 20 | class. Example: 21 | 22 | ```C++ 23 | char msg_in[100]; // should be long enough to hold the longest arriving message 24 | uint8_t opcode_in; 25 | ... 26 | webSocketClient.getData(msg_in, &opcode_in) 27 | ``` 28 | 29 | ### Tests 30 | The optimized code was tested for: 31 | 32 | - `WiFiClient` (`` and ``) 33 | - Arduino UNO and ZERO 34 | - WiFi shield (retired) on Arduino UNO and WiFi shield 101 on Arduino ZERO 35 | - `ws` as Node.js websocket server 36 | 37 | We were able to obtain to reach the target throughput indicated above, with a message length of around 70 bytes (\*): 38 | 39 | (\*) In order to reach that speed, we had to apply the following hack: 40 | 41 | 1. 42 | 43 | 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. 44 | However, when the message rate you want to have is high, then some TCP packets could be lost (data or ACK); in this case, TCP fast-retransmit might not always be triggered, and this might increase the wait time for next packet to arrive. In this case, UDP protocol is strongly suggested. 45 | 46 | 2. After point 1, we had to manually disable the mask flag for websocket messages, by replacing this line in src /WebSocketClient.h: 47 | 48 | ```C++ 49 | #define WS_MASK 0x80 50 | ``` 51 | 52 | with this one: 53 | 54 | ```C++ 55 | #define WS_MASK 0x00 56 | ``` 57 | 58 | This modification disables the message mask, which normally is **compulsory**. `ws` tolerates it however. 59 | 60 | ### MCU compatibility 61 | - Tested: Arduino UNO, ZERO 62 | - 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 ` and `#include ` needed for ZERO board, would also fix compilation for DUE board. Any good-soul tester is welcome to feedback. 63 | 64 | ### Notes 65 | See the original code from Branden for additional notes. 66 | 67 | ### Credits 68 | This is an optimized version of the client code from the excellent job in . Most of the credit goes to Branden. 69 | -------------------------------------------------------------------------------- /examples/SocketIOClient_Demo/SocketIOClient_Demo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Here we define a maximum framelength to 64 bytes. Default is 256. 6 | #define MAX_FRAME_LENGTH 64 7 | 8 | // Define how many callback functions you have. Default is 1. 9 | #define CALLBACK_FUNCTIONS 1 10 | 11 | #define MESSAGE_INTERVAL 30000 12 | #define HEARTBEAT_INTERVAL 25000 13 | 14 | #include 15 | 16 | WiFlyClient client = WiFlyClient(); 17 | WebSocketClient webSocketClient; 18 | 19 | uint64_t messageTimestamp = 0; 20 | uint64_t heartbeatTimestamp = 0; 21 | 22 | void setup() { 23 | 24 | 25 | Serial.begin(9600); 26 | SC16IS750.begin(); 27 | 28 | WiFly.setUart(&SC16IS750); 29 | 30 | WiFly.begin(); 31 | 32 | // This is for an unsecured network 33 | // For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD' 34 | // For a WEP network use auth 2, and in another command send 'set wlan key KEY' 35 | WiFly.sendCommand(F("set wlan auth 1")); 36 | WiFly.sendCommand(F("set wlan channel 0")); 37 | WiFly.sendCommand(F("set ip dhcp 1")); 38 | 39 | Serial.println(F("Joining WiFi network...")); 40 | 41 | 42 | // Here is where you set the network name to join 43 | if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { 44 | Serial.println(F("Association failed.")); 45 | while (1) { 46 | // Hang on failure. 47 | } 48 | } 49 | 50 | if (!WiFly.waitForResponse("DHCP in", 10000)) { 51 | Serial.println(F("DHCP failed.")); 52 | while (1) { 53 | // Hang on failure. 54 | } 55 | } 56 | 57 | // This is how you get the local IP as an IPAddress object 58 | Serial.println(WiFly.localIP()); 59 | 60 | // This delay is needed to let the WiFly respond properly 61 | delay(100); 62 | 63 | // Connect to the websocket server 64 | if (client.connect("echo.websocket.org", 80)) { 65 | Serial.println("Connected"); 66 | } else { 67 | Serial.println("Connection failed."); 68 | while(1) { 69 | // Hang on failure 70 | } 71 | } 72 | 73 | // Handshake with the server 74 | webSocketClient.path = "/"; 75 | webSocketClient.host = "echo.websocket.org"; 76 | 77 | if (webSocketClient.handshake(client, true)) { 78 | Serial.println("Handshake successful"); 79 | // socket.io upgrade confirmation message (required) 80 | webSocketClient.sendData("5"); 81 | } else { 82 | Serial.println("Handshake failed."); 83 | while(1) { 84 | // Hang on failure 85 | } 86 | } 87 | } 88 | 89 | void loop() { 90 | String data; 91 | 92 | if (client.connected()) { 93 | 94 | webSocketClient.getData(data); 95 | 96 | if (data.length() > 0) { 97 | Serial.print("Received data: "); 98 | Serial.println(data); 99 | } 100 | 101 | uint64_t now = millis(); 102 | 103 | if(now - messageTimestamp > MESSAGE_INTERVAL) { 104 | messageTimestamp = now; 105 | // capture the value of analog 1, send it along 106 | pinMode(1, INPUT); 107 | data = String(analogRead(1)); 108 | 109 | // example socket.io message with type "messageType" and JSON payload 110 | char message[128]; 111 | sprintf(message, "42[\"messageType\",{\"data\":\"%s\"}]", data.c_str()); 112 | webSocket.sendData(message); 113 | } 114 | if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) { 115 | heartbeatTimestamp = now; 116 | // socket.io heartbeat message 117 | webSocket.sendData("2"); 118 | } 119 | 120 | } else { 121 | 122 | Serial.println("Client disconnected."); 123 | while (1) { 124 | // Hang on disconnect. 125 | } 126 | } 127 | 128 | // wait to fully let the client disconnect 129 | delay(3000); 130 | } 131 | -------------------------------------------------------------------------------- /examples/SocketIOClient_ISP8266_Demo/SocketIOClient_ISP8266_Demo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Here we define a maximum framelength to 64 bytes. Default is 256. 4 | #define MAX_FRAME_LENGTH 64 5 | 6 | // Define how many callback functions you have. Default is 1. 7 | #define CALLBACK_FUNCTIONS 1 8 | 9 | #define MESSAGE_INTERVAL 30000 10 | #define HEARTBEAT_INTERVAL 25000 11 | 12 | #include 13 | 14 | WiFiClient client; 15 | WebSocketClient webSocketClient; 16 | 17 | uint64_t messageTimestamp = 0; 18 | uint64_t heartbeatTimestamp = 0; 19 | 20 | void setup() { 21 | 22 | Serial.begin(115200); 23 | 24 | Serial.print("Connecting to Wifi"); 25 | WiFi.begin("ssid", "password"); 26 | 27 | while (WiFi.status() != WL_CONNECTED) { 28 | delay(100); 29 | Serial.print("."); 30 | } 31 | Serial.println("WiFi connected"); 32 | 33 | // This is how you get the local IP as an IPAddress object 34 | Serial.println("IP address: "); 35 | Serial.println(WiFi.localIP()); 36 | 37 | // Connect to the websocket server 38 | if (client.connect("echo.websocket.org", 80)) { 39 | Serial.println("Connected"); 40 | } else { 41 | Serial.println("Connection failed."); 42 | while(1) { 43 | // Hang on failure 44 | } 45 | } 46 | 47 | // Handshake with the server 48 | webSocketClient.path = "/"; 49 | webSocketClient.host = "echo.websocket.org"; 50 | 51 | if (webSocketClient.handshake(client, true)) { 52 | Serial.println("Handshake successful"); 53 | // socket.io upgrade confirmation message (required) 54 | webSocketClient.sendData("5"); 55 | } else { 56 | Serial.println("Handshake failed."); 57 | while(1) { 58 | // Hang on failure 59 | } 60 | } 61 | } 62 | 63 | void loop() { 64 | String data; 65 | 66 | if (client.connected()) { 67 | 68 | webSocketClient.getData(data); 69 | 70 | if (data.length() > 0) { 71 | Serial.print("Received data: "); 72 | Serial.println(data); 73 | } 74 | 75 | uint64_t now = millis(); 76 | 77 | if(now - messageTimestamp > MESSAGE_INTERVAL) { 78 | messageTimestamp = now; 79 | // capture the value of analog 1, send it along 80 | pinMode(1, INPUT); 81 | data = String(analogRead(1)); 82 | 83 | // example socket.io message with type "messageType" and JSON payload 84 | char message[128]; 85 | sprintf(message, "42[\"messageType\",{\"data\":\"%s\"}]", data.c_str()); 86 | webSocket.sendData(message); 87 | } 88 | if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) { 89 | heartbeatTimestamp = now; 90 | // socket.io heartbeat message 91 | webSocket.sendData("2"); 92 | } 93 | 94 | } else { 95 | 96 | Serial.println("Client disconnected."); 97 | while (1) { 98 | // Hang on disconnect. 99 | } 100 | } 101 | 102 | // wait to fully let the client disconnect 103 | delay(3000); 104 | } 105 | -------------------------------------------------------------------------------- /examples/WebSocketClient_Demo/WebSocketClient_Demo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Here we define a maximum framelength to 64 bytes. Default is 256. 6 | #define MAX_FRAME_LENGTH 64 7 | 8 | // Define how many callback functions you have. Default is 1. 9 | #define CALLBACK_FUNCTIONS 1 10 | 11 | #include 12 | 13 | WiFlyClient client = WiFlyClient(); 14 | WebSocketClient webSocketClient; 15 | 16 | void setup() { 17 | 18 | 19 | Serial.begin(9600); 20 | SC16IS750.begin(); 21 | 22 | WiFly.setUart(&SC16IS750); 23 | 24 | WiFly.begin(); 25 | 26 | // This is for an unsecured network 27 | // For a WPA1/2 network use auth 3, and in another command send 'set wlan phrase PASSWORD' 28 | // For a WEP network use auth 2, and in another command send 'set wlan key KEY' 29 | WiFly.sendCommand(F("set wlan auth 1")); 30 | WiFly.sendCommand(F("set wlan channel 0")); 31 | WiFly.sendCommand(F("set ip dhcp 1")); 32 | 33 | Serial.println(F("Joining WiFi network...")); 34 | 35 | 36 | // Here is where you set the network name to join 37 | if (!WiFly.sendCommand(F("join arduino_wifi"), "Associated!", 20000, false)) { 38 | Serial.println(F("Association failed.")); 39 | while (1) { 40 | // Hang on failure. 41 | } 42 | } 43 | 44 | if (!WiFly.waitForResponse("DHCP in", 10000)) { 45 | Serial.println(F("DHCP failed.")); 46 | while (1) { 47 | // Hang on failure. 48 | } 49 | } 50 | 51 | // This is how you get the local IP as an IPAddress object 52 | Serial.println(WiFly.localIP()); 53 | 54 | // This delay is needed to let the WiFly respond properly 55 | delay(100); 56 | 57 | // Connect to the websocket server 58 | if (client.connect("echo.websocket.org", 80)) { 59 | Serial.println("Connected"); 60 | } else { 61 | Serial.println("Connection failed."); 62 | while(1) { 63 | // Hang on failure 64 | } 65 | } 66 | 67 | // Handshake with the server 68 | webSocketClient.path = "/"; 69 | webSocketClient.host = "echo.websocket.org"; 70 | 71 | if (webSocketClient.handshake(client)) { 72 | Serial.println("Handshake successful"); 73 | } else { 74 | Serial.println("Handshake failed."); 75 | while(1) { 76 | // Hang on failure 77 | } 78 | } 79 | } 80 | 81 | void loop() { 82 | String data; 83 | 84 | if (client.connected()) { 85 | 86 | webSocketClient.getData(data); 87 | 88 | if (data.length() > 0) { 89 | Serial.print("Received data: "); 90 | Serial.println(data); 91 | } 92 | 93 | // capture the value of analog 1, send it along 94 | pinMode(1, INPUT); 95 | data = String(analogRead(1)); 96 | 97 | webSocketClient.sendData(data); 98 | 99 | } else { 100 | 101 | Serial.println("Client disconnected."); 102 | while (1) { 103 | // Hang on disconnect. 104 | } 105 | } 106 | 107 | // wait to fully let the client disconnect 108 | delay(3000); 109 | } 110 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map WebsocketFast 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | WebSocketClient KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | handshake KEYWORD2 15 | getData KEYWORD2 16 | sendData KEYWORD2 17 | path KEYWORD2 18 | host KEYWORD2 19 | protocol KEYWORD2 20 | 21 | ####################################### 22 | # Constants (LITERAL1) 23 | ####################################### 24 | 25 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Arduino-Websocket-Fast 2 | version=1.0.0 3 | author=Davide Monari (KULeuven) 4 | maintainer=Davide Monari 5 | sentence=Websocket client library (fast data sending). 6 | 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. 7 | category=Communication 8 | url=https://github.com/u0078867/Arduino-Websocket-Fast 9 | architectures=avr,samd 10 | -------------------------------------------------------------------------------- /src/Base64.cpp: -------------------------------------------------------------------------------- 1 | #include "Base64.h" 2 | 3 | const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 4 | "abcdefghijklmnopqrstuvwxyz" 5 | "0123456789+/"; 6 | 7 | /* 'Private' declarations */ 8 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3); 9 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4); 10 | inline unsigned char b64_lookup(char c); 11 | 12 | int base64_encode(char *output, char *input, int inputLen) { 13 | int i = 0, j = 0; 14 | int encLen = 0; 15 | unsigned char a3[3]; 16 | unsigned char a4[4]; 17 | 18 | while(inputLen--) { 19 | a3[i++] = *(input++); 20 | if(i == 3) { 21 | a3_to_a4(a4, a3); 22 | 23 | for(i = 0; i < 4; i++) { 24 | output[encLen++] = b64_alphabet[a4[i]]; 25 | } 26 | 27 | i = 0; 28 | } 29 | } 30 | 31 | if(i) { 32 | for(j = i; j < 3; j++) { 33 | a3[j] = '\0'; 34 | } 35 | 36 | a3_to_a4(a4, a3); 37 | 38 | for(j = 0; j < i + 1; j++) { 39 | output[encLen++] = b64_alphabet[a4[j]]; 40 | } 41 | 42 | while((i++ < 3)) { 43 | output[encLen++] = '='; 44 | } 45 | } 46 | output[encLen] = '\0'; 47 | return encLen; 48 | } 49 | 50 | int base64_decode(char * output, char * input, int inputLen) { 51 | int i = 0, j = 0; 52 | int decLen = 0; 53 | unsigned char a3[3]; 54 | unsigned char a4[4]; 55 | 56 | 57 | while (inputLen--) { 58 | if(*input == '=') { 59 | break; 60 | } 61 | 62 | a4[i++] = *(input++); 63 | if (i == 4) { 64 | for (i = 0; i <4; i++) { 65 | a4[i] = b64_lookup(a4[i]); 66 | } 67 | 68 | a4_to_a3(a3,a4); 69 | 70 | for (i = 0; i < 3; i++) { 71 | output[decLen++] = a3[i]; 72 | } 73 | i = 0; 74 | } 75 | } 76 | 77 | if (i) { 78 | for (j = i; j < 4; j++) { 79 | a4[j] = '\0'; 80 | } 81 | 82 | for (j = 0; j <4; j++) { 83 | a4[j] = b64_lookup(a4[j]); 84 | } 85 | 86 | a4_to_a3(a3,a4); 87 | 88 | for (j = 0; j < i - 1; j++) { 89 | output[decLen++] = a3[j]; 90 | } 91 | } 92 | output[decLen] = '\0'; 93 | return decLen; 94 | } 95 | 96 | int base64_enc_len(int plainLen) { 97 | int n = plainLen; 98 | return (n + 2 - ((n + 2) % 3)) / 3 * 4; 99 | } 100 | 101 | int base64_dec_len(char * input, int inputLen) { 102 | int i = 0; 103 | int numEq = 0; 104 | for(i = inputLen - 1; input[i] == '='; i--) { 105 | numEq++; 106 | } 107 | 108 | return ((6 * inputLen) / 8) - numEq; 109 | } 110 | 111 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { 112 | a4[0] = (a3[0] & 0xfc) >> 2; 113 | a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); 114 | a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); 115 | a4[3] = (a3[2] & 0x3f); 116 | } 117 | 118 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { 119 | a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); 120 | a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); 121 | a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; 122 | } 123 | 124 | inline unsigned char b64_lookup(char c) { 125 | int i; 126 | for(i = 0; i < 64; i++) { 127 | if(b64_alphabet[i] == c) { 128 | return i; 129 | } 130 | } 131 | 132 | return -1; 133 | } 134 | -------------------------------------------------------------------------------- /src/Base64.h: -------------------------------------------------------------------------------- 1 | #ifndef _BASE64_H 2 | #define _BASE64_H 3 | 4 | /* b64_alphabet: 5 | * Description: Base64 alphabet table, a mapping between integers 6 | * and base64 digits 7 | * Notes: This is an extern here but is defined in Base64.c 8 | */ 9 | extern const char b64_alphabet[]; 10 | 11 | /* base64_encode: 12 | * Description: 13 | * Encode a string of characters as base64 14 | * Parameters: 15 | * output: the output buffer for the encoding, stores the encoded string 16 | * input: the input buffer for the encoding, stores the binary to be encoded 17 | * inputLen: the length of the input buffer, in bytes 18 | * Return value: 19 | * Returns the length of the encoded string 20 | * Requirements: 21 | * 1. output must not be null or empty 22 | * 2. input must not be null 23 | * 3. inputLen must be greater than or equal to 0 24 | */ 25 | int base64_encode(char *output, char *input, int inputLen); 26 | 27 | /* base64_decode: 28 | * Description: 29 | * Decode a base64 encoded string into bytes 30 | * Parameters: 31 | * output: the output buffer for the decoding, 32 | * stores the decoded binary 33 | * input: the input buffer for the decoding, 34 | * stores the base64 string to be decoded 35 | * inputLen: the length of the input buffer, in bytes 36 | * Return value: 37 | * Returns the length of the decoded string 38 | * Requirements: 39 | * 1. output must not be null or empty 40 | * 2. input must not be null 41 | * 3. inputLen must be greater than or equal to 0 42 | */ 43 | int base64_decode(char *output, char *input, int inputLen); 44 | 45 | /* base64_enc_len: 46 | * Description: 47 | * Returns the length of a base64 encoded string whose decoded 48 | * form is inputLen bytes long 49 | * Parameters: 50 | * inputLen: the length of the decoded string 51 | * Return value: 52 | * The length of a base64 encoded string whose decoded form 53 | * is inputLen bytes long 54 | * Requirements: 55 | * None 56 | */ 57 | int base64_enc_len(int inputLen); 58 | 59 | /* base64_dec_len: 60 | * Description: 61 | * Returns the length of the decoded form of a 62 | * base64 encoded string 63 | * Parameters: 64 | * input: the base64 encoded string to be measured 65 | * inputLen: the length of the base64 encoded string 66 | * Return value: 67 | * Returns the length of the decoded form of a 68 | * base64 encoded string 69 | * Requirements: 70 | * 1. input must not be null 71 | * 2. input must be greater than or equal to zero 72 | */ 73 | int base64_dec_len(char *input, int inputLen); 74 | 75 | #endif // _BASE64_H 76 | -------------------------------------------------------------------------------- /src/WebSocketClient.cpp: -------------------------------------------------------------------------------- 1 | //#define DEBUGGING 2 | 3 | #include "global.h" 4 | #include "WebSocketClient.h" 5 | 6 | #include "sha1.h" 7 | #include "base64.h" 8 | 9 | 10 | bool WebSocketClient::handshake(Client &client, bool socketio) { 11 | 12 | socket_client = &client; 13 | issocketio = socketio; 14 | strcpy(sid, ""); 15 | 16 | // If there is a connected client-> 17 | if (socket_client->connected()) { 18 | // Check request and look for websocket handshake 19 | #ifdef DEBUGGING 20 | Serial.println(F("Client connected")); 21 | #endif 22 | if (issocketio && strlen(sid) == 0) { 23 | analyzeRequest(); 24 | } 25 | 26 | if (analyzeRequest()) { 27 | #ifdef DEBUGGING 28 | Serial.println(F("Websocket established")); 29 | #endif 30 | 31 | return true; 32 | 33 | } else { 34 | // Might just need to break until out of socket_client loop. 35 | #ifdef DEBUGGING 36 | Serial.println(F("Invalid handshake")); 37 | #endif 38 | disconnectStream(); 39 | 40 | return false; 41 | } 42 | } else { 43 | return false; 44 | } 45 | } 46 | 47 | bool WebSocketClient::analyzeRequest() { 48 | String temp; 49 | 50 | int bite; 51 | bool foundupgrade = false; 52 | bool foundsid = false; 53 | unsigned long intkey[2]; 54 | String serverKey; 55 | char keyStart[17]; 56 | char b64Key[25]; 57 | String key = "------------------------"; 58 | 59 | if (!issocketio || (issocketio && strlen(sid) > 0)) { 60 | 61 | #ifdef DEBUGGING 62 | Serial.println(F("Sending websocket upgrade headers")); 63 | #endif 64 | 65 | randomSeed(analogRead(0)); 66 | 67 | for (int i=0; i<16; ++i) { 68 | keyStart[i] = (char)random(1, 256); 69 | } 70 | 71 | base64_encode(b64Key, keyStart, 16); 72 | 73 | for (int i=0; i<24; ++i) { 74 | key[i] = b64Key[i]; 75 | } 76 | 77 | socket_client->print(F("GET ")); 78 | socket_client->print(path); 79 | if (issocketio) { 80 | socket_client->print(F("socket.io/?EIO=3&transport=websocket&sid=")); 81 | socket_client->print(sid); 82 | } 83 | socket_client->print(F(" HTTP/1.1\r\n")); 84 | socket_client->print(F("Upgrade: websocket\r\n")); 85 | socket_client->print(F("Connection: Upgrade\r\n")); 86 | socket_client->print(F("Sec-WebSocket-Key: ")); 87 | socket_client->print(key); 88 | socket_client->print(CRLF); 89 | socket_client->print(F("Sec-WebSocket-Protocol: ")); 90 | socket_client->print(protocol); 91 | socket_client->print(CRLF); 92 | socket_client->print(F("Sec-WebSocket-Version: 13\r\n")); 93 | 94 | } else { 95 | 96 | #ifdef DEBUGGING 97 | Serial.println(F("Sending socket.io session request headers")); 98 | #endif 99 | 100 | socket_client->print(F("GET ")); 101 | socket_client->print(path); 102 | socket_client->print(F("socket.io/?EIO=3&transport=polling HTTP/1.1\r\n")); 103 | socket_client->print(F("Connection: keep-alive\r\n")); 104 | } 105 | 106 | socket_client->print(F("Host: ")); 107 | socket_client->print(host); 108 | socket_client->print(CRLF); 109 | socket_client->print(CRLF); 110 | 111 | #ifdef DEBUGGING 112 | Serial.println(F("Analyzing response headers")); 113 | #endif 114 | 115 | while (socket_client->connected() && !socket_client->available()) { 116 | delay(100); 117 | Serial.println("Waiting..."); 118 | } 119 | 120 | // TODO: More robust string extraction 121 | while ((bite = socket_client->read()) != -1) { 122 | 123 | temp += (char)bite; 124 | 125 | if ((char)bite == '\n') { 126 | #ifdef DEBUGGING 127 | Serial.print("Got Header: " + temp); 128 | #endif 129 | if (!foundupgrade && temp.startsWith("Upgrade: websocket")) { 130 | foundupgrade = true; 131 | } else if (temp.startsWith("Sec-WebSocket-Accept: ")) { 132 | serverKey = temp.substring(22,temp.length() - 2); // Don't save last CR+LF 133 | } else if (!foundsid && temp.startsWith("Set-Cookie: ")) { 134 | foundsid = true; 135 | String tempsid = temp.substring(temp.indexOf("=") + 1, temp.length() - 2); // Don't save last CR+LF 136 | strcpy(sid, tempsid.c_str()); 137 | } 138 | temp = ""; 139 | } 140 | 141 | if (!socket_client->available()) { 142 | delay(20); 143 | } 144 | } 145 | 146 | if (issocketio && foundsid && !foundupgrade) { 147 | return true; 148 | } 149 | 150 | key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 151 | uint8_t *hash; 152 | char result[21]; 153 | char b64Result[30]; 154 | 155 | Sha1.init(); 156 | Sha1.print(key); 157 | hash = Sha1.result(); 158 | 159 | for (int i=0; i<20; ++i) { 160 | result[i] = (char)hash[i]; 161 | } 162 | result[20] = '\0'; 163 | 164 | base64_encode(b64Result, result, 20); 165 | 166 | // if the keys match, good to go 167 | return serverKey.equals(String(b64Result)); 168 | } 169 | 170 | 171 | bool WebSocketClient::handleStream(String& data, uint8_t *opcode) { 172 | uint8_t msgtype; 173 | uint8_t bite; 174 | unsigned int length; 175 | uint8_t mask[4]; 176 | uint8_t index; 177 | unsigned int i; 178 | bool hasMask = false; 179 | 180 | if (!socket_client->connected() || !socket_client->available()) 181 | { 182 | return false; 183 | } 184 | 185 | msgtype = timedRead(); 186 | if (!socket_client->connected()) { 187 | return false; 188 | } 189 | 190 | length = timedRead(); 191 | 192 | if (length & WS_MASK) { 193 | hasMask = true; 194 | length = length & ~WS_MASK; 195 | } 196 | 197 | 198 | if (!socket_client->connected()) { 199 | return false; 200 | } 201 | 202 | index = 6; 203 | 204 | if (length == WS_SIZE16) { 205 | length = timedRead() << 8; 206 | if (!socket_client->connected()) { 207 | return false; 208 | } 209 | 210 | length |= timedRead(); 211 | if (!socket_client->connected()) { 212 | return false; 213 | } 214 | 215 | } else if (length == WS_SIZE64) { 216 | #ifdef DEBUGGING 217 | Serial.println(F("No support for over 16 bit sized messages")); 218 | #endif 219 | return false; 220 | } 221 | 222 | if (hasMask) { 223 | // get the mask 224 | mask[0] = timedRead(); 225 | if (!socket_client->connected()) { 226 | return false; 227 | } 228 | 229 | mask[1] = timedRead(); 230 | if (!socket_client->connected()) { 231 | 232 | return false; 233 | } 234 | 235 | mask[2] = timedRead(); 236 | if (!socket_client->connected()) { 237 | return false; 238 | } 239 | 240 | mask[3] = timedRead(); 241 | if (!socket_client->connected()) { 242 | return false; 243 | } 244 | } 245 | 246 | data = ""; 247 | 248 | if (opcode != NULL) 249 | { 250 | *opcode = msgtype & ~WS_FIN; 251 | } 252 | 253 | if (hasMask) { 254 | for (i=0; iconnected()) { 257 | return false; 258 | } 259 | } 260 | } else { 261 | for (i=0; iconnected()) { 264 | return false; 265 | } 266 | } 267 | } 268 | 269 | return true; 270 | } 271 | 272 | bool WebSocketClient::handleStream(char *data, uint8_t *opcode) { 273 | uint8_t msgtype; 274 | uint8_t bite; 275 | unsigned int length; 276 | uint8_t mask[4]; 277 | uint8_t index; 278 | unsigned int i; 279 | bool hasMask = false; 280 | 281 | if (!socket_client->connected() || !socket_client->available()) 282 | { 283 | return false; 284 | } 285 | 286 | msgtype = timedRead(); 287 | if (!socket_client->connected()) { 288 | return false; 289 | } 290 | 291 | length = timedRead(); 292 | 293 | if (length & WS_MASK) { 294 | hasMask = true; 295 | length = length & ~WS_MASK; 296 | } 297 | 298 | 299 | if (!socket_client->connected()) { 300 | return false; 301 | } 302 | 303 | index = 6; 304 | 305 | if (length == WS_SIZE16) { 306 | length = timedRead() << 8; 307 | if (!socket_client->connected()) { 308 | return false; 309 | } 310 | 311 | length |= timedRead(); 312 | if (!socket_client->connected()) { 313 | return false; 314 | } 315 | 316 | } else if (length == WS_SIZE64) { 317 | #ifdef DEBUGGING 318 | Serial.println(F("No support for over 16 bit sized messages")); 319 | #endif 320 | return false; 321 | } 322 | 323 | if (hasMask) { 324 | // get the mask 325 | mask[0] = timedRead(); 326 | if (!socket_client->connected()) { 327 | return false; 328 | } 329 | 330 | mask[1] = timedRead(); 331 | if (!socket_client->connected()) { 332 | 333 | return false; 334 | } 335 | 336 | mask[2] = timedRead(); 337 | if (!socket_client->connected()) { 338 | return false; 339 | } 340 | 341 | mask[3] = timedRead(); 342 | if (!socket_client->connected()) { 343 | return false; 344 | } 345 | } 346 | 347 | strcpy(data, ""); 348 | 349 | if (opcode != NULL) 350 | { 351 | *opcode = msgtype & ~WS_FIN; 352 | } 353 | 354 | if (hasMask) { 355 | for (i=0; iconnected()) { 358 | return false; 359 | } 360 | } 361 | } else { 362 | for (i=0; iconnected()) { 365 | return false; 366 | } 367 | } 368 | } 369 | 370 | return true; 371 | } 372 | 373 | void WebSocketClient::disconnectStream() { 374 | #ifdef DEBUGGING 375 | Serial.println(F("Terminating socket")); 376 | #endif 377 | // Should send 0x8700 to server to tell it I'm quitting here. 378 | socket_client->write((uint8_t) 0x87); 379 | socket_client->write((uint8_t) 0x00); 380 | 381 | socket_client->flush(); 382 | delay(10); 383 | socket_client->stop(); 384 | strcpy(sid, ""); 385 | } 386 | 387 | bool WebSocketClient::getData(String& data, uint8_t *opcode) { 388 | return handleStream(data, opcode); 389 | } 390 | 391 | bool WebSocketClient::getData(char *data, uint8_t *opcode) { 392 | return handleStream(data, opcode); 393 | } 394 | 395 | void WebSocketClient::sendData(const char *str, uint8_t opcode, bool fast) { 396 | #ifdef DEBUGGING 397 | Serial.print(F("Sending data: ")); 398 | Serial.println(str); 399 | #endif 400 | if (socket_client->connected()) { 401 | if (fast) { 402 | sendEncodedDataFast(str, opcode); 403 | } else { 404 | sendEncodedData(str, opcode); 405 | } 406 | } 407 | } 408 | 409 | void WebSocketClient::sendData(String str, uint8_t opcode, bool fast) { 410 | #ifdef DEBUGGING 411 | Serial.print(F("Sending data: ")); 412 | Serial.println(str); 413 | #endif 414 | if (socket_client->connected()) { 415 | if (fast) { 416 | sendEncodedDataFast(str, opcode); 417 | } else { 418 | sendEncodedData(str, opcode); 419 | } 420 | } 421 | } 422 | 423 | int WebSocketClient::timedRead() { 424 | while (!socket_client->available()) { 425 | //delay(20); 426 | } 427 | 428 | return socket_client->read(); 429 | } 430 | 431 | void WebSocketClient::sendEncodedData(char *str, uint8_t opcode) { 432 | uint8_t mask[4]; 433 | int size = strlen(str); 434 | 435 | // Opcode; final fragment 436 | socket_client->write(opcode | WS_FIN); 437 | 438 | // NOTE: no support for > 16-bit sized messages 439 | if (size > 125) { 440 | socket_client->write(WS_SIZE16 | WS_MASK); 441 | socket_client->write((uint8_t) (size >> 8)); 442 | socket_client->write((uint8_t) (size & 0xFF)); 443 | } else { 444 | socket_client->write((uint8_t) size | WS_MASK); 445 | } 446 | 447 | if (WS_MASK > 0) { 448 | //Serial.println("MASK"); 449 | mask[0] = random(0, 256); 450 | mask[1] = random(0, 256); 451 | mask[2] = random(0, 256); 452 | mask[3] = random(0, 256); 453 | 454 | socket_client->write(mask[0]); 455 | socket_client->write(mask[1]); 456 | socket_client->write(mask[2]); 457 | socket_client->write(mask[3]); 458 | } 459 | 460 | for (int i=0; i 0) { 462 | //Serial.println("send with MASK"); 463 | //delay(20); 464 | socket_client->write(str[i] ^ mask[i % 4]); 465 | } else { 466 | socket_client->write(str[i]); 467 | } 468 | } 469 | } 470 | 471 | void WebSocketClient::sendEncodedDataFast(char *str, uint8_t opcode) { 472 | uint8_t mask[4]; 473 | int size = strlen(str); 474 | int size_buf = size + 1; 475 | if (size > 125) { 476 | size_buf += 3; 477 | } else { 478 | size_buf += 1; 479 | } 480 | if (WS_MASK > 0) { 481 | size_buf += 4; 482 | } 483 | 484 | char buf[size_buf]; 485 | char tmp[2]; 486 | 487 | // Opcode; final fragment 488 | sprintf(tmp, "%c", (char)(opcode | WS_FIN)); 489 | strcpy(buf, tmp); 490 | 491 | // NOTE: no support for > 16-bit sized messages 492 | if (size > 125) { 493 | sprintf(tmp, "%c", (char)(WS_SIZE16 | WS_MASK)); 494 | strcat(buf, tmp); 495 | sprintf(tmp, "%c", (char) (size >> 8)); 496 | strcat(buf, tmp); 497 | sprintf(tmp, "%c", (char) (size & 0xFF)); 498 | strcat(buf, tmp); 499 | } else { 500 | sprintf(tmp, "%c", (char) size | WS_MASK); 501 | strcat(buf, tmp); 502 | } 503 | 504 | if (WS_MASK > 0) { 505 | mask[0] = random(0, 256); 506 | mask[1] = random(0, 256); 507 | mask[2] = random(0, 256); 508 | mask[3] = random(0, 256); 509 | 510 | sprintf(tmp, "%c", (char) mask[0]); 511 | strcat(buf, tmp); 512 | sprintf(tmp, "%c", (char) mask[1]); 513 | strcat(buf, tmp); 514 | sprintf(tmp, "%c", (char) mask[2]); 515 | strcat(buf, tmp); 516 | sprintf(tmp, "%c", (char) mask[3]); 517 | strcat(buf, tmp); 518 | 519 | for (int i=0; iwrite((uint8_t*)buf, size_buf); 526 | } 527 | 528 | 529 | void WebSocketClient::sendEncodedData(String str, uint8_t opcode) { 530 | int size = str.length() + 1; 531 | char cstr[size]; 532 | 533 | str.toCharArray(cstr, size); 534 | 535 | sendEncodedData(cstr, opcode); 536 | } 537 | 538 | 539 | void WebSocketClient::sendEncodedDataFast(String str, uint8_t opcode) { 540 | int size = str.length() + 1; 541 | char cstr[size]; 542 | 543 | str.toCharArray(cstr, size); 544 | 545 | sendEncodedDataFast(cstr, opcode); 546 | } 547 | -------------------------------------------------------------------------------- /src/WebSocketClient.h: -------------------------------------------------------------------------------- 1 | /* 2 | Websocket-Arduino, a websocket implementation for Arduino 3 | Copyright 2016 Brendan Hall 4 | 5 | Based on previous implementations by 6 | Copyright 2011 Brendan Hall 7 | and 8 | Copyright 2010 Ben Swanson 9 | and 10 | Copyright 2010 Randall Brewer 11 | and 12 | Copyright 2010 Oliver Smith 13 | 14 | Some code and concept based off of Webduino library 15 | Copyright 2009 Ben Combee, Ran Talbott 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in 25 | all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 33 | THE SOFTWARE. 34 | 35 | ------------- 36 | Now based off 37 | http://www.whatwg.org/specs/web-socket-protocol/ 38 | 39 | - OLD - 40 | Currently based off of "The Web Socket protocol" draft (v 75): 41 | http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 42 | */ 43 | 44 | 45 | #ifndef WEBSOCKETCLIENT_H_ 46 | #define WEBSOCKETCLIENT_H_ 47 | 48 | #include 49 | #include 50 | #include "String.h" 51 | #include "Client.h" 52 | 53 | // CRLF characters to terminate lines/handshakes in headers. 54 | #define CRLF "\r\n" 55 | 56 | // Amount of time (in ms) a user may be connected before getting disconnected 57 | // for timing out (i.e. not sending any data to the server). 58 | #define TIMEOUT_IN_MS 10000 59 | 60 | // ACTION_SPACE is how many actions are allowed in a program. Defaults to 61 | // 5 unless overwritten by user. 62 | #ifndef CALLBACK_FUNCTIONS 63 | #define CALLBACK_FUNCTIONS 1 64 | #endif 65 | 66 | // Don't allow the client to send big frames of data. This will flood the Arduinos 67 | // memory and might even crash it. 68 | #ifndef MAX_FRAME_LENGTH 69 | #define MAX_FRAME_LENGTH 256 70 | #endif 71 | 72 | #define SIZE(array) (sizeof(array) / sizeof(*array)) 73 | 74 | // WebSocket protocol constants 75 | // First byte 76 | #define WS_FIN 0x80 77 | #define WS_OPCODE_TEXT 0x01 78 | #define WS_OPCODE_BINARY 0x02 79 | #define WS_OPCODE_CLOSE 0x08 80 | #define WS_OPCODE_PING 0x09 81 | #define WS_OPCODE_PONG 0x0a 82 | // Second byte 83 | #define WS_MASK 0x80 84 | //#define WS_MASK 0x00 85 | #define WS_SIZE16 126 86 | #define WS_SIZE64 127 87 | 88 | 89 | class WebSocketClient { 90 | public: 91 | 92 | // Handle connection requests to validate and process/refuse 93 | // connections. 94 | bool handshake(Client &client, bool socketio = false); 95 | 96 | // Get data off of the stream 97 | bool getData(String& data, uint8_t *opcode = NULL); 98 | bool getData(char *data, uint8_t *opcode = NULL); 99 | 100 | // Write data to the stream 101 | void sendData(const char *str, uint8_t opcode = WS_OPCODE_TEXT, bool fast = true); 102 | void sendData(String str, uint8_t opcode = WS_OPCODE_TEXT, bool fast = true); 103 | 104 | bool issocketio; 105 | char *path; 106 | char *host; 107 | char *protocol; 108 | 109 | private: 110 | Client *socket_client; 111 | // socket.io session id 112 | char sid[32]; 113 | unsigned long _startMillis; 114 | 115 | const char *socket_urlPrefix; 116 | 117 | // Discovers if the client's header is requesting an upgrade to a 118 | // websocket connection. 119 | bool analyzeRequest(); 120 | 121 | bool handleStream(String& data, uint8_t *opcode); 122 | bool handleStream(char *data, uint8_t *opcode); 123 | 124 | // Disconnect user gracefully. 125 | void disconnectStream(); 126 | 127 | int timedRead(); 128 | 129 | void sendEncodedData(char *str, uint8_t opcode); 130 | void sendEncodedData(String str, uint8_t opcode); 131 | 132 | void sendEncodedDataFast(char *str, uint8_t opcode); 133 | void sendEncodedDataFast(String str, uint8_t opcode); 134 | }; 135 | 136 | 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | /* GLOBAL.H - RSAREF types and constants */ 2 | 3 | /* PROTOTYPES should be set to one if and only if the compiler 4 | * supports function argument prototyping. 5 | * The following makes PROTOTYPES default to 0 if it has not already 6 | * been defined with C compiler flags. 7 | */ 8 | #ifndef PROTOTYPES 9 | #define PROTOTYPES 0 10 | #endif 11 | 12 | /*Modified by MMoore http://mikestechspot.blogspot.com 13 | Changed typedefs to be fully compatible w/ Arduino 08/09/2010 */ 14 | 15 | /* POINTER defines a generic pointer type */ 16 | typedef unsigned char *POINTER; 17 | 18 | /* UINT2 defines a two byte word */ 19 | typedef unsigned int UINT2; 20 | 21 | /* UINT4 defines a four byte word */ 22 | typedef unsigned long UINT4; 23 | 24 | /* PROTO_LIST is defined depending on how PROTOTYPES is defined above. 25 | * If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it 26 | * returns an empty list. 27 | */ 28 | #if PROTOTYPES 29 | #define PROTO_LIST(list) list 30 | #else 31 | #define PROTO_LIST(list) () 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /src/sha1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | //#ifdef ARDUINO_SAMD_ZERO 3 | //#else 4 | #ifdef __AVR__ 5 | #include 6 | #include 7 | #endif 8 | #include "sha1.h" 9 | 10 | #define SHA1_K0 0x5a827999 11 | #define SHA1_K20 0x6ed9eba1 12 | #define SHA1_K40 0x8f1bbcdc 13 | #define SHA1_K60 0xca62c1d6 14 | 15 | const uint8_t sha1InitState[] PROGMEM = { 16 | 0x01,0x23,0x45,0x67, // H0 17 | 0x89,0xab,0xcd,0xef, // H1 18 | 0xfe,0xdc,0xba,0x98, // H2 19 | 0x76,0x54,0x32,0x10, // H3 20 | 0xf0,0xe1,0xd2,0xc3 // H4 21 | }; 22 | 23 | void Sha1Class::init(void) { 24 | memcpy_P(state.b,sha1InitState,HASH_LENGTH); 25 | byteCount = 0; 26 | bufferOffset = 0; 27 | } 28 | 29 | uint32_t Sha1Class::rol32(uint32_t number, uint8_t bits) { 30 | return ((number << bits) | (number >> (32-bits))); 31 | } 32 | 33 | void Sha1Class::hashBlock() { 34 | uint8_t i; 35 | uint32_t a,b,c,d,e,t; 36 | 37 | a=state.w[0]; 38 | b=state.w[1]; 39 | c=state.w[2]; 40 | d=state.w[3]; 41 | e=state.w[4]; 42 | for (i=0; i<80; i++) { 43 | if (i>=16) { 44 | t = buffer.w[(i+13)&15] ^ buffer.w[(i+8)&15] ^ buffer.w[(i+2)&15] ^ buffer.w[i&15]; 45 | buffer.w[i&15] = rol32(t,1); 46 | } 47 | if (i<20) { 48 | t = (d ^ (b & (c ^ d))) + SHA1_K0; 49 | } else if (i<40) { 50 | t = (b ^ c ^ d) + SHA1_K20; 51 | } else if (i<60) { 52 | t = ((b & c) | (d & (b | c))) + SHA1_K40; 53 | } else { 54 | t = (b ^ c ^ d) + SHA1_K60; 55 | } 56 | t+=rol32(a,5) + e + buffer.w[i&15]; 57 | e=d; 58 | d=c; 59 | c=rol32(b,30); 60 | b=a; 61 | a=t; 62 | } 63 | state.w[0] += a; 64 | state.w[1] += b; 65 | state.w[2] += c; 66 | state.w[3] += d; 67 | state.w[4] += e; 68 | } 69 | 70 | void Sha1Class::addUncounted(uint8_t data) { 71 | buffer.b[bufferOffset ^ 3] = data; 72 | bufferOffset++; 73 | if (bufferOffset == BLOCK_LENGTH) { 74 | hashBlock(); 75 | bufferOffset = 0; 76 | } 77 | } 78 | 79 | size_t Sha1Class::write(uint8_t data) { 80 | ++byteCount; 81 | addUncounted(data); 82 | 83 | return sizeof(data); 84 | } 85 | 86 | void Sha1Class::pad() { 87 | // Implement SHA-1 padding (fips180-2 §5.1.1) 88 | 89 | // Pad with 0x80 followed by 0x00 until the end of the block 90 | addUncounted(0x80); 91 | while (bufferOffset != 56) addUncounted(0x00); 92 | 93 | // Append length in the last 8 bytes 94 | addUncounted(0); // We're only using 32 bit lengths 95 | addUncounted(0); // But SHA-1 supports 64 bit lengths 96 | addUncounted(0); // So zero pad the top bits 97 | addUncounted(byteCount >> 29); // Shifting to multiply by 8 98 | addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as 99 | addUncounted(byteCount >> 13); // byte. 100 | addUncounted(byteCount >> 5); 101 | addUncounted(byteCount << 3); 102 | } 103 | 104 | 105 | uint8_t* Sha1Class::result(void) { 106 | // Pad to complete the last block 107 | pad(); 108 | 109 | // Swap byte order back 110 | for (int i=0; i<5; i++) { 111 | uint32_t a,b; 112 | a=state.w[i]; 113 | b=a<<24; 114 | b|=(a<<8) & 0x00ff0000; 115 | b|=(a>>8) & 0x0000ff00; 116 | b|=a>>24; 117 | state.w[i]=b; 118 | } 119 | 120 | // Return pointer to hash (20 characters) 121 | return state.b; 122 | } 123 | 124 | #define HMAC_IPAD 0x36 125 | #define HMAC_OPAD 0x5c 126 | 127 | void Sha1Class::initHmac(const uint8_t* key, int keyLength) { 128 | uint8_t i; 129 | memset(keyBuffer,0,BLOCK_LENGTH); 130 | if (keyLength > BLOCK_LENGTH) { 131 | // Hash long keys 132 | init(); 133 | for (;keyLength--;) write(*key++); 134 | memcpy(keyBuffer,result(),HASH_LENGTH); 135 | } else { 136 | // Block length keys are used as is 137 | memcpy(keyBuffer,key,keyLength); 138 | } 139 | // Start inner hash 140 | init(); 141 | for (i=0; i 5 | #include "Print.h" 6 | 7 | #define HASH_LENGTH 20 8 | #define BLOCK_LENGTH 64 9 | 10 | union _buffer { 11 | uint8_t b[BLOCK_LENGTH]; 12 | uint32_t w[BLOCK_LENGTH/4]; 13 | }; 14 | union _state { 15 | uint8_t b[HASH_LENGTH]; 16 | uint32_t w[HASH_LENGTH/4]; 17 | }; 18 | 19 | class Sha1Class : public Print 20 | { 21 | public: 22 | void init(void); 23 | void initHmac(const uint8_t* secret, int secretLength); 24 | uint8_t* result(void); 25 | uint8_t* resultHmac(void); 26 | virtual size_t write(uint8_t); 27 | using Print::write; 28 | private: 29 | void pad(); 30 | void addUncounted(uint8_t data); 31 | void hashBlock(); 32 | uint32_t rol32(uint32_t number, uint8_t bits); 33 | _buffer buffer; 34 | uint8_t bufferOffset; 35 | _state state; 36 | uint32_t byteCount; 37 | uint8_t keyBuffer[BLOCK_LENGTH]; 38 | uint8_t innerHash[HASH_LENGTH]; 39 | 40 | }; 41 | extern Sha1Class Sha1; 42 | 43 | #endif 44 | --------------------------------------------------------------------------------