Added WebSocket protocol

Revision:
0:6d4dbf28d4ee
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Websocket.cpp	Wed Apr 19 21:26:46 2017 +0000
@@ -0,0 +1,516 @@
+#include "Websocket.h"
+#include "Pmed_reset.h"
+#include "Log.h"
+
+#define MAX_TRY_WRITE 20
+#define MAX_TRY_READ 10
+#define MAX_TRY_CONNECT 50
+#define WAIT_CONNECT_TIME 10000
+
+//Debug is disabled by default
+#if 0
+#define DBG(x, ...) std::printf("[WebSocket : DBG]"x"\r\n", ##__VA_ARGS__); 
+#define WARN(x, ...) std::printf("[WebSocket : WARN]"x"\r\n", ##__VA_ARGS__); 
+#define ERR(x, ...) std::printf("[WebSocket : ERR]"x"\r\n", ##__VA_ARGS__); 
+#else
+#define DBG(x, ...) 
+#define WARN(x, ...)
+#define ERR(x, ...) 
+#endif
+
+#define INFO(x, ...) printf("[WebSocket : INFO]"x"\r\n", ##__VA_ARGS__);
+
+char * Websocket::captureMessage;
+bool Websocket::haveMessage;
+bool Websocket::isConect;
+bool disconnectedWS = false;
+
+void Websocket::Websocket_Thread(void const *arg)
+{
+    //Log::writeEntry("WebSocket Thread Start");
+    printf("Thread WebSocket Iniciada\r\n");
+    int i = 0, f = 0;
+    char *comando = NULL;
+    char *tomada = NULL;
+    char *canal = NULL;
+    char *limite = NULL;
+    char server[30];
+    char buffer[1024] = {};
+    //bool external = false;
+    strcpy(server, "ws://");
+    strcat(server, Settings::get_networkServer());
+    strcat(server, ":8080");
+    
+    //printf("Servidor WS: %s", server);
+    
+    Websocket ws(server);
+    
+    while(1){
+        if(ws.is_connected()){
+            if (f == 0){
+                //Log::writeEntry("WebSocket Conectado.");
+                //printf("\nMensagem do MBED para servidor WebSocket em PHP\n\n");
+                //ws.send("#*MBEDStart*#Mensagem do MBED para servidor WebSocket em PHP\n"); //mensagem para registrar login do mbed no banco de dados (ainda por implementar)
+                f = 1;
+            }
+            if(disconnectedWS != false){
+                disconnectedWS = false;
+            }
+            if(haveMessage){
+                ws.send(captureMessage);
+                haveMessage = false;
+                captureMessage = NULL;
+            }    
+            if (ws.read(buffer)) {
+                //ws.send("#*MBEDTest*#");
+                //printf("Recebido Mensagem WebSocket: %s\r\n", buffer);
+                comando = strtok(buffer, ":");
+                if(strcmp(comando, "test") == 0){
+                    //printf("\r\nRecebido por WebSocket o Comando de Teste.");
+                    ws.send("#*MBEDTest*#");
+                    //Log::writeEntry("Recebido por WebSocket o Comando de Teste.");
+                }
+                if(strcmp(comando, "capture") == 0){
+                    tomada = strtok(NULL, ":");
+                    canal = strtok(NULL, " ");
+                    /*
+                    printf("Comando: %s\n", comando);
+                    printf("Tomada: %s\n", tomada);
+                    printf("Canal: %s\n", canal);
+                    printf("Tamanho da string: %d\n", strlen(buffer));
+                    */
+                    
+                    EventDetector::externalCapture(atoi(tomada), canal[0]);
+                }
+                if(strcmp(comando, "reset") == 0){
+                    Pmed_reset("Reiniciando o MBED por WebSocket.");
+                }
+                
+                if(strcmp(comando, "setLimit") == 0 || strcmp(comando, "setStandByLimit") == 0){
+                    tomada = strtok(NULL, ":");
+                    canal = strtok(NULL, ":");
+                    limite = strtok(NULL, " ");
+                    for(i=0; (unsigned)i < _PRTGMD_SETTINGS_MAX_NUMBER_OF_OUTLETS_; i++){
+                        if(atoi(tomada) == Settings::get_outlet(i)){
+                            if(strcmp(comando, "setLimit") == 0){
+                                if(strcmp(canal, "p") == 0){
+                                    printf("Entrou canal fase e setou limite %s para tomada %s\r\n", limite, tomada);
+                                    Settings::set_limit(i*2,strtod(limite, NULL));
+                                }
+                                if(strcmp(canal, "d") == 0){
+                                    printf("Entrou canal fuga e setou limite %s para tomada %s\r\n", limite, tomada);
+                                    Settings::set_limit(i*2+1,strtod(limite, NULL));
+                                }
+                            }
+                            if(strcmp(comando, "setStandByLimit") == 0){
+                                if(strcmp(canal, "p") == 0){
+                                    printf("Entrou canal StandBy de fase e setou limite %s para tomada %s\r\n", limite, tomada);
+                                    Settings::set_standbyLimit(i*2,strtod(limite, NULL));
+                                }
+                                if(strcmp(canal, "d") == 0){
+                                    printf("Entrou canal StandBy de fuga e setou limite %s para tomada %s\r\n", limite, tomada);
+                                    Settings::set_standbyLimit(i*2+1,strtod(limite, NULL));
+                                }
+                            }
+                            break;
+                        }else{
+                            if(i == _PRTGMD_SETTINGS_MAX_NUMBER_OF_OUTLETS_ - 1){
+                                printf("Tomada %d nao encontrada para atualizar limite!\r\n", atoi(tomada));
+                            }
+                        }
+                    }
+                    //printf("Comando: %s\n", comando);
+                    //printf("Tomada: %d\n", atoi(tomada));
+                    //printf("Canal: %s\n", canal);
+                    //printf("Limite: %f\n", strtod(limite, NULL));
+                }
+                /*
+                if(strcmp(comando, "iniciar") == 0){
+                    printf("\n\nChegou o Iniciar!!!\n\n");
+                    external = true;
+                }
+                if(strcmp(comando, "parar") == 0){
+                    printf("\n\nChegou o Parar!!!\n\n");
+                    external = false;
+                }
+                if(external){
+                    
+                }*/
+            }
+            /*
+            if (!ws.is_connected()){
+                printf("Caiu a conexao!!!\n");
+                Thread::wait(Settings::get_delayTry());
+                for(int i=0; i < MAX_TRY_CONNECT; i++){
+                    Thread::wait(Settings::get_delayTry());
+                    //if(ws.connect()){
+                      //  break;
+                    //}
+                }
+                //Websocket ws("ws://192.168.103.101:8080");
+                ws.connect();
+            }*/
+        }else{
+            if(!disconnectedWS){
+                if(ws.close()){
+                    disconnectedWS = true;
+                    Log::writeEntry("WebSocket Desconectado.");
+                }
+            }
+            Thread::wait(WAIT_CONNECT_TIME);
+            if(ws.connect()){
+                //Log::writeEntry("WebSocket Conectado.");
+                printf("Thread WebSocket Conectou no Servidor %s\r\n", Settings::get_networkServer());
+            }
+            //Thread::wait(10000);
+        }
+    }
+    
+}
+
+Websocket::Websocket(char const * url) {
+    fillFields(url);
+    socket.set_blocking(false, 400);
+}
+
+bool Websocket::wsIsConnected(){
+    return isConect;
+}
+
+void Websocket::fillFields(char const * url) {
+  int ret = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
+  if(ret)
+  {
+    ERR("URL parsing failed; please use: \"ws://ip-or-domain[:port]/path\"");
+    return;
+  }
+
+  if(port == 0) //TODO do handle WSS->443
+  {
+    port = 80;
+  }
+  
+  if(strcmp(scheme, "ws"))
+  {
+    ERR("Wrong scheme, please use \"ws\" instead");
+  }
+}
+
+int Websocket::parseURL(const char* url, char* scheme, size_t maxSchemeLen, char* host, size_t maxHostLen, uint16_t* port, char* path, size_t maxPathLen) //Parse URL
+{
+  char* schemePtr = (char*) url;
+  char* hostPtr = (char*) strstr(url, "://");
+  if(hostPtr == NULL)
+  {
+    WARN("Could not find host");
+    return -1; //URL is invalid
+  }
+
+  if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
+  {
+    WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
+    return -1;
+  }
+  memcpy(scheme, schemePtr, hostPtr - schemePtr);
+  scheme[hostPtr - schemePtr] = '\0';
+
+  hostPtr+=3;
+
+  size_t hostLen = 0;
+
+  char* portPtr = strchr(hostPtr, ':');
+  if( portPtr != NULL )
+  {
+    hostLen = portPtr - hostPtr;
+    portPtr++;
+    if( sscanf(portPtr, "%hu", port) != 1)
+    {
+      WARN("Could not find port");
+      return -1;
+    }
+  }
+  else
+  {
+    *port=0;
+  }
+  char* pathPtr = strchr(hostPtr, '/');
+  if( hostLen == 0 )
+  {
+    hostLen = pathPtr - hostPtr;
+  }
+
+  if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
+  {
+    WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
+    return -1;
+  }
+  memcpy(host, hostPtr, hostLen);
+  host[hostLen] = '\0';
+
+  size_t pathLen;
+  char* fragmentPtr = strchr(hostPtr, '#');
+  if(fragmentPtr != NULL)
+  {
+    pathLen = fragmentPtr - pathPtr;
+  }
+  else
+  {
+    pathLen = strlen(pathPtr);
+  }
+
+  if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
+  {
+    WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
+    return -1;
+  }
+  memcpy(path, pathPtr, pathLen);
+  path[pathLen] = '\0';
+
+  return 0;
+}
+
+
+bool Websocket::connect() {
+    char cmd[200];
+
+    while (socket.connect(host, port) < 0) {
+        ERR("Unable to connect to (%s) on port (%d)", host, port);
+        Thread::wait(200);
+        return false;
+    }
+
+    // sent http header to upgrade to the ws protocol
+    sprintf(cmd, "GET %s HTTP/1.1\r\n", path);
+    write(cmd, strlen(cmd));
+    
+    sprintf(cmd, "Host: %s:%d\r\n", host, port);
+    write(cmd, strlen(cmd));
+
+    sprintf(cmd, "Upgrade: WebSocket\r\n");
+    write(cmd, strlen(cmd));
+
+    sprintf(cmd, "Connection: Upgrade\r\n");
+    write(cmd, strlen(cmd));
+
+    sprintf(cmd, "Sec-WebSocket-Key: L159VM0TWUzyDxwJEIEzjw==\r\n");
+    write(cmd, strlen(cmd));
+
+    sprintf(cmd, "Sec-WebSocket-Version: 13\r\n\r\n");
+    int ret = write(cmd, strlen(cmd));
+    if ((unsigned)ret != strlen(cmd)) {
+        close();
+        ERR("Could not send request");
+        return false;
+    }
+    
+    ret = read(cmd, 200, 100);
+    if (ret < 0) {
+        close();
+        ERR("Could not receive answer\r\n");
+        return false;
+    }
+    cmd[ret] = '\0';
+    DBG("recv: %s\r\n", cmd);
+
+    if ( strstr(cmd, "DdLWT/1JcX+nQFHebYP+rqEx5xI=") == NULL ) {
+        ERR("Wrong answer from server, got \"%s\" instead\r\n", cmd);
+        do {
+            ret = read(cmd, 200, 100);
+            if (ret < 0) {
+                ERR("Could not receive answer\r\n");
+                return false;
+            }
+            cmd[ret] = '\0';
+            printf("%s",cmd);
+        } while (ret > 0);
+        close();
+        return false;
+    }
+    
+    //INFO("\r\nhost: %s\r\npath: %s\r\nport: %d\r\n\r\n", host, path, port);
+    return true;
+}
+
+int Websocket::sendLength(uint32_t len, char * msg) {
+
+    if (len < 126) {
+        msg[0] = len | (1<<7);
+        return 1;
+    } else if (len < 65535) {
+        msg[0] = 126 | (1<<7);
+        msg[1] = (len >> 8) & 0xff;
+        msg[2] = len & 0xff;
+        return 3;
+    } else {
+        msg[0] = 127 | (1<<7);
+        for (int i = 0; i < 8; i++) {
+            msg[i+1] = (len >> i*8) & 0xff;
+        }
+        return 9;
+    }
+}
+
+int Websocket::readChar(char * pC, bool block) {
+    return read(pC, 1, 1);
+}
+
+int Websocket::sendOpcode(uint8_t opcode, char * msg) {
+    msg[0] = 0x80 | (opcode & 0x0f);
+    return 1;
+}
+
+int Websocket::sendMask(char * msg) {
+    for (int i = 0; i < 4; i++) {
+        msg[i] = 0;
+    }
+    return 4;
+}
+
+int Websocket::send(char * str) {
+    char msg[strlen(str) + 15];
+    int idx = 0;
+    idx = sendOpcode(0x01, msg);
+    idx += sendLength(strlen(str), msg + idx);
+    idx += sendMask(msg + idx);
+    memcpy(msg+idx, str, strlen(str));
+    int res = write(msg, idx + strlen(str));
+    return res;
+}
+
+
+bool Websocket::read(char * message) {
+    int i = 0;
+    uint32_t len_msg;
+    char opcode = 0;
+    char c;
+    char mask[4] = {0, 0, 0, 0};
+    bool is_masked = false;
+    Timer tmr;
+
+    // read the opcode
+    tmr.start();
+    while (true) {
+        if (tmr.read() > 3) {
+            DBG("timeout ws\r\n");
+            return false;
+        }
+        
+        if(!socket.is_connected())
+        {
+            WARN("Connection was closed by server");
+            return false;
+        }
+
+        socket.set_blocking(false, 1);
+        if (socket.receive(&opcode, 1) != 1) {
+            socket.set_blocking(false, 2000);
+            return false;
+        }
+
+        socket.set_blocking(false, 2000);
+
+        if (opcode == 0x81)
+            break;
+    }
+    DBG("opcode: 0x%X\r\n", opcode);
+
+    readChar(&c);
+    len_msg = c & 0x7f;
+    is_masked = c & 0x80;
+    if (len_msg == 126) {
+        readChar(&c);
+        len_msg = c << 8;
+        readChar(&c);
+        len_msg += c;
+    } else if (len_msg == 127) {
+        len_msg = 0;
+        for (int i = 0; i < 8; i++) {
+            readChar(&c);
+            len_msg += (c << (7-i)*8);
+        }
+    }
+
+    if (len_msg == 0) {
+        return false;
+    }
+    DBG("length: %d\r\n", len_msg);
+    
+    if (is_masked) {
+        for (i = 0; i < 4; i++)
+            readChar(&c);
+        mask[i] = c;
+    }
+
+    int nb = read(message, len_msg, len_msg);
+    if (nb != len_msg)
+        return false;
+
+    for (i = 0; (unsigned)i < len_msg; i++) {
+        message[i] = message[i] ^ mask[i % 4];
+    }
+
+    message[len_msg] = '\0';
+
+    return true;
+}
+
+bool Websocket::close() {
+    //if (!is_connected())
+        //return false;
+
+    int ret = socket.close(true);
+    if (ret < 0) {
+        ERR("Could not disconnect");
+        return false;
+    }
+    return true;
+}
+
+bool Websocket::is_connected() {
+    isConect = socket.is_connected();
+    return socket.is_connected();
+}
+
+char* Websocket::getPath() {
+    return path;
+}
+
+int Websocket::write(char * str, int len) {
+    int res = 0, idx = 0;
+    
+    for (int j = 0; j < MAX_TRY_WRITE; j++) {
+    
+        if(!socket.is_connected())
+        {
+            WARN("Connection was closed by server");
+            break;
+        }
+
+        if ((res = socket.send_all(str + idx, len - idx)) == -1)
+            continue;
+
+        idx += res;
+        
+        if (idx == len)
+            return len;
+    }
+    
+    return (idx == 0) ? -1 : idx;
+}
+
+int Websocket::read(char * str, int len, int min_len) {
+    int res = 0, idx = 0;
+    
+    for (int j = 0; j < MAX_TRY_WRITE; j++) {
+
+        if ((res = socket.receive_all(str + idx, len - idx)) == -1)
+            continue;
+
+        idx += res;
+        
+        if (idx == len || (min_len != -1 && idx > min_len))
+            return idx;
+    }
+    
+    return (idx == 0) ? -1 : idx;
+}