diff options
Diffstat (limited to 'thirdparty/libwebsockets/roles/ws')
-rw-r--r-- | thirdparty/libwebsockets/roles/ws/client-parser-ws.c | 605 | ||||
-rw-r--r-- | thirdparty/libwebsockets/roles/ws/client-ws.c | 629 | ||||
-rw-r--r-- | thirdparty/libwebsockets/roles/ws/ops-ws.c | 1994 | ||||
-rw-r--r-- | thirdparty/libwebsockets/roles/ws/private.h | 164 | ||||
-rw-r--r-- | thirdparty/libwebsockets/roles/ws/server-ws.c | 836 |
5 files changed, 4228 insertions, 0 deletions
diff --git a/thirdparty/libwebsockets/roles/ws/client-parser-ws.c b/thirdparty/libwebsockets/roles/ws/client-parser-ws.c new file mode 100644 index 0000000000..7287fb1590 --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/client-parser-ws.c @@ -0,0 +1,605 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 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/roles/ws/client-ws.c b/thirdparty/libwebsockets/roles/ws/client-ws.c new file mode 100644 index 0000000000..fd6cf42551 --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/client-ws.c @@ -0,0 +1,629 @@ +/* + * 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(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) +{ + 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: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + 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) +{ + int n, len, okay = 0; + struct lws_context *context = wsi->context; + const char *pc; + char *p; +#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; + char ignore; + int more = 1; +#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; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); + if (!p) { + lwsl_info("no Connection hdr\n"); + *cce = "HS: CONNECTION missing"; + goto bail3; + } + strtolower(p); + if (strcmp(p, "upgrade")) { + lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); + *cce = "HS: UPGRADE malformed"; + goto bail3; + } + + 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 == ' ') + 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_vhost_lock(wsi->vhost); + + wsi->same_vh_protocol_prev = /* guy who points to us */ + &wsi->vhost->same_vh_protocol_list[n]; + wsi->same_vh_protocol_next = /* old first guy is our next */ + wsi->vhost->same_vh_protocol_list[n]; + /* we become the new first guy */ + wsi->vhost->same_vh_protocol_list[n] = wsi; + + if (wsi->same_vh_protocol_next) + /* old first guy points back to us now */ + wsi->same_vh_protocol_next->same_vh_protocol_prev = + &wsi->same_vh_protocol_next; + wsi->on_same_vh_list = 1; + + lws_vhost_unlock(wsi->vhost); + +#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/roles/ws/ops-ws.c b/thirdparty/libwebsockets/roles/ws/ops-ws.c new file mode 100644 index 0000000000..665b2c9b74 --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/ops-ws.c @@ -0,0 +1,1994 @@ +/* + * 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->parent_carries_io && + !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) +{ + struct lws_tokens ebuf; + unsigned int pending = 0; + 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 && wsi1->trunc_len) + /* 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 indication, awaiting 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++) { + struct lws *wsi = vh->same_vh_protocol_list[n]; + + while (wsi) { + 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; + } + wsi = wsi->same_vh_protocol_next; + } + } + + 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 !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); + + if (wsi->trunc_alloc) + /* not going to be completed... nuke it */ + lws_free_set_NULL(wsi->trunc_alloc); + + 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, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_WRITEABLE, + LWS_CALLBACK_SERVER_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLIENT_CLOSED, + LWS_CALLBACK_CLOSED }, + /* file handles */ 0 +}; diff --git a/thirdparty/libwebsockets/roles/ws/private.h b/thirdparty/libwebsockets/roles/ws/private.h new file mode 100644 index 0000000000..71ffcaea96 --- /dev/null +++ b/thirdparty/libwebsockets/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/roles/ws/server-ws.c b/thirdparty/libwebsockets/roles/ws/server-ws.c new file mode 100644 index 0000000000..62bcd8524f --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/server-ws.c @@ -0,0 +1,836 @@ +/* + * 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 == ' ') + 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("extension 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]; + char protocol_list[128], protocol_name[64], *p; + int protocol_len, hit, n = 0, non_space_char_found = 0; + + if (!wsi->protocol) + lwsl_err("NULL protocol at lws_read\n"); + + /* + * It's either websocket or h2->websocket + * + * Select the first protocol we support from the list + * the client sent us. + * + * Copy it to remove header fragmentation + */ + + if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, + WSI_TOKEN_PROTOCOL) < 0) { + lwsl_err("protocol list too long"); + return 1; + } + + protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + protocol_list[protocol_len] = '\0'; + p = protocol_list; + hit = 0; + + while (*p && !hit) { + n = 0; + non_space_char_found = 0; + while (n < (int)sizeof(protocol_name) - 1 && + *p && *p != ',') { + /* ignore leading spaces */ + if (!non_space_char_found && *p == ' ') { + n++; + continue; + } + non_space_char_found = 1; + protocol_name[n++] = *p++; + } + protocol_name[n] = '\0'; + if (*p) + p++; + + lwsl_debug("checking %s\n", protocol_name); + + n = 0; + while (wsi->vhost->protocols[n].callback) { + lwsl_debug("try %s\n", + wsi->vhost->protocols[n].name); + + if (wsi->vhost->protocols[n].name && + !strcmp(wsi->vhost->protocols[n].name, + protocol_name)) { + wsi->protocol = &wsi->vhost->protocols[n]; + hit = 1; + break; + } + + n++; + } + } + + /* we didn't find a protocol he wanted? */ + + if (!hit) { + if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { + lwsl_notice("No protocol from \"%s\" supported\n", + protocol_list); + return 1; + } + /* + * 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("defaulting to prot handler %d\n", + wsi->vhost->default_protocol_index); + n = wsi->vhost->default_protocol_index; + wsi->protocol = &wsi->vhost->protocols[ + (int)wsi->vhost->default_protocol_index]; + } + + /* 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; + } + } 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_same_vh_protocol_insert(wsi, 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 | LWSIFR_P_ENCAP_H2, + LRS_ESTABLISHED, &role_ops_ws); + else + lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, + &role_ops_ws); + + lws_pt_unlock(pt); + + 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) +{ + uint8_t *buffer = *buf, mask[4]; + struct lws_tokens ebuf; + unsigned int avail = (unsigned int)len; +#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 <= 0) + 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; +} |