Simple websocket client

Dependents:   WebsocketClient Web_suck_et APS SO - ALARME CONTROLADO VIA SOCKETS F411-mbed-os-iot-project ... more

Fork of WebSocketClient by mbed official

Committer:
mbed_official
Date:
Thu Mar 16 21:10:27 2017 +0000
Revision:
9:efa2c147bee1
Parent:
8:ccedee13be8d
Updated library to utilize mbed-os

Who changed what in which revision?

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