I wrote a very basic WebSocket Server for the ESP32. It is far from perfect, the code is ugly, it does not handle any special cases but I think a good starting point and it works fine with my environment (Chrome).
Next I will check if I need to implement "ping" for WebSockets and then I will try to send data from the server to the browser(Client)
Code: Select all
/**
* \file WebSocket_Task.c
* \author Thomas Barth
* \date 16.01.2017
* \version 0.1
*
* \brief Global definitions for datatypes and definitions
*
* \warning This software is a PROTOTYPE version and is not designed or intended for use in production, especially not for safety-critical applications!
* The user represents and warrants that it will NOT use or redistribute the Software for such purposes.
* This prototype is for research purposes only. This software is provided "AS IS," without a warranty of any kind.
*/
#include "WebSocket_Task.h"
#include "freertos/FreeRTOS.h"
#include "esp_heap_alloc_caps.h"
#include "hwcrypto/sha.h"
#include "esp_system.h"
#include "lwip/API.h"
#include <string.h>
#define WS_CLIENT_KEY_L 24
#define SHA1_RES_L 20
#define WS_PORT 9998
//buffer for sending data to the client
char out_buf[200];
char WS_sec_WS_keys[]="Sec-WebSocket-Key:";
char WS_sec_conKey[]="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};
static int mod_table[] = {0, 2, 1};
char *base64_encode(char *data, uint8_t input_length, uint16_t *output_length) {
uint8_t i,j;
*output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(*output_length);
if (encoded_data == NULL) return NULL;
for (i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
}
for (i = 0; i < mod_table[input_length % 3]; i++)
encoded_data[*output_length - 1 - i] = '=';
return encoded_data;
}
static void ws_server_netconn_serve(struct netconn *conn) {
//Netbuf
struct netbuf *inbuf;
//message buffer
char *buf;
//number of received bytes from netbuf
uint16_t buflen;
//pointer to buffer (multi purpose)
char* p_buf;
//Client Sec Key buffer
char __SHA1_Inp[WS_CLIENT_KEY_L+sizeof(WS_sec_conKey)];
//SHA1 result
char result[SHA1_RES_L];
//multi purpose number buffer
uint16_t i;
//length of received payload
uint16_t payload_length;
//will point to the received payload
char* p_payload;
//receive handshake request
if(netconn_recv(conn, &inbuf)==ERR_OK) {
//read buffer
netbuf_data(inbuf, (void**) &buf, &buflen);
//write static key into SHA1 Input
for(i=0;i<sizeof(WS_sec_conKey);i++)
__SHA1_Inp[i+WS_CLIENT_KEY_L]=WS_sec_conKey[i];
//find Client Sec-WebSocket-Key:
p_buf=strstr(buf, WS_sec_WS_keys);
//check if needle "Sec-WebSocket-Key:" was found
if(p_buf!=NULL){
//get Client Key
for(i=0;i<WS_CLIENT_KEY_L;i++)
__SHA1_Inp[i]=*(p_buf+sizeof(WS_sec_WS_keys)+i);
// calculate hash
esp_sha(SHA1,(unsigned char*)__SHA1_Inp,strlen(__SHA1_Inp),(unsigned char*)result);
//hex to base64
p_buf =base64_encode(result, SHA1_RES_L,&i);
//send handshake
sprintf(out_buf,"HTTP/1.1 101 Switching Protocols \r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n\r\n", i, p_buf);
netconn_write(conn, out_buf, strlen(out_buf), NETCONN_COPY);
//free base 64 encoded sec key
free(p_buf);
//Wait for new data
while(netconn_recv(conn, &inbuf)==ERR_OK){
//read data from inbuf
netbuf_data(inbuf, (void**) &buf, &buflen);
/*
* get payload length.
* The playload is the uint in the 7 LSBs of the second byte.
* If the value is smaller than 126, the length can directly be read from the byte.
* If the value is 126, the playload length is in byte 2 and 3.
* If the value is 127, the playload length is in byte 2, 3, 4, 5, 6, 7, 8 and 9
*/
if((buf[1]&(~BIT(7)))<126){
//get payload length
payload_length=(buf[1]&(~BIT(7)));
//set pointer to first mask byte
p_buf=&buf[2];
}
else if((buf[1]&(~BIT(7)))==126){
//get payload length
payload_length=buf[2]<<8|buf[3];
//set pointer to first mask byte
p_buf=&buf[4];
}
else{
//we will not handle messages longer than 2^16 bytes on an embedded target! ;)
payload_length=0;
break;
}
//allocate memory for decoded message
p_payload = pvPortMallocCaps(payload_length,MALLOC_CAP_8BIT);
//check if malloc succeeded
if(p_payload!=NULL){
//decode playload
for (i = 0; i < payload_length; i++) {
p_payload[i] = (p_buf+4)[i] ^ p_buf[i % 4];
}
//do stuff
printf("content %.*s\n",payload_length,p_payload);
//free payload buffer
free(p_payload);
}
//free input buffer
netbuf_delete(inbuf);
}//while(netconn_recv(conn, &inbuf)==ERR_OK)
}//check if needle "Sec-WebSocket-Key:" was found
}//receive handshake
//delete buffer
netbuf_delete(inbuf);
// Close the connection
netconn_close(conn);
//Delete connection
netconn_delete(conn);
}
void ws_server(void *pvParameters){
struct netconn *conn, *newconn;
esp_err_t err;
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, NULL, WS_PORT);
netconn_listen(conn);
while(netconn_accept(conn, &newconn)==ESP_OK)
ws_server_netconn_serve(newconn);
netconn_close(conn);
netconn_delete(conn);
}