ws.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. /***************************************************************************
  2. * _ _ ____ _
  3. * Project ___| | | | _ \| |
  4. * / __| | | | |_) | |
  5. * | (__| |_| | _ <| |___
  6. * \___|\___/|_| \_\_____|
  7. *
  8. * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
  9. *
  10. * This software is licensed as described in the file COPYING, which
  11. * you should have received as part of this distribution. The terms
  12. * are also available at https://curl.se/docs/copyright.html.
  13. *
  14. * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  15. * copies of the Software, and permit persons to whom the Software is
  16. * furnished to do so, under the terms of the COPYING file.
  17. *
  18. * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  19. * KIND, either express or implied.
  20. *
  21. * SPDX-License-Identifier: curl
  22. *
  23. ***************************************************************************/
  24. #include "curl_setup.h"
  25. #include <curl/curl.h>
  26. #ifdef USE_WEBSOCKETS
  27. #include "urldata.h"
  28. #include "dynbuf.h"
  29. #include "rand.h"
  30. #include "curl_base64.h"
  31. #include "sendf.h"
  32. #include "multiif.h"
  33. #include "ws.h"
  34. #include "easyif.h"
  35. #include "transfer.h"
  36. #include "nonblock.h"
  37. /* The last 3 #include files should be in this order */
  38. #include "curl_printf.h"
  39. #include "curl_memory.h"
  40. #include "memdebug.h"
  41. struct wsfield {
  42. const char *name;
  43. const char *val;
  44. };
  45. CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
  46. {
  47. unsigned int i;
  48. CURLcode result = CURLE_OK;
  49. unsigned char rand[16];
  50. char *randstr;
  51. size_t randlen;
  52. char keyval[40];
  53. struct SingleRequest *k = &data->req;
  54. const struct wsfield heads[]= {
  55. {
  56. /* The request MUST contain an |Upgrade| header field whose value
  57. MUST include the "websocket" keyword. */
  58. "Upgrade:", "websocket"
  59. },
  60. {
  61. /* The request MUST contain a |Connection| header field whose value
  62. MUST include the "Upgrade" token. */
  63. "Connection:", "Upgrade",
  64. },
  65. {
  66. /* The request MUST include a header field with the name
  67. |Sec-WebSocket-Version|. The value of this header field MUST be
  68. 13. */
  69. "Sec-WebSocket-Version:", "13",
  70. },
  71. {
  72. /* The request MUST include a header field with the name
  73. |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
  74. consisting of a randomly selected 16-byte value that has been
  75. base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
  76. selected randomly for each connection. */
  77. "Sec-WebSocket-Key:", &keyval[0]
  78. }
  79. };
  80. /* 16 bytes random */
  81. result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
  82. if(result)
  83. return result;
  84. result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
  85. if(result)
  86. return result;
  87. DEBUGASSERT(randlen < sizeof(keyval));
  88. if(randlen >= sizeof(keyval))
  89. return CURLE_FAILED_INIT;
  90. strcpy(keyval, randstr);
  91. free(randstr);
  92. for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
  93. if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
  94. #ifdef USE_HYPER
  95. char field[128];
  96. msnprintf(field, sizeof(field), "%s %s", heads[i].name,
  97. heads[i].val);
  98. result = Curl_hyper_header(data, req, field);
  99. #else
  100. (void)data;
  101. result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
  102. heads[i].val);
  103. #endif
  104. }
  105. }
  106. k->upgr101 = UPGR101_WS;
  107. Curl_dyn_init(&data->req.p.http->ws.buf, MAX_WS_SIZE * 2);
  108. return result;
  109. }
  110. CURLcode Curl_ws_accept(struct Curl_easy *data)
  111. {
  112. struct SingleRequest *k = &data->req;
  113. struct HTTP *ws = data->req.p.http;
  114. struct connectdata *conn = data->conn;
  115. CURLcode result;
  116. /* Verify the Sec-WebSocket-Accept response.
  117. The sent value is the base64 encoded version of a SHA-1 hash done on the
  118. |Sec-WebSocket-Key| header field concatenated with
  119. the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
  120. */
  121. /* If the response includes a |Sec-WebSocket-Extensions| header field and
  122. this header field indicates the use of an extension that was not present
  123. in the client's handshake (the server has indicated an extension not
  124. requested by the client), the client MUST Fail the WebSocket Connection.
  125. */
  126. /* If the response includes a |Sec-WebSocket-Protocol| header field
  127. and this header field indicates the use of a subprotocol that was
  128. not present in the client's handshake (the server has indicated a
  129. subprotocol not requested by the client), the client MUST Fail
  130. the WebSocket Connection. */
  131. /* 4 bytes random */
  132. result = Curl_rand(data, (unsigned char *)&ws->ws.mask, sizeof(ws->ws.mask));
  133. if(result)
  134. return result;
  135. infof(data, "Recevied 101, switch to WebSockets; mask %02x%02x%02x%02x",
  136. ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]);
  137. k->upgr101 = UPGR101_RECEIVED;
  138. if(data->set.connect_only)
  139. /* switch off non-blocking sockets */
  140. (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE);
  141. return result;
  142. }
  143. #define WSBIT_FIN 0x80
  144. #define WSBIT_OPCODE_CONT 0
  145. #define WSBIT_OPCODE_TEXT (1)
  146. #define WSBIT_OPCODE_BIN (2)
  147. #define WSBIT_OPCODE_CLOSE (8)
  148. #define WSBIT_OPCODE_PING (9)
  149. #define WSBIT_OPCODE_PONG (0xa)
  150. #define WSBIT_OPCODE_MASK (0xf)
  151. #define WSBIT_MASK 0x80
  152. /* remove the spent bytes from the beginning of the buffer as that part has
  153. now been delivered to the application */
  154. static void ws_decode_clear(struct Curl_easy *data)
  155. {
  156. struct websockets *wsp = &data->req.p.http->ws;
  157. size_t spent = wsp->usedbuf;
  158. size_t len = Curl_dyn_len(&wsp->buf);
  159. size_t keep = len - spent;
  160. DEBUGASSERT(len >= spent);
  161. Curl_dyn_tail(&wsp->buf, keep);
  162. }
  163. /* ws_decode() decodes a binary frame into structured WebSocket data,
  164. wpkt - the incoming raw data. If NULL, work on the already buffered data.
  165. ilen - the size of the provided data, perhaps too little, perhaps too much
  166. out - stored pointed to extracted data
  167. olen - stored length of the extracted data
  168. endp - stored pointer to data immediately following the parsed data, if
  169. there is more data in there. NULL if there's no more data.
  170. flags - stored bitmask about the frame
  171. Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it
  172. stores the first part in the ->extra buffer to be used in the next call
  173. when more data is provided.
  174. */
  175. static CURLcode ws_decode(struct Curl_easy *data,
  176. unsigned char *wpkt, size_t ilen,
  177. unsigned char **out, size_t *olen,
  178. unsigned char **endp,
  179. unsigned int *flags)
  180. {
  181. bool fin;
  182. unsigned char opcode;
  183. size_t total;
  184. size_t dataindex = 2;
  185. size_t plen; /* size of data in the buffer */
  186. size_t payloadssize;
  187. struct websockets *wsp = &data->req.p.http->ws;
  188. unsigned char *p;
  189. CURLcode result;
  190. *olen = 0;
  191. /* add the incoming bytes, if any */
  192. if(wpkt) {
  193. result = Curl_dyn_addn(&wsp->buf, wpkt, ilen);
  194. if(result)
  195. return result;
  196. }
  197. plen = Curl_dyn_len(&wsp->buf);
  198. if(plen < 2) {
  199. /* the smallest possible frame is two bytes */
  200. infof(data, "WS: plen == %u, EAGAIN", (int)plen);
  201. return CURLE_AGAIN;
  202. }
  203. p = Curl_dyn_uptr(&wsp->buf);
  204. fin = p[0] & WSBIT_FIN;
  205. opcode = p[0] & WSBIT_OPCODE_MASK;
  206. infof(data, "WS:%d received FIN bit %u", __LINE__, (int)fin);
  207. *flags = 0;
  208. switch(opcode) {
  209. case WSBIT_OPCODE_CONT:
  210. if(!fin)
  211. *flags |= CURLWS_CONT;
  212. infof(data, "WS: received OPCODE CONT");
  213. break;
  214. case WSBIT_OPCODE_TEXT:
  215. infof(data, "WS: received OPCODE TEXT");
  216. *flags |= CURLWS_TEXT;
  217. break;
  218. case WSBIT_OPCODE_BIN:
  219. infof(data, "WS: received OPCODE BINARY");
  220. *flags |= CURLWS_BINARY;
  221. break;
  222. case WSBIT_OPCODE_CLOSE:
  223. infof(data, "WS: received OPCODE CLOSE");
  224. *flags |= CURLWS_CLOSE;
  225. break;
  226. case WSBIT_OPCODE_PING:
  227. infof(data, "WS: received OPCODE PING");
  228. *flags |= CURLWS_PING;
  229. break;
  230. case WSBIT_OPCODE_PONG:
  231. infof(data, "WS: received OPCODE PONG");
  232. *flags |= CURLWS_PONG;
  233. break;
  234. }
  235. if(p[1] & WSBIT_MASK) {
  236. /* A client MUST close a connection if it detects a masked frame. */
  237. failf(data, "WS: masked input frame");
  238. return CURLE_RECV_ERROR;
  239. }
  240. payloadssize = p[1];
  241. if(payloadssize == 126) {
  242. if(plen < 4) {
  243. infof(data, "WS:%d plen == %u, EAGAIN", __LINE__, (int)plen);
  244. return CURLE_AGAIN; /* not enough data available */
  245. }
  246. payloadssize = (p[2] << 8) | p[3];
  247. dataindex += 2;
  248. }
  249. else if(payloadssize == 127) {
  250. failf(data, "WS: too large frame received");
  251. return CURLE_RECV_ERROR;
  252. }
  253. total = dataindex + payloadssize;
  254. if(total > plen) {
  255. /* not enough data in buffer yet */
  256. infof(data, "WS:%d plen == %u (%u), EAGAIN", __LINE__, (int)plen,
  257. (int)total);
  258. return CURLE_AGAIN;
  259. }
  260. /* point to the payload */
  261. *out = &p[dataindex];
  262. /* return the payload length */
  263. *olen = payloadssize;
  264. wsp->usedbuf = total; /* number of bytes "used" from the buffer */
  265. *endp = &p[total];
  266. infof(data, "WS: received %uz bytes payload", payloadssize);
  267. return CURLE_OK;
  268. }
  269. /* Curl_ws_writecb() is the write callback for websocket traffic. The
  270. websocket data is provided to this raw, in chunks. This function should
  271. handle/decode the data and call the "real" underlying callback accordingly.
  272. */
  273. size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
  274. size_t nitems, void *userp)
  275. {
  276. struct HTTP *ws = (struct HTTP *)userp;
  277. struct Curl_easy *data = ws->ws.data;
  278. void *writebody_ptr = data->set.out;
  279. if(data->set.ws_raw_mode)
  280. return data->set.fwrite_func(buffer, size, nitems, writebody_ptr);
  281. else if(nitems) {
  282. unsigned char *wsp;
  283. size_t wslen;
  284. unsigned int recvflags;
  285. CURLcode result;
  286. unsigned char *endp;
  287. decode:
  288. result = ws_decode(data, (unsigned char *)buffer, nitems,
  289. &wsp, &wslen, &endp, &recvflags);
  290. if(result == CURLE_AGAIN)
  291. /* insufficient amount of data, keep it for later */
  292. return nitems;
  293. else if(result) {
  294. infof(data, "WS: decode error %d", (int)result);
  295. return nitems - 1;
  296. }
  297. /* auto-respond to PINGs */
  298. if(recvflags & CURLWS_PING) {
  299. size_t bytes;
  300. infof(data, "WS: auto-respond to PING with a PONG");
  301. /* send back the exact same content as a PONG */
  302. result = curl_ws_send(data, wsp, wslen, &bytes, CURLWS_PONG);
  303. if(result)
  304. return result;
  305. }
  306. else {
  307. /* Store details about the frame to be reachable with curl_ws_meta()
  308. from within the write callback */
  309. ws->ws.handout.age = 0;
  310. ws->ws.handout.recvflags = recvflags;
  311. /* deliver the decoded frame to the user callback */
  312. if(data->set.fwrite_func((char *)wsp, 1, wslen, writebody_ptr) != wslen)
  313. return 0;
  314. }
  315. /* the websocket frame has been delivered */
  316. ws_decode_clear(data);
  317. if(endp) {
  318. /* there's more websocket data to deal with in the buffer */
  319. buffer = NULL; /* don't pass in the data again */
  320. goto decode;
  321. }
  322. }
  323. return nitems;
  324. }
  325. CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
  326. size_t buflen,
  327. size_t *nread, unsigned int *recvflags)
  328. {
  329. size_t bytes;
  330. CURLcode result;
  331. *nread = 0;
  332. *recvflags = 0;
  333. /* get a download buffer */
  334. result = Curl_preconnect(data);
  335. if(result)
  336. return result;
  337. do {
  338. result = curl_easy_recv(data, data->state.buffer,
  339. data->set.buffer_size, &bytes);
  340. if(result)
  341. return result;
  342. if(bytes) {
  343. unsigned char *out;
  344. size_t olen;
  345. unsigned char *endp;
  346. infof(data, "WS: got %u websocket bytes to decode", (int)bytes);
  347. result = ws_decode(data, (unsigned char *)data->state.buffer,
  348. bytes, &out, &olen, &endp, recvflags);
  349. if(result == CURLE_AGAIN)
  350. /* a packet fragment only */
  351. break;
  352. else if(result)
  353. return result;
  354. /* auto-respond to PINGs */
  355. if(*recvflags & CURLWS_PING) {
  356. infof(data, "WS: auto-respond to PING with a PONG");
  357. /* send back the exact same content as a PONG */
  358. result = curl_ws_send(data, out, olen, &bytes, CURLWS_PONG);
  359. if(result)
  360. return result;
  361. }
  362. else {
  363. if(olen < buflen) {
  364. /* copy the payload to the user buffer */
  365. memcpy(buffer, out, olen);
  366. *nread = olen;
  367. }
  368. else {
  369. /* Received a larger websocket frame than what could fit in the user
  370. provided buffer! */
  371. infof(data, "WS: too large websocket frame received");
  372. return CURLE_RECV_ERROR;
  373. }
  374. }
  375. /* the websocket frame has been delivered */
  376. ws_decode_clear(data);
  377. }
  378. else
  379. *nread = bytes;
  380. break;
  381. } while(1);
  382. return CURLE_OK;
  383. }
  384. /***
  385. RFC 6455 Section 5.2
  386. 0 1 2 3
  387. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  388. +-+-+-+-+-------+-+-------------+-------------------------------+
  389. |F|R|R|R| opcode|M| Payload len | Extended payload length |
  390. |I|S|S|S| (4) |A| (7) | (16/64) |
  391. |N|V|V|V| |S| | (if payload len==126/127) |
  392. | |1|2|3| |K| | |
  393. +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
  394. | Extended payload length continued, if payload len == 127 |
  395. + - - - - - - - - - - - - - - - +-------------------------------+
  396. | |Masking-key, if MASK set to 1 |
  397. +-------------------------------+-------------------------------+
  398. | Masking-key (continued) | Payload Data |
  399. +-------------------------------- - - - - - - - - - - - - - - - +
  400. : Payload Data continued ... :
  401. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
  402. | Payload Data continued ... |
  403. +---------------------------------------------------------------+
  404. */
  405. static size_t ws_packet(struct Curl_easy *data,
  406. const unsigned char *payload, size_t len,
  407. unsigned int flags)
  408. {
  409. struct HTTP *ws = data->req.p.http;
  410. unsigned char *out = (unsigned char *)data->state.ulbuf;
  411. unsigned char firstbyte = 0;
  412. int outi;
  413. unsigned char opcode;
  414. unsigned int xori;
  415. unsigned int i;
  416. if(flags & CURLWS_TEXT) {
  417. opcode = WSBIT_OPCODE_TEXT;
  418. infof(data, "WS: send OPCODE TEXT");
  419. }
  420. else if(flags & CURLWS_CLOSE) {
  421. opcode = WSBIT_OPCODE_CLOSE;
  422. infof(data, "WS: send OPCODE CLOSE");
  423. }
  424. else if(flags & CURLWS_PING) {
  425. opcode = WSBIT_OPCODE_PING;
  426. infof(data, "WS: send OPCODE PING");
  427. }
  428. else if(flags & CURLWS_PONG) {
  429. opcode = WSBIT_OPCODE_PONG;
  430. infof(data, "WS: send OPCODE PONG");
  431. }
  432. else {
  433. opcode = WSBIT_OPCODE_BIN;
  434. infof(data, "WS: send OPCODE BINARY");
  435. }
  436. if(!(flags & CURLWS_CONT)) {
  437. /* if not marked as continuing, assume this is the final fragment */
  438. firstbyte |= WSBIT_FIN | opcode;
  439. ws->ws.contfragment = FALSE;
  440. }
  441. else if(ws->ws.contfragment) {
  442. /* the previous fragment was not a final one and this isn't either, keep a
  443. CONT opcode and no FIN bit */
  444. firstbyte |= WSBIT_OPCODE_CONT;
  445. }
  446. else {
  447. ws->ws.contfragment = TRUE;
  448. }
  449. out[0] = firstbyte;
  450. if(len > 126) {
  451. /* no support for > 16 bit fragment sizes */
  452. out[1] = 126 | WSBIT_MASK;
  453. out[2] = (len >> 8) & 0xff;
  454. out[3] = len & 0xff;
  455. outi = 4;
  456. }
  457. else {
  458. out[1] = (unsigned char)len | WSBIT_MASK;
  459. outi = 2;
  460. }
  461. infof(data, "WS: send FIN bit %u (byte %02x)",
  462. firstbyte & WSBIT_FIN ? 1 : 0,
  463. firstbyte);
  464. infof(data, "WS: send payload len %u", (int)len);
  465. /* 4 bytes mask */
  466. memcpy(&out[outi], &ws->ws.mask, 4);
  467. if(data->set.upload_buffer_size < (len + 10))
  468. return 0;
  469. /* pass over the mask */
  470. outi += 4;
  471. /* append payload after the mask, XOR appropriately */
  472. for(i = 0, xori = 0; i < len; i++, outi++) {
  473. out[outi] = payload[i] ^ ws->ws.mask[xori];
  474. xori++;
  475. xori &= 3;
  476. }
  477. /* return packet size */
  478. return outi;
  479. }
  480. CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer,
  481. size_t buflen, size_t *sent,
  482. unsigned int sendflags)
  483. {
  484. size_t bytes;
  485. CURLcode result;
  486. size_t plen;
  487. char *out;
  488. if(buflen > MAX_WS_SIZE) {
  489. failf(data, "too large packet");
  490. return CURLE_BAD_FUNCTION_ARGUMENT;
  491. }
  492. if(!data->set.ws_raw_mode) {
  493. result = Curl_get_upload_buffer(data);
  494. if(result)
  495. return result;
  496. }
  497. if(Curl_is_in_callback(data)) {
  498. ssize_t written;
  499. if(data->set.ws_raw_mode) {
  500. /* raw mode sends exactly what was requested, and this is from within
  501. the write callback */
  502. result = Curl_write(data, data->conn->writesockfd, buffer, buflen,
  503. &written);
  504. infof(data, "WS: wanted to send %u bytes, sent %u bytes",
  505. (int)buflen, (int)written);
  506. }
  507. else {
  508. plen = ws_packet(data, buffer, buflen, sendflags);
  509. out = data->state.ulbuf;
  510. result = Curl_write(data, data->conn->writesockfd, out, plen,
  511. &written);
  512. infof(data, "WS: wanted to send %u bytes, sent %u bytes",
  513. (int)plen, (int)written);
  514. }
  515. bytes = written;
  516. }
  517. else {
  518. plen = ws_packet(data, buffer, buflen, sendflags);
  519. out = data->state.ulbuf;
  520. result = Curl_senddata(data, out, plen, &bytes);
  521. (void)sendflags;
  522. }
  523. *sent = bytes;
  524. return result;
  525. }
  526. void Curl_ws_done(struct Curl_easy *data)
  527. {
  528. struct websockets *wsp = &data->req.p.http->ws;
  529. DEBUGASSERT(wsp);
  530. Curl_dyn_free(&wsp->buf);
  531. }
  532. CURL_EXTERN struct curl_ws_metadata *curl_ws_meta(struct Curl_easy *data)
  533. {
  534. /* we only return something for websockets, called from within the callback
  535. when not using raw mode */
  536. if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->req.p.http &&
  537. !data->set.ws_raw_mode)
  538. return &data->req.p.http->ws.handout;
  539. return NULL;
  540. }
  541. #else
  542. CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
  543. size_t *nread, unsigned int *recvflags)
  544. {
  545. (void)curl;
  546. (void)buffer;
  547. (void)buflen;
  548. (void)nread;
  549. (void)recvflags;
  550. return CURLE_OK;
  551. }
  552. CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
  553. size_t buflen, size_t *sent,
  554. unsigned int sendflags)
  555. {
  556. (void)curl;
  557. (void)buffer;
  558. (void)buflen;
  559. (void)sent;
  560. (void)sendflags;
  561. return CURLE_OK;
  562. }
  563. CURL_EXTERN struct curl_ws_metadata *curl_ws_meta(struct Curl_easy *data)
  564. {
  565. (void)data;
  566. return NULL;
  567. }
  568. #endif /* USE_WEBSOCKETS */