summaryrefslogtreecommitdiff
path: root/thirdparty/libwebsockets/lib/roles/ws
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/libwebsockets/lib/roles/ws')
-rw-r--r--thirdparty/libwebsockets/lib/roles/ws/client-parser-ws.c607
-rw-r--r--thirdparty/libwebsockets/lib/roles/ws/client-ws.c634
-rw-r--r--thirdparty/libwebsockets/lib/roles/ws/ops-ws.c2021
-rw-r--r--thirdparty/libwebsockets/lib/roles/ws/private.h164
-rw-r--r--thirdparty/libwebsockets/lib/roles/ws/server-ws.c874
5 files changed, 4300 insertions, 0 deletions
diff --git a/thirdparty/libwebsockets/lib/roles/ws/client-parser-ws.c b/thirdparty/libwebsockets/lib/roles/ws/client-parser-ws.c
new file mode 100644
index 0000000000..f5aaa6dbb5
--- /dev/null
+++ b/thirdparty/libwebsockets/lib/roles/ws/client-parser-ws.c
@@ -0,0 +1,607 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include "core/private.h"
+
+/*
+ * parsers.c: lws_ws_rx_sm() needs to be roughly kept in
+ * sync with changes here, esp related to ext draining
+ */
+
+int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c)
+{
+ int callback_action = LWS_CALLBACK_CLIENT_RECEIVE;
+ int handled, m;
+ unsigned short close_code;
+ struct lws_tokens ebuf;
+ unsigned char *pp;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ int rx_draining_ext = 0, n;
+#endif
+
+ ebuf.token = NULL;
+ ebuf.len = 0;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->rx_draining_ext) {
+ assert(!c);
+
+ lws_remove_wsi_from_draining_ext_list(wsi);
+ rx_draining_ext = 1;
+ lwsl_debug("%s: doing draining flow\n", __func__);
+
+ goto drain_extension;
+ }
+#endif
+
+ if (wsi->socket_is_permanently_unusable)
+ return -1;
+
+ switch (wsi->lws_rx_parse_state) {
+ case LWS_RXPS_NEW:
+ /* control frames (PING) may interrupt checkable sequences */
+ wsi->ws->defeat_check_utf8 = 0;
+
+ switch (wsi->ws->ietf_spec_revision) {
+ case 13:
+ wsi->ws->opcode = c & 0xf;
+ /* revisit if an extension wants them... */
+ switch (wsi->ws->opcode) {
+ case LWSWSOPC_TEXT_FRAME:
+ wsi->ws->rsv_first_msg = (c & 0x70);
+ wsi->ws->continuation_possible = 1;
+ wsi->ws->check_utf8 = lws_check_opt(
+ wsi->context->options,
+ LWS_SERVER_OPTION_VALIDATE_UTF8);
+ wsi->ws->utf8 = 0;
+ wsi->ws->first_fragment = 1;
+ break;
+ case LWSWSOPC_BINARY_FRAME:
+ wsi->ws->rsv_first_msg = (c & 0x70);
+ wsi->ws->check_utf8 = 0;
+ wsi->ws->continuation_possible = 1;
+ wsi->ws->first_fragment = 1;
+ break;
+ case LWSWSOPC_CONTINUATION:
+ if (!wsi->ws->continuation_possible) {
+ lwsl_info("disordered continuation\n");
+ return -1;
+ }
+ wsi->ws->first_fragment = 0;
+ break;
+ case LWSWSOPC_CLOSE:
+ wsi->ws->check_utf8 = 0;
+ wsi->ws->utf8 = 0;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 0xb:
+ case 0xc:
+ case 0xd:
+ case 0xe:
+ case 0xf:
+ lwsl_info("illegal opcode\n");
+ return -1;
+ default:
+ wsi->ws->defeat_check_utf8 = 1;
+ break;
+ }
+ wsi->ws->rsv = (c & 0x70);
+ /* revisit if an extension wants them... */
+ if (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ !wsi->ws->count_act_ext &&
+#endif
+ wsi->ws->rsv) {
+ lwsl_info("illegal rsv bits set\n");
+ return -1;
+ }
+ wsi->ws->final = !!((c >> 7) & 1);
+ lwsl_ext("%s: This RX frame Final %d\n", __func__,
+ wsi->ws->final);
+
+ if (wsi->ws->owed_a_fin &&
+ (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+ wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) {
+ lwsl_info("hey you owed us a FIN\n");
+ return -1;
+ }
+ if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) {
+ wsi->ws->continuation_possible = 0;
+ wsi->ws->owed_a_fin = 0;
+ }
+
+ if ((wsi->ws->opcode & 8) && !wsi->ws->final) {
+ lwsl_info("control msg can't be fragmented\n");
+ return -1;
+ }
+ if (!wsi->ws->final)
+ wsi->ws->owed_a_fin = 1;
+
+ switch (wsi->ws->opcode) {
+ case LWSWSOPC_TEXT_FRAME:
+ case LWSWSOPC_BINARY_FRAME:
+ wsi->ws->frame_is_binary = wsi->ws->opcode ==
+ LWSWSOPC_BINARY_FRAME;
+ break;
+ }
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
+ break;
+
+ default:
+ lwsl_err("unknown spec version %02d\n",
+ wsi->ws->ietf_spec_revision);
+ break;
+ }
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN:
+
+ wsi->ws->this_frame_masked = !!(c & 0x80);
+
+ switch (c & 0x7f) {
+ case 126:
+ /* control frames are not allowed to have big lengths */
+ if (wsi->ws->opcode & 8)
+ goto illegal_ctl_length;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
+ break;
+ case 127:
+ /* control frames are not allowed to have big lengths */
+ if (wsi->ws->opcode & 8)
+ goto illegal_ctl_length;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
+ break;
+ default:
+ wsi->ws->rx_packet_length = c & 0x7f;
+ if (wsi->ws->this_frame_masked)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+ else {
+ if (wsi->ws->rx_packet_length) {
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_WS_FRAME_PAYLOAD;
+ } else {
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+ }
+ break;
+ }
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN16_2:
+ wsi->ws->rx_packet_length = c << 8;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN16_1:
+ wsi->ws->rx_packet_length |= c;
+ if (wsi->ws->this_frame_masked)
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+ else {
+ if (wsi->ws->rx_packet_length)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_WS_FRAME_PAYLOAD;
+ else {
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+ }
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_8:
+ if (c & 0x80) {
+ lwsl_warn("b63 of length must be zero\n");
+ /* kill the connection */
+ return -1;
+ }
+#if defined __LP64__
+ wsi->ws->rx_packet_length = ((size_t)c) << 56;
+#else
+ wsi->ws->rx_packet_length = 0;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_7:
+#if defined __LP64__
+ wsi->ws->rx_packet_length |= ((size_t)c) << 48;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_6:
+#if defined __LP64__
+ wsi->ws->rx_packet_length |= ((size_t)c) << 40;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_5:
+#if defined __LP64__
+ wsi->ws->rx_packet_length |= ((size_t)c) << 32;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_4:
+ wsi->ws->rx_packet_length |= ((size_t)c) << 24;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_3:
+ wsi->ws->rx_packet_length |= ((size_t)c) << 16;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_2:
+ wsi->ws->rx_packet_length |= ((size_t)c) << 8;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_1:
+ wsi->ws->rx_packet_length |= (size_t)c;
+ if (wsi->ws->this_frame_masked)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+ else {
+ if (wsi->ws->rx_packet_length)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_WS_FRAME_PAYLOAD;
+ else {
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+ }
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
+ wsi->ws->mask[0] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
+ wsi->ws->mask[1] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
+ wsi->ws->mask[2] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
+ wsi->ws->mask[3] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+
+ if (wsi->ws->rx_packet_length)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_WS_FRAME_PAYLOAD;
+ else {
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+ break;
+
+ case LWS_RXPS_WS_FRAME_PAYLOAD:
+
+ assert(wsi->ws->rx_ubuf);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->rx_draining_ext)
+ goto drain_extension;
+#endif
+ if (wsi->ws->this_frame_masked && !wsi->ws->all_zero_nonce)
+ c ^= wsi->ws->mask[(wsi->ws->mask_idx++) & 3];
+
+ wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] = c;
+
+ if (--wsi->ws->rx_packet_length == 0) {
+ /* spill because we have the whole frame */
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+
+ /*
+ * if there's no protocol max frame size given, we are
+ * supposed to default to context->pt_serv_buf_size
+ */
+ if (!wsi->protocol->rx_buffer_size &&
+ wsi->ws->rx_ubuf_head != wsi->context->pt_serv_buf_size)
+ break;
+
+ if (wsi->protocol->rx_buffer_size &&
+ wsi->ws->rx_ubuf_head != wsi->protocol->rx_buffer_size)
+ break;
+
+ /* spill because we filled our rx buffer */
+spill:
+
+ handled = 0;
+
+ /*
+ * is this frame a control packet we should take care of at this
+ * layer? If so service it and hide it from the user callback
+ */
+
+ switch (wsi->ws->opcode) {
+ case LWSWSOPC_CLOSE:
+ pp = (unsigned char *)&wsi->ws->rx_ubuf[LWS_PRE];
+ if (lws_check_opt(wsi->context->options,
+ LWS_SERVER_OPTION_VALIDATE_UTF8) &&
+ wsi->ws->rx_ubuf_head > 2 &&
+ lws_check_utf8(&wsi->ws->utf8, pp + 2,
+ wsi->ws->rx_ubuf_head - 2))
+ goto utf8_fail;
+
+ /* is this an acknowledgment of our close? */
+ if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
+ /*
+ * fine he has told us he is closing too, let's
+ * finish our close
+ */
+ lwsl_parser("seen server's close ack\n");
+ return -1;
+ }
+
+ lwsl_parser("client sees server close len = %d\n",
+ wsi->ws->rx_ubuf_head);
+ if (wsi->ws->rx_ubuf_head >= 2) {
+ close_code = (pp[0] << 8) | pp[1];
+ if (close_code < 1000 ||
+ close_code == 1004 ||
+ close_code == 1005 ||
+ close_code == 1006 ||
+ close_code == 1012 ||
+ close_code == 1013 ||
+ close_code == 1014 ||
+ close_code == 1015 ||
+ (close_code >= 1016 && close_code < 3000)
+ ) {
+ pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
+ pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
+ }
+ }
+ if (user_callback_handle_rxflow(
+ wsi->protocol->callback, wsi,
+ LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
+ wsi->user_space, pp,
+ wsi->ws->rx_ubuf_head))
+ return -1;
+
+ memcpy(wsi->ws->ping_payload_buf + LWS_PRE, pp,
+ wsi->ws->rx_ubuf_head);
+ wsi->ws->close_in_ping_buffer_len =
+ wsi->ws->rx_ubuf_head;
+
+ lwsl_info("%s: scheduling return close as ack\n",
+ __func__);
+ __lws_change_pollfd(wsi, LWS_POLLIN, 0);
+ lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 3);
+ wsi->waiting_to_send_close_frame = 1;
+ wsi->close_needs_ack = 0;
+ lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE);
+ lws_callback_on_writable(wsi);
+ handled = 1;
+ break;
+
+ case LWSWSOPC_PING:
+ lwsl_info("received %d byte ping, sending pong\n",
+ wsi->ws->rx_ubuf_head);
+
+ /* he set a close reason on this guy, ignore PING */
+ if (wsi->ws->close_in_ping_buffer_len)
+ goto ping_drop;
+
+ if (wsi->ws->ping_pending_flag) {
+ /*
+ * there is already a pending ping payload
+ * we should just log and drop
+ */
+ lwsl_parser("DROP PING since one pending\n");
+ goto ping_drop;
+ }
+
+ /* control packets can only be < 128 bytes long */
+ if (wsi->ws->rx_ubuf_head > 128 - 3) {
+ lwsl_parser("DROP PING payload too large\n");
+ goto ping_drop;
+ }
+
+ /* stash the pong payload */
+ memcpy(wsi->ws->ping_payload_buf + LWS_PRE,
+ &wsi->ws->rx_ubuf[LWS_PRE],
+ wsi->ws->rx_ubuf_head);
+
+ wsi->ws->ping_payload_len = wsi->ws->rx_ubuf_head;
+ wsi->ws->ping_pending_flag = 1;
+
+ /* get it sent as soon as possible */
+ lws_callback_on_writable(wsi);
+ping_drop:
+ wsi->ws->rx_ubuf_head = 0;
+ handled = 1;
+ break;
+
+ case LWSWSOPC_PONG:
+ lwsl_info("client received pong\n");
+ lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE],
+ wsi->ws->rx_ubuf_head);
+
+ if (wsi->pending_timeout ==
+ PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) {
+ lwsl_info("%p: received expected PONG\n", wsi);
+ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+ }
+
+ /* issue it */
+ callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG;
+ break;
+
+ case LWSWSOPC_CONTINUATION:
+ case LWSWSOPC_TEXT_FRAME:
+ case LWSWSOPC_BINARY_FRAME:
+ break;
+
+ default:
+ /* not handled or failed */
+ lwsl_ext("Unhandled ext opc 0x%x\n", wsi->ws->opcode);
+ wsi->ws->rx_ubuf_head = 0;
+
+ return -1;
+ }
+
+ /*
+ * No it's real payload, pass it up to the user callback.
+ * It's nicely buffered with the pre-padding taken care of
+ * so it can be sent straight out again using lws_write
+ */
+ if (handled)
+ goto already_done;
+
+ ebuf.token = &wsi->ws->rx_ubuf[LWS_PRE];
+ ebuf.len = wsi->ws->rx_ubuf_head;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+drain_extension:
+ lwsl_ext("%s: passing %d to ext\n", __func__, ebuf.len);
+
+ n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0);
+ lwsl_ext("Ext RX returned %d\n", n);
+ if (n < 0) {
+ wsi->socket_is_permanently_unusable = 1;
+ return -1;
+ }
+#endif
+ lwsl_debug("post inflate ebuf len %d\n", ebuf.len);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (rx_draining_ext && !ebuf.len) {
+ lwsl_debug(" --- ending drain on 0 read result\n");
+ goto already_done;
+ }
+#endif
+
+ if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+ if (lws_check_utf8(&wsi->ws->utf8,
+ (unsigned char *)ebuf.token,
+ ebuf.len)) {
+ lws_close_reason(wsi,
+ LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+ (uint8_t *)"bad utf8", 8);
+ goto utf8_fail;
+ }
+
+ /* we are ending partway through utf-8 character? */
+ if (!wsi->ws->rx_packet_length && wsi->ws->final &&
+ wsi->ws->utf8
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ && !n
+#endif
+ ) {
+ lwsl_info("FINAL utf8 error\n");
+ lws_close_reason(wsi,
+ LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+ (uint8_t *)"partial utf8", 12);
+utf8_fail:
+ lwsl_info("utf8 error\n");
+ lwsl_hexdump_info(ebuf.token, ebuf.len);
+
+ return -1;
+ }
+ }
+
+ if (ebuf.len < 0 &&
+ callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG)
+ goto already_done;
+
+ if (!ebuf.token)
+ goto already_done;
+
+ ebuf.token[ebuf.len] = '\0';
+
+ if (!wsi->protocol->callback)
+ goto already_done;
+
+ if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG)
+ lwsl_info("Client doing pong callback\n");
+
+ if (
+ /* coverity says dead code otherwise */
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ n &&
+#endif
+ ebuf.len)
+ /* extension had more... main loop will come back
+ * we want callback to be done with this set, if so,
+ * because lws_is_final() hides it was final until the
+ * last chunk
+ */
+ lws_add_wsi_to_draining_ext_list(wsi);
+ else
+ lws_remove_wsi_from_draining_ext_list(wsi);
+
+ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+ lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE ||
+ lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK)
+ goto already_done;
+
+ m = wsi->protocol->callback(wsi,
+ (enum lws_callback_reasons)callback_action,
+ wsi->user_space, ebuf.token, ebuf.len);
+
+ wsi->ws->first_fragment = 0;
+
+ // lwsl_notice("%s: bulk ws rx: input used %d, output %d\n",
+ // __func__, wsi->ws->rx_ubuf_head, ebuf.len);
+
+ /* if user code wants to close, let caller know */
+ if (m)
+ return 1;
+
+already_done:
+ wsi->ws->rx_ubuf_head = 0;
+ break;
+ default:
+ lwsl_err("client rx illegal state\n");
+ return 1;
+ }
+
+ return 0;
+
+illegal_ctl_length:
+ lwsl_warn("Control frame asking for extended length is illegal\n");
+
+ /* kill the connection */
+ return -1;
+}
+
+
diff --git a/thirdparty/libwebsockets/lib/roles/ws/client-ws.c b/thirdparty/libwebsockets/lib/roles/ws/client-ws.c
new file mode 100644
index 0000000000..d88833f381
--- /dev/null
+++ b/thirdparty/libwebsockets/lib/roles/ws/client-ws.c
@@ -0,0 +1,634 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <core/private.h>
+
+/*
+ * In-place str to lower case
+ */
+
+static void
+strtolower(char *s)
+{
+ while (*s) {
+#ifdef LWS_PLAT_OPTEE
+ int tolower_optee(int c);
+ *s = tolower_optee((int)*s);
+#else
+ *s = tolower((int)*s);
+#endif
+ s++;
+ }
+}
+
+int
+lws_create_client_ws_object(const struct lws_client_connect_info *i,
+ struct lws *wsi)
+{
+ int v = SPEC_LATEST_SUPPORTED;
+
+ /* allocate the ws struct for the wsi */
+ wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct");
+ if (!wsi->ws) {
+ lwsl_notice("OOM\n");
+ return 1;
+ }
+
+ /* -1 means just use latest supported */
+ if (i->ietf_version_or_minus_one != -1 &&
+ i->ietf_version_or_minus_one)
+ v = i->ietf_version_or_minus_one;
+
+ wsi->ws->ietf_spec_revision = v;
+
+ return 0;
+}
+
+#if !defined(LWS_NO_CLIENT)
+int
+lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
+{
+ if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) &&
+ (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) &&
+ (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) &&
+ !lwsi_role_client(wsi))
+ return 0;
+
+ // lwsl_notice("%s: hs client gets %d in\n", __func__, (int)len);
+
+ while (len) {
+ /*
+ * we were accepting input but now we stopped doing so
+ */
+ if (lws_is_flowcontrolled(wsi)) {
+ //lwsl_notice("%s: caching %ld\n", __func__, (long)len);
+ lws_rxflow_cache(wsi, *buf, 0, (int)len);
+ *buf += len;
+ return 0;
+ }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->rx_draining_ext) {
+ int m;
+
+ //lwsl_notice("%s: draining ext\n", __func__);
+ if (lwsi_role_client(wsi))
+ m = lws_ws_client_rx_sm(wsi, 0);
+ else
+ m = lws_ws_rx_sm(wsi, 0, 0);
+ if (m < 0)
+ return -1;
+ continue;
+ }
+#endif
+ /* caller will account for buflist usage */
+
+ if (lws_ws_client_rx_sm(wsi, *(*buf)++)) {
+ lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n",
+ __func__, (int)len);
+ return -1;
+ }
+ len--;
+ }
+ // lwsl_notice("%s: finished with %ld\n", __func__, (long)len);
+
+ return 0;
+}
+#endif
+
+char *
+lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1)
+{
+ char buf[128], hash[20], key_b64[40];
+ int n;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ const struct lws_extension *ext;
+ int ext_count = 0;
+#endif
+
+ /*
+ * create the random key
+ */
+ n = lws_get_random(wsi->context, hash, 16);
+ if (n != 16) {
+ lwsl_err("Unable to read from random dev %s\n",
+ SYSTEM_RANDOM_FILEPATH);
+ return NULL;
+ }
+
+ lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64));
+
+ p += sprintf(p, "Upgrade: websocket\x0d\x0a"
+ "Connection: %sUpgrade\x0d\x0a"
+ "Sec-WebSocket-Key: ", conn1);
+ strcpy(p, key_b64);
+ p += strlen(key_b64);
+ p += sprintf(p, "\x0d\x0a");
+ if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS))
+ p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a",
+ lws_hdr_simple_ptr(wsi,
+ _WSI_TOKEN_CLIENT_SENT_PROTOCOLS));
+
+ /* tell the server what extensions we could support */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ ext = wsi->vhost->ws.extensions;
+ while (ext && ext->callback) {
+
+ n = wsi->vhost->protocols[0].callback(wsi,
+ LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
+ wsi->user_space, (char *)ext->name, 0);
+
+ /*
+ * zero return from callback means go ahead and allow
+ * the extension, it's what we get if the callback is
+ * unhandled
+ */
+
+ if (n) {
+ ext++;
+ continue;
+ }
+
+ /* apply it */
+
+ if (ext_count)
+ *p++ = ',';
+ else
+ p += sprintf(p, "Sec-WebSocket-Extensions: ");
+ p += sprintf(p, "%s", ext->client_offer);
+ ext_count++;
+
+ ext++;
+ }
+ if (ext_count)
+ p += sprintf(p, "\x0d\x0a");
+#endif
+
+ if (wsi->ws->ietf_spec_revision)
+ p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a",
+ wsi->ws->ietf_spec_revision);
+
+ /* prepare the expected server accept response */
+
+ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */
+ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
+ key_b64);
+
+ lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash);
+
+ lws_b64_encode_string(hash, 20,
+ wsi->http.ah->initial_handshake_hash_base64,
+ sizeof(wsi->http.ah->initial_handshake_hash_base64));
+
+ return p;
+}
+
+int
+lws_client_ws_upgrade(struct lws *wsi, const char **cce)
+{
+ struct lws_context *context = wsi->context;
+ struct lws_tokenize ts;
+ int n, len, okay = 0;
+ lws_tokenize_elem e;
+ char *p, buf[64];
+ const char *pc;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+ char *sb = (char *)&pt->serv_buf[0];
+ const struct lws_ext_options *opts;
+ const struct lws_extension *ext;
+ char ext_name[128];
+ const char *c, *a;
+ int more = 1;
+ char ignore;
+#endif
+
+ if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */
+ lwsl_warn("%s: client ws-over-h2 upgrade not supported yet\n",
+ __func__);
+ *cce = "HS: h2 / ws upgrade unsupported";
+ goto bail3;
+ }
+
+ if (wsi->http.ah->http_response == 401) {
+ lwsl_warn(
+ "lws_client_handshake: got bad HTTP response '%d'\n",
+ wsi->http.ah->http_response);
+ *cce = "HS: ws upgrade unauthorized";
+ goto bail3;
+ }
+
+ if (wsi->http.ah->http_response != 101) {
+ lwsl_warn(
+ "lws_client_handshake: got bad HTTP response '%d'\n",
+ wsi->http.ah->http_response);
+ *cce = "HS: ws upgrade response not 101";
+ goto bail3;
+ }
+
+ if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
+ lwsl_info("no ACCEPT\n");
+ *cce = "HS: ACCEPT missing";
+ goto bail3;
+ }
+
+ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
+ if (!p) {
+ lwsl_info("no UPGRADE\n");
+ *cce = "HS: UPGRADE missing";
+ goto bail3;
+ }
+ strtolower(p);
+ if (strcmp(p, "websocket")) {
+ lwsl_warn(
+ "lws_client_handshake: got bad Upgrade header '%s'\n", p);
+ *cce = "HS: Upgrade to something other than websocket";
+ goto bail3;
+ }
+
+ /* connection: must have "upgrade" */
+
+ lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+ LWS_TOKENIZE_F_MINUS_NONTERM);
+ ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION);
+ if (ts.len <= 0) /* won't fit, or absent */
+ goto bad_conn_format;
+
+ do {
+ e = lws_tokenize(&ts);
+ switch (e) {
+ case LWS_TOKZE_TOKEN:
+ if (!strcasecmp(ts.token, "upgrade"))
+ e = LWS_TOKZE_ENDED;
+ break;
+
+ case LWS_TOKZE_DELIMITER:
+ break;
+
+ default: /* includes ENDED */
+bad_conn_format:
+ *cce = "HS: UPGRADE malformed";
+ goto bail3;
+ }
+ } while (e > 0);
+
+ pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
+ if (!pc) {
+ lwsl_parser("lws_client_int_s_hs: no protocol list\n");
+ } else
+ lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc);
+
+ /*
+ * confirm the protocol the server wants to talk was in the list
+ * of protocols we offered
+ */
+
+ len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
+ if (!len) {
+ lwsl_info("%s: WSI_TOKEN_PROTOCOL is null\n", __func__);
+ /*
+ * no protocol name to work from,
+ * default to first protocol
+ */
+ n = 0;
+ wsi->protocol = &wsi->vhost->protocols[0];
+ goto check_extensions;
+ }
+
+ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
+ len = (int)strlen(p);
+
+ while (pc && *pc && !okay) {
+ if (!strncmp(pc, p, len) &&
+ (pc[len] == ',' || pc[len] == '\0')) {
+ okay = 1;
+ continue;
+ }
+ while (*pc && *pc++ != ',')
+ ;
+ while (*pc == ' ')
+ pc++;
+ }
+
+ if (!okay) {
+ lwsl_info("%s: got bad protocol %s\n", __func__, p);
+ *cce = "HS: PROTOCOL malformed";
+ goto bail2;
+ }
+
+ /*
+ * identify the selected protocol struct and set it
+ */
+ n = 0;
+ /* keep client connection pre-bound protocol */
+ if (!lwsi_role_client(wsi))
+ wsi->protocol = NULL;
+
+ while (wsi->vhost->protocols[n].callback) {
+ if (!wsi->protocol &&
+ strcmp(p, wsi->vhost->protocols[n].name) == 0) {
+ wsi->protocol = &wsi->vhost->protocols[n];
+ break;
+ }
+ n++;
+ }
+
+ if (!wsi->vhost->protocols[n].callback) { /* no match */
+ /* if server, that's already fatal */
+ if (!lwsi_role_client(wsi)) {
+ lwsl_info("%s: fail protocol %s\n", __func__, p);
+ *cce = "HS: Cannot match protocol";
+ goto bail2;
+ }
+
+ /* for client, find the index of our pre-bound protocol */
+
+ n = 0;
+ while (wsi->vhost->protocols[n].callback) {
+ if (wsi->protocol && strcmp(wsi->protocol->name,
+ wsi->vhost->protocols[n].name) == 0) {
+ wsi->protocol = &wsi->vhost->protocols[n];
+ break;
+ }
+ n++;
+ }
+
+ if (!wsi->vhost->protocols[n].callback) {
+ if (wsi->protocol)
+ lwsl_err("Failed to match protocol %s\n",
+ wsi->protocol->name);
+ else
+ lwsl_err("No protocol on client\n");
+ goto bail2;
+ }
+ }
+
+ lwsl_debug("Selected protocol %s\n", wsi->protocol->name);
+
+check_extensions:
+ /*
+ * stitch protocol choice into the vh protocol linked list
+ * We always insert ourselves at the start of the list
+ *
+ * X <-> B
+ * X <-> pAn <-> pB
+ */
+
+ lws_same_vh_protocol_insert(wsi, n);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ /* instantiate the accepted extensions */
+
+ if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
+ lwsl_ext("no client extensions allowed by server\n");
+ goto check_accept;
+ }
+
+ /*
+ * break down the list of server accepted extensions
+ * and go through matching them or identifying bogons
+ */
+
+ if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size,
+ WSI_TOKEN_EXTENSIONS) < 0) {
+ lwsl_warn("ext list from server failed to copy\n");
+ *cce = "HS: EXT: list too big";
+ goto bail2;
+ }
+
+ c = sb;
+ n = 0;
+ ignore = 0;
+ a = NULL;
+ while (more) {
+
+ if (*c && (*c != ',' && *c != '\t')) {
+ if (*c == ';') {
+ ignore = 1;
+ if (!a)
+ a = c + 1;
+ }
+ if (ignore || *c == ' ') {
+ c++;
+ continue;
+ }
+
+ ext_name[n] = *c++;
+ if (n < (int)sizeof(ext_name) - 1)
+ n++;
+ continue;
+ }
+ ext_name[n] = '\0';
+ ignore = 0;
+ if (!*c)
+ more = 0;
+ else {
+ c++;
+ if (!n)
+ continue;
+ }
+
+ /* check we actually support it */
+
+ lwsl_notice("checking client ext %s\n", ext_name);
+
+ n = 0;
+ ext = wsi->vhost->ws.extensions;
+ while (ext && ext->callback) {
+ if (strcmp(ext_name, ext->name)) {
+ ext++;
+ continue;
+ }
+
+ n = 1;
+ lwsl_notice("instantiating client ext %s\n", ext_name);
+
+ /* instantiate the extension on this conn */
+
+ wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext;
+
+ /* allow him to construct his ext instance */
+
+ if (ext->callback(lws_get_context(wsi), ext, wsi,
+ LWS_EXT_CB_CLIENT_CONSTRUCT,
+ (void *)&wsi->ws->act_ext_user[
+ wsi->ws->count_act_ext],
+ (void *)&opts, 0)) {
+ lwsl_info(" ext %s failed construction\n",
+ ext_name);
+ ext++;
+ continue;
+ }
+
+ /*
+ * allow the user code to override ext defaults if it
+ * wants to
+ */
+ ext_name[0] = '\0';
+ if (user_callback_handle_rxflow(wsi->protocol->callback,
+ wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
+ (char *)ext->name, ext_name,
+ sizeof(ext_name))) {
+ *cce = "HS: EXT: failed setting defaults";
+ goto bail2;
+ }
+
+ if (ext_name[0] &&
+ lws_ext_parse_options(ext, wsi,
+ wsi->ws->act_ext_user[
+ wsi->ws->count_act_ext],
+ opts, ext_name,
+ (int)strlen(ext_name))) {
+ lwsl_err("%s: unable to parse user defaults '%s'",
+ __func__, ext_name);
+ *cce = "HS: EXT: failed parsing defaults";
+ goto bail2;
+ }
+
+ /*
+ * give the extension the server options
+ */
+ if (a && lws_ext_parse_options(ext, wsi,
+ wsi->ws->act_ext_user[
+ wsi->ws->count_act_ext],
+ opts, a, lws_ptr_diff(c, a))) {
+ lwsl_err("%s: unable to parse remote def '%s'",
+ __func__, a);
+ *cce = "HS: EXT: failed parsing options";
+ goto bail2;
+ }
+
+ if (ext->callback(lws_get_context(wsi), ext, wsi,
+ LWS_EXT_CB_OPTION_CONFIRM,
+ wsi->ws->act_ext_user[wsi->ws->count_act_ext],
+ NULL, 0)) {
+ lwsl_err("%s: ext %s rejects server options %s",
+ __func__, ext->name, a);
+ *cce = "HS: EXT: Rejects server options";
+ goto bail2;
+ }
+
+ wsi->ws->count_act_ext++;
+
+ ext++;
+ }
+
+ if (n == 0) {
+ lwsl_warn("Unknown ext '%s'!\n", ext_name);
+ *cce = "HS: EXT: unknown ext";
+ goto bail2;
+ }
+
+ a = NULL;
+ n = 0;
+ }
+
+check_accept:
+#endif
+
+ /*
+ * Confirm his accept token is the one we precomputed
+ */
+
+ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
+ if (strcmp(p, wsi->http.ah->initial_handshake_hash_base64)) {
+ lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p,
+ wsi->http.ah->initial_handshake_hash_base64);
+ *cce = "HS: Accept hash wrong";
+ goto bail2;
+ }
+
+ /* allocate the per-connection user memory (if any) */
+ if (lws_ensure_user_space(wsi)) {
+ lwsl_err("Problem allocating wsi user mem\n");
+ *cce = "HS: OOM";
+ goto bail2;
+ }
+
+ /*
+ * we seem to be good to go, give client last chance to check
+ * headers and OK it
+ */
+ if (wsi->protocol->callback(wsi,
+ LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
+ wsi->user_space, NULL, 0)) {
+ *cce = "HS: Rejected by filter cb";
+ goto bail2;
+ }
+
+ /* clear his proxy connection timeout */
+ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+ /* free up his parsing allocations */
+ lws_header_table_detach(wsi, 0);
+
+ lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED,
+ &role_ops_ws);
+ lws_restart_ws_ping_pong_timer(wsi);
+
+ wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
+
+ /*
+ * create the frame buffer for this connection according to the
+ * size mentioned in the protocol definition. If 0 there, then
+ * use a big default for compatibility
+ */
+ n = (int)wsi->protocol->rx_buffer_size;
+ if (!n)
+ n = context->pt_serv_buf_size;
+ n += LWS_PRE;
+ wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */,
+ "client frame buffer");
+ if (!wsi->ws->rx_ubuf) {
+ lwsl_err("Out of Mem allocating rx buffer %d\n", n);
+ *cce = "HS: OOM";
+ goto bail2;
+ }
+ wsi->ws->rx_ubuf_alloc = n;
+ lwsl_info("Allocating client RX buffer %d\n", n);
+
+#if !defined(LWS_WITH_ESP32)
+ if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
+ (const char *)&n, sizeof n)) {
+ lwsl_warn("Failed to set SNDBUF to %d", n);
+ *cce = "HS: SO_SNDBUF failed";
+ goto bail3;
+ }
+#endif
+
+ lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name);
+
+ /* call him back to inform him he is up */
+
+ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
+ wsi->user_space, NULL, 0)) {
+ *cce = "HS: Rejected at CLIENT_ESTABLISHED";
+ goto bail3;
+ }
+
+ return 0;
+
+bail3:
+ return 3;
+
+bail2:
+ return 2;
+}
diff --git a/thirdparty/libwebsockets/lib/roles/ws/ops-ws.c b/thirdparty/libwebsockets/lib/roles/ws/ops-ws.c
new file mode 100644
index 0000000000..1cbc6ac6a6
--- /dev/null
+++ b/thirdparty/libwebsockets/lib/roles/ws/ops-ws.c
@@ -0,0 +1,2021 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <core/private.h>
+
+#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
+
+/*
+ * client-parser.c: lws_ws_client_rx_sm() needs to be roughly kept in
+ * sync with changes here, esp related to ext draining
+ */
+
+int
+lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c)
+{
+ int callback_action = LWS_CALLBACK_RECEIVE;
+ int ret = 0;
+ unsigned short close_code;
+ struct lws_tokens ebuf;
+ unsigned char *pp;
+ int n = 0;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ int rx_draining_ext = 0;
+ int lin;
+#endif
+
+ ebuf.token = NULL;
+ ebuf.len = 0;
+ if (wsi->socket_is_permanently_unusable)
+ return -1;
+
+ switch (wsi->lws_rx_parse_state) {
+ case LWS_RXPS_NEW:
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->rx_draining_ext) {
+ ebuf.token = NULL;
+ ebuf.len = 0;
+ lws_remove_wsi_from_draining_ext_list(wsi);
+ rx_draining_ext = 1;
+ lwsl_debug("%s: doing draining flow\n", __func__);
+
+ goto drain_extension;
+ }
+#endif
+ switch (wsi->ws->ietf_spec_revision) {
+ case 13:
+ /*
+ * no prepended frame key any more
+ */
+ wsi->ws->all_zero_nonce = 1;
+ goto handle_first;
+
+ default:
+ lwsl_warn("lws_ws_rx_sm: unknown spec version %d\n",
+ wsi->ws->ietf_spec_revision);
+ break;
+ }
+ break;
+ case LWS_RXPS_04_mask_1:
+ wsi->ws->mask[1] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_mask_2;
+ break;
+ case LWS_RXPS_04_mask_2:
+ wsi->ws->mask[2] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_mask_3;
+ break;
+ case LWS_RXPS_04_mask_3:
+ wsi->ws->mask[3] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+
+ /*
+ * start from the zero'th byte in the XOR key buffer since
+ * this is the start of a frame with a new key
+ */
+
+ wsi->ws->mask_idx = 0;
+
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1;
+ break;
+
+ /*
+ * 04 logical framing from the spec (all this is masked when incoming
+ * and has to be unmasked)
+ *
+ * We ignore the possibility of extension data because we don't
+ * negotiate any extensions at the moment.
+ *
+ * 0 1 2 3
+ * 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
+ * +-+-+-+-+-------+-+-------------+-------------------------------+
+ * |F|R|R|R| opcode|R| Payload len | Extended payload length |
+ * |I|S|S|S| (4) |S| (7) | (16/63) |
+ * |N|V|V|V| |V| | (if payload len==126/127) |
+ * | |1|2|3| |4| | |
+ * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ * | Extended payload length continued, if payload len == 127 |
+ * + - - - - - - - - - - - - - - - +-------------------------------+
+ * | | Extension data |
+ * +-------------------------------+ - - - - - - - - - - - - - - - +
+ * : :
+ * +---------------------------------------------------------------+
+ * : Application data :
+ * +---------------------------------------------------------------+
+ *
+ * We pass payload through to userland as soon as we get it, ignoring
+ * FIN. It's up to userland to buffer it up if it wants to see a
+ * whole unfragmented block of the original size (which may be up to
+ * 2^63 long!)
+ */
+
+ case LWS_RXPS_04_FRAME_HDR_1:
+handle_first:
+
+ wsi->ws->opcode = c & 0xf;
+ wsi->ws->rsv = c & 0x70;
+ wsi->ws->final = !!((c >> 7) & 1);
+ wsi->ws->defeat_check_utf8 = 0;
+
+ if (((wsi->ws->opcode) & 8) && !wsi->ws->final) {
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+ (uint8_t *)"frag ctl", 8);
+ return -1;
+ }
+
+ switch (wsi->ws->opcode) {
+ case LWSWSOPC_TEXT_FRAME:
+ wsi->ws->check_utf8 = lws_check_opt(
+ wsi->context->options,
+ LWS_SERVER_OPTION_VALIDATE_UTF8);
+ /* fallthru */
+ case LWSWSOPC_BINARY_FRAME:
+ if (wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)
+ wsi->ws->check_utf8 = 0;
+ if (wsi->ws->continuation_possible) {
+ lws_close_reason(wsi,
+ LWS_CLOSE_STATUS_PROTOCOL_ERR,
+ (uint8_t *)"bad cont", 8);
+ return -1;
+ }
+ wsi->ws->rsv_first_msg = (c & 0x70);
+ wsi->ws->frame_is_binary =
+ wsi->ws->opcode == LWSWSOPC_BINARY_FRAME;
+ wsi->ws->first_fragment = 1;
+ wsi->ws->continuation_possible = !wsi->ws->final;
+ break;
+ case LWSWSOPC_CONTINUATION:
+ if (!wsi->ws->continuation_possible) {
+ lws_close_reason(wsi,
+ LWS_CLOSE_STATUS_PROTOCOL_ERR,
+ (uint8_t *)"bad cont", 8);
+ return -1;
+ }
+ break;
+ case LWSWSOPC_CLOSE:
+ wsi->ws->check_utf8 = 0;
+ wsi->ws->utf8 = 0;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 0xb:
+ case 0xc:
+ case 0xd:
+ case 0xe:
+ case 0xf:
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+ (uint8_t *)"bad opc", 7);
+ lwsl_info("illegal opcode\n");
+ return -1;
+ }
+
+ if (wsi->ws->owed_a_fin &&
+ (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+ wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) {
+ lwsl_info("hey you owed us a FIN\n");
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+ (uint8_t *)"bad fin", 7);
+ return -1;
+ }
+ if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) {
+ wsi->ws->continuation_possible = 0;
+ wsi->ws->owed_a_fin = 0;
+ }
+
+ if (!wsi->ws->final)
+ wsi->ws->owed_a_fin = 1;
+
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
+ if (wsi->ws->rsv &&
+ (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ !wsi->ws->count_act_ext ||
+#endif
+ (wsi->ws->rsv & ~0x40))) {
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+ (uint8_t *)"rsv bits", 8);
+ return -1;
+ }
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN:
+
+ wsi->ws->this_frame_masked = !!(c & 0x80);
+
+ switch (c & 0x7f) {
+ case 126:
+ /* control frames are not allowed to have big lengths */
+ if (wsi->ws->opcode & 8)
+ goto illegal_ctl_length;
+
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
+ break;
+ case 127:
+ /* control frames are not allowed to have big lengths */
+ if (wsi->ws->opcode & 8)
+ goto illegal_ctl_length;
+
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
+ break;
+ default:
+ wsi->ws->rx_packet_length = c & 0x7f;
+
+
+ if (wsi->ws->this_frame_masked)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+ else
+ if (wsi->ws->rx_packet_length) {
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_WS_FRAME_PAYLOAD;
+ } else {
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+ break;
+ }
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN16_2:
+ wsi->ws->rx_packet_length = c << 8;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN16_1:
+ wsi->ws->rx_packet_length |= c;
+ if (wsi->ws->this_frame_masked)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+ else {
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_WS_FRAME_PAYLOAD;
+ }
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_8:
+ if (c & 0x80) {
+ lwsl_warn("b63 of length must be zero\n");
+ /* kill the connection */
+ return -1;
+ }
+#if defined __LP64__
+ wsi->ws->rx_packet_length = ((size_t)c) << 56;
+#else
+ wsi->ws->rx_packet_length = 0;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_7:
+#if defined __LP64__
+ wsi->ws->rx_packet_length |= ((size_t)c) << 48;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_6:
+#if defined __LP64__
+ wsi->ws->rx_packet_length |= ((size_t)c) << 40;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_5:
+#if defined __LP64__
+ wsi->ws->rx_packet_length |= ((size_t)c) << 32;
+#endif
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_4:
+ wsi->ws->rx_packet_length |= ((size_t)c) << 24;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_3:
+ wsi->ws->rx_packet_length |= ((size_t)c) << 16;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_2:
+ wsi->ws->rx_packet_length |= ((size_t)c) << 8;
+ wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
+ break;
+
+ case LWS_RXPS_04_FRAME_HDR_LEN64_1:
+ wsi->ws->rx_packet_length |= ((size_t)c);
+ if (wsi->ws->this_frame_masked)
+ wsi->lws_rx_parse_state =
+ LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+ else
+ wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
+ wsi->ws->mask[0] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
+ wsi->ws->mask[1] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
+ wsi->ws->mask[2] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
+ break;
+
+ case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
+ wsi->ws->mask[3] = c;
+ if (c)
+ wsi->ws->all_zero_nonce = 0;
+ wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD;
+ wsi->ws->mask_idx = 0;
+ if (wsi->ws->rx_packet_length == 0) {
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+ break;
+
+
+ case LWS_RXPS_WS_FRAME_PAYLOAD:
+ assert(wsi->ws->rx_ubuf);
+
+ if (wsi->ws->rx_ubuf_head + LWS_PRE >= wsi->ws->rx_ubuf_alloc) {
+ lwsl_err("Attempted overflow \n");
+ return -1;
+ }
+ if (!(already_processed & ALREADY_PROCESSED_IGNORE_CHAR)) {
+ if (wsi->ws->all_zero_nonce)
+ wsi->ws->rx_ubuf[LWS_PRE +
+ (wsi->ws->rx_ubuf_head++)] = c;
+ else
+ wsi->ws->rx_ubuf[LWS_PRE +
+ (wsi->ws->rx_ubuf_head++)] =
+ c ^ wsi->ws->mask[(wsi->ws->mask_idx++) & 3];
+
+ --wsi->ws->rx_packet_length;
+ }
+
+ if (!wsi->ws->rx_packet_length) {
+ lwsl_debug("%s: ws fragment length exhausted\n",
+ __func__);
+ /* spill because we have the whole frame */
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+ goto spill;
+ }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->rx_draining_ext) {
+ lwsl_debug("%s: UNTIL_EXHAUSTED draining\n", __func__);
+ goto drain_extension;
+ }
+#endif
+ /*
+ * if there's no protocol max frame size given, we are
+ * supposed to default to context->pt_serv_buf_size
+ */
+ if (!wsi->protocol->rx_buffer_size &&
+ wsi->ws->rx_ubuf_head != wsi->context->pt_serv_buf_size)
+ break;
+
+ if (wsi->protocol->rx_buffer_size &&
+ wsi->ws->rx_ubuf_head != wsi->protocol->rx_buffer_size)
+ break;
+
+ /* spill because we filled our rx buffer */
+spill:
+ /*
+ * is this frame a control packet we should take care of at this
+ * layer? If so service it and hide it from the user callback
+ */
+
+ lwsl_parser("spill on %s\n", wsi->protocol->name);
+
+ switch (wsi->ws->opcode) {
+ case LWSWSOPC_CLOSE:
+
+ if (wsi->ws->peer_has_sent_close)
+ break;
+
+ wsi->ws->peer_has_sent_close = 1;
+
+ pp = (unsigned char *)&wsi->ws->rx_ubuf[LWS_PRE];
+ if (lws_check_opt(wsi->context->options,
+ LWS_SERVER_OPTION_VALIDATE_UTF8) &&
+ wsi->ws->rx_ubuf_head > 2 &&
+ lws_check_utf8(&wsi->ws->utf8, pp + 2,
+ wsi->ws->rx_ubuf_head - 2))
+ goto utf8_fail;
+
+ /* is this an acknowledgment of our close? */
+ if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
+ /*
+ * fine he has told us he is closing too, let's
+ * finish our close
+ */
+ lwsl_parser("seen client close ack\n");
+ return -1;
+ }
+ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+ /* if he sends us 2 CLOSE, kill him */
+ return -1;
+
+ if (lws_partial_buffered(wsi)) {
+ /*
+ * if we're in the middle of something,
+ * we can't do a normal close response and
+ * have to just close our end.
+ */
+ wsi->socket_is_permanently_unusable = 1;
+ lwsl_parser("Closing on peer close "
+ "due to pending tx\n");
+ return -1;
+ }
+
+ if (wsi->ws->rx_ubuf_head >= 2) {
+ close_code = (pp[0] << 8) | pp[1];
+ if (close_code < 1000 ||
+ close_code == 1004 ||
+ close_code == 1005 ||
+ close_code == 1006 ||
+ close_code == 1012 ||
+ close_code == 1013 ||
+ close_code == 1014 ||
+ close_code == 1015 ||
+ (close_code >= 1016 && close_code < 3000)
+ ) {
+ pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
+ pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
+ }
+ }
+
+ if (user_callback_handle_rxflow(
+ wsi->protocol->callback, wsi,
+ LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
+ wsi->user_space,
+ &wsi->ws->rx_ubuf[LWS_PRE],
+ wsi->ws->rx_ubuf_head))
+ return -1;
+
+ lwsl_parser("server sees client close packet\n");
+ lwsi_set_state(wsi, LRS_RETURNED_CLOSE);
+ /* deal with the close packet contents as a PONG */
+ wsi->ws->payload_is_close = 1;
+ goto process_as_ping;
+
+ case LWSWSOPC_PING:
+ lwsl_info("received %d byte ping, sending pong\n",
+ wsi->ws->rx_ubuf_head);
+
+ if (wsi->ws->ping_pending_flag) {
+ /*
+ * there is already a pending ping payload
+ * we should just log and drop
+ */
+ lwsl_parser("DROP PING since one pending\n");
+ goto ping_drop;
+ }
+process_as_ping:
+ /* control packets can only be < 128 bytes long */
+ if (wsi->ws->rx_ubuf_head > 128 - 3) {
+ lwsl_parser("DROP PING payload too large\n");
+ goto ping_drop;
+ }
+
+ /* stash the pong payload */
+ memcpy(wsi->ws->ping_payload_buf + LWS_PRE,
+ &wsi->ws->rx_ubuf[LWS_PRE],
+ wsi->ws->rx_ubuf_head);
+
+ wsi->ws->ping_payload_len = wsi->ws->rx_ubuf_head;
+ wsi->ws->ping_pending_flag = 1;
+
+ /* get it sent as soon as possible */
+ lws_callback_on_writable(wsi);
+ping_drop:
+ wsi->ws->rx_ubuf_head = 0;
+ return 0;
+
+ case LWSWSOPC_PONG:
+ lwsl_info("received pong\n");
+ lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE],
+ wsi->ws->rx_ubuf_head);
+
+ if (wsi->pending_timeout ==
+ PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) {
+ lwsl_info("received expected PONG on wsi %p\n",
+ wsi);
+ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+ }
+
+ /* issue it */
+ callback_action = LWS_CALLBACK_RECEIVE_PONG;
+ break;
+
+ case LWSWSOPC_TEXT_FRAME:
+ case LWSWSOPC_BINARY_FRAME:
+ case LWSWSOPC_CONTINUATION:
+ break;
+
+ default:
+ lwsl_parser("unknown opc %x\n", wsi->ws->opcode);
+
+ return -1;
+ }
+
+ /*
+ * No it's real payload, pass it up to the user callback.
+ * It's nicely buffered with the pre-padding taken care of
+ * so it can be sent straight out again using lws_write
+ */
+
+ ebuf.token = &wsi->ws->rx_ubuf[LWS_PRE];
+ ebuf.len = wsi->ws->rx_ubuf_head;
+
+ if (wsi->ws->opcode == LWSWSOPC_PONG && !ebuf.len)
+ goto already_done;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+drain_extension:
+#endif
+ // lwsl_notice("%s: passing %d to ext\n", __func__, ebuf.len);
+
+ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+ lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK)
+ goto already_done;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ lin = ebuf.len;
+ //if (lin)
+ // lwsl_hexdump_notice(ebuf.token, ebuf.len);
+ n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0);
+ lwsl_debug("%s: ext says %d / ebuf.len %d\n", __func__,
+ n, ebuf.len);
+ if (wsi->ws->rx_draining_ext)
+ already_processed &= ~ALREADY_PROCESSED_NO_CB;
+#endif
+ /*
+ * ebuf may be pointing somewhere completely different now,
+ * it's the output
+ */
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (n < 0) {
+ /*
+ * we may rely on this to get RX, just drop connection
+ */
+ wsi->socket_is_permanently_unusable = 1;
+ return -1;
+ }
+#endif
+ if (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ rx_draining_ext &&
+#endif
+ ebuf.len == 0)
+ goto already_done;
+
+ if (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ n &&
+#endif
+ ebuf.len)
+ /* extension had more... main loop will come back */
+ lws_add_wsi_to_draining_ext_list(wsi);
+ else
+ lws_remove_wsi_from_draining_ext_list(wsi);
+
+ if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+ if (lws_check_utf8(&wsi->ws->utf8,
+ (unsigned char *)ebuf.token,
+ ebuf.len)) {
+ lws_close_reason(wsi,
+ LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+ (uint8_t *)"bad utf8", 8);
+ goto utf8_fail;
+ }
+
+ /* we are ending partway through utf-8 character? */
+ if (!wsi->ws->rx_packet_length && wsi->ws->final &&
+ wsi->ws->utf8 && !n) {
+ lwsl_info("FINAL utf8 error\n");
+ lws_close_reason(wsi,
+ LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+ (uint8_t *)"partial utf8", 12);
+utf8_fail:
+ lwsl_notice("utf8 error\n");
+ lwsl_hexdump_notice(ebuf.token, ebuf.len);
+
+ return -1;
+ }
+ }
+
+ if (!wsi->wsistate_pre_close && (ebuf.len >= 0 ||
+ callback_action == LWS_CALLBACK_RECEIVE_PONG)) {
+ if (ebuf.len)
+ ebuf.token[ebuf.len] = '\0';
+
+ if (wsi->protocol->callback &&
+ !(already_processed & ALREADY_PROCESSED_NO_CB)) {
+ if (callback_action == LWS_CALLBACK_RECEIVE_PONG)
+ lwsl_info("Doing pong callback\n");
+
+ ret = user_callback_handle_rxflow(
+ wsi->protocol->callback,
+ wsi, (enum lws_callback_reasons)
+ callback_action,
+ wsi->user_space,
+ ebuf.token,
+ ebuf.len);
+ }
+ wsi->ws->first_fragment = 0;
+ }
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (!lin)
+ break;
+#endif
+
+already_done:
+ wsi->ws->rx_ubuf_head = 0;
+ break;
+ }
+
+ return ret;
+
+illegal_ctl_length:
+
+ lwsl_warn("Control frame with xtended length is illegal\n");
+ /* kill the connection */
+ return -1;
+}
+
+
+LWS_VISIBLE size_t
+lws_remaining_packet_payload(struct lws *wsi)
+{
+ return wsi->ws->rx_packet_length;
+}
+
+LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi)
+{
+ return wsi->ws->frame_is_binary;
+}
+
+void
+lws_add_wsi_to_draining_ext_list(struct lws *wsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+ if (wsi->ws->rx_draining_ext)
+ return;
+
+ lwsl_debug("%s: RX EXT DRAINING: Adding to list\n", __func__);
+
+ wsi->ws->rx_draining_ext = 1;
+ wsi->ws->rx_draining_ext_list = pt->ws.rx_draining_ext_list;
+ pt->ws.rx_draining_ext_list = wsi;
+#endif
+}
+
+void
+lws_remove_wsi_from_draining_ext_list(struct lws *wsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+ struct lws **w = &pt->ws.rx_draining_ext_list;
+
+ if (!wsi->ws->rx_draining_ext)
+ return;
+
+ lwsl_debug("%s: RX EXT DRAINING: Removing from list\n", __func__);
+
+ wsi->ws->rx_draining_ext = 0;
+
+ /* remove us from context draining ext list */
+ while (*w) {
+ if (*w == wsi) {
+ /* if us, point it instead to who we were pointing to */
+ *w = wsi->ws->rx_draining_ext_list;
+ break;
+ }
+ w = &((*w)->ws->rx_draining_ext_list);
+ }
+ wsi->ws->rx_draining_ext_list = NULL;
+#endif
+}
+
+LWS_EXTERN void
+lws_restart_ws_ping_pong_timer(struct lws *wsi)
+{
+ if (!wsi->context->ws_ping_pong_interval ||
+ !lwsi_role_ws(wsi))
+ return;
+
+ wsi->ws->time_next_ping_check = (time_t)lws_now_secs();
+}
+
+static int
+lws_0405_frame_mask_generate(struct lws *wsi)
+{
+ int n;
+ /* fetch the per-frame nonce */
+
+ n = lws_get_random(lws_get_context(wsi), wsi->ws->mask, 4);
+ if (n != 4) {
+ lwsl_parser("Unable to read from random device %s %d\n",
+ SYSTEM_RANDOM_FILEPATH, n);
+ return 1;
+ }
+
+ /* start masking from first byte of masking key buffer */
+ wsi->ws->mask_idx = 0;
+
+ return 0;
+}
+
+int
+lws_server_init_wsi_for_ws(struct lws *wsi)
+{
+ int n;
+
+ lwsi_set_state(wsi, LRS_ESTABLISHED);
+ lws_restart_ws_ping_pong_timer(wsi);
+
+ /*
+ * create the frame buffer for this connection according to the
+ * size mentioned in the protocol definition. If 0 there, use
+ * a big default for compatibility
+ */
+
+ n = (int)wsi->protocol->rx_buffer_size;
+ if (!n)
+ n = wsi->context->pt_serv_buf_size;
+ n += LWS_PRE;
+ wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf");
+ if (!wsi->ws->rx_ubuf) {
+ lwsl_err("Out of Mem allocating rx buffer %d\n", n);
+ return 1;
+ }
+ wsi->ws->rx_ubuf_alloc = n;
+ lwsl_debug("Allocating RX buffer %d\n", n);
+
+#if !defined(LWS_WITH_ESP32)
+ if (!wsi->h2_stream_carries_ws)
+ if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
+ (const char *)&n, sizeof n)) {
+ lwsl_warn("Failed to set SNDBUF to %d", n);
+ return 1;
+ }
+#endif
+
+ /* notify user code that we're ready to roll */
+
+ if (wsi->protocol->callback)
+ if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED,
+ wsi->user_space,
+#ifdef LWS_WITH_TLS
+ wsi->tls.ssl,
+#else
+ NULL,
+#endif
+ wsi->h2_stream_carries_ws))
+ return 1;
+
+ lwsl_debug("ws established\n");
+
+ return 0;
+}
+
+
+
+LWS_VISIBLE int
+lws_is_final_fragment(struct lws *wsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ lwsl_debug("%s: final %d, rx pk length %ld, draining %ld\n", __func__,
+ wsi->ws->final, (long)wsi->ws->rx_packet_length,
+ (long)wsi->ws->rx_draining_ext);
+ return wsi->ws->final && !wsi->ws->rx_packet_length &&
+ !wsi->ws->rx_draining_ext;
+#else
+ return wsi->ws->final && !wsi->ws->rx_packet_length;
+#endif
+}
+
+LWS_VISIBLE int
+lws_is_first_fragment(struct lws *wsi)
+{
+ return wsi->ws->first_fragment;
+}
+
+LWS_VISIBLE unsigned char
+lws_get_reserved_bits(struct lws *wsi)
+{
+ return wsi->ws->rsv;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_get_close_length(struct lws *wsi)
+{
+ return wsi->ws->close_in_ping_buffer_len;
+}
+
+LWS_VISIBLE LWS_EXTERN unsigned char *
+lws_get_close_payload(struct lws *wsi)
+{
+ return &wsi->ws->ping_payload_buf[LWS_PRE];
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_close_reason(struct lws *wsi, enum lws_close_status status,
+ unsigned char *buf, size_t len)
+{
+ unsigned char *p, *start;
+ int budget = sizeof(wsi->ws->ping_payload_buf) - LWS_PRE;
+
+ assert(lwsi_role_ws(wsi));
+
+ start = p = &wsi->ws->ping_payload_buf[LWS_PRE];
+
+ *p++ = (((int)status) >> 8) & 0xff;
+ *p++ = ((int)status) & 0xff;
+
+ if (buf)
+ while (len-- && p < start + budget)
+ *p++ = *buf++;
+
+ wsi->ws->close_in_ping_buffer_len = lws_ptr_diff(p, start);
+}
+
+static int
+lws_is_ws_with_ext(struct lws *wsi)
+{
+#if defined(LWS_WITHOUT_EXTENSIONS)
+ return 0;
+#else
+ return lwsi_role_ws(wsi) && !!wsi->ws->count_act_ext;
+#endif
+}
+
+static int
+rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
+ struct lws_pollfd *pollfd)
+{
+ unsigned int pending = 0;
+ struct lws_tokens ebuf;
+ char buffered = 0;
+ int n = 0, m;
+#if defined(LWS_WITH_HTTP2)
+ struct lws *wsi1;
+#endif
+
+ if (!wsi->ws) {
+ lwsl_err("ws role wsi with no ws\n");
+ return 1;
+ }
+
+ // lwsl_notice("%s: %s\n", __func__, wsi->protocol->name);
+
+ //lwsl_info("%s: wsistate 0x%x, pollout %d\n", __func__,
+ // wsi->wsistate, pollfd->revents & LWS_POLLOUT);
+
+ /*
+ * something went wrong with parsing the handshake, and
+ * we ended up back in the event loop without completing it
+ */
+ if (lwsi_state(wsi) == LRS_PRE_WS_SERVING_ACCEPT) {
+ wsi->socket_is_permanently_unusable = 1;
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ }
+
+ ebuf.token = NULL;
+ ebuf.len = 0;
+
+ if (lwsi_state(wsi) == LRS_WAITING_CONNECT) {
+#if !defined(LWS_NO_CLIENT)
+ if ((pollfd->revents & LWS_POLLOUT) &&
+ lws_handle_POLLOUT_event(wsi, pollfd)) {
+ lwsl_debug("POLLOUT event closed it\n");
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ }
+
+ n = lws_client_socket_service(wsi, pollfd, NULL);
+ if (n)
+ return LWS_HPI_RET_WSI_ALREADY_DIED;
+#endif
+ return LWS_HPI_RET_HANDLED;
+ }
+
+ //lwsl_notice("%s: wsi->ws->tx_draining_ext %d revents 0x%x 0x%x %d\n",
+ //__func__, wsi->ws->tx_draining_ext, pollfd->revents, wsi->wsistate,
+ //lwsi_state_can_handle_POLLOUT(wsi));
+
+ /* 1: something requested a callback when it was OK to write */
+
+ if ((pollfd->revents & LWS_POLLOUT) &&
+ lwsi_state_can_handle_POLLOUT(wsi) &&
+ lws_handle_POLLOUT_event(wsi, pollfd)) {
+ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+ lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE);
+
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ }
+
+ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+ lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) {
+ /*
+ * we stopped caring about anything except control
+ * packets. Force flow control off, defeat tx
+ * draining.
+ */
+ lws_rx_flow_control(wsi, 1);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws)
+ wsi->ws->tx_draining_ext = 0;
+#endif
+ }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->tx_draining_ext)
+ /*
+ * We cannot deal with new RX until the TX ext path has
+ * been drained. It's because new rx will, eg, crap on
+ * the wsi rx buf that may be needed to retain state.
+ *
+ * TX ext drain path MUST go through event loop to avoid
+ * blocking.
+ */
+ return LWS_HPI_RET_HANDLED;
+#endif
+ if (lws_is_flowcontrolled(wsi)) {
+ /* We cannot deal with any kind of new RX because we are
+ * RX-flowcontrolled.
+ */
+ lwsl_info("flowcontrolled\n");
+ return LWS_HPI_RET_HANDLED;
+ }
+
+#if defined(LWS_WITH_HTTP2)
+ if (wsi->http2_substream || wsi->upgraded_to_http2) {
+ wsi1 = lws_get_network_wsi(wsi);
+ if (wsi1 && lws_has_buffered_out(wsi1))
+ /* We cannot deal with any kind of new RX
+ * because we are dealing with a partial send
+ * (new RX may trigger new http_action() that
+ * expect to be able to send)
+ */
+ return LWS_HPI_RET_HANDLED;
+ }
+#endif
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ /* 2: RX Extension needs to be drained
+ */
+
+ if (wsi->ws->rx_draining_ext) {
+
+ lwsl_debug("%s: RX EXT DRAINING: Service\n", __func__);
+#ifndef LWS_NO_CLIENT
+ if (lwsi_role_client(wsi)) {
+ n = lws_ws_client_rx_sm(wsi, 0);
+ if (n < 0)
+ /* we closed wsi */
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ } else
+#endif
+ n = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
+
+ return LWS_HPI_RET_HANDLED;
+ }
+
+ if (wsi->ws->rx_draining_ext)
+ /*
+ * We have RX EXT content to drain, but can't do it
+ * right now. That means we cannot do anything lower
+ * priority either.
+ */
+ return LWS_HPI_RET_HANDLED;
+#endif
+
+ /* 3: buflist needs to be drained
+ */
+read:
+ //lws_buflist_describe(&wsi->buflist, wsi);
+ ebuf.len = (int)lws_buflist_next_segment_len(&wsi->buflist,
+ (uint8_t **)&ebuf.token);
+ if (ebuf.len) {
+ lwsl_info("draining buflist (len %d)\n", ebuf.len);
+ buffered = 1;
+ goto drain;
+ }
+
+ if (!(pollfd->revents & pollfd->events & LWS_POLLIN) && !wsi->http.ah)
+ return LWS_HPI_RET_HANDLED;
+
+ if (lws_is_flowcontrolled(wsi)) {
+ lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n",
+ __func__, wsi, wsi->rxflow_bitmap);
+ return LWS_HPI_RET_HANDLED;
+ }
+
+ if (!(lwsi_role_client(wsi) &&
+ (lwsi_state(wsi) != LRS_ESTABLISHED &&
+ lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK &&
+ lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) {
+ /*
+ * In case we are going to react to this rx by scheduling
+ * writes, we need to restrict the amount of rx to the size
+ * the protocol reported for rx buffer.
+ *
+ * Otherwise we get a situation we have to absorb possibly a
+ * lot of reads before we get a chance to drain them by writing
+ * them, eg, with echo type tests in autobahn.
+ */
+
+ buffered = 0;
+ ebuf.token = (char *)pt->serv_buf;
+ if (lwsi_role_ws(wsi))
+ ebuf.len = wsi->ws->rx_ubuf_alloc;
+ else
+ ebuf.len = wsi->context->pt_serv_buf_size;
+
+ if ((unsigned int)ebuf.len > wsi->context->pt_serv_buf_size)
+ ebuf.len = wsi->context->pt_serv_buf_size;
+
+ if ((int)pending > ebuf.len)
+ pending = ebuf.len;
+
+ ebuf.len = lws_ssl_capable_read(wsi, (uint8_t *)ebuf.token,
+ pending ? (int)pending :
+ ebuf.len);
+ switch (ebuf.len) {
+ case 0:
+ lwsl_info("%s: zero length read\n",
+ __func__);
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ case LWS_SSL_CAPABLE_MORE_SERVICE:
+ lwsl_info("SSL Capable more service\n");
+ return LWS_HPI_RET_HANDLED;
+ case LWS_SSL_CAPABLE_ERROR:
+ lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n",
+ __func__);
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ }
+ // lwsl_notice("Actual RX %d\n", ebuf.len);
+
+ lws_restart_ws_ping_pong_timer(wsi);
+
+ /*
+ * coverity thinks ssl_capable_read() may read over
+ * 2GB. Dissuade it...
+ */
+ ebuf.len &= 0x7fffffff;
+ }
+
+drain:
+
+ /*
+ * give any active extensions a chance to munge the buffer
+ * before parse. We pass in a pointer to an lws_tokens struct
+ * prepared with the default buffer and content length that's in
+ * there. Rather than rewrite the default buffer, extensions
+ * that expect to grow the buffer can adapt .token to
+ * point to their own per-connection buffer in the extension
+ * user allocation. By default with no extensions or no
+ * extension callback handling, just the normal input buffer is
+ * used then so it is efficient.
+ */
+ m = 0;
+ do {
+
+ /* service incoming data */
+ //lws_buflist_describe(&wsi->buflist, wsi);
+ if (ebuf.len) {
+#if defined(LWS_ROLE_H2)
+ if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY)
+ n = lws_read_h2(wsi, (unsigned char *)ebuf.token,
+ ebuf.len);
+ else
+#endif
+ n = lws_read_h1(wsi, (unsigned char *)ebuf.token,
+ ebuf.len);
+
+ if (n < 0) {
+ /* we closed wsi */
+ n = 0;
+ return LWS_HPI_RET_WSI_ALREADY_DIED;
+ }
+ //lws_buflist_describe(&wsi->buflist, wsi);
+ //lwsl_notice("%s: consuming %d / %d\n", __func__, n, ebuf.len);
+ if (lws_buflist_aware_consume(wsi, &ebuf, n, buffered))
+ return LWS_HPI_RET_PLEASE_CLOSE_ME;
+ }
+
+ ebuf.token = NULL;
+ ebuf.len = 0;
+ } while (m);
+
+ if (wsi->http.ah
+#if !defined(LWS_NO_CLIENT)
+ && !wsi->client_h2_alpn
+#endif
+ ) {
+ lwsl_info("%s: %p: detaching ah\n", __func__, wsi);
+ lws_header_table_detach(wsi, 0);
+ }
+
+ pending = lws_ssl_pending(wsi);
+ if (pending) {
+ if (lws_is_ws_with_ext(wsi))
+ pending = pending > wsi->ws->rx_ubuf_alloc ?
+ wsi->ws->rx_ubuf_alloc : pending;
+ else
+ pending = pending > wsi->context->pt_serv_buf_size ?
+ wsi->context->pt_serv_buf_size : pending;
+ goto read;
+ }
+
+ if (buffered && /* were draining, now nothing left */
+ !lws_buflist_next_segment_len(&wsi->buflist, NULL)) {
+ lwsl_info("%s: %p flow buf: drained\n", __func__, wsi);
+ /* having drained the rxflow buffer, can rearm POLLIN */
+#ifdef LWS_NO_SERVER
+ n =
+#endif
+ __lws_rx_flow_control(wsi);
+ /* n ignored, needed for NO_SERVER case */
+ }
+
+ /* n = 0 */
+ return LWS_HPI_RET_HANDLED;
+}
+
+
+int rops_handle_POLLOUT_ws(struct lws *wsi)
+{
+ int write_type = LWS_WRITE_PONG;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ struct lws_tokens ebuf;
+ int ret, m;
+#endif
+ int n;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ lwsl_debug("%s: %s: wsi->ws->tx_draining_ext %d\n", __func__,
+ wsi->protocol->name, wsi->ws->tx_draining_ext);
+#endif
+
+ /* Priority 3: pending control packets (pong or close)
+ *
+ * 3a: close notification packet requested from close api
+ */
+
+ if (lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) {
+ lwsl_debug("sending close packet\n");
+ lwsl_hexdump_debug(&wsi->ws->ping_payload_buf[LWS_PRE],
+ wsi->ws->close_in_ping_buffer_len);
+ wsi->waiting_to_send_close_frame = 0;
+ n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
+ wsi->ws->close_in_ping_buffer_len,
+ LWS_WRITE_CLOSE);
+ if (n >= 0) {
+ if (wsi->close_needs_ack) {
+ lwsi_set_state(wsi, LRS_AWAITING_CLOSE_ACK);
+ lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK,
+ 5);
+ lwsl_debug("sent close, await ack\n");
+
+ return LWS_HP_RET_BAIL_OK;
+ }
+ wsi->close_needs_ack = 0;
+ lwsi_set_state(wsi, LRS_RETURNED_CLOSE);
+ }
+
+ return LWS_HP_RET_BAIL_DIE;
+ }
+
+ /* else, the send failed and we should just hang up */
+
+ if ((lwsi_role_ws(wsi) && wsi->ws->ping_pending_flag) ||
+ (lwsi_state(wsi) == LRS_RETURNED_CLOSE &&
+ wsi->ws->payload_is_close)) {
+
+ if (wsi->ws->payload_is_close)
+ write_type = LWS_WRITE_CLOSE;
+ else {
+ if (wsi->wsistate_pre_close) {
+ /* we started close flow, forget pong */
+ wsi->ws->ping_pending_flag = 0;
+ return LWS_HP_RET_BAIL_OK;
+ }
+ lwsl_info("issuing pong %d on wsi %p\n",
+ wsi->ws->ping_payload_len, wsi);
+ }
+
+ n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
+ wsi->ws->ping_payload_len, write_type);
+ if (n < 0)
+ return LWS_HP_RET_BAIL_DIE;
+
+ /* well he is sent, mark him done */
+ wsi->ws->ping_pending_flag = 0;
+ if (wsi->ws->payload_is_close) {
+ // assert(0);
+ /* oh... a close frame was it... then we are done */
+ return LWS_HP_RET_BAIL_DIE;
+ }
+
+ /* otherwise for PING, leave POLLOUT active either way */
+ return LWS_HP_RET_BAIL_OK;
+ }
+
+ if (!wsi->socket_is_permanently_unusable && wsi->ws->send_check_ping) {
+
+ lwsl_info("issuing ping on wsi %p\n", wsi);
+ wsi->ws->send_check_ping = 0;
+ n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
+ 0, LWS_WRITE_PING);
+ if (n < 0)
+ return LWS_HP_RET_BAIL_DIE;
+
+ /*
+ * we apparently were able to send the PING in a reasonable time
+ * now reset the clock on our peer to be able to send the
+ * PONG in a reasonable time.
+ */
+
+ lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG,
+ wsi->context->timeout_secs);
+
+ return LWS_HP_RET_BAIL_OK;
+ }
+
+ /* Priority 4: if we are closing, not allowed to send more data frags
+ * which means user callback or tx ext flush banned now
+ */
+ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+ return LWS_HP_RET_USER_SERVICE;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ /* Priority 5: Tx path extension with more to send
+ *
+ * These are handled as new fragments each time around
+ * So while we must block new writeable callback to enforce
+ * payload ordering, but since they are always complete
+ * fragments control packets can interleave OK.
+ */
+ if (wsi->ws->tx_draining_ext) {
+ lwsl_ext("SERVICING TX EXT DRAINING\n");
+ if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0)
+ return LWS_HP_RET_BAIL_DIE;
+ /* leave POLLOUT active */
+ return LWS_HP_RET_BAIL_OK;
+ }
+
+ /* Priority 6: extensions
+ */
+ if (!wsi->ws->extension_data_pending && !wsi->ws->tx_draining_ext) {
+ lwsl_ext("%s: !wsi->ws->extension_data_pending\n", __func__);
+ return LWS_HP_RET_USER_SERVICE;
+ }
+
+ /*
+ * check in on the active extensions, see if they
+ * had pending stuff to spill... they need to get the
+ * first look-in otherwise sequence will be disordered
+ *
+ * NULL, zero-length ebuf means just spill pending
+ */
+
+ ret = 1;
+ if (wsi->role_ops == &role_ops_raw_skt ||
+ wsi->role_ops == &role_ops_raw_file)
+ ret = 0;
+
+ while (ret == 1) {
+
+ /* default to nobody has more to spill */
+
+ ret = 0;
+ ebuf.token = NULL;
+ ebuf.len = 0;
+
+ /* give every extension a chance to spill */
+
+ m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND,
+ &ebuf, 0);
+ if (m < 0) {
+ lwsl_err("ext reports fatal error\n");
+ return LWS_HP_RET_BAIL_DIE;
+ }
+ if (m)
+ /*
+ * at least one extension told us he has more
+ * to spill, so we will go around again after
+ */
+ ret = 1;
+
+ /* assuming they gave us something to send, send it */
+
+ if (ebuf.len) {
+ n = lws_issue_raw(wsi, (unsigned char *)ebuf.token,
+ ebuf.len);
+ if (n < 0) {
+ lwsl_info("closing from POLLOUT spill\n");
+ return LWS_HP_RET_BAIL_DIE;
+ }
+ /*
+ * Keep amount spilled small to minimize chance of this
+ */
+ if (n != ebuf.len) {
+ lwsl_err("Unable to spill ext %d vs %d\n",
+ ebuf.len, n);
+ return LWS_HP_RET_BAIL_DIE;
+ }
+ } else
+ continue;
+
+ /* no extension has more to spill */
+
+ if (!ret)
+ continue;
+
+ /*
+ * There's more to spill from an extension, but we just sent
+ * something... did that leave the pipe choked?
+ */
+
+ if (!lws_send_pipe_choked(wsi))
+ /* no we could add more */
+ continue;
+
+ lwsl_info("choked in POLLOUT service\n");
+
+ /*
+ * Yes, he's choked. Leave the POLLOUT masked on so we will
+ * come back here when he is unchoked. Don't call the user
+ * callback to enforce ordering of spilling, he'll get called
+ * when we come back here and there's nothing more to spill.
+ */
+
+ return LWS_HP_RET_BAIL_OK;
+ }
+
+ wsi->ws->extension_data_pending = 0;
+#endif
+
+ return LWS_HP_RET_USER_SERVICE;
+}
+
+static int
+rops_periodic_checks_ws(struct lws_context *context, int tsi, time_t now)
+{
+ struct lws_vhost *vh;
+
+ if (!context->ws_ping_pong_interval ||
+ context->last_ws_ping_pong_check_s >= now + 10)
+ return 0;
+
+ vh = context->vhost_list;
+ context->last_ws_ping_pong_check_s = now;
+
+ while (vh) {
+ int n;
+
+ lws_vhost_lock(vh);
+
+ for (n = 0; n < vh->count_protocols; n++) {
+
+ lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1,
+ vh->same_vh_protocol_heads[n].next) {
+ struct lws *wsi = lws_container_of(d,
+ struct lws, same_vh_protocol);
+
+ if (lwsi_role_ws(wsi) &&
+ !wsi->socket_is_permanently_unusable &&
+ !wsi->ws->send_check_ping &&
+ wsi->ws->time_next_ping_check &&
+ lws_compare_time_t(context, now,
+ wsi->ws->time_next_ping_check) >
+ context->ws_ping_pong_interval) {
+
+ lwsl_info("req pp on wsi %p\n", wsi);
+ wsi->ws->send_check_ping = 1;
+ lws_set_timeout(wsi,
+ PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING,
+ context->timeout_secs);
+ lws_callback_on_writable(wsi);
+ wsi->ws->time_next_ping_check = now;
+ }
+
+ } lws_end_foreach_dll_safe(d, d1);
+ }
+
+ lws_vhost_unlock(vh);
+ vh = vh->vhost_next;
+ }
+
+ return 0;
+}
+
+static int
+rops_service_flag_pending_ws(struct lws_context *context, int tsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ struct lws_context_per_thread *pt = &context->pt[tsi];
+ struct lws *wsi;
+ int forced = 0;
+
+ /* POLLIN faking (the pt lock is taken by the parent) */
+
+ /*
+ * 1) For all guys with already-available ext data to drain, if they are
+ * not flowcontrolled, fake their POLLIN status
+ */
+ wsi = pt->ws.rx_draining_ext_list;
+ while (wsi && wsi->position_in_fds_table != LWS_NO_FDS_POS) {
+ pt->fds[wsi->position_in_fds_table].revents |=
+ pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
+ if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN)
+ forced = 1;
+
+ wsi = wsi->ws->rx_draining_ext_list;
+ }
+
+ return forced;
+#else
+ return 0;
+#endif
+}
+
+static int
+rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason)
+{
+ if (!wsi->ws)
+ return 0;
+
+ if (!wsi->ws->close_in_ping_buffer_len && /* already a reason */
+ (reason == LWS_CLOSE_STATUS_NOSTATUS ||
+ reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY))
+ return 0;
+
+ lwsl_debug("%s: sending close indication...\n", __func__);
+
+ /* if no prepared close reason, use 1000 and no aux data */
+
+ if (!wsi->ws->close_in_ping_buffer_len) {
+ wsi->ws->close_in_ping_buffer_len = 2;
+ wsi->ws->ping_payload_buf[LWS_PRE] = (reason >> 8) & 0xff;
+ wsi->ws->ping_payload_buf[LWS_PRE + 1] = reason & 0xff;
+ }
+
+ wsi->waiting_to_send_close_frame = 1;
+ wsi->close_needs_ack = 1;
+ lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE);
+ __lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5);
+
+ lws_callback_on_writable(wsi);
+
+ return 1;
+}
+
+static int
+rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
+{
+ if (!wsi->ws)
+ return 0;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+
+ if (wsi->ws->rx_draining_ext) {
+ struct lws **w = &pt->ws.rx_draining_ext_list;
+
+ wsi->ws->rx_draining_ext = 0;
+ /* remove us from context draining ext list */
+ while (*w) {
+ if (*w == wsi) {
+ *w = wsi->ws->rx_draining_ext_list;
+ break;
+ }
+ w = &((*w)->ws->rx_draining_ext_list);
+ }
+ wsi->ws->rx_draining_ext_list = NULL;
+ }
+
+ if (wsi->ws->tx_draining_ext) {
+ struct lws **w = &pt->ws.tx_draining_ext_list;
+ lwsl_ext("%s: CLEARING tx_draining_ext\n", __func__);
+ wsi->ws->tx_draining_ext = 0;
+ /* remove us from context draining ext list */
+ while (*w) {
+ if (*w == wsi) {
+ *w = wsi->ws->tx_draining_ext_list;
+ break;
+ }
+ w = &((*w)->ws->tx_draining_ext_list);
+ }
+ wsi->ws->tx_draining_ext_list = NULL;
+ }
+#endif
+ lws_free_set_NULL(wsi->ws->rx_ubuf);
+
+ wsi->ws->ping_payload_len = 0;
+ wsi->ws->ping_pending_flag = 0;
+
+ /* deallocate any active extension contexts */
+
+ if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
+ lwsl_warn("extension destruction failed\n");
+
+ return 0;
+}
+
+static int
+rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
+ enum lws_write_protocol *wp)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+ enum lws_write_protocol wpt;
+#endif
+ int masked7 = lwsi_role_client(wsi);
+ unsigned char is_masked_bit = 0;
+ unsigned char *dropmask = NULL;
+ struct lws_tokens ebuf;
+ size_t orig_len = len;
+ int pre = 0, n = 0;
+
+ // lwsl_err("%s: wp 0x%x len %d\n", __func__, *wp, (int)len);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->tx_draining_ext) {
+ /* remove us from the list */
+ struct lws **w = &pt->ws.tx_draining_ext_list;
+
+ lwsl_ext("%s: CLEARING tx_draining_ext\n", __func__);
+ wsi->ws->tx_draining_ext = 0;
+ /* remove us from context draining ext list */
+ while (*w) {
+ if (*w == wsi) {
+ *w = wsi->ws->tx_draining_ext_list;
+ break;
+ }
+ w = &((*w)->ws->tx_draining_ext_list);
+ }
+ wsi->ws->tx_draining_ext_list = NULL;
+
+ wpt = *wp;
+ *wp = (wsi->ws->tx_draining_stashed_wp & 0xc0)|
+ LWS_WRITE_CONTINUATION;
+
+ /*
+ * When we are just flushing (len == 0), we can trust the
+ * stashed wp info completely. Otherwise adjust it to the
+ * FIN status of the incoming packet.
+ */
+
+ if (!(wpt & LWS_WRITE_NO_FIN) && len)
+ *wp &= ~LWS_WRITE_NO_FIN;
+
+ lwsl_ext("FORCED draining wp to 0x%02X "
+ "(stashed 0x%02X, incoming 0x%02X)\n", *wp,
+ wsi->ws->tx_draining_stashed_wp, wpt);
+ // assert(0);
+ }
+#endif
+ lws_restart_ws_ping_pong_timer(wsi);
+
+ if (((*wp) & 0x1f) == LWS_WRITE_HTTP ||
+ ((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL ||
+ ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION ||
+ ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS)
+ goto send_raw;
+
+
+
+ /* if we are continuing a frame that already had its header done */
+
+ if (wsi->ws->inside_frame) {
+ lwsl_debug("INSIDE FRAME\n");
+ goto do_more_inside_frame;
+ }
+
+ wsi->ws->clean_buffer = 1;
+
+ /*
+ * give a chance to the extensions to modify payload
+ * the extension may decide to produce unlimited payload erratically
+ * (eg, compression extension), so we require only that if he produces
+ * something, it will be a complete fragment of the length known at
+ * the time (just the fragment length known), and if he has
+ * more we will come back next time he is writeable and allow him to
+ * produce more fragments until he's drained.
+ *
+ * This allows what is sent each time it is writeable to be limited to
+ * a size that can be sent without partial sends or blocking, allows
+ * interleaving of control frames and other connection service.
+ */
+ ebuf.token = (char *)buf;
+ ebuf.len = (int)len;
+
+ switch ((int)*wp) {
+ case LWS_WRITE_PING:
+ case LWS_WRITE_PONG:
+ case LWS_WRITE_CLOSE:
+ break;
+ default:
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ // lwsl_notice("LWS_EXT_CB_PAYLOAD_TX\n");
+ // m = (int)ebuf.len;
+ /* returns 0 if no more tx pending, 1 if more pending */
+ n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &ebuf, *wp);
+ if (n < 0)
+ return -1;
+ // lwsl_notice("ext processed %d plaintext into %d compressed"
+ // " (wp 0x%x)\n", m, (int)ebuf.len, *wp);
+
+ if (n && ebuf.len) {
+ lwsl_ext("write drain len %d (wp 0x%x) SETTING "
+ "tx_draining_ext\n", (int)ebuf.len, *wp);
+ /* extension requires further draining */
+ wsi->ws->tx_draining_ext = 1;
+ wsi->ws->tx_draining_ext_list =
+ pt->ws.tx_draining_ext_list;
+ pt->ws.tx_draining_ext_list = wsi;
+ /* we must come back to do more */
+ lws_callback_on_writable(wsi);
+ /*
+ * keep a copy of the write type for the overall
+ * action that has provoked generation of these
+ * fragments, so the last guy can use its FIN state.
+ */
+ wsi->ws->tx_draining_stashed_wp = *wp;
+ /* this is definitely not actually the last fragment
+ * because the extension asserted he has more coming
+ * So make sure this intermediate one doesn't go out
+ * with a FIN.
+ */
+ *wp |= LWS_WRITE_NO_FIN;
+ }
+#endif
+ if (ebuf.len && wsi->ws->stashed_write_pending) {
+ wsi->ws->stashed_write_pending = 0;
+ *wp = ((*wp) & 0xc0) | (int)wsi->ws->stashed_write_type;
+ }
+ }
+
+ /*
+ * an extension did something we need to keep... for example, if
+ * compression extension, it has already updated its state according
+ * to this being issued
+ */
+ if ((char *)buf != ebuf.token) {
+ /*
+ * ext might eat it, but not have anything to issue yet.
+ * In that case we have to follow his lead, but stash and
+ * replace the write type that was lost here the first time.
+ */
+ if (len && !ebuf.len) {
+ if (!wsi->ws->stashed_write_pending)
+ wsi->ws->stashed_write_type =
+ (char)(*wp) & 0x3f;
+ wsi->ws->stashed_write_pending = 1;
+ return (int)len;
+ }
+ /*
+ * extension recreated it:
+ * need to buffer this if not all sent
+ */
+ wsi->ws->clean_buffer = 0;
+ }
+
+ buf = (unsigned char *)ebuf.token;
+ len = ebuf.len;
+
+ if (!buf) {
+ lwsl_err("null buf (%d)\n", (int)len);
+ return -1;
+ }
+
+ switch (wsi->ws->ietf_spec_revision) {
+ case 13:
+ if (masked7) {
+ pre += 4;
+ dropmask = &buf[0 - pre];
+ is_masked_bit = 0x80;
+ }
+
+ switch ((*wp) & 0xf) {
+ case LWS_WRITE_TEXT:
+ n = LWSWSOPC_TEXT_FRAME;
+ break;
+ case LWS_WRITE_BINARY:
+ n = LWSWSOPC_BINARY_FRAME;
+ break;
+ case LWS_WRITE_CONTINUATION:
+ n = LWSWSOPC_CONTINUATION;
+ break;
+
+ case LWS_WRITE_CLOSE:
+ n = LWSWSOPC_CLOSE;
+ break;
+ case LWS_WRITE_PING:
+ n = LWSWSOPC_PING;
+ break;
+ case LWS_WRITE_PONG:
+ n = LWSWSOPC_PONG;
+ break;
+ default:
+ lwsl_warn("lws_write: unknown write opc / wp\n");
+ return -1;
+ }
+
+ if (!((*wp) & LWS_WRITE_NO_FIN))
+ n |= 1 << 7;
+
+ if (len < 126) {
+ pre += 2;
+ buf[-pre] = n;
+ buf[-pre + 1] = (unsigned char)(len | is_masked_bit);
+ } else {
+ if (len < 65536) {
+ pre += 4;
+ buf[-pre] = n;
+ buf[-pre + 1] = 126 | is_masked_bit;
+ buf[-pre + 2] = (unsigned char)(len >> 8);
+ buf[-pre + 3] = (unsigned char)len;
+ } else {
+ pre += 10;
+ buf[-pre] = n;
+ buf[-pre + 1] = 127 | is_masked_bit;
+#if defined __LP64__
+ buf[-pre + 2] = (len >> 56) & 0x7f;
+ buf[-pre + 3] = len >> 48;
+ buf[-pre + 4] = len >> 40;
+ buf[-pre + 5] = len >> 32;
+#else
+ buf[-pre + 2] = 0;
+ buf[-pre + 3] = 0;
+ buf[-pre + 4] = 0;
+ buf[-pre + 5] = 0;
+#endif
+ buf[-pre + 6] = (unsigned char)(len >> 24);
+ buf[-pre + 7] = (unsigned char)(len >> 16);
+ buf[-pre + 8] = (unsigned char)(len >> 8);
+ buf[-pre + 9] = (unsigned char)len;
+ }
+ }
+ break;
+ }
+
+do_more_inside_frame:
+
+ /*
+ * Deal with masking if we are in client -> server direction and
+ * the wp demands it
+ */
+
+ if (masked7) {
+ if (!wsi->ws->inside_frame)
+ if (lws_0405_frame_mask_generate(wsi)) {
+ lwsl_err("frame mask generation failed\n");
+ return -1;
+ }
+
+ /*
+ * in v7, just mask the payload
+ */
+ if (dropmask) { /* never set if already inside frame */
+ for (n = 4; n < (int)len + 4; n++)
+ dropmask[n] = dropmask[n] ^ wsi->ws->mask[
+ (wsi->ws->mask_idx++) & 3];
+
+ /* copy the frame nonce into place */
+ memcpy(dropmask, wsi->ws->mask, 4);
+ }
+ }
+
+ if (lwsi_role_h2_ENCAPSULATION(wsi)) {
+ struct lws *encap = lws_get_network_wsi(wsi);
+
+ assert(encap != wsi);
+ return encap->role_ops->write_role_protocol(wsi, buf - pre,
+ len + pre, wp);
+ }
+
+ switch ((*wp) & 0x1f) {
+ case LWS_WRITE_TEXT:
+ case LWS_WRITE_BINARY:
+ case LWS_WRITE_CONTINUATION:
+ if (!wsi->h2_stream_carries_ws) {
+
+ /*
+ * give any active extensions a chance to munge the
+ * buffer before send. We pass in a pointer to an
+ * lws_tokens struct prepared with the default buffer
+ * and content length that's in there. Rather than
+ * rewrite the default buffer, extensions that expect
+ * to grow the buffer can adapt .token to point to their
+ * own per-connection buffer in the extension user
+ * allocation. By default with no extensions or no
+ * extension callback handling, just the normal input
+ * buffer is used then so it is efficient.
+ *
+ * callback returns 1 in case it wants to spill more
+ * buffers
+ *
+ * This takes care of holding the buffer if send is
+ * incomplete, ie, if wsi->ws->clean_buffer is 0
+ * (meaning an extension meddled with the buffer). If
+ * wsi->ws->clean_buffer is 1, it will instead return
+ * to the user code how much OF THE USER BUFFER was
+ * consumed.
+ */
+
+ n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre);
+ wsi->ws->inside_frame = 1;
+ if (n <= 0)
+ return n;
+
+ if (n == (int)len + pre) {
+ /* everything in the buffer was handled
+ * (or rebuffered...) */
+ wsi->ws->inside_frame = 0;
+ return (int)orig_len;
+ }
+
+ /*
+ * it is how many bytes of user buffer got sent... may
+ * be < orig_len in which case callback when writable
+ * has already been arranged and user code can call
+ * lws_write() again with the rest later.
+ */
+
+ return n - pre;
+ }
+ break;
+ default:
+ break;
+ }
+
+send_raw:
+ return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre);
+}
+
+static int
+rops_close_kill_connection_ws(struct lws *wsi, enum lws_close_status reason)
+{
+ /* deal with ws encapsulation in h2 */
+#if defined(LWS_WITH_HTTP2)
+ if (wsi->http2_substream && wsi->h2_stream_carries_ws)
+ return role_ops_h2.close_kill_connection(wsi, reason);
+
+ return 0;
+#else
+ return 0;
+#endif
+}
+
+static int
+rops_callback_on_writable_ws(struct lws *wsi)
+{
+#if defined(LWS_WITH_HTTP2)
+ if (lwsi_role_h2_ENCAPSULATION(wsi)) {
+ /* we know then that it has an h2 parent */
+ struct lws *enc = role_ops_h2.encapsulation_parent(wsi);
+
+ assert(enc);
+ if (enc->role_ops->callback_on_writable(wsi))
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static int
+rops_init_vhost_ws(struct lws_vhost *vh,
+ const struct lws_context_creation_info *info)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+#ifdef LWS_WITH_PLUGINS
+ struct lws_plugin *plugin = vh->context->plugin_list;
+ int m;
+
+ if (vh->context->plugin_extension_count) {
+
+ m = 0;
+ while (info->extensions && info->extensions[m].callback)
+ m++;
+
+ /*
+ * give the vhost a unified list of extensions including the
+ * ones that came from plugins
+ */
+ vh->ws.extensions = lws_zalloc(sizeof(struct lws_extension) *
+ (m + vh->context->plugin_extension_count + 1),
+ "extensions");
+ if (!vh->ws.extensions)
+ return 1;
+
+ memcpy((struct lws_extension *)vh->ws.extensions, info->extensions,
+ sizeof(struct lws_extension) * m);
+ plugin = vh->context->plugin_list;
+ while (plugin) {
+ memcpy((struct lws_extension *)&vh->ws.extensions[m],
+ plugin->caps.extensions,
+ sizeof(struct lws_extension) *
+ plugin->caps.count_extensions);
+ m += plugin->caps.count_extensions;
+ plugin = plugin->list;
+ }
+ } else
+#endif
+ vh->ws.extensions = info->extensions;
+#endif
+
+ return 0;
+}
+
+static int
+rops_destroy_vhost_ws(struct lws_vhost *vh)
+{
+#ifdef LWS_WITH_PLUGINS
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (vh->context->plugin_extension_count)
+ lws_free((void *)vh->ws.extensions);
+#endif
+#endif
+
+ return 0;
+}
+
+static int
+rops_destroy_role_ws(struct lws *wsi)
+{
+ lws_free_set_NULL(wsi->ws);
+
+ return 0;
+}
+
+struct lws_role_ops role_ops_ws = {
+ /* role name */ "ws",
+ /* alpn id */ NULL,
+ /* check_upgrades */ NULL,
+ /* init_context */ NULL,
+ /* init_vhost */ rops_init_vhost_ws,
+ /* destroy_vhost */ rops_destroy_vhost_ws,
+ /* periodic_checks */ rops_periodic_checks_ws,
+ /* service_flag_pending */ rops_service_flag_pending_ws,
+ /* handle_POLLIN */ rops_handle_POLLIN_ws,
+ /* handle_POLLOUT */ rops_handle_POLLOUT_ws,
+ /* perform_user_POLLOUT */ NULL,
+ /* callback_on_writable */ rops_callback_on_writable_ws,
+ /* tx_credit */ NULL,
+ /* write_role_protocol */ rops_write_role_protocol_ws,
+ /* encapsulation_parent */ NULL,
+ /* alpn_negotiated */ NULL,
+ /* close_via_role_protocol */ rops_close_via_role_protocol_ws,
+ /* close_role */ rops_close_role_ws,
+ /* close_kill_connection */ rops_close_kill_connection_ws,
+ /* destroy_role */ rops_destroy_role_ws,
+ /* adoption_bind */ NULL,
+ /* client_bind */ NULL,
+ /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_WRITEABLE,
+ LWS_CALLBACK_SERVER_WRITEABLE },
+ /* close cb clnt, srv */ { LWS_CALLBACK_CLIENT_CLOSED,
+ LWS_CALLBACK_CLOSED },
+ /* protocol_bind cb c, srv */ { LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL,
+ LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL },
+ /* protocol_unbind cb c, srv */ { LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL,
+ LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL },
+ /* file handles */ 0
+};
diff --git a/thirdparty/libwebsockets/lib/roles/ws/private.h b/thirdparty/libwebsockets/lib/roles/ws/private.h
new file mode 100644
index 0000000000..71ffcaea96
--- /dev/null
+++ b/thirdparty/libwebsockets/lib/roles/ws/private.h
@@ -0,0 +1,164 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ *
+ * This is included from core/private.h if LWS_ROLE_WS
+ */
+
+extern struct lws_role_ops role_ops_ws;
+
+#define lwsi_role_ws(wsi) (wsi->role_ops == &role_ops_ws)
+
+enum lws_rx_parse_state {
+ LWS_RXPS_NEW,
+
+ LWS_RXPS_04_mask_1,
+ LWS_RXPS_04_mask_2,
+ LWS_RXPS_04_mask_3,
+
+ LWS_RXPS_04_FRAME_HDR_1,
+ LWS_RXPS_04_FRAME_HDR_LEN,
+ LWS_RXPS_04_FRAME_HDR_LEN16_2,
+ LWS_RXPS_04_FRAME_HDR_LEN16_1,
+ LWS_RXPS_04_FRAME_HDR_LEN64_8,
+ LWS_RXPS_04_FRAME_HDR_LEN64_7,
+ LWS_RXPS_04_FRAME_HDR_LEN64_6,
+ LWS_RXPS_04_FRAME_HDR_LEN64_5,
+ LWS_RXPS_04_FRAME_HDR_LEN64_4,
+ LWS_RXPS_04_FRAME_HDR_LEN64_3,
+ LWS_RXPS_04_FRAME_HDR_LEN64_2,
+ LWS_RXPS_04_FRAME_HDR_LEN64_1,
+
+ LWS_RXPS_07_COLLECT_FRAME_KEY_1,
+ LWS_RXPS_07_COLLECT_FRAME_KEY_2,
+ LWS_RXPS_07_COLLECT_FRAME_KEY_3,
+ LWS_RXPS_07_COLLECT_FRAME_KEY_4,
+
+ LWS_RXPS_WS_FRAME_PAYLOAD
+};
+
+enum lws_websocket_opcodes_07 {
+ LWSWSOPC_CONTINUATION = 0,
+ LWSWSOPC_TEXT_FRAME = 1,
+ LWSWSOPC_BINARY_FRAME = 2,
+
+ LWSWSOPC_NOSPEC__MUX = 7,
+
+ /* control extensions 8+ */
+
+ LWSWSOPC_CLOSE = 8,
+ LWSWSOPC_PING = 9,
+ LWSWSOPC_PONG = 0xa,
+};
+
+/* this is not usable directly by user code any more, lws_close_reason() */
+#define LWS_WRITE_CLOSE 4
+
+#define ALREADY_PROCESSED_IGNORE_CHAR 1
+#define ALREADY_PROCESSED_NO_CB 2
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+struct lws_vhost_role_ws {
+ const struct lws_extension *extensions;
+};
+
+struct lws_pt_role_ws {
+ struct lws *rx_draining_ext_list;
+ struct lws *tx_draining_ext_list;
+};
+#endif
+
+struct _lws_websocket_related {
+ char *rx_ubuf;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE];
+ void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE];
+ struct lws *rx_draining_ext_list;
+ struct lws *tx_draining_ext_list;
+#endif
+ /* Also used for close content... control opcode == < 128 */
+ uint8_t ping_payload_buf[128 - 3 + LWS_PRE];
+ uint8_t mask[4];
+
+ time_t time_next_ping_check;
+ size_t rx_packet_length;
+ uint32_t rx_ubuf_head;
+ uint32_t rx_ubuf_alloc;
+
+ uint8_t ping_payload_len;
+ uint8_t mask_idx;
+ uint8_t opcode;
+ uint8_t rsv;
+ uint8_t rsv_first_msg;
+ /* zero if no info, or length including 2-byte close code */
+ uint8_t close_in_ping_buffer_len;
+ uint8_t utf8;
+ uint8_t stashed_write_type;
+ uint8_t tx_draining_stashed_wp;
+ uint8_t ietf_spec_revision;
+
+ unsigned int final:1;
+ unsigned int frame_is_binary:1;
+ unsigned int all_zero_nonce:1;
+ unsigned int this_frame_masked:1;
+ unsigned int inside_frame:1; /* next write will be more of frame */
+ unsigned int clean_buffer:1; /* buffer not rewritten by extension */
+ unsigned int payload_is_close:1; /* process as PONG, but it is close */
+ unsigned int ping_pending_flag:1;
+ unsigned int continuation_possible:1;
+ unsigned int owed_a_fin:1;
+ unsigned int check_utf8:1;
+ unsigned int defeat_check_utf8:1;
+ unsigned int stashed_write_pending:1;
+ unsigned int send_check_ping:1;
+ unsigned int first_fragment:1;
+ unsigned int peer_has_sent_close:1;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ unsigned int extension_data_pending:1;
+ unsigned int rx_draining_ext:1;
+ unsigned int tx_draining_ext:1;
+
+ uint8_t count_act_ext;
+#endif
+};
+
+int
+lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+LWS_VISIBLE void
+lws_context_init_extensions(const struct lws_context_creation_info *info,
+ struct lws_context *context);
+LWS_EXTERN int
+lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r,
+ void *v, size_t len);
+
+LWS_EXTERN int
+lws_ext_cb_active(struct lws *wsi, int reason, void *buf, int len);
+LWS_EXTERN int
+lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, int reason,
+ void *arg, int len);
+#endif
+
+int
+handshake_0405(struct lws_context *context, struct lws *wsi);
+int
+lws_process_ws_upgrade(struct lws *wsi);
+int
+lws_server_init_wsi_for_ws(struct lws *wsi);
diff --git a/thirdparty/libwebsockets/lib/roles/ws/server-ws.c b/thirdparty/libwebsockets/lib/roles/ws/server-ws.c
new file mode 100644
index 0000000000..bdc45367d4
--- /dev/null
+++ b/thirdparty/libwebsockets/lib/roles/ws/server-ws.c
@@ -0,0 +1,874 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <core/private.h>
+
+#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+static int
+lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
+{
+ struct lws_context *context = wsi->context;
+ struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+ char ext_name[64], *args, *end = (*p) + budget - 1;
+ const struct lws_ext_options *opts, *po;
+ const struct lws_extension *ext;
+ struct lws_ext_option_arg oa;
+ int n, m, more = 1;
+ int ext_count = 0;
+ char ignore;
+ char *c;
+
+ /*
+ * Figure out which extensions the client has that we want to
+ * enable on this connection, and give him back the list
+ */
+ if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS))
+ return 0;
+
+ /*
+ * break down the list of client extensions
+ * and go through them
+ */
+
+ if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size,
+ WSI_TOKEN_EXTENSIONS) < 0)
+ return 1;
+
+ c = (char *)pt->serv_buf;
+ lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c);
+ wsi->ws->count_act_ext = 0;
+ ignore = 0;
+ n = 0;
+ args = NULL;
+
+ /*
+ * We may get a simple request
+ *
+ * Sec-WebSocket-Extensions: permessage-deflate
+ *
+ * or an elaborated one with requested options
+ *
+ * Sec-WebSocket-Extensions: permessage-deflate; \
+ * server_no_context_takeover; \
+ * client_no_context_takeover
+ */
+
+ while (more) {
+
+ if (c >= (char *)pt->serv_buf + 255)
+ return -1;
+
+ if (*c && (*c != ',' && *c != '\t')) {
+ if (*c == ';') {
+ ignore = 1;
+ if (!args)
+ args = c + 1;
+ }
+ if (ignore || *c == ' ') {
+ c++;
+ continue;
+ }
+ ext_name[n] = *c++;
+ if (n < (int)sizeof(ext_name) - 1)
+ n++;
+ continue;
+ }
+ ext_name[n] = '\0';
+
+ ignore = 0;
+ if (!*c)
+ more = 0;
+ else {
+ c++;
+ if (!n)
+ continue;
+ }
+
+ while (args && *args == ' ')
+ args++;
+
+ /* check a client's extension against our support */
+
+ ext = wsi->vhost->ws.extensions;
+
+ while (ext && ext->callback) {
+
+ if (strcmp(ext_name, ext->name)) {
+ ext++;
+ continue;
+ }
+
+ /*
+ * oh, we do support this one he asked for... but let's
+ * confirm he only gave it once
+ */
+ for (m = 0; m < wsi->ws->count_act_ext; m++)
+ if (wsi->ws->active_extensions[m] == ext) {
+ lwsl_info("ext mentioned twice\n");
+ return 1; /* shenanigans */
+ }
+
+ /*
+ * ask user code if it's OK to apply it on this
+ * particular connection + protocol
+ */
+ m = (wsi->protocol->callback)(wsi,
+ LWS_CALLBACK_CONFIRM_EXTENSION_OKAY,
+ wsi->user_space, ext_name, 0);
+
+ /*
+ * zero return from callback means go ahead and allow
+ * the extension, it's what we get if the callback is
+ * unhandled
+ */
+ if (m) {
+ ext++;
+ continue;
+ }
+
+ /* apply it */
+
+ ext_count++;
+
+ /* instantiate the extension on this conn */
+
+ wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext;
+
+ /* allow him to construct his context */
+
+ if (ext->callback(lws_get_context(wsi), ext, wsi,
+ LWS_EXT_CB_CONSTRUCT,
+ (void *)&wsi->ws->act_ext_user[
+ wsi->ws->count_act_ext],
+ (void *)&opts, 0)) {
+ lwsl_info("ext %s failed construction\n",
+ ext_name);
+ ext_count--;
+ ext++;
+
+ continue;
+ }
+
+ if (ext_count > 1)
+ *(*p)++ = ',';
+ else
+ LWS_CPYAPP(*p,
+ "\x0d\x0aSec-WebSocket-Extensions: ");
+ *p += lws_snprintf(*p, (end - *p), "%s", ext_name);
+
+ /*
+ * The client may send a bunch of different option
+ * sets for the same extension, we are supposed to
+ * pick one we like the look of. The option sets are
+ * separated by comma.
+ *
+ * Actually we just either accept the first one or
+ * nothing.
+ *
+ * Go through the options trying to apply the
+ * recognized ones
+ */
+
+ lwsl_info("ext args %s\n", args);
+
+ while (args && *args && *args != ',') {
+ while (*args == ' ')
+ args++;
+ po = opts;
+ while (po->name) {
+ /* only support arg-less options... */
+ if (po->type != EXTARG_NONE ||
+ strncmp(args, po->name,
+ strlen(po->name))) {
+ po++;
+ continue;
+ }
+ oa.option_name = NULL;
+ oa.option_index = (int)(po - opts);
+ oa.start = NULL;
+ oa.len = 0;
+ lwsl_info("setting '%s'\n", po->name);
+ if (!ext->callback(lws_get_context(wsi),
+ ext, wsi,
+ LWS_EXT_CB_OPTION_SET,
+ wsi->ws->act_ext_user[
+ wsi->ws->count_act_ext],
+ &oa, (end - *p))) {
+
+ *p += lws_snprintf(*p,
+ (end - *p),
+ "; %s", po->name);
+ lwsl_debug("adding option %s\n",
+ po->name);
+ }
+ po++;
+ }
+ while (*args && *args != ',' && *args != ';')
+ args++;
+
+ if (*args == ';')
+ args++;
+ }
+
+ wsi->ws->count_act_ext++;
+ lwsl_parser("cnt_act_ext <- %d\n",
+ wsi->ws->count_act_ext);
+
+ if (args && *args == ',')
+ more = 0;
+
+ ext++;
+ }
+
+ n = 0;
+ args = NULL;
+ }
+
+ return 0;
+}
+#endif
+
+int
+lws_process_ws_upgrade(struct lws *wsi)
+{
+ struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+ const struct lws_protocols *pcol = NULL;
+ char buf[128], name[64];
+ struct lws_tokenize ts;
+ lws_tokenize_elem e;
+
+ if (!wsi->protocol)
+ lwsl_err("NULL protocol at lws_read\n");
+
+ /*
+ * We are upgrading to ws, so http/1.1 + h2 and keepalive + pipelined
+ * header considerations about keeping the ah around no longer apply.
+ *
+ * However it's common for the first ws protocol data to have been
+ * coalesced with the browser upgrade request and to already be in the
+ * ah rx buffer.
+ */
+
+ lws_pt_lock(pt, __func__);
+
+ if (!wsi->h2_stream_carries_ws)
+ lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED,
+ &role_ops_ws);
+
+ lws_pt_unlock(pt);
+
+ /*
+ * It's either websocket or h2->websocket
+ *
+ * If we are on h1, confirm we got the required "connection: upgrade"
+ * header. h2 / ws-over-h2 does not have this.
+ */
+
+#if defined(LWS_WITH_HTTP2)
+ if (wsi->http2_substream)
+ goto check_protocol;
+#endif
+
+ lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+ LWS_TOKENIZE_F_MINUS_NONTERM);
+ ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION);
+ if (ts.len <= 0)
+ goto bad_conn_format;
+
+ do {
+ e = lws_tokenize(&ts);
+ switch (e) {
+ case LWS_TOKZE_TOKEN:
+ if (!strcasecmp(ts.token, "upgrade"))
+ e = LWS_TOKZE_ENDED;
+ break;
+
+ case LWS_TOKZE_DELIMITER:
+ break;
+
+ default: /* includes ENDED */
+bad_conn_format:
+ lwsl_err("%s: malformed or absent connection hdr\n",
+ __func__);
+
+ return 1;
+ }
+ } while (e > 0);
+
+#if defined(LWS_WITH_HTTP2)
+check_protocol:
+#endif
+
+ /*
+ * Select the first protocol we support from the list
+ * the client sent us.
+ */
+
+ lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+ LWS_TOKENIZE_F_MINUS_NONTERM |
+ LWS_TOKENIZE_F_RFC7230_DELIMS);
+ ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_PROTOCOL);
+ if (ts.len < 0) {
+ lwsl_err("%s: protocol list too long\n", __func__);
+ return 1;
+ }
+ if (!ts.len) {
+ int n = wsi->vhost->default_protocol_index;
+ /*
+ * some clients only have one protocol and do not send the
+ * protocol list header... allow it and match to the vhost's
+ * default protocol (which itself defaults to zero)
+ */
+ lwsl_info("%s: defaulting to prot handler %d\n", __func__, n);
+
+ lws_bind_protocol(wsi, &wsi->vhost->protocols[n],
+ "ws upgrade default pcol");
+
+ goto alloc_ws;
+ }
+
+ /* otherwise go through the user-provided protocol list */
+
+ do {
+ e = lws_tokenize(&ts);
+ switch (e) {
+ case LWS_TOKZE_TOKEN:
+
+ if (lws_tokenize_cstr(&ts, name, sizeof(name))) {
+ lwsl_err("%s: pcol name too long\n", __func__);
+
+ return 1;
+ }
+ lwsl_debug("checking %s\n", name);
+ pcol = lws_vhost_name_to_protocol(wsi->vhost, name);
+ if (pcol) {
+ /* if we know it, bind to it and stop looking */
+ lws_bind_protocol(wsi, pcol, "ws upg pcol");
+ e = LWS_TOKZE_ENDED;
+ }
+ break;
+
+ case LWS_TOKZE_DELIMITER:
+ case LWS_TOKZE_ENDED:
+ break;
+
+ default:
+ lwsl_err("%s: malformatted protocol list", __func__);
+
+ return 1;
+ }
+ } while (e > 0);
+
+ /* we didn't find a protocol he wanted? */
+
+ if (!pcol) {
+ lwsl_notice("No supported protocol \"%s\"\n", buf);
+
+ return 1;
+ }
+
+alloc_ws:
+
+ /* allocate the ws struct for the wsi */
+
+ wsi->ws = lws_zalloc(sizeof(*wsi->ws), "ws struct");
+ if (!wsi->ws) {
+ lwsl_notice("OOM\n");
+ return 1;
+ }
+
+ if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION))
+ wsi->ws->ietf_spec_revision =
+ atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION));
+
+ /* allocate wsi->user storage */
+ if (lws_ensure_user_space(wsi)) {
+ lwsl_notice("problem with user space\n");
+ return 1;
+ }
+
+ /*
+ * Give the user code a chance to study the request and
+ * have the opportunity to deny it
+ */
+ if ((wsi->protocol->callback)(wsi,
+ LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
+ wsi->user_space,
+ lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) {
+ lwsl_warn("User code denied connection\n");
+ return 1;
+ }
+
+ /*
+ * Perform the handshake according to the protocol version the
+ * client announced
+ */
+
+ switch (wsi->ws->ietf_spec_revision) {
+ default:
+ lwsl_notice("Unknown client spec version %d\n",
+ wsi->ws->ietf_spec_revision);
+ wsi->ws->ietf_spec_revision = 13;
+ //return 1;
+ /* fallthru */
+ case 13:
+#if defined(LWS_WITH_HTTP2)
+ if (wsi->h2_stream_carries_ws) {
+ if (lws_h2_ws_handshake(wsi)) {
+ lwsl_notice("h2 ws handshake failed\n");
+ return 1;
+ }
+ lws_role_transition(wsi,
+ LWSIFR_SERVER | LWSIFR_P_ENCAP_H2,
+ LRS_ESTABLISHED, &role_ops_ws);
+ } else
+#endif
+ {
+ lwsl_parser("lws_parse calling handshake_04\n");
+ if (handshake_0405(wsi->context, wsi)) {
+ lwsl_notice("hs0405 has failed the connection\n");
+ return 1;
+ }
+ }
+ break;
+ }
+
+ lws_server_init_wsi_for_ws(wsi);
+ lwsl_parser("accepted v%02d connection\n", wsi->ws->ietf_spec_revision);
+
+ lwsl_info("%s: %p: dropping ah on ws upgrade\n", __func__, wsi);
+ lws_header_table_detach(wsi, 1);
+
+ return 0;
+}
+
+int
+handshake_0405(struct lws_context *context, struct lws *wsi)
+{
+ struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+ struct lws_process_html_args args;
+ unsigned char hash[20];
+ int n, accept_len;
+ char *response;
+ char *p;
+
+ if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) ||
+ !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) {
+ lwsl_info("handshake_04 missing pieces\n");
+ /* completed header processing, but missing some bits */
+ goto bail;
+ }
+
+ if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >=
+ MAX_WEBSOCKET_04_KEY_LEN) {
+ lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN);
+ goto bail;
+ }
+
+ /*
+ * since key length is restricted above (currently 128), cannot
+ * overflow
+ */
+ n = sprintf((char *)pt->serv_buf,
+ "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
+ lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY));
+
+ lws_SHA1(pt->serv_buf, n, hash);
+
+ accept_len = lws_b64_encode_string((char *)hash, 20,
+ (char *)pt->serv_buf, context->pt_serv_buf_size);
+ if (accept_len < 0) {
+ lwsl_warn("Base64 encoded hash too long\n");
+ goto bail;
+ }
+
+ /* allocate the per-connection user memory (if any) */
+ if (lws_ensure_user_space(wsi))
+ goto bail;
+
+ /* create the response packet */
+
+ /* make a buffer big enough for everything */
+
+ response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN +
+ 256 + LWS_PRE;
+ p = response;
+ LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
+ "Upgrade: WebSocket\x0d\x0a"
+ "Connection: Upgrade\x0d\x0a"
+ "Sec-WebSocket-Accept: ");
+ strcpy(p, (char *)pt->serv_buf);
+ p += accept_len;
+
+ /* we can only return the protocol header if:
+ * - one came in, and ... */
+ if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
+ /* - it is not an empty string */
+ wsi->protocol->name &&
+ wsi->protocol->name[0]) {
+ LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: ");
+ p += lws_snprintf(p, 128, "%s", wsi->protocol->name);
+ }
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ /*
+ * Figure out which extensions the client has that we want to
+ * enable on this connection, and give him back the list.
+ *
+ * Give him a limited write bugdet
+ */
+ if (lws_extension_server_handshake(wsi, &p, 192))
+ goto bail;
+#endif
+ LWS_CPYAPP(p, "\x0d\x0a");
+
+ args.p = p;
+ args.max_len = lws_ptr_diff((char *)pt->serv_buf +
+ context->pt_serv_buf_size, p);
+ if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+ LWS_CALLBACK_ADD_HEADERS,
+ wsi->user_space, &args, 0))
+ goto bail;
+
+ p = args.p;
+
+ /* end of response packet */
+
+ LWS_CPYAPP(p, "\x0d\x0a");
+
+ /* okay send the handshake response accepting the connection */
+
+ lwsl_parser("issuing resp pkt %d len\n",
+ lws_ptr_diff(p, response));
+#if defined(DEBUG)
+ fwrite(response, 1, p - response, stderr);
+#endif
+ n = lws_write(wsi, (unsigned char *)response, p - response,
+ LWS_WRITE_HTTP_HEADERS);
+ if (n != (p - response)) {
+ lwsl_info("%s: ERROR writing to socket %d\n", __func__, n);
+ goto bail;
+ }
+
+ /* alright clean up and set ourselves into established state */
+
+ lwsi_set_state(wsi, LRS_ESTABLISHED);
+ wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+
+ {
+ const char * uri_ptr =
+ lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI);
+ int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
+ const struct lws_http_mount *hit =
+ lws_find_mount(wsi, uri_ptr, uri_len);
+ if (hit && hit->cgienv &&
+ wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO,
+ wsi->user_space, (void *)hit->cgienv, 0))
+ return 1;
+ }
+
+ return 0;
+
+bail:
+ /* caller will free up his parsing allocations */
+ return -1;
+}
+
+
+
+/*
+ * Once we reach LWS_RXPS_WS_FRAME_PAYLOAD, we know how much
+ * to expect in that state and can deal with it in bulk more efficiently.
+ */
+
+static int
+lws_ws_frame_rest_is_payload(struct lws *wsi, uint8_t **buf, size_t len)
+{
+ unsigned int avail = (unsigned int)len;
+ uint8_t *buffer = *buf, mask[4];
+ struct lws_tokens ebuf;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ unsigned int old_packet_length = (int)wsi->ws->rx_packet_length;
+#endif
+ int n = 0;
+
+ /*
+ * With zlib, we can give it as much input as we like. The pmd
+ * extension will draw it down in chunks (default 1024).
+ *
+ * If we try to restrict how much we give it, because we must go
+ * back to the event loop each time, we will drop the remainder...
+ */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (!wsi->ws->count_act_ext)
+#endif
+ {
+ if (wsi->protocol->rx_buffer_size)
+ avail = (int)wsi->protocol->rx_buffer_size;
+ else
+ avail = wsi->context->pt_serv_buf_size;
+ }
+
+ /* do not consume more than we should */
+ if (avail > wsi->ws->rx_packet_length)
+ avail = (unsigned int)wsi->ws->rx_packet_length;
+
+ /* do not consume more than what is in the buffer */
+ if (avail > len)
+ avail = (unsigned int)len;
+
+ if (!avail)
+ return 0;
+
+ ebuf.token = (char *)buffer;
+ ebuf.len = avail;
+
+ //lwsl_hexdump_notice(ebuf.token, ebuf.len);
+
+ if (!wsi->ws->all_zero_nonce) {
+
+ for (n = 0; n < 4; n++)
+ mask[n] = wsi->ws->mask[(wsi->ws->mask_idx + n) & 3];
+
+ /* deal with 4-byte chunks using unwrapped loop */
+ n = avail >> 2;
+ while (n--) {
+ *(buffer) = *(buffer) ^ mask[0];
+ buffer++;
+ *(buffer) = *(buffer) ^ mask[1];
+ buffer++;
+ *(buffer) = *(buffer) ^ mask[2];
+ buffer++;
+ *(buffer) = *(buffer) ^ mask[3];
+ buffer++;
+ }
+ /* and the remaining bytes bytewise */
+ for (n = 0; n < (int)(avail & 3); n++) {
+ *(buffer) = *(buffer) ^ mask[n];
+ buffer++;
+ }
+
+ wsi->ws->mask_idx = (wsi->ws->mask_idx + avail) & 3;
+ }
+
+ lwsl_info("%s: using %d of raw input (total %d on offer)\n", __func__,
+ avail, (int)len);
+
+ (*buf) += avail;
+ len -= avail;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0);
+ lwsl_info("%s: ext says %d / ebuf.len %d\n", __func__, n, ebuf.len);
+#endif
+ /*
+ * ebuf may be pointing somewhere completely different now,
+ * it's the output
+ */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (n < 0) {
+ /*
+ * we may rely on this to get RX, just drop connection
+ */
+ lwsl_notice("%s: LWS_EXT_CB_PAYLOAD_RX blew out\n", __func__);
+ wsi->socket_is_permanently_unusable = 1;
+ return -1;
+ }
+#endif
+
+ wsi->ws->rx_packet_length -= avail;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ /*
+ * if we had an rx fragment right at the last compressed byte of the
+ * message, we can get a zero length inflated output, where no prior
+ * rx inflated output marked themselves with FIN, since there was
+ * raw ws payload still to drain at that time.
+ *
+ * Then we need to generate a zero length ws rx that can be understood
+ * as the message completion.
+ */
+
+ if (!ebuf.len && /* zero-length inflation output */
+ !n && /* nothing left to drain from the inflator */
+ wsi->ws->count_act_ext && /* we are using pmd */
+ old_packet_length && /* we gave the inflator new input */
+ !wsi->ws->rx_packet_length && /* raw ws packet payload all gone */
+ wsi->ws->final && /* the raw ws packet is a FIN guy */
+ wsi->protocol->callback &&
+ !wsi->wsistate_pre_close) {
+
+ if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+ LWS_CALLBACK_RECEIVE,
+ wsi->user_space, NULL, 0))
+ return -1;
+
+ return avail;
+ }
+#endif
+
+ if (!ebuf.len)
+ return avail;
+
+ if (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ n &&
+#endif
+ ebuf.len)
+ /* extension had more... main loop will come back */
+ lws_add_wsi_to_draining_ext_list(wsi);
+ else
+ lws_remove_wsi_from_draining_ext_list(wsi);
+
+ if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+ if (lws_check_utf8(&wsi->ws->utf8,
+ (unsigned char *)ebuf.token, ebuf.len)) {
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+ (uint8_t *)"bad utf8", 8);
+ goto utf8_fail;
+ }
+
+ /* we are ending partway through utf-8 character? */
+ if (!wsi->ws->rx_packet_length && wsi->ws->final &&
+ wsi->ws->utf8 && !n) {
+ lwsl_info("FINAL utf8 error\n");
+ lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+ (uint8_t *)"partial utf8", 12);
+
+utf8_fail:
+ lwsl_info("utf8 error\n");
+ lwsl_hexdump_info(ebuf.token, ebuf.len);
+
+ return -1;
+ }
+ }
+
+ if (wsi->protocol->callback && !wsi->wsistate_pre_close)
+ if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+ LWS_CALLBACK_RECEIVE,
+ wsi->user_space,
+ ebuf.token, ebuf.len))
+ return -1;
+
+ wsi->ws->first_fragment = 0;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ lwsl_info("%s: input used %d, output %d, rem len %d, rx_draining_ext %d\n",
+ __func__, avail, ebuf.len, (int)len, wsi->ws->rx_draining_ext);
+#endif
+
+ return avail; /* how much we used from the input */
+}
+
+
+int
+lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len)
+{
+ int m, bulk = 0;
+
+ lwsl_debug("%s: received %d byte packet\n", __func__, (int)len);
+
+ //lwsl_hexdump_notice(*buf, len);
+
+ /* let the rx protocol state machine have as much as it needs */
+
+ while (len) {
+ /*
+ * we were accepting input but now we stopped doing so
+ */
+ if (wsi->rxflow_bitmap) {
+ lwsl_info("%s: doing rxflow\n", __func__);
+ lws_rxflow_cache(wsi, *buf, 0, (int)len);
+ lwsl_parser("%s: cached %ld\n", __func__, (long)len);
+ *buf += len; /* stashing it is taking care of it */
+ return 1;
+ }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ if (wsi->ws->rx_draining_ext) {
+ lwsl_debug("%s: draining rx ext\n", __func__);
+ m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
+ if (m < 0)
+ return -1;
+ continue;
+ }
+#endif
+
+ /* consume payload bytes efficiently */
+ while (wsi->lws_rx_parse_state == LWS_RXPS_WS_FRAME_PAYLOAD &&
+ (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+ wsi->ws->opcode == LWSWSOPC_BINARY_FRAME ||
+ wsi->ws->opcode == LWSWSOPC_CONTINUATION) &&
+ len) {
+ uint8_t *bin = *buf;
+
+ bulk = 1;
+ m = lws_ws_frame_rest_is_payload(wsi, buf, len);
+ assert((int)lws_ptr_diff(*buf, bin) <= (int)len);
+ len -= lws_ptr_diff(*buf, bin);
+
+ if (!m) {
+
+ break;
+ }
+ if (m < 0) {
+ lwsl_info("%s: rest_is_payload bailed\n",
+ __func__);
+ return -1;
+ }
+ }
+
+ if (!bulk) {
+ /* process the byte */
+ m = lws_ws_rx_sm(wsi, 0, *(*buf)++);
+ len--;
+ } else {
+ /*
+ * We already handled this byte in bulk, just deal
+ * with the ramifications
+ */
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+ lwsl_debug("%s: coming out of bulk with len %d, "
+ "wsi->ws->rx_draining_ext %d\n",
+ __func__, (int)len,
+ wsi->ws->rx_draining_ext);
+#endif
+ m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR |
+ ALREADY_PROCESSED_NO_CB, 0);
+ }
+
+ if (m < 0) {
+ lwsl_info("%s: lws_ws_rx_sm bailed %d\n", __func__,
+ bulk);
+
+ return -1;
+ }
+
+ bulk = 0;
+ }
+
+ lwsl_debug("%s: exit with %d unused\n", __func__, (int)len);
+
+ return 0;
+}