Web Socket Client

Fork of WebSocketClient by Samuel Mokrani

Committer:
dgriffin65
Date:
Thu Jun 15 20:18:14 2017 +0000
Revision:
8:860d3b8e21f9
Parent:
7:4567996414a5
Updated to mbed-os

Who changed what in which revision?

UserRevisionLine numberNew contents of line
samux 3:9589afa4712e 1 #include "Websocket.h"
dgriffin65 8:860d3b8e21f9 2 #include "DNSClient.h"
samux 3:9589afa4712e 3
samux 7:4567996414a5 4 #define MAX_TRY_WRITE 20
samux 7:4567996414a5 5 #define MAX_TRY_READ 10
samux 3:9589afa4712e 6
samux 3:9589afa4712e 7 //Debug is disabled by default
dgriffin65 8:860d3b8e21f9 8 #if 1
samux 3:9589afa4712e 9 #define DBG(x, ...) std::printf("[WebSocket : DBG]"x"\r\n", ##__VA_ARGS__);
samux 3:9589afa4712e 10 #define WARN(x, ...) std::printf("[WebSocket : WARN]"x"\r\n", ##__VA_ARGS__);
samux 3:9589afa4712e 11 #define ERR(x, ...) std::printf("[WebSocket : ERR]"x"\r\n", ##__VA_ARGS__);
samux 3:9589afa4712e 12 #else
samux 3:9589afa4712e 13 #define DBG(x, ...)
samux 3:9589afa4712e 14 #define WARN(x, ...)
samux 3:9589afa4712e 15 #define ERR(x, ...)
samux 3:9589afa4712e 16 #endif
samux 3:9589afa4712e 17
samux 3:9589afa4712e 18 #define INFO(x, ...) printf("[WebSocket : INFO]"x"\r\n", ##__VA_ARGS__);
samux 3:9589afa4712e 19
dgriffin65 8:860d3b8e21f9 20 Websocket::Websocket(NetworkStack *ns, char * url) {
dgriffin65 8:860d3b8e21f9 21 m_ns = ns;
samux 3:9589afa4712e 22 fillFields(url);
dgriffin65 8:860d3b8e21f9 23 socket.open(ns);
dgriffin65 8:860d3b8e21f9 24 socket.set_blocking(false);
dgriffin65 8:860d3b8e21f9 25 socket.set_timeout(400);
samux 3:9589afa4712e 26 }
samux 3:9589afa4712e 27
samux 3:9589afa4712e 28 void Websocket::fillFields(char * url) {
donatien 6:86e89a0369b9 29 int ret = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
donatien 6:86e89a0369b9 30 if(ret)
donatien 6:86e89a0369b9 31 {
donatien 6:86e89a0369b9 32 ERR("URL parsing failed; please use: \"ws://ip-or-domain[:port]/path\"");
donatien 6:86e89a0369b9 33 return;
donatien 6:86e89a0369b9 34 }
samux 3:9589afa4712e 35
donatien 6:86e89a0369b9 36 if(port == 0) //TODO do handle WSS->443
donatien 6:86e89a0369b9 37 {
donatien 6:86e89a0369b9 38 port = 80;
donatien 6:86e89a0369b9 39 }
donatien 6:86e89a0369b9 40
donatien 6:86e89a0369b9 41 if(strcmp(scheme, "ws"))
donatien 6:86e89a0369b9 42 {
donatien 6:86e89a0369b9 43 ERR("Wrong scheme, please use \"ws\" instead");
donatien 6:86e89a0369b9 44 }
donatien 6:86e89a0369b9 45 }
samux 3:9589afa4712e 46
donatien 6:86e89a0369b9 47 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
donatien 6:86e89a0369b9 48 {
donatien 6:86e89a0369b9 49 char* schemePtr = (char*) url;
donatien 6:86e89a0369b9 50 char* hostPtr = (char*) strstr(url, "://");
donatien 6:86e89a0369b9 51 if(hostPtr == NULL)
donatien 6:86e89a0369b9 52 {
donatien 6:86e89a0369b9 53 WARN("Could not find host");
donatien 6:86e89a0369b9 54 return -1; //URL is invalid
donatien 6:86e89a0369b9 55 }
donatien 6:86e89a0369b9 56
donatien 6:86e89a0369b9 57 if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
donatien 6:86e89a0369b9 58 {
donatien 6:86e89a0369b9 59 WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
donatien 6:86e89a0369b9 60 return -1;
donatien 6:86e89a0369b9 61 }
donatien 6:86e89a0369b9 62 memcpy(scheme, schemePtr, hostPtr - schemePtr);
donatien 6:86e89a0369b9 63 scheme[hostPtr - schemePtr] = '\0';
donatien 6:86e89a0369b9 64
donatien 6:86e89a0369b9 65 hostPtr+=3;
donatien 6:86e89a0369b9 66
donatien 6:86e89a0369b9 67 size_t hostLen = 0;
samux 3:9589afa4712e 68
donatien 6:86e89a0369b9 69 char* portPtr = strchr(hostPtr, ':');
donatien 6:86e89a0369b9 70 if( portPtr != NULL )
donatien 6:86e89a0369b9 71 {
donatien 6:86e89a0369b9 72 hostLen = portPtr - hostPtr;
donatien 6:86e89a0369b9 73 portPtr++;
donatien 6:86e89a0369b9 74 if( sscanf(portPtr, "%hu", port) != 1)
donatien 6:86e89a0369b9 75 {
donatien 6:86e89a0369b9 76 WARN("Could not find port");
donatien 6:86e89a0369b9 77 return -1;
donatien 6:86e89a0369b9 78 }
donatien 6:86e89a0369b9 79 }
donatien 6:86e89a0369b9 80 else
donatien 6:86e89a0369b9 81 {
donatien 6:86e89a0369b9 82 *port=0;
donatien 6:86e89a0369b9 83 }
donatien 6:86e89a0369b9 84 char* pathPtr = strchr(hostPtr, '/');
donatien 6:86e89a0369b9 85 if( hostLen == 0 )
donatien 6:86e89a0369b9 86 {
donatien 6:86e89a0369b9 87 hostLen = pathPtr - hostPtr;
donatien 6:86e89a0369b9 88 }
samux 3:9589afa4712e 89
donatien 6:86e89a0369b9 90 if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
donatien 6:86e89a0369b9 91 {
donatien 6:86e89a0369b9 92 WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
donatien 6:86e89a0369b9 93 return -1;
donatien 6:86e89a0369b9 94 }
donatien 6:86e89a0369b9 95 memcpy(host, hostPtr, hostLen);
donatien 6:86e89a0369b9 96 host[hostLen] = '\0';
samux 3:9589afa4712e 97
donatien 6:86e89a0369b9 98 size_t pathLen;
donatien 6:86e89a0369b9 99 char* fragmentPtr = strchr(hostPtr, '#');
donatien 6:86e89a0369b9 100 if(fragmentPtr != NULL)
donatien 6:86e89a0369b9 101 {
donatien 6:86e89a0369b9 102 pathLen = fragmentPtr - pathPtr;
donatien 6:86e89a0369b9 103 }
donatien 6:86e89a0369b9 104 else
donatien 6:86e89a0369b9 105 {
donatien 6:86e89a0369b9 106 pathLen = strlen(pathPtr);
donatien 6:86e89a0369b9 107 }
donatien 6:86e89a0369b9 108
donatien 6:86e89a0369b9 109 if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
donatien 6:86e89a0369b9 110 {
donatien 6:86e89a0369b9 111 WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
donatien 6:86e89a0369b9 112 return -1;
donatien 6:86e89a0369b9 113 }
donatien 6:86e89a0369b9 114 memcpy(path, pathPtr, pathLen);
donatien 6:86e89a0369b9 115 path[pathLen] = '\0';
donatien 6:86e89a0369b9 116
donatien 6:86e89a0369b9 117 return 0;
samux 3:9589afa4712e 118 }
samux 3:9589afa4712e 119
samux 3:9589afa4712e 120
samux 3:9589afa4712e 121 bool Websocket::connect() {
dgriffin65 8:860d3b8e21f9 122 DNSClient dns(m_ns);
samux 3:9589afa4712e 123 char cmd[200];
dgriffin65 8:860d3b8e21f9 124
dgriffin65 8:860d3b8e21f9 125 if (!dns.lookup(host)) {
dgriffin65 8:860d3b8e21f9 126 ERR("Websocket: Unable to resolve hostname (%s)", host);
samux 3:9589afa4712e 127 wait(0.2);
dgriffin65 8:860d3b8e21f9 128 connected = false;
donatien 6:86e89a0369b9 129 return false;
samux 3:9589afa4712e 130 }
dgriffin65 8:860d3b8e21f9 131
dgriffin65 8:860d3b8e21f9 132 while (socket.connect(dns.get_ip_address(), port) < 0) {
dgriffin65 8:860d3b8e21f9 133 ERR("Websocket: Unable to connect to (%s) on port (%d)", host, port);
dgriffin65 8:860d3b8e21f9 134 wait(0.2);
dgriffin65 8:860d3b8e21f9 135 connected = false;
dgriffin65 8:860d3b8e21f9 136 return false;
dgriffin65 8:860d3b8e21f9 137 }
dgriffin65 8:860d3b8e21f9 138
dgriffin65 8:860d3b8e21f9 139 connected = true;
samux 3:9589afa4712e 140
samux 3:9589afa4712e 141 // sent http header to upgrade to the ws protocol
donatien 6:86e89a0369b9 142 sprintf(cmd, "GET %s HTTP/1.1\r\n", path);
samux 3:9589afa4712e 143 write(cmd, strlen(cmd));
donatien 6:86e89a0369b9 144
donatien 6:86e89a0369b9 145 sprintf(cmd, "Host: %s:%d\r\n", host, port);
samux 3:9589afa4712e 146 write(cmd, strlen(cmd));
samux 3:9589afa4712e 147
samux 3:9589afa4712e 148 sprintf(cmd, "Upgrade: WebSocket\r\n");
samux 3:9589afa4712e 149 write(cmd, strlen(cmd));
samux 3:9589afa4712e 150
samux 3:9589afa4712e 151 sprintf(cmd, "Connection: Upgrade\r\n");
samux 3:9589afa4712e 152 write(cmd, strlen(cmd));
samux 3:9589afa4712e 153
samux 3:9589afa4712e 154 sprintf(cmd, "Sec-WebSocket-Key: L159VM0TWUzyDxwJEIEzjw==\r\n");
samux 3:9589afa4712e 155 write(cmd, strlen(cmd));
samux 3:9589afa4712e 156
samux 3:9589afa4712e 157 sprintf(cmd, "Sec-WebSocket-Version: 13\r\n\r\n");
samux 3:9589afa4712e 158 int ret = write(cmd, strlen(cmd));
samux 3:9589afa4712e 159 if (ret != strlen(cmd)) {
samux 3:9589afa4712e 160 close();
samux 3:9589afa4712e 161 ERR("Could not send request");
samux 3:9589afa4712e 162 return false;
samux 3:9589afa4712e 163 }
samux 3:9589afa4712e 164
samux 3:9589afa4712e 165 ret = read(cmd, 200, 100);
samux 3:9589afa4712e 166 if (ret < 0) {
samux 3:9589afa4712e 167 close();
samux 3:9589afa4712e 168 ERR("Could not receive answer\r\n");
samux 3:9589afa4712e 169 return false;
samux 3:9589afa4712e 170 }
samux 3:9589afa4712e 171
samux 3:9589afa4712e 172 cmd[ret] = '\0';
samux 3:9589afa4712e 173 DBG("recv: %s\r\n", cmd);
samux 3:9589afa4712e 174
samux 3:9589afa4712e 175 if ( strstr(cmd, "DdLWT/1JcX+nQFHebYP+rqEx5xI=") == NULL ) {
samux 3:9589afa4712e 176 ERR("Wrong answer from server, got \"%s\" instead\r\n", cmd);
samux 3:9589afa4712e 177 do {
samux 3:9589afa4712e 178 ret = read(cmd, 200, 100);
samux 3:9589afa4712e 179 if (ret < 0) {
samux 3:9589afa4712e 180 ERR("Could not receive answer\r\n");
samux 3:9589afa4712e 181 return false;
samux 3:9589afa4712e 182 }
samux 3:9589afa4712e 183 cmd[ret] = '\0';
samux 3:9589afa4712e 184 printf("%s",cmd);
samux 3:9589afa4712e 185 } while (ret > 0);
samux 3:9589afa4712e 186 close();
samux 3:9589afa4712e 187 return false;
samux 3:9589afa4712e 188 }
samux 3:9589afa4712e 189
donatien 6:86e89a0369b9 190 INFO("\r\nhost: %s\r\npath: %s\r\nport: %d\r\n\r\n", host, path, port);
samux 3:9589afa4712e 191 return true;
samux 3:9589afa4712e 192 }
samux 3:9589afa4712e 193
samux 4:466f90b7849a 194 int Websocket::sendLength(uint32_t len, char * msg) {
samux 3:9589afa4712e 195
samux 3:9589afa4712e 196 if (len < 126) {
samux 4:466f90b7849a 197 msg[0] = len | (1<<7);
samux 3:9589afa4712e 198 return 1;
samux 3:9589afa4712e 199 } else if (len < 65535) {
samux 4:466f90b7849a 200 msg[0] = 126 | (1<<7);
samux 4:466f90b7849a 201 msg[1] = (len >> 8) & 0xff;
samux 4:466f90b7849a 202 msg[2] = len & 0xff;
samux 3:9589afa4712e 203 return 3;
samux 3:9589afa4712e 204 } else {
samux 4:466f90b7849a 205 msg[0] = 127 | (1<<7);
samux 3:9589afa4712e 206 for (int i = 0; i < 8; i++) {
samux 4:466f90b7849a 207 msg[i+1] = (len >> i*8) & 0xff;
samux 3:9589afa4712e 208 }
samux 3:9589afa4712e 209 return 9;
samux 3:9589afa4712e 210 }
samux 3:9589afa4712e 211 }
samux 3:9589afa4712e 212
samux 3:9589afa4712e 213 int Websocket::readChar(char * pC, bool block) {
samux 3:9589afa4712e 214 return read(pC, 1, 1);
samux 3:9589afa4712e 215 }
samux 3:9589afa4712e 216
samux 4:466f90b7849a 217 int Websocket::sendOpcode(uint8_t opcode, char * msg) {
samux 4:466f90b7849a 218 msg[0] = 0x80 | (opcode & 0x0f);
samux 4:466f90b7849a 219 return 1;
samux 3:9589afa4712e 220 }
samux 3:9589afa4712e 221
samux 4:466f90b7849a 222 int Websocket::sendMask(char * msg) {
samux 3:9589afa4712e 223 for (int i = 0; i < 4; i++) {
samux 4:466f90b7849a 224 msg[i] = 0;
samux 3:9589afa4712e 225 }
samux 3:9589afa4712e 226 return 4;
samux 3:9589afa4712e 227 }
samux 3:9589afa4712e 228
samux 3:9589afa4712e 229 int Websocket::send(char * str) {
samux 4:466f90b7849a 230 char msg[strlen(str) + 15];
samux 4:466f90b7849a 231 int idx = 0;
samux 4:466f90b7849a 232 idx = sendOpcode(0x01, msg);
samux 4:466f90b7849a 233 idx += sendLength(strlen(str), msg + idx);
samux 4:466f90b7849a 234 idx += sendMask(msg + idx);
samux 4:466f90b7849a 235 memcpy(msg+idx, str, strlen(str));
samux 4:466f90b7849a 236 int res = write(msg, idx + strlen(str));
samux 3:9589afa4712e 237 return res;
samux 3:9589afa4712e 238 }
samux 3:9589afa4712e 239
samux 3:9589afa4712e 240
samux 3:9589afa4712e 241 bool Websocket::read(char * message) {
samux 3:9589afa4712e 242 int i = 0;
samux 3:9589afa4712e 243 uint32_t len_msg;
samux 3:9589afa4712e 244 char opcode = 0;
samux 3:9589afa4712e 245 char c;
samux 3:9589afa4712e 246 char mask[4] = {0, 0, 0, 0};
samux 3:9589afa4712e 247 bool is_masked = false;
samux 3:9589afa4712e 248 Timer tmr;
samux 3:9589afa4712e 249
samux 3:9589afa4712e 250 // read the opcode
samux 3:9589afa4712e 251 tmr.start();
samux 3:9589afa4712e 252 while (true) {
samux 3:9589afa4712e 253 if (tmr.read() > 3) {
samux 3:9589afa4712e 254 DBG("timeout ws\r\n");
samux 3:9589afa4712e 255 return false;
samux 3:9589afa4712e 256 }
donatien 5:bb09d7a6c92f 257
dgriffin65 8:860d3b8e21f9 258 if(!connected)
donatien 5:bb09d7a6c92f 259 {
donatien 5:bb09d7a6c92f 260 WARN("Connection was closed by server");
donatien 5:bb09d7a6c92f 261 return false;
donatien 5:bb09d7a6c92f 262 }
samux 3:9589afa4712e 263
dgriffin65 8:860d3b8e21f9 264 socket.set_blocking(false);
dgriffin65 8:860d3b8e21f9 265 socket.set_timeout(1);
dgriffin65 8:860d3b8e21f9 266 if (socket.recv(&opcode, 1) != 1) {
dgriffin65 8:860d3b8e21f9 267 socket.set_blocking(false);
dgriffin65 8:860d3b8e21f9 268 socket.set_timeout(2000);
samux 3:9589afa4712e 269 return false;
samux 3:9589afa4712e 270 }
samux 3:9589afa4712e 271
dgriffin65 8:860d3b8e21f9 272 socket.set_blocking(false);
dgriffin65 8:860d3b8e21f9 273 socket.set_timeout(2000);
samux 3:9589afa4712e 274
samux 3:9589afa4712e 275 if (opcode == 0x81)
samux 3:9589afa4712e 276 break;
samux 3:9589afa4712e 277 }
samux 3:9589afa4712e 278 DBG("opcode: 0x%X\r\n", opcode);
samux 3:9589afa4712e 279
samux 3:9589afa4712e 280 readChar(&c);
samux 3:9589afa4712e 281 len_msg = c & 0x7f;
samux 3:9589afa4712e 282 is_masked = c & 0x80;
samux 3:9589afa4712e 283 if (len_msg == 126) {
samux 3:9589afa4712e 284 readChar(&c);
samux 3:9589afa4712e 285 len_msg = c << 8;
samux 3:9589afa4712e 286 readChar(&c);
samux 3:9589afa4712e 287 len_msg += c;
samux 3:9589afa4712e 288 } else if (len_msg == 127) {
samux 3:9589afa4712e 289 len_msg = 0;
samux 3:9589afa4712e 290 for (int i = 0; i < 8; i++) {
samux 3:9589afa4712e 291 readChar(&c);
samux 3:9589afa4712e 292 len_msg += (c << (7-i)*8);
samux 3:9589afa4712e 293 }
samux 3:9589afa4712e 294 }
samux 3:9589afa4712e 295
samux 3:9589afa4712e 296 if (len_msg == 0) {
samux 3:9589afa4712e 297 return false;
samux 3:9589afa4712e 298 }
samux 3:9589afa4712e 299 DBG("length: %d\r\n", len_msg);
samux 3:9589afa4712e 300
samux 3:9589afa4712e 301 if (is_masked) {
samux 3:9589afa4712e 302 for (i = 0; i < 4; i++)
samux 3:9589afa4712e 303 readChar(&c);
samux 3:9589afa4712e 304 mask[i] = c;
samux 3:9589afa4712e 305 }
samux 3:9589afa4712e 306
samux 3:9589afa4712e 307 int nb = read(message, len_msg, len_msg);
samux 3:9589afa4712e 308 if (nb != len_msg)
samux 3:9589afa4712e 309 return false;
samux 3:9589afa4712e 310
samux 3:9589afa4712e 311 for (i = 0; i < len_msg; i++) {
samux 3:9589afa4712e 312 message[i] = message[i] ^ mask[i % 4];
samux 3:9589afa4712e 313 }
samux 3:9589afa4712e 314
samux 3:9589afa4712e 315 message[len_msg] = '\0';
samux 3:9589afa4712e 316
samux 3:9589afa4712e 317 return true;
samux 3:9589afa4712e 318 }
samux 3:9589afa4712e 319
samux 3:9589afa4712e 320 bool Websocket::close() {
samux 3:9589afa4712e 321 if (!is_connected())
samux 3:9589afa4712e 322 return false;
samux 3:9589afa4712e 323
samux 3:9589afa4712e 324 int ret = socket.close();
samux 3:9589afa4712e 325 if (ret < 0) {
samux 3:9589afa4712e 326 ERR("Could not disconnect");
samux 3:9589afa4712e 327 return false;
samux 3:9589afa4712e 328 }
samux 3:9589afa4712e 329 return true;
samux 3:9589afa4712e 330 }
samux 3:9589afa4712e 331
samux 3:9589afa4712e 332 bool Websocket::is_connected() {
dgriffin65 8:860d3b8e21f9 333 return connected;
samux 3:9589afa4712e 334 }
samux 3:9589afa4712e 335
donatien 6:86e89a0369b9 336 char* Websocket::getPath() {
samux 3:9589afa4712e 337 return path;
samux 3:9589afa4712e 338 }
samux 3:9589afa4712e 339
samux 3:9589afa4712e 340 int Websocket::write(char * str, int len) {
samux 3:9589afa4712e 341 int res = 0, idx = 0;
samux 3:9589afa4712e 342
samux 3:9589afa4712e 343 for (int j = 0; j < MAX_TRY_WRITE; j++) {
donatien 5:bb09d7a6c92f 344
dgriffin65 8:860d3b8e21f9 345 if(!connected)
donatien 5:bb09d7a6c92f 346 {
donatien 5:bb09d7a6c92f 347 WARN("Connection was closed by server");
donatien 5:bb09d7a6c92f 348 break;
donatien 5:bb09d7a6c92f 349 }
samux 3:9589afa4712e 350
dgriffin65 8:860d3b8e21f9 351 if ((res = socket.send(str + idx, len - idx)) == -1)
samux 3:9589afa4712e 352 continue;
samux 3:9589afa4712e 353
samux 3:9589afa4712e 354 idx += res;
samux 3:9589afa4712e 355
samux 3:9589afa4712e 356 if (idx == len)
samux 3:9589afa4712e 357 return len;
samux 3:9589afa4712e 358 }
samux 3:9589afa4712e 359
samux 3:9589afa4712e 360 return (idx == 0) ? -1 : idx;
samux 3:9589afa4712e 361 }
samux 3:9589afa4712e 362
samux 3:9589afa4712e 363 int Websocket::read(char * str, int len, int min_len) {
samux 3:9589afa4712e 364 int res = 0, idx = 0;
samux 3:9589afa4712e 365
samux 3:9589afa4712e 366 for (int j = 0; j < MAX_TRY_WRITE; j++) {
samux 3:9589afa4712e 367
dgriffin65 8:860d3b8e21f9 368 if ((res = socket.recv(str + idx, len - idx)) == -1)
samux 3:9589afa4712e 369 continue;
samux 3:9589afa4712e 370
samux 3:9589afa4712e 371 idx += res;
samux 3:9589afa4712e 372
samux 3:9589afa4712e 373 if (idx == len || (min_len != -1 && idx > min_len))
samux 3:9589afa4712e 374 return idx;
samux 3:9589afa4712e 375 }
samux 3:9589afa4712e 376
samux 3:9589afa4712e 377 return (idx == 0) ? -1 : idx;
samux 3:9589afa4712e 378 }