components/websocket/websocket.c

Wed, 10 Jun 2020 09:43:51 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Wed, 10 Jun 2020 09:43:51 +0200
changeset 87
47253f294a9f
parent 34
5c92103c5e72
permissions
-rw-r--r--

SDK settings to reduce bin size. Some log messages to debug level. Added KWH usage registration. Added equipment power usage for HLT and MLT. Equipment database upgraded to version 2, expandable. Fixed some screen errors during temperature mash steps.

/**
 * @file websocket.c
 * @brief Websocket functions.
 * @author Blake Felt - blake.w.felt@gmail.com`
 */

#include "websocket.h"
#include "lwip/tcp.h" // for the netconn structure
#include "esp_system.h" // for esp_random
#include "esp_log.h"
#include "mbedtls/base64.h"
#include "mbedtls/sha1.h"
#include <string.h>

static const char       *TAG = "websocket";

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.connected = true;
  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) {
  ws_send(client,WEBSOCKET_OPCODE_CLOSE,NULL,0,1); // 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->connected = false;
  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 && client.connected)
	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];
    }
  }
}



err_t ws_send(ws_client_t* client,WEBSOCKET_OPCODES_t opcode,char* msg,uint64_t len,bool mask)
{
    static char		*out;
    static char		*encrypt;
    static uint64_t 	pos;
    static uint64_t 	true_len;
    ws_header_t		header;
    static err_t	err;

    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

    if (out == NULL) {
	ESP_LOGE(TAG, "ws_send malloc error for %llu bytes", true_len);
	return ERR_MEM;
    }

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

    err = netconn_write(client->conn,out,true_len,NETCONN_COPY); // finally! send it.
    if (err != ERR_OK) {
	client->connected = false;
	ESP_LOGE(TAG, "ws_send netconn_write error %d", err);
    }
    free(out); // free the entire message
    return err;
}



char* ws_read(ws_client_t* client,ws_header_t* header) 
{
    char		*ret, *append;
    err_t		err;
    struct netbuf	*inbuf, *inbuf2;
    char		*buf, *buf2;
    uint16_t		len, len2;
    uint64_t		pos, cont_len, 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) {
	client->connected = false;
	ESP_LOGE(TAG, "ws_read netconn_recv error %d", err);
	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) {
	ESP_LOGE(TAG, "ws_read malloc error for %llu bytes", header->length+1);
    	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) {
	    ESP_LOGE(TAG, "ws_read netconn_recv error %d", err);
      	    netbuf_delete(inbuf2);
      	    free(ret);
      	    client->unfinished = 0;
      	    client->connected = false;
      	    header->received = 0;
      	    return NULL;
    	}
    
	netbuf_data(inbuf2,(void**)&buf2, &len2);
    	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;
}

mercurial