Files
snapclient/components/websocket/websocket.c
Carlos 15b4baba28 - merge with original master from jorgen
- minimize RAM usage of all components
- use both IRAM and DRAM in player component so we can buffer up to 1s on modules without SPI RAM
- support fragemented pcm chunks so we can use all available RAM if there isn't a big enough block available but still enough HEAP
- reinclude all components from jorgen's master branch
- add custom i2s driver to get a precise timing of initial sync
- change wrong usage of esp_timer for latency measurement of snapcast protocol
- add player component
2021-08-19 21:57:16 +02:00

322 lines
9.0 KiB
C

/*
esp32-websocket - a websocket component on esp-idf
Copyright (C) 2019 Blake Felt - blake.w.felt@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "websocket.h"
#include "lwip/tcp.h" // for the netconn structure
#include "esp_system.h" // for esp_random
#include "mbedtls/base64.h"
#include "mbedtls/sha1.h"
#include <string.h>
ws_client_t ws_connect_client(struct netconn* conn,
char* url,
void (*ccallback)(WEBSOCKET_TYPE_t type,char* msg,uint64_t len),
void (*scallback)(uint8_t num,WEBSOCKET_TYPE_t type,char* msg,uint64_t len)
) {
ws_client_t client;
client.conn = conn;
client.url = url;
client.ping = 0;
client.last_opcode = 0;
client.contin = NULL;
client.len = 0;
client.unfinished = 0;
client.ccallback = ccallback;
client.scallback = scallback;
return client;
}
void ws_disconnect_client(ws_client_t* client,bool mask) {
ws_send(client,WEBSOCKET_OPCODE_CLOSE,NULL,0,mask); // tell the client to close
if(client->conn) {
client->conn->callback = NULL; // shut off the callback
netconn_close(client->conn);
netconn_delete(client->conn);
client->conn = NULL;
}
client->url = NULL;
client->last_opcode = 0;
if(client->len) {
if(client->contin)
free(client->contin);
client->len = 0;
}
client->ccallback = NULL;
client->scallback = NULL;
}
bool ws_is_connected(ws_client_t client) {
if(client.conn)
return 1;
return 0;
}
static void ws_generate_mask(ws_header_t* header) {
header->param.bit.MASK = 1;
header->key.full = esp_random(); // generate a random 32 bit number
}
static void ws_encrypt_decrypt(char* msg,ws_header_t header) {
if(header.param.bit.MASK) {
for(uint64_t i=0; i<header.length; i++) {
msg[i] ^= header.key.part[i%4];
}
}
}
int ws_send(ws_client_t* client,WEBSOCKET_OPCODES_t opcode,char* msg,uint64_t len,bool mask) {
char* out;
char* encrypt;
uint64_t pos;
uint64_t true_len;
ws_header_t header;
int ret;
header.param.pos.ZERO = 0; // reset the whole header
header.param.pos.ONE = 0;
header.param.bit.FIN = 1; // all pieces are done (you don't need a huge message anyway...)
header.param.bit.OPCODE = opcode;
// populate LEN field
pos = 2;
header.length = len;
if(len<=125) {
header.param.bit.LEN = len;
}
else if(len<65536) {
header.param.bit.LEN = 126;
pos += 2;
}
else {
header.param.bit.LEN = 127;
pos += 8;
}
if(mask) {
ws_generate_mask(&header); // get a key
encrypt = malloc(len); // allocate memory for the encryption
memcpy(encrypt,msg,len);
ws_encrypt_decrypt(encrypt,header); // encrypt it!
pos += 4; // add the position
}
true_len = pos+len; // get the length of the entire message
pos = 2;
out = malloc(true_len); // allocate dat memory
out[0] = header.param.pos.ZERO; // save header
out[1] = header.param.pos.ONE;
// put in the length, if necessary
if(header.param.bit.LEN == 126) {
out[2] = (len >> 8) & 0xFF;
out[3] = (len ) & 0xFF;
pos = 4;
}
if(header.param.bit.LEN == 127) {
//memcpy(&out[2],&len,8);
out[2] = (len >> 56) & 0xFF;
out[3] = (len >> 48) & 0xFF;
out[4] = (len >> 40) & 0xFF;
out[5] = (len >> 32) & 0xFF;
out[6] = (len >> 24) & 0xFF;
out[7] = (len >> 16) & 0xFF;
out[8] = (len >> 8) & 0xFF;
out[9] = (len) & 0xFF;
pos = 10;
}
if(mask) {
out[pos] = header.key.part[0]; pos++;
out[pos] = header.key.part[1]; pos++;
out[pos] = header.key.part[2]; pos++;
out[pos] = header.key.part[3]; pos++;
memcpy(&out[pos],encrypt,len); // put in the encrypted message
free(encrypt);
}
else {
memcpy(&out[pos],msg,len);
}
ret = netconn_write(client->conn,out,true_len,NETCONN_COPY); // finally! send it.
free(out); // free the entire message
return ret;
}
char* ws_read(ws_client_t* client,ws_header_t* header) {
char* ret;
char* append;
err_t err;
struct netbuf* inbuf;
struct netbuf* inbuf2;
char* buf;
char* buf2;
uint16_t len;
uint16_t len2;
uint64_t pos;
uint64_t cont_len;
uint64_t cont_pos;
// if we read from this previously (not cont frames), stop reading
if(client->unfinished) {
client->unfinished--;
return NULL;
}
err = netconn_recv(client->conn,&inbuf);
if(err != ERR_OK) return NULL;
netbuf_data(inbuf,(void**)&buf, &len);
if(!buf) return NULL;
// get the header
header->param.pos.ZERO = buf[0];
header->param.pos.ONE = buf[1];
// get the message length
pos = 2;
if(header->param.bit.LEN <= 125) {
header->length = header->param.bit.LEN;
}
else if(header->param.bit.LEN == 126) {
header->length = buf[2] << 8 | buf[3];
pos = 4;
}
else { // LEN = 127
header->length = (uint64_t)buf[2] << 56 | (uint64_t)buf[3] << 48
| (uint64_t)buf[4] << 40 | (uint64_t)buf[5] << 32
| (uint64_t)buf[6] << 24 | (uint64_t)buf[7] << 16
| (uint64_t)buf[8] << 8 | (uint64_t)buf[9];
pos = 10;
}
if(header->param.bit.MASK) {
memcpy(&(header->key.full),&buf[pos],4); // extract the key
pos += 4;
}
ret = malloc(header->length+1); // allocate memory, plus a byte
if(!ret) {
netbuf_delete(inbuf);
header->received = 0;
return NULL;
}
cont_len = len-pos; // get the actual length
memcpy(ret,&buf[pos],header->length); // allocate the total memory
cont_pos = cont_len; // get the initial position
// netconn gives messages in pieces, so we need to get those (different than OPCODE_CONT)
while(cont_len < header->length) { // while the actual length is less than the header stated
err = netconn_recv(client->conn,&inbuf2);
if(err != ERR_OK) {
netbuf_delete(inbuf2);
free(ret);
client->unfinished = 0;
header->received = 0;
return NULL;
}
netbuf_data(inbuf2,(void**)&buf2, &len2);
// Prevent catastrophic failure due to memory leakage
if(cont_len + len2 > header->length) {
netbuf_delete(inbuf2);
free(ret);
client->unfinished = 0;
header->received = 0;
return NULL;
}
memcpy(&ret[cont_pos],buf2,len2);
cont_pos += len2;
if(!buf2) {
client->unfinished = 0;
header->received = 0;
}
netbuf_delete(inbuf2);
client->unfinished++;
cont_len += len2;
}
ret[header->length] = '\0'; // end string
ws_encrypt_decrypt(ret,*header); // unencrypt, if necessary
if(header->param.bit.FIN == 0) { // if the message isn't done
if((header->param.bit.OPCODE == WEBSOCKET_OPCODE_CONT) &&
((client->last_opcode==WEBSOCKET_OPCODE_BIN) || (client->last_opcode==WEBSOCKET_OPCODE_TEXT))) {
cont_len = header->length + client->len;
append = malloc(cont_len);
memcpy(append,client->contin,client->len);
memcpy(&append[client->len],ret,header->length);
free(client->contin);
client->contin = malloc(cont_len);
client->len = cont_len;
free(append);
free(ret);
netbuf_delete(inbuf);
//free(buf);
return NULL;
}
else if((header->param.bit.OPCODE==WEBSOCKET_OPCODE_BIN) || (header->param.bit.OPCODE==WEBSOCKET_OPCODE_TEXT)) {
if(client->len) {
free(client->contin);
}
client->contin = malloc(header->length);
memcpy(client->contin,ret,header->length);
client->len = header->length;
client->last_opcode = header->param.bit.OPCODE;
free(ret);
netbuf_delete(inbuf);
//free(buf);
return NULL;
}
else { // there shouldn't be another FIN code....
free(ret);
netbuf_delete(inbuf);
//free(buf);
return NULL;
}
}
client->last_opcode = header->param.bit.OPCODE;
if(inbuf) netbuf_delete(inbuf);
header->received = 1;
return ret;
}
char* ws_hash_handshake(char* handshake,uint8_t len) {
const char hash[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
const uint8_t hash_len = sizeof(hash);
char* ret;
char key[64];
unsigned char sha1sum[20];
unsigned int ret_len;
if(!len) return NULL;
ret = malloc(32);
memcpy(key,handshake,len);
strlcpy(&key[len],hash,sizeof(key));
mbedtls_sha1((unsigned char*)key,len+hash_len-1,sha1sum);
mbedtls_base64_encode(NULL, 0, &ret_len, sha1sum, 20);
if(!mbedtls_base64_encode((unsigned char*)ret,32,&ret_len,sha1sum,20)) {
ret[ret_len] = '\0';
return ret;
}
free(ret);
return NULL;
}