diff --git a/CMakeLists.txt b/CMakeLists.txt index de16b5d8..260979cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,6 +196,18 @@ if(GIT_EXECUTABLE) message("Git commit hash: ${LWS_BUILD_HASH}") endif() +# translate old functionality enables to set up ROLE enables so nothing changes + +set(LWS_ROLE_H1 1) +set(LWS_ROLE_WS 1) +set(LWS_ROLE_RAW 1) +if (LWS_WITH_HTTP2) + set(LWS_ROLE_H2 1) +endif() +if (LWS_WITH_CGI) + set(LWS_ROLE_CGI 1) +endif() + if (LWS_WITH_HTTP2 AND LWS_WITHOUT_SERVER) message(FATAL_ERROR "HTTP2 can only be used with server at the moment") endif() @@ -449,8 +461,10 @@ else() set(LWS_OPENSSL_CLIENT_CERTS /etc/pki/tls/certs/ CACHE PATH "Client SSL certificate directory") endif() -if (LWS_WITH_SSL) +# LWS_OPENSSL_SUPPORT deprecated... use LWS_WITH_TLS +if (LWS_WITH_SSL OR LWS_WITH_MBEDTLS) set(LWS_OPENSSL_SUPPORT 1) + set(LWS_WITH_TLS 1) endif() if (LWS_SSL_CLIENT_USE_OS_CA_CERTS) @@ -673,37 +687,70 @@ set(HDR_PUBLIC set(SOURCES lib/misc/base64-decode.c - lib/handshake.c lib/libwebsockets.c lib/service.c lib/pollfd.c lib/output.c - lib/server/parsers.c + lib/roles/http/server/parsers.c lib/context.c lib/alloc.c - lib/header.c + lib/roles/http/header.c + lib/roles/pipe/ops-pipe.c lib/misc/lws-ring.c) -if (LWS_WITH_CGI) +if (LWS_ROLE_H1) list(APPEND SOURCES - lib/server/cgi.c) + lib/roles/h1/ops-h1.c) + if (NOT LWS_WITHOUT_CLIENT) + list(APPEND SOURCES + lib/roles/h1/client-h1.c) + endif() +endif() + +if (LWS_ROLE_WS) + list(APPEND SOURCES + lib/roles/ws/ops-ws.c) + if (NOT LWS_WITHOUT_CLIENT) + list(APPEND SOURCES + lib/roles/ws/client-ws.c + lib/roles/ws/client-parser.c) + endif() + if (NOT LWS_WITHOUT_SERVER) + list(APPEND SOURCES + lib/roles/ws/server-ws.c) + endif() +endif() + +if (LWS_ROLE_RAW) + list(APPEND SOURCES + lib/roles/raw/ops-raw.c) +endif() + +if (LWS_ROLE_CGI) + list(APPEND SOURCES + lib/roles/cgi/cgi-server.c + lib/roles/cgi/ops-cgi.c) endif() if (LWS_WITH_ACCESS_LOG) list(APPEND SOURCES - lib/server/access-log.c) + lib/roles/http/server/access-log.c) endif() if (LWS_WITH_PEER_LIMITS) list(APPEND SOURCES - lib/server/peer-limits.c) + lib/misc/peer-limits.c) endif() if (NOT LWS_WITHOUT_CLIENT) list(APPEND SOURCES - lib/client/client.c - lib/client/client-handshake.c - lib/client/client-parser.c) + lib/roles/http/client/client.c + lib/roles/http/client/client-handshake.c) +endif() + +if (NOT LWS_WITHOUT_SERVER) + list(APPEND SOURCES + lib/roles/listen/ops-listen.c) endif() if (LWS_WITH_MBEDTLS) @@ -785,7 +832,7 @@ if (LWS_WITH_SSL) if (NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES - lib/server/ssl-server.c) + lib/tls/tls-server.c) if (LWS_WITH_MBEDTLS) list(APPEND SOURCES lib/tls/mbedtls/mbedtls-server.c) @@ -796,7 +843,7 @@ if (LWS_WITH_SSL) endif() if (NOT LWS_WITHOUT_CLIENT) list(APPEND SOURCES - lib/client/ssl-client.c) + lib/tls/tls-client.c) if (LWS_WITH_MBEDTLS) list(APPEND SOURCES lib/tls/mbedtls/mbedtls-client.c) @@ -815,9 +862,10 @@ endif() if (LWS_WITH_HTTP2 AND NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES - lib/http2/http2.c - lib/http2/hpack.c - lib/http2/ssl-http2.c) + lib/roles/h2/http2.c + lib/roles/h2/hpack.c + lib/roles/h2/ssl-http2.c + lib/roles/h2/ops-h2.c) endif() # select the active platform files @@ -843,22 +891,21 @@ endif() if (NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES - lib/server/server.c - lib/server/lws-spa.c - lib/server/server-handshake.c) + lib/roles/http/server/server.c + lib/roles/http/server/lws-spa.c) endif() if (NOT LWS_WITHOUT_EXTENSIONS) list(APPEND HDR_PRIVATE - lib/ext/extension-permessage-deflate.h) + lib/roles/ws/ext/extension-permessage-deflate.h) list(APPEND SOURCES - lib/ext/extension.c - lib/ext/extension-permessage-deflate.c) + lib/roles/ws/ext/extension.c + lib/roles/ws/ext/extension-permessage-deflate.c) endif() if (LWS_WITH_HTTP_PROXY) list(APPEND SOURCES - lib/server/rewrite.c) + lib/roles/http/server/rewrite.c) endif() if (LWS_WITH_LIBEV) @@ -882,7 +929,7 @@ if (LWS_WITH_LEJP) endif() if (LWS_WITH_LEJP_CONF) list(APPEND SOURCES - "lib/server/lejp-conf.c" + "lib/roles/http/server/lejp-conf.c" ) endif() @@ -893,13 +940,13 @@ endif() if (LWS_WITH_RANGES) list(APPEND SOURCES - lib/server/ranges.c) + lib/roles/http/server/ranges.c) endif() if (LWS_WITH_ZIP_FOPS) if (LWS_WITH_ZLIB) list(APPEND SOURCES - lib/server/fops-zip.c) + lib/roles/http/server/fops-zip.c) else() message(FATAL_ERROR "Pre-zipped file support (LWS_WITH_ZIP_FOPS) requires ZLIB (LWS_WITH_ZLIB)") endif() @@ -930,7 +977,7 @@ else() # Unix. if (NOT LWS_WITHOUT_DAEMONIZE) list(APPEND SOURCES - lib/server/daemonize.c) + lib/misc/daemonize.c) endif() endif() diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index b9652dd0..ed99139d 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -8,6 +8,12 @@ #define LWS_INSTALL_DATADIR "${CMAKE_INSTALL_PREFIX}/share" +#cmakedefine LWS_ROLE_H1 +#cmakedefine LWS_ROLE_WS +#cmakedefine LWS_ROLE_RAW +#cmakedefine LWS_ROLE_H2 +#cmakedefine LWS_ROLE_CGI + /* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL. * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */ #cmakedefine USE_WOLFSSL @@ -36,8 +42,9 @@ /* The current git commit hash that we're building from */ #cmakedefine LWS_BUILD_HASH "${LWS_BUILD_HASH}" -/* Build with OpenSSL support */ +/* Build with OpenSSL support ... alias of LWS_WITH_TLS for compatibility*/ #cmakedefine LWS_OPENSSL_SUPPORT +#cmakedefine LWS_WITH_TLS /* The client should load and trust CA root certs it finds in the OS */ #cmakedefine LWS_SSL_CLIENT_USE_OS_CA_CERTS diff --git a/lib/context.c b/lib/context.c index dca7e294..ed8ff38c 100644 --- a/lib/context.c +++ b/lib/context.c @@ -50,70 +50,6 @@ static const char * const mount_protocols[] = { "callback://" }; -#if defined(LWS_WITH_HTTP2) -/* - * These are the standardized defaults. - * Override what actually goes in the vhost settings in platform or user code. - * Leave these alone because they are used to determine "what is different - * from the protocol defaults". - */ -const struct http2_settings lws_h2_defaults = { { - 1, - /* H2SET_HEADER_TABLE_SIZE */ 4096, - /* *** This controls how many entries in the dynamic table *** - * Allows the sender to inform the remote endpoint of the maximum - * size of the header compression table used to decode header - * blocks, in octets. The encoder can select any size equal to or - * less than this value by using signaling specific to the header - * compression format inside a header block (see [COMPRESSION]). - * The initial value is 4,096 octets. - */ - /* H2SET_ENABLE_PUSH */ 1, - /* H2SET_MAX_CONCURRENT_STREAMS */ 0x7fffffff, - /* H2SET_INITIAL_WINDOW_SIZE */ 65535, - /* H2SET_MAX_FRAME_SIZE */ 16384, - /* H2SET_MAX_HEADER_LIST_SIZE */ 0x7fffffff, - /*< This advisory setting informs a peer of the maximum size of - * header list that the sender is prepared to accept, in octets. - * The value is based on the uncompressed size of header fields, - * including the length of the name and value in octets plus an - * overhead of 32 octets for each header field. - */ - /* H2SET_RESERVED7 */ 0, - /* H2SET_ENABLE_CONNECT_PROTOCOL */ 0, -}}; - -/* these are the "lws defaults"... they can be overridden in plat */ - -const struct http2_settings lws_h2_stock_settings = { { - 1, - /* H2SET_HEADER_TABLE_SIZE */ 65536, /* ffox */ - /* *** This controls how many entries in the dynamic table *** - * Allows the sender to inform the remote endpoint of the maximum - * size of the header compression table used to decode header - * blocks, in octets. The encoder can select any size equal to or - * less than this value by using signaling specific to the header - * compression format inside a header block (see [COMPRESSION]). - * The initial value is 4,096 octets. - * - * Can't pass h2spec with less than 4096 here... - */ - /* H2SET_ENABLE_PUSH */ 1, - /* H2SET_MAX_CONCURRENT_STREAMS */ 24, - /* H2SET_INITIAL_WINDOW_SIZE */ 65535, - /* H2SET_MAX_FRAME_SIZE */ 16384, - /* H2SET_MAX_HEADER_LIST_SIZE */ 4096, - /*< This advisory setting informs a peer of the maximum size of - * header list that the sender is prepared to accept, in octets. - * The value is based on the uncompressed size of header fields, - * including the length of the name and value in octets plus an - * overhead of 32 octets for each header field. - */ - /* H2SET_RESERVED7 */ 0, - /* H2SET_ENABLE_CONNECT_PROTOCOL */ 1, -}}; -#endif - LWS_VISIBLE void * lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot, int size) @@ -265,7 +201,7 @@ lws_protocol_init(struct lws_context *context) pvo = pvo1->options; } -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) any |= !!vh->ssl_ctx; #endif @@ -617,10 +553,11 @@ lws_create_vhost(struct lws_context *context, vh->pvo = info->pvo; vh->headers = info->headers; vh->user = info->user; - if (!info->h2_rx_scratch_size) - vh->h2_rx_scratch_size = LWS_H2_RX_SCRATCH_SIZE; - else - vh->h2_rx_scratch_size = info->h2_rx_scratch_size; + +#if defined(LWS_ROLE_H2) + role_ops_h2.init_vhost(vh, info); +#endif + vh->ssl_info_event_mask = info->ssl_info_event_mask; if (info->keepalive_timeout) vh->keepalive_timeout = info->keepalive_timeout; @@ -632,7 +569,7 @@ lws_create_vhost(struct lws_context *context, else vh->timeout_secs_ah_idle = 10; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (info->ecdh_curve) lws_strncpy(vh->ecdh_curve, info->ecdh_curve, sizeof(vh->ecdh_curve)); @@ -966,7 +903,7 @@ lws_create_event_pipes(struct lws_context *context) return 1; } wsi->context = context; - lwsi_set_role(wsi, LWSI_ROLE_EVENT_PIPE); + lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_pipe); wsi->protocol = NULL; wsi->tsi = n; wsi->vhost = NULL; @@ -1023,7 +960,7 @@ lws_create_context(struct lws_context_creation_info *info) #if defined(GCC_VER) lwsl_info("Compiled with %s\n", GCC_VER); #endif -#if LWS_POSIX + #ifdef LWS_WITH_IPV6 if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_IPV6)) lwsl_info("IPV6 compiled in and enabled\n"); @@ -1035,7 +972,6 @@ lws_create_context(struct lws_context_creation_info *info) #if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_ESP32) lws_feature_status_libev(info); lws_feature_status_libuv(info); -#endif #endif lwsl_info(" LWS_DEF_HEADER_LEN : %u\n", LWS_DEF_HEADER_LEN); lwsl_info(" LWS_MAX_PROTOCOLS : %u\n", LWS_MAX_PROTOCOLS); @@ -1044,9 +980,7 @@ lws_create_context(struct lws_context_creation_info *info) #if defined(LWS_WITH_STATS) lwsl_info(" LWS_WITH_STATS : on\n"); #endif -#if LWS_POSIX lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH); -#endif #if defined(LWS_WITH_HTTP2) lwsl_info(" HTTP2 support : available\n"); #else @@ -1065,8 +999,8 @@ lws_create_context(struct lws_context_creation_info *info) else context->pt_serv_buf_size = 4096; -#if defined(LWS_WITH_HTTP2) - context->set = lws_h2_stock_settings; +#if defined(LWS_ROLE_H2) + role_ops_h2.init_context(context, info); #endif #if LWS_MAX_SMP > 1 diff --git a/lib/event-libs/libev.c b/lib/event-libs/libev.c index ca8992cc..d7afc671 100644 --- a/lib/event-libs/libev.c +++ b/lib/event-libs/libev.c @@ -185,7 +185,7 @@ lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc) if (!LWS_LIBEV_ENABLED(context)) return; - if (lwsi_role(new_wsi) == LWSI_ROLE_RAW_FILE) + if (new_wsi->role_ops == &role_ops_raw_file) fd = desc.filefd; else fd = desc.sockfd; diff --git a/lib/event-libs/libevent.c b/lib/event-libs/libevent.c index ddd9dde5..a3c054c7 100644 --- a/lib/event-libs/libevent.c +++ b/lib/event-libs/libevent.c @@ -174,7 +174,7 @@ lws_libevent_accept(struct lws *new_wsi, lws_sock_file_fd_type desc) // Initialize the event pt = &context->pt[(int)new_wsi->tsi]; - if (lwsi_role(new_wsi) == LWSI_ROLE_RAW_FILE) + if (new_wsi->role_ops == role_ops_raw_file) fd = desc.filefd; else fd = desc.sockfd; diff --git a/lib/event-libs/libuv.c b/lib/event-libs/libuv.c index 15f93cc3..2420d8b9 100644 --- a/lib/event-libs/libuv.c +++ b/lib/event-libs/libuv.c @@ -429,7 +429,7 @@ lws_libuv_accept(struct lws *wsi, lws_sock_file_fd_type desc) return; wsi->w_read.context = context; - if (lwsi_role(wsi) == LWSI_ROLE_RAW_FILE || wsi->event_pipe) + if (wsi->role_ops == &role_ops_raw_file || wsi->event_pipe) uv_poll_init(pt->io_loop_uv, &wsi->w_read.uv_watcher, (int)(long long)desc.filefd); else @@ -543,8 +543,7 @@ lws_libuv_closewsi(uv_handle_t* handle) * We get called back here for every wsi that closes */ - if (lwsi_role(wsi) == LWSI_ROLE_LISTEN_SOCKET && - wsi->context->deprecated) { + if (wsi->role_ops == &role_ops_listen && wsi->context->deprecated) { lspd = 1; context->deprecation_pending_listen_close_count--; if (!context->deprecation_pending_listen_close_count) @@ -637,6 +636,13 @@ lws_libuv_stop(struct lws_context *context) void lws_libuv_closehandle(struct lws *wsi) { + if (wsi->told_event_loop_closed) { + assert(0); + return; + } + + wsi->told_event_loop_closed = 1; + /* required to defer actual deletion until libuv has processed it */ uv_close((uv_handle_t*)&wsi->w_read.uv_watcher, lws_libuv_closewsi); } diff --git a/lib/handshake.c b/lib/handshake.c deleted file mode 100644 index 2a89fec0..00000000 --- a/lib/handshake.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green - * - * 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 "private-libwebsockets.h" - -/* - * -04 of the protocol (actually the 80th version) has a radically different - * handshake. The 04 spec gives the following idea - * - * The handshake from the client looks as follows: - * - * GET /chat HTTP/1.1 - * Host: server.example.com - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - * Sec-WebSocket-Origin: http://example.com - * Sec-WebSocket-Protocol: chat, superchat - * Sec-WebSocket-Version: 4 - * - * The handshake from the server looks as follows: - * - * HTTP/1.1 101 Switching Protocols - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= - * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== - * Sec-WebSocket-Protocol: chat - */ - -#ifndef min -#define min(a, b) ((a) < (b) ? (a) : (b)) -#endif - -/* - * We have to take care about parsing because the headers may be split - * into multiple fragments. They may contain unknown headers with arbitrary - * argument lengths. So, we parse using a single-character at a time state - * machine that is completely independent of packet size. - * - * Returns <0 for error or length of chars consumed from buf (up to len) - */ - -LWS_VISIBLE int -lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) -{ - unsigned char *last_char, *oldbuf = buf; - lws_filepos_t body_chunk_len; - size_t n; - -#if defined(LWS_WITH_HTTP2) - - if (lwsi_role_h2(wsi) && - !lwsi_role_ws(wsi) && - lwsi_state(wsi) != LRS_BODY) { - int m; - - // lwsl_notice("%s: h2 path: wsistate 0x%x len %d\n", __func__, - // wsi->wsistate, (int)len); - - /* - * wsi here is always the network connection wsi, not a stream - * wsi. Once we unpicked the framing we will find the right - * swsi and make it the target of the frame. - * - * If it's ws over h2, the nwsi will get us here to do the h2 - * processing, and that will call us back with the swsi + - * ESTABLISHED state for the inner payload, handled in a later - * case. - */ - while (len) { - /* - * we were accepting input but now we stopped doing so - */ - if (lws_is_flowcontrolled(wsi)) { - lws_rxflow_cache(wsi, buf, 0, (int)len); - - return 1; - } - - /* - * lws_h2_parser() may send something; when it gets the - * whole frame, it will want to perform some action - * involving a reply. But we may be in a partial send - * situation on the network wsi... - * - * Even though we may be in a partial send and unable to - * send anything new, we still have to parse the network - * wsi in order to gain tx credit to send, which is - * potentially necessary to clear the old partial send. - * - * ALL network wsi-specific frames are sent by PPS - * already, these are sent as a priority on the writable - * handler, and so respect partial sends. The only - * problem is when a stream wsi wants to send an, eg, - * reply headers frame in response to the parsing - * we will do now... the *stream wsi* must stall in a - * different state until it is able to do so from a - * priority on the WRITABLE callback, same way that - * file transfers operate. - */ - - m = lws_h2_parser(wsi, buf, len, &body_chunk_len); - if (m && m != 2) { - lwsl_debug("%s: http2_parser bailed\n", __func__); - goto bail; - } - if (m && m == 2) { - /* swsi has been closed */ - buf += body_chunk_len; - len -= body_chunk_len; - goto read_ok; - } - - /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) { - wsi->rxflow_pos += (int)body_chunk_len; - assert(wsi->rxflow_pos <= wsi->rxflow_len); - } - - buf += body_chunk_len; - len -= body_chunk_len; - } -// lwsl_debug("%s: used up block\n", __func__); - goto read_ok; - } -#endif - - // lwsl_notice("%s: h1 path: wsi state 0x%x\n", __func__, lwsi_state(wsi)); - - switch (lwsi_state(wsi)) { - - case LRS_ISSUING_FILE: - return 0; - - case LRS_ESTABLISHED: - - if (lwsi_role_non_ws_client(wsi)) - break; - - if (lwsi_role_ws(wsi)) - goto ws_mode; - - wsi->hdr_parsing_completed = 0; - - /* fallthru */ - - case LRS_HEADERS: - if (!wsi->ah) { - lwsl_err("%s: LRS_HEADERS: NULL ah\n", __func__); - assert(0); - } - lwsl_parser("issuing %d bytes to parser\n", (int)len); - - if (lws_handshake_client(wsi, &buf, (size_t)len)) - goto bail; - - last_char = buf; - if (lws_handshake_server(wsi, &buf, (size_t)len)) - /* Handshake indicates this session is done. */ - goto bail; - - /* we might have transitioned to RAW */ - if (lwsi_role_raw(wsi)) - /* we gave the read buffer to RAW handler already */ - goto read_ok; - - /* - * It's possible that we've exhausted our data already, or - * rx flow control has stopped us dealing with this early, - * but lws_handshake_server doesn't update len for us. - * Figure out how much was read, so that we can proceed - * appropriately: - */ - len -= (buf - last_char); -// lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len); - - if (!wsi->hdr_parsing_completed) - /* More header content on the way */ - goto read_ok; - - switch (lwsi_state(wsi)) { - case LRS_ESTABLISHED: - case LRS_HEADERS: - goto read_ok; - case LRS_ISSUING_FILE: - goto read_ok; - case LRS_BODY: - wsi->http.rx_content_remain = - wsi->http.rx_content_length; - if (wsi->http.rx_content_remain) - goto http_postbody; - - /* there is no POST content */ - goto postbody_completion; - default: - break; - } - break; - - case LRS_BODY: -http_postbody: - //lwsl_notice("http post body\n"); - while (len && wsi->http.rx_content_remain) { - /* Copy as much as possible, up to the limit of: - * what we have in the read buffer (len) - * remaining portion of the POST body (content_remain) - */ - body_chunk_len = min(wsi->http.rx_content_remain, len); - wsi->http.rx_content_remain -= body_chunk_len; - len -= body_chunk_len; -#ifdef LWS_WITH_CGI - if (wsi->cgi) { - struct lws_cgi_args args; - - args.ch = LWS_STDIN; - args.stdwsi = &wsi->cgi->stdwsi[0]; - args.data = buf; - args.len = body_chunk_len; - - /* returns how much used */ - n = user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, LWS_CALLBACK_CGI_STDIN_DATA, - wsi->user_space, - (void *)&args, 0); - if ((int)n < 0) - goto bail; - } else { -#endif - n = wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_BODY, wsi->user_space, - buf, (size_t)body_chunk_len); - if (n) - goto bail; - n = (size_t)body_chunk_len; -#ifdef LWS_WITH_CGI - } -#endif - buf += n; - - if (wsi->http.rx_content_remain) { - lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, - wsi->context->timeout_secs); - break; - } - /* he sent all the content in time */ -postbody_completion: -#ifdef LWS_WITH_CGI - /* - * If we're running a cgi, we can't let him off the - * hook just because he sent his POST data - */ - if (wsi->cgi) - lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, - wsi->context->timeout_secs); - else -#endif - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); -#ifdef LWS_WITH_CGI - if (!wsi->cgi) -#endif - { - lwsl_info("HTTP_BODY_COMPLETION\n"); - n = wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_BODY_COMPLETION, - wsi->user_space, NULL, 0); - if (n) - goto bail; - - if (wsi->http2_substream) - lwsi_set_state(wsi, LRS_ESTABLISHED); - } - - break; - } - break; - - case LRS_AWAITING_CLOSE_ACK: - case LRS_WAITING_TO_SEND_CLOSE: - case LRS_SHUTDOWN: - -ws_mode: - - if (lws_handshake_client(wsi, &buf, (size_t)len)) - goto bail; - - switch (lwsi_role(wsi)) { - case LWSI_ROLE_WS1_SERVER: - case LWSI_ROLE_WS2_SERVER: - /* - * for h2 we are on the swsi - */ - if (lws_interpret_incoming_packet(wsi, &buf, - (size_t)len) < 0) { - lwsl_info("interpret_incoming_packet bailed\n"); - goto bail; - } - break; - } - break; - - case LRS_DEFERRING_ACTION: - lwsl_debug("%s: LRS_DEFERRING_ACTION\n", __func__); - break; - - case LRS_SSL_ACK_PENDING: - break; - - case LRS_DEAD_SOCKET: - lwsl_err("%s: Unhandled state LRS_DEAD_SOCKET\n", __func__); - assert(0); - /* fallthru */ - - default: - lwsl_err("%s: Unhandled state %d\n", __func__, lwsi_state(wsi)); - goto bail; - } - -read_ok: - /* Nothing more to do for now */ -// lwsl_info("%s: %p: read_ok, used %ld (len %d, state %d)\n", __func__, -// wsi, (long)(buf - oldbuf), (int)len, wsi->state); - - return lws_ptr_diff(buf, oldbuf); - -bail: - /* - * h2 / h2-ws calls us recursively in lws_read()->lws_h2_parser()-> - * lws_read() pattern, having stripped the h2 framing in the middle. - * - * When taking down the whole connection, make sure that only the - * outer lws_read() does the wsi close. - */ - if (!wsi->outer_will_close) - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "lws_read bail"); - - return -1; -} diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 563b594a..6c430b4d 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -124,14 +124,8 @@ __lws_free_wsi(struct lws *wsi) wsi->peer = NULL; #endif -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2 || wsi->http2_substream) { - lws_hpack_destroy_dynamic_header(wsi); - - if (wsi->h2.h2n) - lws_free_set_NULL(wsi->h2.h2n); - } -#endif + if (wsi->role_ops->destroy_role) + wsi->role_ops->destroy_role(wsi); /* since we will destroy the wsi, make absolutely sure now */ @@ -604,10 +598,10 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * wsi->child_list = NULL; } - if (lwsi_role(wsi) == LWSI_ROLE_RAW_FILE) { + if (wsi->role_ops == &role_ops_raw_file) { lws_remove_child_from_any_parent(wsi); __remove_wsi_socket_from_fds(wsi); - wsi->protocol->callback(wsi, LWS_CALLBACK_RAW_CLOSE_FILE, + wsi->protocol->callback(wsi, wsi->role_ops->close_cb[0], wsi->user_space, NULL, 0); goto async_close; } @@ -615,7 +609,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * wsi->wsistate_pre_close = wsi->wsistate; #ifdef LWS_WITH_CGI - if (lwsi_role(wsi) == LWSI_ROLE_CGI) { + if (wsi->role_ops == &role_ops_cgi) { /* we are not a network connection, but a handler for CGI io */ if (wsi->parent && wsi->parent->cgi) { @@ -638,25 +632,21 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * lws_client_stash_destroy(wsi); #endif - if (lwsi_role(wsi) == LWSI_ROLE_RAW_SOCKET) { - wsi->protocol->callback(wsi, - LWS_CALLBACK_RAW_CLOSE, wsi->user_space, NULL, 0); + if (wsi->role_ops == &role_ops_raw_skt) { wsi->socket_is_permanently_unusable = 1; goto just_kill_connection; } - if (lwsi_role_http_server(wsi) && wsi->http.fop_fd != NULL) { + if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && + wsi->http.fop_fd != NULL) lws_vfs_file_close(&wsi->http.fop_fd); - wsi->vhost->protocols->callback(wsi, - LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); - wsi->told_user_closed = 1; - } + if (wsi->socket_is_permanently_unusable || reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY || lwsi_state(wsi) == LRS_SHUTDOWN) goto just_kill_connection; - switch (wsi->wsistate_pre_close & LRS_MASK) { + switch (lwsi_state_PRE_CLOSE(wsi)) { case LRS_DEAD_SOCKET: return; @@ -687,16 +677,14 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * lwsi_state(wsi) == LRS_H1C_ISSUE_HANDSHAKE) goto just_kill_connection; - if (!wsi->told_user_closed && lwsi_role_http_server(wsi)) { + if (!wsi->told_user_closed && lwsi_role_http(wsi) && + lwsi_role_server(wsi)) { if (wsi->user_space && wsi->protocol_bind_balance) { wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, wsi->user_space, NULL, 0); wsi->protocol_bind_balance = 0; } - wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP, - wsi->user_space, NULL, 0); - wsi->told_user_closed = 1; } /* @@ -750,116 +738,14 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * * LRS_AWAITING_CLOSE_ACK and will skip doing this a second time. */ - if ((wsi->wsistate_pre_close & LWSIFR_WS) && - (wsi->ws->close_in_ping_buffer_len || /* already a reason */ - (reason != LWS_CLOSE_STATUS_NOSTATUS && - (reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)))) { - lwsl_debug("sending close indication...\n"); - - /* 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; - } - - lwsl_debug("waiting for chance to send close\n"); - wsi->waiting_to_send_close_frame = 1; - lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE); - __lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5); - lws_callback_on_writable(wsi); - + if (wsi->role_ops->close_via_role_protocol && + wsi->role_ops->close_via_role_protocol(wsi, reason)) return; - } just_kill_connection: -#if defined(LWS_WITH_HTTP2) - - if (wsi->http2_substream && wsi->h2_stream_carries_ws) - lws_h2_rst_stream(wsi, 0, "none"); - - if (wsi->h2.parent_wsi) { - lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, - wsi->h2.parent_wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->h2.parent_wsi->h2.child_list) { - lwsl_info(" \\---- child %p\n", *w); - } lws_end_foreach_llp(w, h2.sibling_list); - } - - if (wsi->upgraded_to_http2 || wsi->http2_substream || wsi->client_h2_substream) { - lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi); - - if (wsi->h2.child_list) { - lwsl_info(" parent %p: closing children: list:\n", wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->h2.child_list) { - lwsl_info(" \\---- child %p\n", *w); - } lws_end_foreach_llp(w, h2.sibling_list); - /* trigger closing of all of our http2 children first */ - lws_start_foreach_llp(struct lws **, w, - wsi->h2.child_list) { - lwsl_info(" closing child %p\n", *w); - /* disconnect from siblings */ - wsi2 = (*w)->h2.sibling_list; - (*w)->h2.sibling_list = NULL; - (*w)->socket_is_permanently_unusable = 1; - __lws_close_free_wsi(*w, reason, "h2 child recurse"); - *w = wsi2; - continue; - } lws_end_foreach_llp(w, h2.sibling_list); - } - } - - if (wsi->upgraded_to_http2) { - /* remove pps */ - struct lws_h2_protocol_send *w = wsi->h2.h2n->pps, *w1; - while (w) { - w1 = w->next; - free(w); - w = w1; - } - wsi->h2.h2n->pps = NULL; - } - - if ((wsi->client_h2_substream || wsi->http2_substream) && - wsi->h2.parent_wsi) { - lwsl_info(" %p: disentangling from siblings\n", wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->h2.parent_wsi->h2.child_list) { - /* disconnect from siblings */ - if (*w == wsi) { - wsi2 = (*w)->h2.sibling_list; - (*w)->h2.sibling_list = NULL; - *w = wsi2; - lwsl_info(" %p disentangled from sibling %p\n", - wsi, wsi2); - break; - } - } lws_end_foreach_llp(w, h2.sibling_list); - wsi->h2.parent_wsi->h2.child_count--; - wsi->h2.parent_wsi = NULL; - if (wsi->h2.pending_status_body) - lws_free_set_NULL(wsi->h2.pending_status_body); - } - - if (wsi->h2_stream_carries_ws) { - struct lws *nwsi = lws_get_network_wsi(wsi); - - nwsi->ws_over_h2_count++; - /* if no ws, then put a timeout on the parent wsi */ - if (!nwsi->ws_over_h2_count) - __lws_set_timeout(nwsi, - PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); - } - - if (wsi->upgraded_to_http2 && wsi->h2.h2n && - wsi->h2.h2n->rx_scratch) - lws_free_set_NULL(wsi->h2.h2n->rx_scratch); -#endif + if (wsi->role_ops->close_kill_connection) + wsi->role_ops->close_kill_connection(wsi, reason); lws_remove_child_from_any_parent(wsi); n = 0; @@ -868,8 +754,7 @@ just_kill_connection: wsi->protocol_bind_balance) { lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, wsi->protocol->name); - wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_DROP_PROTOCOL, + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, wsi->user_space, NULL, 0); wsi->protocol_bind_balance = 0; } @@ -880,18 +765,6 @@ just_kill_connection: LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, NULL, 0); - if (lwsi_role_client(wsi) && !(lwsi_state(wsi) & LWSIFS_NOTEST)) { - const struct lws_protocols *pro = wsi->protocol; - - if (!wsi->protocol) - pro = &wsi->vhost->protocols[0]; - pro->callback(wsi, LWS_CALLBACK_CLOSED_CLIENT_HTTP, - wsi->user_space, NULL, 0); - wsi->told_user_closed = 1; - } - - -#if LWS_POSIX /* * Testing with ab shows that we have to stage the socket close when * the system is under stress... shutdown any further TX, change the @@ -899,14 +772,13 @@ just_kill_connection: * for the POLLIN to show a zero-size rx before coming back and doing * the actual close. */ - if (lwsi_role(wsi) != LWSI_ROLE_RAW_SOCKET && - !lwsi_role_client(wsi) && + if (wsi->role_ops != &role_ops_raw_skt && !lwsi_role_client(wsi) && lwsi_state(wsi) != LRS_SHUTDOWN && lwsi_state(wsi) != LRS_UNCONNECTED && reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && !wsi->socket_is_permanently_unusable) { -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (lws_is_ssl(wsi) && wsi->ssl) { n = 0; switch (__lws_tls_shutdown(wsi)) { @@ -952,7 +824,6 @@ just_kill_connection: } #endif } -#endif lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, wsi, wsi->desc.sockfd); @@ -980,66 +851,22 @@ just_kill_connection: lwsi_set_state(wsi, LRS_DEAD_SOCKET); lws_free_set_NULL(wsi->rxflow_buffer); - if ((wsi->wsistate_pre_close & LWSIFR_WS) || lwsi_role_ws(wsi)) { - - if (wsi->ws->rx_draining_ext) { - struct lws **w = &pt->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->tx_draining_ext_list; - - 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; - } - 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; - } + if (wsi->role_ops->close_role) + wsi->role_ops->close_role(pt, wsi); /* tell the user it's all over for this guy */ - if (wsi->protocol && !wsi->told_user_closed && - wsi->protocol->callback && - lwsi_role(wsi) != LWSI_ROLE_RAW_SOCKET && - !(wsi->wsistate_pre_close & LWSIFS_NOTEST)) { - if (lwsi_role_client(wsi)) - wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CLOSED, - wsi->user_space, NULL, 0); - else - wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED, - wsi->user_space, NULL, 0); - } else if (lwsi_role_http_server(wsi)) { - lwsl_debug("calling back CLOSED_HTTP\n"); - wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP, - wsi->user_space, NULL, 0 ); - } else - lwsl_debug("not calling back closed role=0x%x state=0x%x\n", - lwsi_role(wsi), wsi->wsistate_pre_close); + if (lwsi_state_est_PRE_CLOSE(wsi) && !wsi->told_user_closed && + wsi->role_ops->close_cb[lwsi_role_server(wsi)]) { + const struct lws_protocols *pro = wsi->protocol; + + if (!wsi->protocol) + pro = &wsi->vhost->protocols[0]; + pro->callback(wsi, + wsi->role_ops->close_cb[lwsi_role_server(wsi)], + wsi->user_space, NULL, 0); + wsi->told_user_closed = 1; + } /* deallocate any active extension contexts */ @@ -1084,15 +911,10 @@ __lws_close_free_wsi_final(struct lws *wsi) int n; if (lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { -#if LWS_POSIX n = compatible_close(wsi->desc.sockfd); if (n) lwsl_debug("closing: close ret %d\n", LWS_ERRNO); -#else - compatible_close(wsi->desc.sockfd); - (void)n; -#endif wsi->desc.sockfd = LWS_SOCK_INVALID; } @@ -1147,7 +969,7 @@ lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len) return NULL; } -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) LWS_VISIBLE int interface_to_sa(struct lws_vhost *vh, const char *ifname, struct sockaddr_in *addr, size_t addrlen) @@ -1163,12 +985,10 @@ interface_to_sa(struct lws_vhost *vh, const char *ifname, #endif #ifndef LWS_PLAT_OPTEE -#if LWS_POSIX static int lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, int name_len, char *rip, int rip_len) { -#if LWS_POSIX struct addrinfo ai, *res; struct sockaddr_in addr4; @@ -1233,24 +1053,12 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, return -1; return 0; -#else - (void)vh; - (void)ads; - (void)name; - (void)name_len; - (void)rip; - (void)rip_len; - - return -1; -#endif } -#endif LWS_VISIBLE const char * lws_get_peer_simple(struct lws *wsi, char *name, int namelen) { -#if LWS_POSIX socklen_t len, olen; #ifdef LWS_WITH_IPV6 struct sockaddr_in6 sin6; @@ -1259,10 +1067,7 @@ lws_get_peer_simple(struct lws *wsi, char *name, int namelen) int af = AF_INET; void *p, *q; -#if defined(LWS_WITH_HTTP2) - if (wsi->http2_substream) - wsi = wsi->h2.parent_wsi; -#endif + wsi = lws_get_network_wsi(wsi); if (wsi->parent_carries_io) wsi = wsi->parent; @@ -1288,9 +1093,6 @@ lws_get_peer_simple(struct lws *wsi, char *name, int namelen) } return lws_plat_inet_ntop(af, q, name, namelen); -#else - return NULL; -#endif } #endif @@ -1299,7 +1101,6 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, int name_len, char *rip, int rip_len) { #ifndef LWS_PLAT_OPTEE -#if LWS_POSIX socklen_t len; #ifdef LWS_WITH_IPV6 struct sockaddr_in6 sin6; @@ -1334,7 +1135,6 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, bail: lws_latency(context, wsi, "lws_get_peer_addresses", ret, 1); -#endif #endif (void)wsi; (void)fd; @@ -1631,9 +1431,6 @@ lws_compare_time_t(struct lws_context *context, time_t t1, time_t t2) return (int)(t1 - t2); } - -#if LWS_POSIX - LWS_VISIBLE lws_sockfd_type lws_get_socket_fd(struct lws *wsi) { @@ -1642,8 +1439,6 @@ lws_get_socket_fd(struct lws *wsi) return wsi->desc.sockfd; } -#endif - #ifdef LWS_LATENCY void lws_latency(struct lws_context *context, struct lws *wsi, const char *action, @@ -1961,27 +1756,6 @@ lws_get_protocol(struct lws *wsi) return wsi->protocol; } -LWS_VISIBLE int -lws_is_final_fragment(struct lws *wsi) -{ - lwsl_info("%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; -} - -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; -} int lws_ensure_user_space(struct lws *wsi) @@ -2198,7 +1972,7 @@ lwsl_hexdump(const void *vbuf, size_t len) LWS_VISIBLE int lws_is_ssl(struct lws *wsi) { -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) return wsi->use_ssl & LCCSCF_USE_SSL; #else (void)wsi; @@ -2206,7 +1980,7 @@ lws_is_ssl(struct lws *wsi) #endif } -#if defined(LWS_OPENSSL_SUPPORT) && !defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) LWS_VISIBLE lws_tls_conn* lws_get_ssl(struct lws *wsi) { @@ -2223,24 +1997,28 @@ lws_partial_buffered(struct lws *wsi) LWS_VISIBLE size_t lws_get_peer_write_allowance(struct lws *wsi) { -#ifdef LWS_WITH_HTTP2 - /* only if we are using HTTP2 on this connection */ - if (!lwsi_role_h2(wsi)) - return -1; + if (wsi->role_ops->tx_credit) + return wsi->role_ops->tx_credit(wsi); - return lws_h2_tx_cr_get(wsi); -#else - (void)wsi; return -1; -#endif } LWS_VISIBLE void -lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state) +lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state, + struct lws_role_ops *ops) { - lwsl_debug("%s: %p: role 0x%x, state 0x%x\n", __func__, wsi, role, state); - lwsi_set_role(wsi, role); - lwsi_set_state(wsi, state); +#if defined(_DEBUG) + const char *name = "(unset)"; +#endif + wsi->wsistate = role | state; + if (ops) + wsi->role_ops = ops; +#if defined(_DEBUG) + if (wsi->role_ops) + name = wsi->role_ops->name; + lwsl_debug("%s: %p: wsistate 0x%x, ops %s\n", __func__, wsi, + wsi->wsistate, name); +#endif } LWS_VISIBLE struct lws_plat_file_ops * @@ -2319,38 +2097,6 @@ lws_clear_child_pending_on_writable(struct lws *wsi) wsi->parent_pending_cb_on_writable = 0; } -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); -} LWS_EXTERN int __lws_rx_flow_control(struct lws *wsi) @@ -2538,7 +2284,6 @@ LWS_EXTERN int lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, const char *iface) { -#if LWS_POSIX #ifdef LWS_WITH_UNIX_SOCK struct sockaddr_un serv_unix; #endif @@ -2656,7 +2401,6 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, memcpy(&sain, &sin, sizeof(sain)); port = ntohs(sain.sin_port); } -#endif #endif return port; @@ -2823,15 +2567,7 @@ bail: #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 const char *hex = "0123456789ABCDEF"; @@ -3123,7 +2859,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) " \"h2_subs\":\"%lu\"" , vh->name, vh->listen_port, -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) vh->use_ssl & LCCSCF_USE_SSL, #else 0, diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index fd3056cf..d2c254a7 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -39,8 +39,6 @@ extern "C" { * CARE: everything using cmake defines needs to be below here */ -#define LWS_POSIX 1 - #if defined(LWS_HAS_INTPTR_T) #include #define lws_intptr_t intptr_t @@ -180,7 +178,7 @@ typedef unsigned long long lws_intptr_t; #endif #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL @@ -1648,7 +1646,7 @@ struct lws_vhost; */ ///@{ -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) #if defined(LWS_WITH_MBEDTLS) #include @@ -2808,7 +2806,7 @@ struct lws_context_creation_info { int ka_interval; /**< CONTEXT: if ka_time was nonzero, how long to wait before each ka_probes * attempt */ -#if defined(LWS_OPENSSL_SUPPORT) && !defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) SSL_CTX *provided_client_ssl_ctx; /**< CONTEXT: If non-null, swap out libwebsockets ssl * implementation for the one provided by provided_ssl_ctx. @@ -5931,14 +5929,6 @@ lws_get_close_payload(struct lws *wsi); LWS_VISIBLE LWS_EXTERN struct lws *lws_get_network_wsi(struct lws *wsi); -/* - * \deprecated DEPRECATED Note: this is not normally needed as a user api. - * It's provided in case it is - * useful when integrating with other app poll loop service code. - */ -LWS_VISIBLE LWS_EXTERN int -lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len); - /** * lws_set_allocator() - custom allocator support * @@ -6047,7 +6037,7 @@ struct lws_wifi_scan { /* generic wlan scan item */ uint8_t authmode; }; -#if defined(LWS_OPENSSL_SUPPORT) && !defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) /** * lws_get_ssl() - Return wsi's SSL context structure * \param wsi: websocket connection diff --git a/lib/server/daemonize.c b/lib/misc/daemonize.c similarity index 100% rename from lib/server/daemonize.c rename to lib/misc/daemonize.c diff --git a/lib/server/peer-limits.c b/lib/misc/peer-limits.c similarity index 100% rename from lib/server/peer-limits.c rename to lib/misc/peer-limits.c diff --git a/lib/output.c b/lib/output.c index a7a3353e..c51e7c5a 100644 --- a/lib/output.c +++ b/lib/output.c @@ -21,25 +21,6 @@ #include "private-libwebsockets.h" -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; -} - /* * notice this returns number of bytes consumed, or -1 */ @@ -63,11 +44,12 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) */ if (wsi->could_have_pending) { lwsl_hexdump_level(LLL_ERR, buf, len); - lwsl_err("** %p: vh: %s, prot: %s, " + lwsl_err("** %p: vh: %s, prot: %s, role %s: " "Illegal back-to-back write of %lu detected...\n", wsi, wsi->vhost->name, wsi->protocol->name, + wsi->role_ops->name, (unsigned long)len); - // assert(0); + assert(0); return -1; } @@ -215,12 +197,6 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, enum lws_write_protocol wp) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - int masked7 = lwsi_role_client(wsi); - unsigned char is_masked_bit = 0; - unsigned char *dropmask = NULL; - struct lws_tokens eff_buf; - size_t orig_len = len; - int pre = 0, n, wp1f = wp & 0x1f; if (wsi->parent_carries_io) { struct lws_write_passthru pas; @@ -255,361 +231,11 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, if (wsi->vhost) wsi->vhost->conn_stats.tx += len; - if (wsi->ws && wsi->ws->tx_draining_ext && lwsi_role_ws(wsi)) { - /* remove us from the list */ - struct lws **w = &pt->tx_draining_ext_list; + assert(wsi->role_ops); + if (!wsi->role_ops->write_role_protocol) + return lws_issue_raw(wsi, buf, len); - 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; - wp = (wsi->ws->tx_draining_stashed_wp & 0xc0) | - LWS_WRITE_CONTINUATION; - wp1f = wp & 0x1f; - - lwsl_ext("FORCED draining wp to 0x%02X\n", wp); - } - - lws_restart_ws_ping_pong_timer(wsi); - - if (wp1f == LWS_WRITE_HTTP || - wp1f == LWS_WRITE_HTTP_FINAL || - wp1f == LWS_WRITE_HTTP_HEADERS_CONTINUATION || - wp1f == LWS_WRITE_HTTP_HEADERS) - goto send_raw; - - /* if not in a state to send ws stuff, then just send nothing */ - - if (!lwsi_role_ws(wsi) && - ((lwsi_state(wsi) != LRS_RETURNED_CLOSE && - lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE && - lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK) || - wp1f != LWS_WRITE_CLOSE)) { - //assert(0); - lwsl_debug("binning %d %d\n", lwsi_state(wsi), wp1f); - return 0; - } - - /* 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. - */ - eff_buf.token = (char *)buf; - eff_buf.token_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_debug("LWS_EXT_CB_PAYLOAD_TX\n"); - n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &eff_buf, wp); - if (n < 0) - return -1; - - if (n && eff_buf.token_len) { - lwsl_debug("drain len %d\n", (int)eff_buf.token_len); - /* extension requires further draining */ - wsi->ws->tx_draining_ext = 1; - wsi->ws->tx_draining_ext_list = - pt->tx_draining_ext_list; - pt->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 (eff_buf.token_len && wsi->ws->stashed_write_pending) { - wsi->ws->stashed_write_pending = 0; - wp = (wp &0xc0) | (int)wsi->ws->stashed_write_type; - wp1f = wp & 0x1f; - } - } - - /* - * 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 != eff_buf.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 && !eff_buf.token_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 *)eff_buf.token; - len = eff_buf.token_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); - } - } - -send_raw: - switch (wp1f) { - case LWS_WRITE_TEXT: - case LWS_WRITE_BINARY: - case LWS_WRITE_CONTINUATION: - if (!wsi->h2_stream_carries_ws) - break; - /* fallthru */ - case LWS_WRITE_CLOSE: -/* lwsl_hexdump(&buf[-pre], len); */ - case LWS_WRITE_HTTP: - case LWS_WRITE_HTTP_FINAL: - case LWS_WRITE_HTTP_HEADERS: - case LWS_WRITE_HTTP_HEADERS_CONTINUATION: - case LWS_WRITE_PONG: - case LWS_WRITE_PING: -#ifdef LWS_WITH_HTTP2 - /* - * ws-over-h2 also ends up here after the ws framing applied - */ - if (lwsi_role_h2(wsi)) { - unsigned char flags = 0; - - n = LWS_H2_FRAME_TYPE_DATA; - if (wp1f == LWS_WRITE_HTTP_HEADERS) { - n = LWS_H2_FRAME_TYPE_HEADERS; - if (!(wp & LWS_WRITE_NO_FIN)) - flags = LWS_H2_FLAG_END_HEADERS; - if (wsi->h2.send_END_STREAM || - (wp & LWS_WRITE_H2_STREAM_END)) { - flags |= LWS_H2_FLAG_END_STREAM; - wsi->h2.send_END_STREAM = 1; - } - } - - if (wp1f == LWS_WRITE_HTTP_HEADERS_CONTINUATION) { - n = LWS_H2_FRAME_TYPE_CONTINUATION; - if (!(wp & LWS_WRITE_NO_FIN)) - flags = LWS_H2_FLAG_END_HEADERS; - if (wsi->h2.send_END_STREAM || - (wp & LWS_WRITE_H2_STREAM_END)) { - flags |= LWS_H2_FLAG_END_STREAM; - wsi->h2.send_END_STREAM = 1; - } - } - - if ((wp1f == LWS_WRITE_HTTP || - wp1f == LWS_WRITE_HTTP_FINAL) && - wsi->http.tx_content_length) { - wsi->http.tx_content_remain -= len; - lwsl_info("%s: wsi %p: tx_content_remain = %llu\n", - __func__, wsi, - (unsigned long long)wsi->http.tx_content_remain); - if (!wsi->http.tx_content_remain) { - lwsl_info("%s: selecting final write mode\n", - __func__); - wp = LWS_WRITE_HTTP_FINAL; - wp1f = wp & 0x1f; - } - } - - if (wp1f == LWS_WRITE_HTTP_FINAL || - (wp & LWS_WRITE_H2_STREAM_END)) { - //lws_get_network_wsi(wsi)->h2.END_STREAM) { - lwsl_info("%s: setting END_STREAM\n", __func__); - flags |= LWS_H2_FLAG_END_STREAM; - wsi->h2.send_END_STREAM = 1; - } - - /* if any ws framing, account for that too */ - return lws_h2_frame_write(wsi, n, flags, wsi->h2.my_sid, - (int)len + pre, buf - pre); - } -#endif - return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre); - default: - break; - } - - /* - * 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; + return wsi->role_ops->write_role_protocol(wsi, buf, len, &wp); } LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) @@ -688,26 +314,29 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) poss = wsi->http.tx_content_remain; /* - * if there is a hint about how much we will do well to send at one time, - * restrict ourselves to only trying to send that. + * if there is a hint about how much we will do well to send at + * one time, restrict ourselves to only trying to send that. */ if (wsi->protocol->tx_packet_size && poss > wsi->protocol->tx_packet_size) poss = wsi->protocol->tx_packet_size; -#if defined(LWS_WITH_HTTP2) - m = lws_h2_tx_cr_get(wsi); - if (!m) { - lwsl_info("%s: came here with no tx credit\n", __func__); - return 0; + if (wsi->role_ops->tx_credit) { + lws_filepos_t txc = wsi->role_ops->tx_credit(wsi); + + if (!txc) { + lwsl_info("%s: came here with no tx credit\n", + __func__); + return 0; + } + if (txc < poss) + poss = txc; + + /* + * consumption of the actual payload amount sent will be + * handled when the role data frame is sent + */ } - if ((lws_filepos_t)m < poss) - poss = m; - /* - * consumption of the actual payload amount sent will be handled - * when the http2 data frame is sent - */ -#endif #if defined(LWS_WITH_RANGES) if (wsi->http.range.count_ranges) { @@ -854,7 +483,6 @@ file_had_it: return -1; } -#if LWS_POSIX LWS_VISIBLE int lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) { @@ -879,12 +507,12 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) return n; } -#if LWS_POSIX + if (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK || LWS_ERRNO == LWS_EINTR) return LWS_SSL_CAPABLE_MORE_SERVICE; -#endif + lwsl_notice("error on reading from skt : %d\n", LWS_ERRNO); return LWS_SSL_CAPABLE_ERROR; } @@ -894,7 +522,6 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) { int n = 0; -#if LWS_POSIX if (lws_wsi_is_udp(wsi)) { if (wsi->trunc_len) n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa_pending, wsi->udp->salen_pending); @@ -915,20 +542,13 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) return LWS_SSL_CAPABLE_MORE_SERVICE; } -#else - (void)n; - (void)wsi; - (void)buf; - (void)len; - // !!! -#endif lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", len, wsi->desc.sockfd, n, LWS_ERRNO); return LWS_SSL_CAPABLE_ERROR; } -#endif + LWS_VISIBLE int lws_ssl_pending_no_ssl(struct lws *wsi) { diff --git a/lib/plat/lws-plat-esp32.c b/lib/plat/lws-plat-esp32.c index c0828605..3c4dfb51 100644 --- a/lib/plat/lws-plat-esp32.c +++ b/lib/plat/lws-plat-esp32.c @@ -217,7 +217,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) } } -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (!pt->rx_draining_ext_list && !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) { #else diff --git a/lib/plat/lws-plat-optee.c b/lib/plat/lws-plat-optee.c index 41160cb8..4003f7c8 100644 --- a/lib/plat/lws-plat-optee.c +++ b/lib/plat/lws-plat-optee.c @@ -140,7 +140,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) #if 1 n = poll(pt->fds, pt->fds_count, timeout_ms); -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (!pt->rx_draining_ext_list && !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) { #else diff --git a/lib/plat/lws-plat-unix.c b/lib/plat/lws-plat-unix.c index 9a075f97..6b8eff88 100644 --- a/lib/plat/lws-plat-unix.c +++ b/lib/plat/lws-plat-unix.c @@ -254,7 +254,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) lws_pt_unlock(pt); -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (!n && !pt->rx_draining_ext_list && !lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) { #else diff --git a/lib/pollfd.c b/lib/pollfd.c index d8836e29..d9029c44 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -172,9 +172,7 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) * ... and the service thread is waiting ... * then cancel it to force a restart with our changed events */ -#if LWS_POSIX pa_events = pa->prev_events != pa->events; -#endif if (pa_events) { if (lws_plat_change_pollfd(context, wsi, pfd)) { @@ -264,11 +262,7 @@ __insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) wsi->position_in_fds_table = pt->fds_count; pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd; -#if LWS_POSIX pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN; -#else - pt->fds[wsi->position_in_fds_table].events = 0; -#endif pa.events = pt->fds[pt->fds_count].events; lws_plat_insert_socket_into_fds(context, wsi); @@ -422,10 +416,6 @@ LWS_VISIBLE int lws_callback_on_writable(struct lws *wsi) { struct lws_context_per_thread *pt; -#ifdef LWS_WITH_HTTP2 - struct lws *network_wsi, *wsi2; - int already; -#endif int n; if (lwsi_state(wsi) == LRS_SHUTDOWN) @@ -461,66 +451,13 @@ lws_callback_on_writable(struct lws *wsi) } #endif -#ifdef LWS_WITH_HTTP2 - lwsl_info("%s: %p (role/state 0x%x)\n", __func__, wsi, wsi->wsistate); - if (!lwsi_role_h2(wsi)) - goto network_sock; - - if (wsi->h2.requested_POLLOUT -#if !defined(LWS_NO_CLIENT) - && !wsi->client_h2_alpn -#endif - ) { - lwsl_info("already pending writable\n"); - return 1; + if (wsi->role_ops->callback_on_writable) { + if (wsi->role_ops->callback_on_writable(wsi)) + return 1; + wsi = lws_get_network_wsi(wsi); } - /* is this for DATA or for control messages? */ - if (wsi->upgraded_to_http2 && !wsi->h2.h2n->pps && - !lws_h2_tx_cr_get(wsi)) { - /* - * other side is not able to cope with us sending DATA - * anything so no matter if we have POLLOUT on our side if it's - * DATA we want to send. - * - * Delay waiting for our POLLOUT until peer indicates he has - * space for more using tx window command in http2 layer - */ - lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi, - wsi->h2.tx_cr); - wsi->h2.skint = 1; - return 0; - } - - wsi->h2.skint = 0; - network_wsi = lws_get_network_wsi(wsi); - already = network_wsi->h2.requested_POLLOUT; - - /* mark everybody above him as requesting pollout */ - - wsi2 = wsi; - while (wsi2) { - wsi2->h2.requested_POLLOUT = 1; - lwsl_info("mark %p pending writable\n", wsi2); - wsi2 = wsi2->h2.parent_wsi; - } - - /* for network action, act only on the network wsi */ - - wsi = network_wsi; - if (already && !wsi->client_h2_alpn -#if !defined(LWS_NO_CLIENT) - && !wsi->client_h2_substream -#endif - ) - return 1; -network_sock: -#endif - - if (lws_ext_cb_active(wsi, LWS_EXT_CB_REQUEST_ON_WRITEABLE, NULL, 0)) - return 1; - if (wsi->position_in_fds_table < 0) { lwsl_debug("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd); diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 5dfdb3fb..2edcf9d6 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -210,7 +210,7 @@ int fork(void); #define strerror(x) "" #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL @@ -413,7 +413,7 @@ extern "C" { * Choose the SSL backend */ -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) #if defined(LWS_WITH_MBEDTLS________) struct lws_tls_mbed_ctx { @@ -482,107 +482,89 @@ enum lws_websocket_opcodes_07 { typedef uint32_t lws_wsi_state_t; /* - * 31 16 15 0 - * [ role ] [ state ] + * The wsi->role_ops pointer decides almost everything about what role the wsi + * will play, h2, raw, ws, etc. * - * The role part is generally invariant for the lifetime of the wsi, although - * it can change if the connection role itself does, eg, if the connection - * upgrades from H1 -> WS1 the role is changed at that point. + * However there are a few additional flags needed that vary, such as if the + * role is a client or server side, if it has that concept. And the connection + * fulfilling the role, has a separate dynamic state. + * + * 31 16 15 0 + * [ role flags ] [ state ] + * + * The role flags part is generally invariant for the lifetime of the wsi, + * although it can change if the connection role itself does, eg, if the + * connection upgrades from H1 -> WS1 the role flags may be changed at that + * point. * * The state part reflects the dynamic connection state, and the states are * reused between roles. * * None of the internal role or state representations are made available outside - * of lws internals. + * of lws internals. Even for lws internals, if you add stuff here, please keep + * the constants inside this header only by adding necessary helpers here and + * use the helpers in the actual code. This is to ease any future refactors. + * + * Notice LWSIFR_ENCAP means we have a parent wsi that actually carries our + * data as a stream inside a different protocol. */ #define _RS 16 -#define LWSIFR_HTTP (0x008 << _RS) -#define LWSIFR_H2 (0x010 << _RS) -#define LWSIFR_CLIENT (0x020 << _RS) -#define LWSIFR_SERVER (0x040 << _RS) -#define LWSIFR_WS (0x080 << _RS) -#define LWSIFR_RAW (0x100 << _RS) +#define LWSIFR_CLIENT (0x1000 << _RS) /* client side */ +#define LWSIFR_SERVER (0x2000 << _RS) /* server side */ + +#define LWSIFR_P_ENCAP_H2 (0x0100 << _RS) /* we are encapsulated by h2 */ enum lwsi_role { - LWSI_ROLE_UNSET = (0 << _RS), - - LWSI_ROLE_H1_SERVER = (LWSIFR_SERVER | LWSIFR_HTTP ), - LWSI_ROLE_H2_SERVER = (LWSIFR_SERVER | LWSIFR_HTTP | LWSIFR_H2), - LWSI_ROLE_H1_CLIENT = (LWSIFR_CLIENT | LWSIFR_HTTP ), - LWSI_ROLE_H2_CLIENT = (LWSIFR_CLIENT | LWSIFR_HTTP | LWSIFR_H2), - LWSI_ROLE_WS1_SERVER = (LWSIFR_SERVER | LWSIFR_WS ), - LWSI_ROLE_WS2_SERVER = (LWSIFR_SERVER | LWSIFR_WS | LWSIFR_H2), - LWSI_ROLE_WS1_CLIENT = (LWSIFR_CLIENT | LWSIFR_WS ), - LWSI_ROLE_WS2_CLIENT = (LWSIFR_CLIENT | LWSIFR_WS | LWSIFR_H2), - - LWSI_ROLE_CGI = (1 << _RS), - LWSI_ROLE_LISTEN_SOCKET = (2 << _RS), - LWSI_ROLE_EVENT_PIPE = (3 << _RS), - LWSI_ROLE_RAW_FILE = (LWSIFR_RAW | (4 << _RS)), - LWSI_ROLE_RAW_SOCKET = (LWSIFR_RAW | (5 << _RS)), - - LWSI_ROLE_MASK = (0xffff << _RS), + LWSI_ROLE_MASK = (0xffff << _RS), + LWSI_ROLE_ENCAP_MASK = (0x0f00 << _RS), }; -#define lwsi_role(wsi) \ - (wsi->wsistate & LWSI_ROLE_MASK) +#define lwsi_role(wsi) (wsi->wsistate & LWSI_ROLE_MASK) #if !defined (_DEBUG) #define lwsi_set_role(wsi, role) wsi->wsistate = \ (wsi->wsistate & (~LWSI_ROLE_MASK)) | role #else void lwsi_set_role(struct lws *wsi, lws_wsi_state_t role); #endif -#define lwsi_role_ws(wsi) (!!(wsi->wsistate & LWSIFR_WS)) -#define lwsi_role_ws_client(wsi) \ - ((wsi->wsistate & (LWSIFR_CLIENT | LWSIFR_WS))\ - == (LWSIFR_CLIENT | LWSIFR_WS)) -#define lwsi_role_non_ws_client(wsi) \ - ((wsi->wsistate & (LWSIFR_CLIENT | LWSIFR_WS))\ - == (LWSIFR_CLIENT)) + #define lwsi_role_client(wsi) (!!(wsi->wsistate & LWSIFR_CLIENT)) -#define lwsi_role_raw(wsi) (!!(wsi->wsistate & LWSIFR_RAW)) -#define lwsi_role_http_server(wsi) \ - ((wsi->wsistate & (LWSIFR_SERVER | LWSIFR_HTTP))\ - == (LWSIFR_SERVER | LWSIFR_HTTP)) -#define lwsi_role_http_client(wsi) \ - ((wsi->wsistate & (LWSIFR_CLIENT | LWSIFR_HTTP))\ - == (LWSIFR_CLIENT | LWSIFR_HTTP)) -#define lwsi_role_http(wsi) (!!(wsi->wsistate & LWSIFR_HTTP)) -#define lwsi_role_h2(wsi) (!!(wsi->wsistate & LWSIFR_H2)) +#define lwsi_role_server(wsi) (!!(wsi->wsistate & LWSIFR_SERVER)) +#define lwsi_role_h2_ENCAPSULATION(wsi) \ + ((wsi->wsistate & LWSI_ROLE_ENCAP_MASK) == LWSIFR_P_ENCAP_H2) /* Pollout wants a callback in this state */ #define LWSIFS_POCB (0x100) /* Before any protocol connection was established */ -#define LWSIFS_NOTEST (0x200) +#define LWSIFS_NOT_EST (0x200) enum lwsi_state { /* Phase 1: pre-transport */ - LRS_UNCONNECTED = LWSIFS_NOTEST | 0, - LRS_WAITING_CONNECT = LWSIFS_NOTEST | 1, + LRS_UNCONNECTED = LWSIFS_NOT_EST | 0, + LRS_WAITING_CONNECT = LWSIFS_NOT_EST | 1, /* Phase 2: establishing intermediaries on top of transport */ - LRS_WAITING_PROXY_REPLY = LWSIFS_NOTEST | 2, - LRS_WAITING_SSL = LWSIFS_NOTEST | 3, - LRS_WAITING_SOCKS_GREETING_REPLY = LWSIFS_NOTEST | 4, - LRS_WAITING_SOCKS_CONNECT_REPLY = LWSIFS_NOTEST | 5, - LRS_WAITING_SOCKS_AUTH_REPLY = LWSIFS_NOTEST | 6, + LRS_WAITING_PROXY_REPLY = LWSIFS_NOT_EST | 2, + LRS_WAITING_SSL = LWSIFS_NOT_EST | 3, + LRS_WAITING_SOCKS_GREETING_REPLY = LWSIFS_NOT_EST | 4, + LRS_WAITING_SOCKS_CONNECT_REPLY = LWSIFS_NOT_EST | 5, + LRS_WAITING_SOCKS_AUTH_REPLY = LWSIFS_NOT_EST | 6, /* Phase 3: establishing tls tunnel */ - LRS_SSL_INIT = LWSIFS_NOTEST | 7, - LRS_SSL_ACK_PENDING = LWSIFS_NOTEST | 8, - LRS_PRE_WS_SERVING_ACCEPT = LWSIFS_NOTEST | 9, + LRS_SSL_INIT = LWSIFS_NOT_EST | 7, + LRS_SSL_ACK_PENDING = LWSIFS_NOT_EST | 8, + LRS_PRE_WS_SERVING_ACCEPT = LWSIFS_NOT_EST | 9, /* Phase 4: connected */ - LRS_WAITING_SERVER_REPLY = LWSIFS_NOTEST | 10, - LRS_H2_AWAIT_PREFACE = LWSIFS_NOTEST | 11, - LRS_H2_AWAIT_SETTINGS = LWSIFS_NOTEST | + LRS_WAITING_SERVER_REPLY = LWSIFS_NOT_EST | 10, + LRS_H2_AWAIT_PREFACE = LWSIFS_NOT_EST | 11, + LRS_H2_AWAIT_SETTINGS = LWSIFS_NOT_EST | LWSIFS_POCB | 12, /* Phase 5: protocol logically established */ @@ -590,31 +572,35 @@ enum lwsi_state { LRS_H2_CLIENT_SEND_SETTINGS = LWSIFS_POCB | 13, LRS_H2_WAITING_TO_SEND_HEADERS = LWSIFS_POCB | 14, LRS_DEFERRING_ACTION = LWSIFS_POCB | 15, - LRS_H1C_ISSUE_HANDSHAKE = 16, - LRS_H1C_ISSUE_HANDSHAKE2 = 17, - LRS_ISSUE_HTTP_BODY = 18, - LRS_ISSUING_FILE = 19, - LRS_HEADERS = 20, - LRS_BODY = 21, - LRS_ESTABLISHED = LWSIFS_POCB | 22, + LRS_IDLING = 16, + LRS_H1C_ISSUE_HANDSHAKE = 17, + LRS_H1C_ISSUE_HANDSHAKE2 = 18, + LRS_ISSUE_HTTP_BODY = 19, + LRS_ISSUING_FILE = 20, + LRS_HEADERS = 21, + LRS_BODY = 22, + LRS_ESTABLISHED = LWSIFS_POCB | 23, /* Phase 6: finishing */ - LRS_WAITING_TO_SEND_CLOSE = LWSIFS_POCB | 23, - LRS_RETURNED_CLOSE = LWSIFS_POCB | 24, - LRS_AWAITING_CLOSE_ACK = 25, - LRS_FLUSHING_BEFORE_CLOSE = LWSIFS_POCB | 26, - LRS_SHUTDOWN = 27, + LRS_WAITING_TO_SEND_CLOSE = LWSIFS_POCB | 24, + LRS_RETURNED_CLOSE = LWSIFS_POCB | 25, + LRS_AWAITING_CLOSE_ACK = 26, + LRS_FLUSHING_BEFORE_CLOSE = LWSIFS_POCB | 27, + LRS_SHUTDOWN = 28, /* Phase 7: dead */ - LRS_DEAD_SOCKET = 28, + LRS_DEAD_SOCKET = 29, LRS_MASK = 0xffff }; #define lwsi_state(wsi) ((enum lwsi_state)(wsi->wsistate & LRS_MASK)) -#define lwsi_state_est(wsi) (!(wsi->wsistate & LWSIFS_NOTEST)) +#define lwsi_state_PRE_CLOSE(wsi) ((enum lwsi_state)(wsi->wsistate_pre_close & LRS_MASK)) +#define lwsi_state_est(wsi) (!(wsi->wsistate & LWSIFS_NOT_EST)) +#define lwsi_state_est_PRE_CLOSE(wsi) (!(wsi->wsistate_pre_close & LWSIFS_NOT_EST)) +#define lwsi_state_can_handle_POLLOUT(wsi) (wsi->wsistate & LWSIFS_POCB) #if !defined (_DEBUG) #define lwsi_set_state(wsi, lrs) wsi->wsistate = \ (wsi->wsistate & (~LRS_MASK)) | lrs @@ -622,6 +608,100 @@ enum lwsi_state { void lwsi_set_state(struct lws *wsi, lws_wsi_state_t lrs); #endif +/* + * internal role-specific ops + */ +struct lws_context_per_thread; +struct lws_role_ops { + const char *name; + /* + * After http headers have parsed, this is the last chance for a role + * to upgrade the connection to something else using the headers. + * ws-over-h2 is upgraded from h2 like this. + */ + int (*check_upgrades)(struct lws *wsi); + /* role-specific context init during vhost creation */ + int (*init_context)(struct lws_context *context, + struct lws_context_creation_info *info); + /* role-specific per-vhost init during vhost creation */ + int (*init_vhost)(struct lws_vhost *vh, + struct lws_context_creation_info *info); + /* generic 1Hz callback for the role itself */ + int (*periodic_checks)(struct lws_context *context, int tsi, + time_t now); + /* chance for the role to force POLLIN without network activity */ + int (*service_flag_pending)(struct lws_context *context, int tsi); + /* an fd using this role has POLLIN signalled */ + int (*handle_POLLIN)(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd); + /* an fd using the role wanted a POLLOUT callback and now has it */ + int (*handle_POLLOUT)(struct lws *wsi); + /* perform user pollout */ + int (*perform_user_POLLOUT)(struct lws *wsi); + /* do effective callback on writeable */ + int (*callback_on_writable)(struct lws *wsi); + /* connection-specific tx credit in bytes */ + lws_filepos_t (*tx_credit)(struct lws *wsi); + /* role-specific write formatting */ + int (*write_role_protocol)(struct lws *wsi, unsigned char *buf, + size_t len, enum lws_write_protocol *wp); + + /* cache rx due to rx flow control */ + int (*rxflow_cache)(struct lws *wsi, unsigned char *buf, int n, + int len); + /* get encapsulation parent */ + struct lws * (*encapsulation_parent)(struct lws *wsi); + + /* chance for the role to handle close in the protocol */ + int (*close_via_role_protocol)(struct lws *wsi, + enum lws_close_status reason); + /* role-specific close processing */ + int (*close_role)(struct lws_context_per_thread *pt, struct lws *wsi); + /* role-specific connection close processing */ + int (*close_kill_connection)(struct lws *wsi, + enum lws_close_status reason); + /* role-specific destructor */ + int (*destroy_role)(struct lws *wsi); + + /* + * the callback reasons for WRITEABLE for client, server + * (just client applies if no concept of client or server) + */ + uint16_t writeable_cb[2]; + /* + * the callback reasons for CLOSE for client, server + * (just client applies if no concept of client or server) + */ + uint16_t close_cb[2]; +}; + +extern struct lws_role_ops role_ops_h1, role_ops_h2, role_ops_raw_skt, + role_ops_raw_file, role_ops_ws, role_ops_cgi, + role_ops_listen, role_ops_pipe; + +#define lwsi_role_ws(wsi) (wsi->role_ops == &role_ops_ws) +#define lwsi_role_h1(wsi) (wsi->role_ops == &role_ops_h1) +#if defined(LWS_ROLE_H2) +#define lwsi_role_h2(wsi) (wsi->role_ops == &role_ops_h2) +#else +#define lwsi_role_h2(_a) (0) +#endif +#define lwsi_role_http(wsi) (lwsi_role_h1(wsi) || lwsi_role_h2(wsi)) + +enum { + LWS_HP_RET_BAIL_OK, + LWS_HP_RET_BAIL_DIE, + LWS_HP_RET_USER_SERVICE, + + LWS_HPI_RET_DIE, + LWS_HPI_RET_HANDLED, + LWS_HPI_RET_CLOSE_HANDLED, + + LWS_UPG_RET_DONE, + LWS_UPG_RET_CONTINUE, + LWS_UPG_RET_BAIL +}; + enum http_version { HTTP_VERSION_1_0, HTTP_VERSION_1_1, @@ -888,7 +968,7 @@ struct lws_context_per_thread { const char *last_lock_reason; #endif int ah_wait_list_length; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) struct lws *pending_read_list; /* linked list */ #endif #if defined(LWS_WITH_LIBEV) @@ -1030,7 +1110,7 @@ struct lws_vhost { struct lws_dll_lws dll_active_client_conns; #endif const char *error_document_404; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) lws_tls_ctx *ssl_ctx; lws_tls_ctx *ssl_client_ctx; struct lws_tls_ss_pieces *ss; /* for acme tls certs */ @@ -1063,7 +1143,7 @@ struct lws_vhost { int log_fd; #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) int use_ssl; int allow_non_ssl_on_ssl_port; unsigned int user_supplied_ssl_ctx:1; @@ -1334,7 +1414,7 @@ LWS_EXTERN void lws_feature_status_libev(struct lws_context_creation_info *info) #define lws_libev_run(_a, _b) ((void) 0) #define lws_libev_destroyloop(_a, _b) ((void) 0) #define LWS_LIBEV_ENABLED(context) (0) -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) #define lws_feature_status_libev(_a) \ lwsl_info("libev support not compiled in\n") #else @@ -1364,7 +1444,7 @@ LWS_EXTERN void lws_feature_status_libuv(struct lws_context_creation_info *info) #define lws_libuv_run(_a, _b) ((void) 0) #define lws_libuv_destroyloop(_a, _b) ((void) 0) #define LWS_LIBUV_ENABLED(context) (0) -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) #define lws_feature_status_libuv(_a) \ lwsl_info("libuv support not compiled in\n") #else @@ -1395,7 +1475,7 @@ LWS_EXTERN void lws_feature_status_libevent(struct lws_context_creation_info *in #define lws_libevent_run(_a, _b) ((void) 0) #define lws_libevent_destroyloop(_a, _b) ((void) 0) #define LWS_LIBEVENT_ENABLED(context) (0) -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) #define lws_feature_status_libevent(_a) \ lwsl_info("libevent support not compiled in\n") #else @@ -1988,7 +2068,7 @@ struct lws { const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE]; void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE]; #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) lws_tls_conn *ssl; lws_tls_bio *client_bio; struct lws *pending_read_list_prev, *pending_read_list_next; @@ -2003,10 +2083,13 @@ struct lws { lws_sock_file_fd_type desc; /* .filefd / .sockfd */ #if defined(LWS_WITH_STATS) uint64_t active_writable_req_us; -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) uint64_t accept_start_us; #endif #endif + + struct lws_role_ops *role_ops; + lws_usec_t pending_timer; time_t pending_timeout_set; @@ -2045,6 +2128,7 @@ struct lws { unsigned int interpreting:1; unsigned int already_did_cce:1; unsigned int told_user_closed:1; + unsigned int told_event_loop_closed:1; unsigned int waiting_to_send_close_frame:1; unsigned int ipv6:1; unsigned int parent_carries_io:1; @@ -2081,13 +2165,13 @@ struct lws { #if !defined(LWS_WITHOUT_EXTENSIONS) unsigned int extension_data_pending:1; #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) unsigned int use_ssl; #endif #ifdef _WIN32 unsigned int sock_send_blocking:1; #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) unsigned int redirect_to_https:1; #endif @@ -2118,7 +2202,7 @@ struct lws { #if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT) char reason_bf; /* internal writeable callback reason bitfield */ #endif -#if defined(LWS_WITH_STATS) && defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_STATS) && defined(LWS_WITH_TLS) char seen_rx; #endif uint8_t ws_over_h2_count; @@ -2273,7 +2357,7 @@ LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_client_interpret_server_handshake(struct lws *wsi); LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_rx_sm(struct lws *wsi, unsigned char c); +lws_ws_rx_sm(struct lws *wsi, unsigned char c); LWS_EXTERN int lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, size_t *len); @@ -2282,7 +2366,8 @@ LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len); LWS_EXTERN void -lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state); +lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state, + struct lws_role_ops *ops); LWS_EXTERN int LWS_WARN_UNUSED_RESULT user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, @@ -2346,6 +2431,10 @@ LWS_EXTERN int lws_h2_client_handshake(struct lws *wsi); LWS_EXTERN struct lws * lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi); +int +lws_handle_POLLOUT_event_h2(struct lws *wsi); +int +lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len); #else #define lws_h2_configure_if_upgraded(x) #endif @@ -2396,8 +2485,6 @@ int lws_context_init_server(struct lws_context_creation_info *info, struct lws_vhost *vhost); LWS_EXTERN struct lws_vhost * lws_select_vhost(struct lws_context *context, int port, const char *servername); -LWS_EXTERN int -handshake_0405(struct lws_context *context, struct lws *wsi); LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len); LWS_EXTERN void @@ -2420,7 +2507,7 @@ interface_to_sa(struct lws_vhost *vh, const char *ifname, struct sockaddr_in *addr, size_t addrlen); LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); -#ifndef LWS_OPENSSL_SUPPORT +#if !defined(LWS_WITH_TLS) #define LWS_SSL_ENABLED(context) (0) #define lws_context_init_server_ssl(_a, _b) (0) #define lws_ssl_destroy(_a) @@ -2704,7 +2791,7 @@ LWS_EXTERN struct lws * lws_client_wsi_effective(struct lws *wsi); LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed_client(struct lws *wsi); -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) LWS_EXTERN int lws_context_init_client_ssl(struct lws_context_creation_info *info, struct lws_vhost *vhost); @@ -2732,12 +2819,9 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa); #ifndef LWS_NO_SERVER LWS_EXTERN int -lws_server_socket_service(struct lws_context *context, struct lws *wsi, - struct lws_pollfd *pollfd); -LWS_EXTERN int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); #else -#define lws_server_socket_service(_a, _b, _c) (0) +#define lws_server_socket_service(_b, _c) (0) #define lws_handshake_server(_a, _b, _c) (0) #endif @@ -2900,6 +2984,24 @@ __lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); int __lws_change_pollfd(struct lws *wsi, int _and, int _or); +int +lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len); +int +lws_callback_as_writeable(struct lws *wsi); +int +lws_read_or_use_preamble(struct lws_context_per_thread *pt, struct lws *wsi); +int +lws_process_ws_upgrade(struct lws *wsi); +int +lws_server_init_wsi_for_ws(struct lws *wsi); +int +handshake_0405(struct lws_context *context, struct lws *wsi); +char * +lws_generate_client_ws_handshake(struct lws *wsi, char *p); +int +lws_client_ws_upgrade(struct lws *wsi, const char **cce); +int +lws_create_client_ws_object(struct lws_client_connect_info *i, struct lws *wsi); #ifdef __cplusplus }; #endif diff --git a/lib/server/cgi.c b/lib/roles/cgi/cgi-server.c similarity index 99% rename from lib/server/cgi.c rename to lib/roles/cgi/cgi-server.c index e1cbc12b..1b40ea15 100644 --- a/lib/server/cgi.c +++ b/lib/roles/cgi/cgi-server.c @@ -88,7 +88,7 @@ lws_create_basic_wsi(struct lws_context *context, int tsi) /* initialize the instance struct */ - lws_role_transition(new_wsi, LWSI_ROLE_CGI, LRS_ESTABLISHED); + lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, &role_ops_cgi); new_wsi->hdr_parsing_completed = 0; new_wsi->position_in_fds_table = -1; diff --git a/lib/roles/cgi/ops-cgi.c b/lib/roles/cgi/ops-cgi.c new file mode 100644 index 00000000..88210160 --- /dev/null +++ b/lib/roles/cgi/ops-cgi.c @@ -0,0 +1,99 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +static int +rops_handle_POLLIN_cgi(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_cgi_args args; + + assert(wsi->role_ops == &role_ops_cgi); + + if (wsi->cgi_channel >= LWS_STDOUT && + !(pollfd->revents & pollfd->events & LWS_POLLIN)) + return LWS_HPI_RET_HANDLED; + + if (wsi->cgi_channel == LWS_STDIN && + !(pollfd->revents & pollfd->events & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + if (wsi->cgi_channel == LWS_STDIN && + lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return LWS_HPI_RET_DIE; + } + + args.ch = wsi->cgi_channel; + args.stdwsi = &wsi->parent->cgi->stdwsi[0]; + args.hdr_state = wsi->hdr_state; + + lwsl_debug("CGI LWS_STDOUT %p wsistate 0x%x\n", + wsi->parent, wsi->wsistate); + + if (user_callback_handle_rxflow(wsi->parent->protocol->callback, + wsi->parent, LWS_CALLBACK_CGI, + wsi->parent->user_space, + (void *)&args, 0)) + return 1; + + return LWS_HPI_RET_HANDLED; +} + +static int +rops_handle_POLLOUT_cgi(struct lws *wsi) +{ + return LWS_HP_RET_USER_SERVICE; +} + +static int +rops_periodic_checks_cgi(struct lws_context *context, int tsi, time_t now) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + + lws_cgi_kill_terminated(pt); + + return 0; +} + +struct lws_role_ops role_ops_cgi = { + "cgi", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* periodic_checks */ rops_periodic_checks_cgi, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_cgi, + /* handle_POLLOUT */ rops_handle_POLLOUT_cgi, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, +}; diff --git a/lib/roles/h1/client-h1.c b/lib/roles/h1/client-h1.c new file mode 100644 index 00000000..1229aabb --- /dev/null +++ b/lib/roles/h1/client-h1.c @@ -0,0 +1,69 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +int +lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m; + + 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; + + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (lws_is_flowcontrolled(wsi)) { + lwsl_debug("%s: caching %ld\n", __func__, (long)len); + lws_rxflow_cache(wsi, *buf, 0, (int)len); + return 0; + } + if (wsi->ws->rx_draining_ext) { +#if !defined(LWS_NO_CLIENT) + if (lwsi_role_client(wsi)) + m = lws_client_rx_sm(wsi, 0); + else +#endif + m = lws_ws_rx_sm(wsi, 0); + if (m < 0) + return -1; + continue; + } + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) + wsi->rxflow_pos++; + + if (lws_client_rx_sm(wsi, *(*buf)++)) { + lwsl_debug("client_rx_sm exited\n"); + return -1; + } + len--; + } + lwsl_debug("%s: finished with %ld\n", __func__, (long)len); + + return 0; +} + diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c new file mode 100644 index 00000000..6e6d6142 --- /dev/null +++ b/lib/roles/h1/ops-h1.c @@ -0,0 +1,720 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + + +/* + * We have to take care about parsing because the headers may be split + * into multiple fragments. They may contain unknown headers with arbitrary + * argument lengths. So, we parse using a single-character at a time state + * machine that is completely independent of packet size. + * + * Returns <0 for error or length of chars consumed from buf (up to len) + */ + +int +lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len) +{ + unsigned char *last_char, *oldbuf = buf; + lws_filepos_t body_chunk_len; + size_t n; + + // lwsl_notice("%s: h1 path: wsi state 0x%x\n", __func__, lwsi_state(wsi)); + + switch (lwsi_state(wsi)) { + + case LRS_ISSUING_FILE: + return 0; + + case LRS_ESTABLISHED: + + if (lwsi_role_ws(wsi)) + goto ws_mode; + + if (lwsi_role_client(wsi)) + break; + + wsi->hdr_parsing_completed = 0; + + /* fallthru */ + + case LRS_HEADERS: + if (!wsi->ah) { + lwsl_err("%s: LRS_HEADERS: NULL ah\n", __func__); + assert(0); + } + lwsl_parser("issuing %d bytes to parser\n", (int)len); + + if (lws_handshake_client(wsi, &buf, (size_t)len)) + goto bail; + + last_char = buf; + if (lws_handshake_server(wsi, &buf, (size_t)len)) + /* Handshake indicates this session is done. */ + goto bail; + + /* we might have transitioned to RAW */ + if (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file) + /* we gave the read buffer to RAW handler already */ + goto read_ok; + + /* + * It's possible that we've exhausted our data already, or + * rx flow control has stopped us dealing with this early, + * but lws_handshake_server doesn't update len for us. + * Figure out how much was read, so that we can proceed + * appropriately: + */ + len -= (buf - last_char); +// lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len); + + if (!wsi->hdr_parsing_completed) + /* More header content on the way */ + goto read_ok; + + switch (lwsi_state(wsi)) { + case LRS_ESTABLISHED: + case LRS_HEADERS: + goto read_ok; + case LRS_ISSUING_FILE: + goto read_ok; + case LRS_BODY: + wsi->http.rx_content_remain = + wsi->http.rx_content_length; + if (wsi->http.rx_content_remain) + goto http_postbody; + + /* there is no POST content */ + goto postbody_completion; + default: + break; + } + break; + + case LRS_BODY: +http_postbody: + //lwsl_notice("http post body\n"); + while (len && wsi->http.rx_content_remain) { + /* Copy as much as possible, up to the limit of: + * what we have in the read buffer (len) + * remaining portion of the POST body (content_remain) + */ + body_chunk_len = min(wsi->http.rx_content_remain, len); + wsi->http.rx_content_remain -= body_chunk_len; + len -= body_chunk_len; +#ifdef LWS_WITH_CGI + if (wsi->cgi) { + struct lws_cgi_args args; + + args.ch = LWS_STDIN; + args.stdwsi = &wsi->cgi->stdwsi[0]; + args.data = buf; + args.len = body_chunk_len; + + /* returns how much used */ + n = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_CGI_STDIN_DATA, + wsi->user_space, + (void *)&args, 0); + if ((int)n < 0) + goto bail; + } else { +#endif + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY, wsi->user_space, + buf, (size_t)body_chunk_len); + if (n) + goto bail; + n = (size_t)body_chunk_len; +#ifdef LWS_WITH_CGI + } +#endif + buf += n; + + if (wsi->http.rx_content_remain) { + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + wsi->context->timeout_secs); + break; + } + /* he sent all the content in time */ +postbody_completion: +#ifdef LWS_WITH_CGI + /* + * If we're running a cgi, we can't let him off the + * hook just because he sent his POST data + */ + if (wsi->cgi) + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, + wsi->context->timeout_secs); + else +#endif + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); +#ifdef LWS_WITH_CGI + if (!wsi->cgi) +#endif + { + lwsl_info("HTTP_BODY_COMPLETION\n"); + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY_COMPLETION, + wsi->user_space, NULL, 0); + if (n) + goto bail; + + if (wsi->http2_substream) + lwsi_set_state(wsi, LRS_ESTABLISHED); + } + + break; + } + break; + + case LRS_AWAITING_CLOSE_ACK: + case LRS_WAITING_TO_SEND_CLOSE: + case LRS_SHUTDOWN: + +ws_mode: + + if (lws_handshake_client(wsi, &buf, (size_t)len)) + goto bail; +#if defined(LWS_ROLE_WS) + if (lwsi_role_ws(wsi) && lwsi_role_server(wsi) && + /* + * for h2 we are on the swsi + */ + lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) { + lwsl_info("interpret_incoming_packet bailed\n"); + goto bail; + } +#endif + break; + + case LRS_DEFERRING_ACTION: + lwsl_debug("%s: LRS_DEFERRING_ACTION\n", __func__); + break; + + case LRS_SSL_ACK_PENDING: + break; + + case LRS_DEAD_SOCKET: + lwsl_err("%s: Unhandled state LRS_DEAD_SOCKET\n", __func__); + assert(0); + /* fallthru */ + + default: + lwsl_err("%s: Unhandled state %d\n", __func__, lwsi_state(wsi)); + assert(0); + goto bail; + } + +read_ok: + /* Nothing more to do for now */ +// lwsl_info("%s: %p: read_ok, used %ld (len %d, state %d)\n", __func__, +// wsi, (long)(buf - oldbuf), (int)len, wsi->state); + + return lws_ptr_diff(buf, oldbuf); + +bail: + /* + * h2 / h2-ws calls us recursively in lws_read()->lws_h2_parser()-> + * lws_read() pattern, having stripped the h2 framing in the middle. + * + * When taking down the whole connection, make sure that only the + * outer lws_read() does the wsi close. + */ + if (!wsi->outer_will_close) + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "lws_read bail"); + + return -1; +} + +int +lws_h1_server_socket_service(struct lws *wsi, struct lws_pollfd *pollfd) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct allocated_headers *ah; + int n, len; + + if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) + goto try_pollout; + + /* any incoming data ready? */ + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) + goto try_pollout; + + /* + * If we previously just did POLLIN when IN and OUT were + * signalled (because POLLIN processing may have used up + * the POLLOUT), don't let that happen twice in a row... + * next time we see the situation favour POLLOUT + */ + if (wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT)) { + // lwsl_notice("favouring pollout\n"); + wsi->favoured_pollin = 0; + goto try_pollout; + } + + /* + * We haven't processed that the tunnel is set up yet, so + * defer reading + */ + if (lwsi_state(wsi) == LRS_SSL_ACK_PENDING) + return LWS_HPI_RET_HANDLED; + + /* these states imply we MUST have an ah attached */ + + if ((lwsi_state(wsi) == LRS_ESTABLISHED || + lwsi_state(wsi) == LRS_ISSUING_FILE || + lwsi_state(wsi) == LRS_HEADERS)) { + if (!wsi->ah) { + /* no autoservice beacuse we will do it next */ + if (lws_header_table_attach(wsi, 0)) { + lwsl_info("wsi %p: ah get fail\n", wsi); + goto try_pollout; + } + } + ah = wsi->ah; + + assert(ah->rxpos <= ah->rxlen); + /* if nothing in ah rx buffer, get some fresh rx */ + if (ah->rxpos == ah->rxlen) { + + if (wsi->preamble_rx) { + memcpy(ah->rx, wsi->preamble_rx, wsi->preamble_rx_len); + lws_free_set_NULL(wsi->preamble_rx); + ah->rxlen = wsi->preamble_rx_len; + wsi->preamble_rx_len = 0; + } else { + ah->rxlen = lws_ssl_capable_read(wsi, ah->rx, + sizeof(ah->rx)); + } + + ah->rxpos = 0; + switch (ah->rxlen) { + case 0: + lwsl_info("%s: read 0 len a\n", + __func__); + wsi->seen_zero_length_recv = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + goto try_pollout; + //goto fail; + + case LWS_SSL_CAPABLE_ERROR: + goto fail; + case LWS_SSL_CAPABLE_MORE_SERVICE: + ah->rxlen = ah->rxpos = 0; + goto try_pollout; + } + } + + if (!(ah->rxpos != ah->rxlen && ah->rxlen)) { + lwsl_err("%s: assert: rxpos %d, rxlen %d\n", + __func__, ah->rxpos, ah->rxlen); + + assert(0); + } + + /* just ignore incoming if waiting for close */ + if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE || + lwsi_state(wsi) == LRS_ISSUING_FILE) + goto try_pollout; + + /* + * otherwise give it to whoever wants it + * according to the connection state + */ +#if defined(LWS_ROLE_H2) + if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) + n = lws_read_h2(wsi, ah->rx + ah->rxpos, + ah->rxlen - ah->rxpos); + else +#endif + n = lws_read_h1(wsi, ah->rx + ah->rxpos, + ah->rxlen - ah->rxpos); + if (n < 0) /* we closed wsi */ + return LWS_HPI_RET_DIE; + + if (!wsi->ah) + return LWS_HPI_RET_HANDLED; + if (wsi->ah->rxlen) + wsi->ah->rxpos += n; + + lwsl_debug("%s: wsi %p: ah read rxpos %d, rxlen %d\n", + __func__, wsi, wsi->ah->rxpos, + wsi->ah->rxlen); + + if (lws_header_table_is_in_detachable_state(wsi) && + (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file)) // ??? + lws_header_table_detach(wsi, 1); + + /* during the parsing we upgraded to ws */ + + if (wsi->ah && wsi->ah->rxpos == wsi->ah->rxlen && + lwsi_role_ws(wsi)) { + lwsl_info("%s: %p: dropping ah on ws post-upgrade\n", + __func__, wsi); + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); + } + + return LWS_HPI_RET_HANDLED; + } + + len = lws_read_or_use_preamble(pt, wsi); + if (len < 0) + goto fail; + + if (!len) + goto try_pollout; + + /* just ignore incoming if waiting for close */ + if (lwsi_state(wsi) != LRS_FLUSHING_BEFORE_CLOSE && + lwsi_state(wsi) != LRS_ISSUING_FILE) { + /* + * this may want to send + * (via HTTP callback for example) + * + * returns number of bytes used + */ +#if defined(LWS_ROLE_H2) + if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) + n = lws_read_h2(wsi, pt->serv_buf, len); + else +#endif + n = lws_read_h1(wsi, pt->serv_buf, len); + if (n < 0) /* we closed wsi */ + return LWS_HPI_RET_DIE; + + if (n != len) { + if (wsi->preamble_rx) { + lwsl_err("DISCARDING %d (ah %p)\n", len - n, wsi->ah); + + goto fail; + } + assert(n < len); + wsi->preamble_rx = lws_malloc(len - n, "preamble_rx"); + if (!wsi->preamble_rx) { + lwsl_err("OOM\n"); + goto fail; + } + memcpy(wsi->preamble_rx, pt->serv_buf + n, len - n); + wsi->preamble_rx_len = (int)len - n; + lwsl_debug("stashed %d\n", (int)wsi->preamble_rx_len); + } + + /* + * he may have used up the + * writability above, if we will defer POLLOUT + * processing in favour of POLLIN, note it + */ + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; + return LWS_HPI_RET_HANDLED; + } + /* + * he may have used up the + * writability above, if we will defer POLLOUT + * processing in favour of POLLIN, note it + */ + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; + +try_pollout: + + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + /* one shot */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_notice("%s a\n", __func__); + goto fail; + } + + /* clear back-to-back write detection */ + wsi->could_have_pending = 0; + + if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) { + lwsl_debug("%s: LRS_DEFERRING_ACTION now writable\n", + __func__); + + if (wsi->ah) + lwsl_debug(" existing ah rxpos %d / rxlen %d\n", + wsi->ah->rxpos, wsi->ah->rxlen); + lwsi_set_state(wsi, LRS_ESTABLISHED); + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + goto fail; + } + } + + if (!wsi->hdr_parsing_completed) + return LWS_HPI_RET_HANDLED; + + if (lwsi_state(wsi) != LRS_ISSUING_FILE) { + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_HTTP_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + + return LWS_HPI_RET_HANDLED; + } + + /* >0 == completion, <0 == error + * + * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when + * it's done. That's the case even if we just completed the + * send, so wait for that. + */ + n = lws_serve_http_file_fragment(wsi); + if (n < 0) + goto fail; + + return LWS_HPI_RET_HANDLED; + + +fail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "server socket svc fail"); + + return LWS_HPI_RET_DIE; +} + +static int +rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + +// lwsl_notice("%s: %p: wsistate 0x%x %s, revents 0x%x\n", __func__, wsi, +// wsi->wsistate, wsi->role_ops->name, pollfd->revents); + +#ifdef LWS_WITH_CGI + if (wsi->cgi && (pollfd->revents & LWS_POLLOUT)) { + if (lws_handle_POLLOUT_event(wsi, pollfd)) + return LWS_HPI_RET_CLOSE_HANDLED; + + return LWS_HPI_RET_HANDLED; + } +#endif + + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE || + lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) { + /* + * we stopped caring about anything except control + * packets. Force flow control off, defeat tx + * draining. + */ + lws_rx_flow_control(wsi, 1); + if (wsi->ws) + wsi->ws->tx_draining_ext = 0; + } + + if (lws_is_flowcontrolled(wsi)) + /* We cannot deal with any kind of new RX because we are + * RX-flowcontrolled. + */ + return LWS_HPI_RET_HANDLED; + +#if !defined(LWS_NO_SERVER) + if (!lwsi_role_client(wsi)) { + int n; + + lwsl_debug("%s: %p: wsistate 0x%x\n", __func__, wsi, wsi->wsistate); + n = lws_h1_server_socket_service(wsi, pollfd); + if (n != LWS_HPI_RET_HANDLED) + return n; + if (lwsi_state(wsi) != LRS_SSL_INIT) + if (lws_server_socket_service_ssl(wsi, LWS_SOCK_INVALID)) + return LWS_HPI_RET_DIE; + + return LWS_HPI_RET_HANDLED; + } +#endif + +#ifndef LWS_NO_CLIENT + if ((pollfd->revents & LWS_POLLIN) && + wsi->hdr_parsing_completed && !wsi->told_user_closed) { + + /* + * In SSL mode we get POLLIN notification about + * encrypted data in. + * + * But that is not necessarily related to decrypted + * data out becoming available; in may need to perform + * other in or out before that happens. + * + * simply mark ourselves as having readable data + * and turn off our POLLIN + */ + wsi->client_rx_avail = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + + //lwsl_notice("calling back %s\n", wsi->protocol->name); + + /* let user code know, he'll usually ask for writeable + * callback and drain / re-enable it there + */ + if (user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + lwsl_info("RECEIVE_CLIENT_HTTP closed it\n"); + return LWS_HPI_RET_CLOSE_HANDLED; + } + + return LWS_HPI_RET_HANDLED; + } +#endif + +// if (lwsi_state(wsi) == LRS_ESTABLISHED) +// return LWS_HPI_RET_HANDLED; + +#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_CLOSE_HANDLED; + } + + if (lws_client_socket_service(wsi, pollfd, NULL)) + return LWS_HPI_RET_DIE; +#endif + + return LWS_HPI_RET_HANDLED; +} + +int rops_handle_POLLOUT_h1(struct lws *wsi) +{ + if (lwsi_state(wsi) == LRS_ISSUE_HTTP_BODY) + return LWS_HP_RET_USER_SERVICE; + + if (lwsi_role_client(wsi)) + return LWS_HP_RET_USER_SERVICE; + + return LWS_HP_RET_BAIL_OK; +} + +static int +rops_service_flag_pending_h1(struct lws_context *context, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct allocated_headers *ah; + int forced = 0; + + /* POLLIN faking (the pt lock is taken by the parent) */ + + /* + * 3) For any wsi who have an ah with pending RX who did not + * complete their current headers, and are not flowcontrolled, + * fake their POLLIN status so they will be able to drain the + * rx buffered in the ah + */ + ah = pt->ah_list; + while (ah) { + if ((ah->rxpos != ah->rxlen && + !ah->wsi->hdr_parsing_completed) || ah->wsi->preamble_rx) { + pt->fds[ah->wsi->position_in_fds_table].revents |= + pt->fds[ah->wsi->position_in_fds_table].events & + LWS_POLLIN; + if (pt->fds[ah->wsi->position_in_fds_table].revents & + LWS_POLLIN) { + forced = 1; + break; + } + } + ah = ah->next; + } + + return forced; +} + +static int +rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ +#if 0 + /* if not in a state to send stuff, then just send nothing */ + + if ((lwsi_state(wsi) != LRS_RETURNED_CLOSE && + lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE && + lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK)) { + //assert(0); + lwsl_debug("binning %d %d\n", lwsi_state(wsi), *wp); + return 0; + } +#endif + + return lws_issue_raw(wsi, (unsigned char *)buf, len); +} + +struct lws_role_ops role_ops_h1 = { + "h1", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ rops_service_flag_pending_h1, + /* handle_POLLIN */ rops_handle_POLLIN_h1, + /* handle_POLLOUT */ rops_handle_POLLOUT_h1, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ rops_write_role_protocol_h1, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_HTTP_WRITEABLE, + LWS_CALLBACK_HTTP_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLOSED_CLIENT_HTTP, + LWS_CALLBACK_CLOSED_HTTP }, +}; diff --git a/lib/http2/hpack.c b/lib/roles/h2/hpack.c similarity index 100% rename from lib/http2/hpack.c rename to lib/roles/h2/hpack.c diff --git a/lib/http2/http2.c b/lib/roles/h2/http2.c similarity index 94% rename from lib/http2/http2.c rename to lib/roles/h2/http2.c index 0ea20ff3..8563204a 100644 --- a/lib/http2/http2.c +++ b/lib/roles/h2/http2.c @@ -187,6 +187,7 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, wsi->seen_nonpseudoheader = 0; wsi->h2.parent_wsi = parent_wsi; + wsi->role_ops = parent_wsi->role_ops; /* new guy's sibling is whoever was the first child before */ wsi->h2.sibling_list = parent_wsi->h2.child_list; /* first child is now the new guy */ @@ -258,8 +259,8 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi) if (lws_ensure_user_space(wsi)) goto bail1; - lwsi_set_role(wsi, LWSI_ROLE_H2_CLIENT); - lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS, + &role_ops_h2); lws_callback_on_writable(wsi); @@ -290,8 +291,8 @@ int lws_h2_issue_preface(struct lws *wsi) (int)strlen(preface)) return 1; - lwsi_set_role(wsi, LWSI_ROLE_H2_CLIENT); - lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS, + &role_ops_h2); h2n->count = 0; wsi->h2.tx_cr = 65535; @@ -1090,6 +1091,7 @@ lws_h2_parse_frame_header(struct lws *wsi) if (w->h2.my_sid < h2n->sid && w->h2.h2_state == LWS_H2_STATE_IDLE) lws_close_free_wsi(w, 0, "h2 sid close"); + assert(w->h2.sibling_list != w); } lws_end_foreach_ll(w, h2.sibling_list); @@ -1200,11 +1202,13 @@ lws_h2_parse_end_of_frame(struct lws *wsi) assert(lws_h2_wsi_from_id(wsi, 1) == h2n->swsi); - lwsi_set_role(wsi, LWSI_ROLE_H2_CLIENT); - lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_H2_WAITING_TO_SEND_HEADERS, + &role_ops_h2); - lwsi_set_role(h2n->swsi, LWSI_ROLE_H2_CLIENT); - lwsi_set_state(h2n->swsi, LRS_H2_WAITING_TO_SEND_HEADERS); + lws_role_transition(h2n->swsi, LWSIFR_CLIENT, + LRS_H2_WAITING_TO_SEND_HEADERS, + &role_ops_h2); /* pass on the initial headers to SID 1 */ h2n->swsi->ah = wsi->ah; @@ -1772,17 +1776,21 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, * more waiting leave it for next time around */ - n = lws_read(h2n->swsi, in - 1, n); + n = lws_read_h1(h2n->swsi, in - 1, n); h2n->swsi->outer_will_close = 0; /* - * can return 0 in POST body with content len - * exhausted somehow. + * can return 0 in POST body with + * content len exhausted somehow. */ if (n <= 0) { + lwsl_debug("%s: lws_read_h1 told %d %d / %d\n", + __func__, n, h2n->count, h2n->length); in += h2n->length - h2n->count; h2n->inside = h2n->length; h2n->count = h2n->length - 1; - lwsl_debug("%s: lws_read told %d\n", __func__, n); + + if (n < 0) + goto already_closed_swsi; goto close_swsi_and_return; } @@ -1944,6 +1952,7 @@ close_swsi_and_return: h2n->frame_state = 0; h2n->count = 0; +already_closed_swsi: *inused = in - oldin; return 2; @@ -2108,3 +2117,84 @@ lws_h2_ws_handshake(struct lws *wsi) return 0; } + +int +lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len) +{ + unsigned char *oldbuf = buf; + lws_filepos_t body_chunk_len; + int m; + + // lwsl_notice("%s: h2 path: wsistate 0x%x len %d\n", __func__, + // wsi->wsistate, (int)len); + + /* + * wsi here is always the network connection wsi, not a stream + * wsi. Once we unpicked the framing we will find the right + * swsi and make it the target of the frame. + * + * If it's ws over h2, the nwsi will get us here to do the h2 + * processing, and that will call us back with the swsi + + * ESTABLISHED state for the inner payload, handled in a later + * case. + */ + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (lws_is_flowcontrolled(wsi)) { + lws_rxflow_cache(wsi, buf, 0, (int)len); + buf += len; + break; + } + + /* + * lws_h2_parser() may send something; when it gets the + * whole frame, it will want to perform some action + * involving a reply. But we may be in a partial send + * situation on the network wsi... + * + * Even though we may be in a partial send and unable to + * send anything new, we still have to parse the network + * wsi in order to gain tx credit to send, which is + * potentially necessary to clear the old partial send. + * + * ALL network wsi-specific frames are sent by PPS + * already, these are sent as a priority on the writable + * handler, and so respect partial sends. The only + * problem is when a stream wsi wants to send an, eg, + * reply headers frame in response to the parsing + * we will do now... the *stream wsi* must stall in a + * different state until it is able to do so from a + * priority on the WRITABLE callback, same way that + * file transfers operate. + */ + + m = lws_h2_parser(wsi, buf, len, &body_chunk_len); + if (m && m != 2) { + lwsl_debug("%s: http2_parser bailed\n", __func__); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "lws_read_h2 bail"); + + return -1; + } + if (m == 2) { + /* swsi has been closed */ + buf += body_chunk_len; + len -= body_chunk_len; + break; + } + + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) { + wsi->rxflow_pos += (int)body_chunk_len; + assert(wsi->rxflow_pos <= wsi->rxflow_len); + } + + buf += body_chunk_len; + len -= body_chunk_len; + } + + return lws_ptr_diff(buf, oldbuf); +} + diff --git a/lib/http2/huftable.h b/lib/roles/h2/huftable.h similarity index 100% rename from lib/http2/huftable.h rename to lib/roles/h2/huftable.h diff --git a/lib/http2/minihuf.c b/lib/roles/h2/minihuf.c similarity index 100% rename from lib/http2/minihuf.c rename to lib/roles/h2/minihuf.c diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c new file mode 100644 index 00000000..ded3ba24 --- /dev/null +++ b/lib/roles/h2/ops-h2.c @@ -0,0 +1,1082 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +/* + * These are the standardized defaults. + * Override what actually goes in the vhost settings in platform or user code. + * Leave these alone because they are used to determine "what is different + * from the protocol defaults". + */ +const struct http2_settings lws_h2_defaults = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 4096, + /* *** This controls how many entries in the dynamic table *** + * Allows the sender to inform the remote endpoint of the maximum + * size of the header compression table used to decode header + * blocks, in octets. The encoder can select any size equal to or + * less than this value by using signaling specific to the header + * compression format inside a header block (see [COMPRESSION]). + * The initial value is 4,096 octets. + */ + /* H2SET_ENABLE_PUSH */ 1, + /* H2SET_MAX_CONCURRENT_STREAMS */ 0x7fffffff, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 0x7fffffff, + /*< This advisory setting informs a peer of the maximum size of + * header list that the sender is prepared to accept, in octets. + * The value is based on the uncompressed size of header fields, + * including the length of the name and value in octets plus an + * overhead of 32 octets for each header field. + */ + /* H2SET_RESERVED7 */ 0, + /* H2SET_ENABLE_CONNECT_PROTOCOL */ 0, +}}; + +/* these are the "lws defaults"... they can be overridden in plat */ + +const struct http2_settings lws_h2_stock_settings = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 65536, /* ffox */ + /* *** This controls how many entries in the dynamic table *** + * Allows the sender to inform the remote endpoint of the maximum + * size of the header compression table used to decode header + * blocks, in octets. The encoder can select any size equal to or + * less than this value by using signaling specific to the header + * compression format inside a header block (see [COMPRESSION]). + * The initial value is 4,096 octets. + * + * Can't pass h2spec with less than 4096 here... + */ + /* H2SET_ENABLE_PUSH */ 1, + /* H2SET_MAX_CONCURRENT_STREAMS */ 24, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 4096, + /*< This advisory setting informs a peer of the maximum size of + * header list that the sender is prepared to accept, in octets. + * The value is based on the uncompressed size of header fields, + * including the length of the name and value in octets plus an + * overhead of 32 octets for each header field. + */ + /* H2SET_RESERVED7 */ 0, + /* H2SET_ENABLE_CONNECT_PROTOCOL */ 1, +}}; + +static int +rops_handle_POLLIN_h2(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_tokens eff_buf; + unsigned int pending = 0; + char draining_flow = 0; + struct lws *wsi1; + int n; + +#ifdef LWS_WITH_CGI + if (wsi->cgi && (pollfd->revents & LWS_POLLOUT)) { + if (lws_handle_POLLOUT_event(wsi, pollfd)) + return LWS_HPI_RET_CLOSE_HANDLED; + + return LWS_HPI_RET_HANDLED; + } +#endif + + 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_CLOSE_HANDLED; + } + + 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_CLOSE_HANDLED; + } + + n = lws_client_socket_service(wsi, pollfd, NULL); + if (n) + return LWS_HPI_RET_DIE; +#endif + return LWS_HPI_RET_HANDLED; + } + + /* 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); + /* the write failed... it's had it */ + wsi->socket_is_permanently_unusable = 1; + + return LWS_HPI_RET_CLOSE_HANDLED; + } + + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE || + lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) { + /* + * we stopped caring about anything except control + * packets. Force flow control off, defeat tx + * draining. + */ + lws_rx_flow_control(wsi, 1); + if (wsi->ws) + wsi->ws->tx_draining_ext = 0; + } + + if (lws_is_flowcontrolled(wsi)) + /* We cannot deal with any kind of new RX because we are + * RX-flowcontrolled. + */ + return LWS_HPI_RET_HANDLED; + + 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; + } + + /* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained + */ + + if (wsi->rxflow_buffer) { + lwsl_info("draining rxflow (len %d)\n", + wsi->rxflow_len - wsi->rxflow_pos); + assert(wsi->rxflow_pos < wsi->rxflow_len); + /* well, drain it */ + eff_buf.token = (char *)wsi->rxflow_buffer + + wsi->rxflow_pos; + eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; + draining_flow = 1; + goto drain; + } + + if (wsi->upgraded_to_http2) { + struct lws_h2_netconn *h2n = wsi->h2.h2n; + + if (h2n->rx_scratch_len) { + lwsl_info("%s: %p: h2 rx pos = %d len = %d\n", + __func__, wsi, h2n->rx_scratch_pos, + h2n->rx_scratch_len); + eff_buf.token = (char *)h2n->rx_scratch + + h2n->rx_scratch_pos; + eff_buf.token_len = h2n->rx_scratch_len; + + h2n->rx_scratch_len = 0; + goto drain; + } + } + + /* 4: any incoming (or ah-stashed incoming rx) data ready? + * notice if rx flow going off raced poll(), rx flow wins + */ + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) + return LWS_HPI_RET_HANDLED; + +read: + 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 (wsi->ah && wsi->ah->rxlen - wsi->ah->rxpos) { + lwsl_info("%s: %p: inherited ah rx %d\n", __func__, + wsi, wsi->ah->rxlen - wsi->ah->rxpos); + eff_buf.token_len = wsi->ah->rxlen - wsi->ah->rxpos; + eff_buf.token = (char *)wsi->ah->rx + wsi->ah->rxpos; + } else { + if (!(lwsi_role_client(wsi) && + (lwsi_state(wsi) != LRS_ESTABLISHED && + lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) { + /* + * extension may not consume everything + * (eg, pmd may be constrained + * as to what it can output...) has to go in + * per-wsi rx buf area. + * Otherwise in large temp serv_buf area. + */ + + if (wsi->upgraded_to_http2) { + if (!wsi->h2.h2n->rx_scratch) { + wsi->h2.h2n->rx_scratch = + lws_malloc( + wsi->vhost->h2_rx_scratch_size, + "h2 rx scratch"); + if (!wsi->h2.h2n->rx_scratch) + return LWS_HPI_RET_CLOSE_HANDLED; + } + eff_buf.token = wsi->h2.h2n->rx_scratch; + eff_buf.token_len = wsi->vhost->h2_rx_scratch_size; + } else { + eff_buf.token = (char *)pt->serv_buf; + eff_buf.token_len = + wsi->context->pt_serv_buf_size; + + if ((unsigned int)eff_buf.token_len > + wsi->context->pt_serv_buf_size) + eff_buf.token_len = + wsi->context->pt_serv_buf_size; + } + + if ((int)pending > eff_buf.token_len) + pending = eff_buf.token_len; + + eff_buf.token_len = lws_ssl_capable_read(wsi, + (unsigned char *)eff_buf.token, + pending ? (int)pending : + eff_buf.token_len); + switch (eff_buf.token_len) { + case 0: + lwsl_info("%s: zero length read\n", + __func__); + return LWS_HPI_RET_CLOSE_HANDLED; + 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_CLOSE_HANDLED; + } + // lwsl_notice("Actual RX %d\n", eff_buf.token_len); + } + } + +drain: +#ifndef LWS_NO_CLIENT + if (lwsi_role_http(wsi) && lwsi_role_client(wsi) && + wsi->hdr_parsing_completed && !wsi->told_user_closed) { + + /* + * In SSL mode we get POLLIN notification about + * encrypted data in. + * + * But that is not necessarily related to decrypted + * data out becoming available; in may need to perform + * other in or out before that happens. + * + * simply mark ourselves as having readable data + * and turn off our POLLIN + */ + wsi->client_rx_avail = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + + /* let user code know, he'll usually ask for writeable + * callback and drain / re-enable it there + */ + if (user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + lwsl_info("RECEIVE_CLIENT_HTTP closed it\n"); + return LWS_HPI_RET_CLOSE_HANDLED; + } + + return LWS_HPI_RET_HANDLED; + } +#endif + + /* service incoming data */ + + if (eff_buf.token_len) { + /* + * if draining from rxflow buffer, not + * critical to track what was used since at the + * use it bumps wsi->rxflow_pos. If we come + * around again it will pick up from where it + * left off. + */ + + if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) + n = lws_read_h2(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + else + n = lws_read_h1(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + + if (n < 0) { + /* we closed wsi */ + n = 0; + return LWS_HPI_RET_DIE; + } + } + + eff_buf.token = NULL; + eff_buf.token_len = 0; + + if (wsi->ah +#if !defined(LWS_NO_CLIENT) + && !wsi->client_h2_alpn +#endif + ) { + lwsl_info("%s: %p: detaching ah\n", __func__, wsi); + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); + } + + pending = lws_ssl_pending(wsi); + if (pending) { + pending = pending > wsi->context->pt_serv_buf_size ? + wsi->context->pt_serv_buf_size : pending; + goto read; + } + + if (draining_flow && wsi->rxflow_buffer && + wsi->rxflow_pos == wsi->rxflow_len) { + lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); + lws_free_set_NULL(wsi->rxflow_buffer); + /* 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_h2(struct lws *wsi) +{ + // lwsl_notice("%s\n", __func__); + + if (lwsi_state(wsi) == LRS_ISSUE_HTTP_BODY) + return LWS_HP_RET_USER_SERVICE; + + /* + * Priority 2: H2 protocol packets + */ + if ((wsi->upgraded_to_http2 +#if !defined(LWS_NO_CLIENT) + || wsi->client_h2_alpn +#endif + ) && wsi->h2.h2n->pps) { + lwsl_info("servicing pps\n"); + if (lws_h2_do_pps_send(wsi)) { + wsi->socket_is_permanently_unusable = 1; + return LWS_HP_RET_BAIL_DIE; + } + if (wsi->h2.h2n->pps) + return LWS_HP_RET_BAIL_OK; + + /* we can resume whatever we were doing */ + lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_ENABLE | + LWS_RXFLOW_REASON_H2_PPS_PENDING); + + return LWS_HP_RET_BAIL_OK; /* leave POLLOUT active */ + } + + /* 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; + + return LWS_HP_RET_USER_SERVICE; +} + +static int +rops_service_flag_pending_h2(struct lws_context *context, int tsi) +{ + /* h1 will deal with this if both h1 and h2 enabled */ + +#if !defined(LWS_ROLE_H1) + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct allocated_headers *ah; + int forced = 0; + + /* POLLIN faking (the pt lock is taken by the parent) */ + + /* + * 3) For any wsi who have an ah with pending RX who did not + * complete their current headers, and are not flowcontrolled, + * fake their POLLIN status so they will be able to drain the + * rx buffered in the ah + */ + ah = pt->ah_list; + while (ah) { + if ((ah->rxpos != ah->rxlen && + !ah->wsi->hdr_parsing_completed) || ah->wsi->preamble_rx) { + pt->fds[ah->wsi->position_in_fds_table].revents |= + pt->fds[ah->wsi->position_in_fds_table].events & + LWS_POLLIN; + if (pt->fds[ah->wsi->position_in_fds_table].revents & + LWS_POLLIN) { + forced = 1; + break; + } + } + ah = ah->next; + } + + return forced; +#else + return 0; +#endif +} + +static int +rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ + unsigned char flags = 0; + int n; + + /* if not in a state to send stuff, then just send nothing */ + + if (!lwsi_role_ws(wsi) && + ((*wp) & 0x1f) != LWS_WRITE_HTTP && + ((*wp) & 0x1f) != LWS_WRITE_HTTP_FINAL && + ((*wp) & 0x1f) != LWS_WRITE_HTTP_HEADERS_CONTINUATION && + ((*wp) & 0x1f) != LWS_WRITE_HTTP_HEADERS && + ((lwsi_state(wsi) != LRS_RETURNED_CLOSE && + lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE && + lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK) || + ((*wp) & 0x1f) != LWS_WRITE_CLOSE)) { + //assert(0); + lwsl_notice("binning wsistate 0x%x %d\n", wsi->wsistate, *wp); + return 0; + } + + /* + * ws-over-h2 also ends up here after the ws framing applied + */ + + n = LWS_H2_FRAME_TYPE_DATA; + if ((*wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) { + n = LWS_H2_FRAME_TYPE_HEADERS; + if (!((*wp) & LWS_WRITE_NO_FIN)) + flags = LWS_H2_FLAG_END_HEADERS; + if (wsi->h2.send_END_STREAM || + ((*wp) & LWS_WRITE_H2_STREAM_END)) { + flags |= LWS_H2_FLAG_END_STREAM; + wsi->h2.send_END_STREAM = 1; + } + } + + if ((*wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION) { + n = LWS_H2_FRAME_TYPE_CONTINUATION; + if (!((*wp) & LWS_WRITE_NO_FIN)) + flags = LWS_H2_FLAG_END_HEADERS; + if (wsi->h2.send_END_STREAM || + ((*wp) & LWS_WRITE_H2_STREAM_END)) { + flags |= LWS_H2_FLAG_END_STREAM; + wsi->h2.send_END_STREAM = 1; + } + } + + if (((*wp & 0x1f) == LWS_WRITE_HTTP || + (*wp & 0x1f) == LWS_WRITE_HTTP_FINAL) && + wsi->http.tx_content_length) { + wsi->http.tx_content_remain -= len; + lwsl_info("%s: wsi %p: tx_content_remain = %llu\n", + __func__, wsi, + (unsigned long long)wsi->http.tx_content_remain); + if (!wsi->http.tx_content_remain) { + lwsl_info("%s: selecting final write mode\n", + __func__); + *wp = LWS_WRITE_HTTP_FINAL; + } + } + + if ((*wp & 0x1f) == LWS_WRITE_HTTP_FINAL || + ((*wp) & LWS_WRITE_H2_STREAM_END)) { + //lws_get_network_wsi(wsi)->h2.END_STREAM) { + lwsl_info("%s: setting END_STREAM\n", __func__); + flags |= LWS_H2_FLAG_END_STREAM; + wsi->h2.send_END_STREAM = 1; + } + + return lws_h2_frame_write(wsi, n, flags, wsi->h2.my_sid, + (int)len, buf); +} + +static int +rops_check_upgrades_h2(struct lws *wsi) +{ +#if defined(LWS_ROLE_WS) + struct lws *nwsi; + char *p; + + /* + * with H2 there's also a way to upgrade a stream to something + * else... :method is CONNECT and :protocol says the name of + * the new protocol we want to carry. We have to have sent a + * SETTINGS saying that we support it though. + */ + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + if (!wsi->vhost->set.s[H2SET_ENABLE_CONNECT_PROTOCOL] || + !wsi->http2_substream || !p || strcmp(p, "CONNECT")) + return LWS_UPG_RET_CONTINUE; + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_COLON_PROTOCOL); + if (!p || strcmp(p, "websocket")) + return LWS_UPG_RET_CONTINUE; + + nwsi = lws_get_network_wsi(wsi); + + wsi->vhost->conn_stats.ws_upg++; + lwsl_info("Upgrade h2 to ws\n"); + wsi->h2_stream_carries_ws = 1; + nwsi->ws_over_h2_count++; + if (lws_process_ws_upgrade(wsi)) + return LWS_UPG_RET_BAIL; + + if (nwsi->ws_over_h2_count == 1) + lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0); + + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_info("Upgraded h2 to ws OK\n"); + + return LWS_UPG_RET_DONE; +#else + return LWS_UPG_RET_CONTINUE; +#endif +} + +static int +rops_init_vhost_h2(struct lws_vhost *vh, + struct lws_context_creation_info *info) +{ + if (!info->h2_rx_scratch_size) + vh->h2_rx_scratch_size = LWS_H2_RX_SCRATCH_SIZE; + else + vh->h2_rx_scratch_size = info->h2_rx_scratch_size; + + return 0; +} + +static int +rops_init_context_h2(struct lws_context *context, + struct lws_context_creation_info *info) +{ + context->set = lws_h2_stock_settings; + + return 0; +} + +static lws_filepos_t +rops_tx_credit_h2(struct lws *wsi) +{ + return lws_h2_tx_cr_get(wsi); +} + +static int +rops_destroy_role_h2(struct lws *wsi) +{ + if (wsi->upgraded_to_http2 || wsi->http2_substream) { + lws_hpack_destroy_dynamic_header(wsi); + + if (wsi->h2.h2n) + lws_free_set_NULL(wsi->h2.h2n); + } + + return 0; +} + +static int +rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason) +{ + struct lws *wsi2; + + if (wsi->http2_substream && wsi->h2_stream_carries_ws) + lws_h2_rst_stream(wsi, 0, "none"); + + if (wsi->h2.parent_wsi) { + lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, + wsi->h2.parent_wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->h2.parent_wsi->h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, h2.sibling_list); + } + + if (wsi->upgraded_to_http2 || wsi->http2_substream || wsi->client_h2_substream) { + lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi); + + if (wsi->h2.child_list) { + lwsl_info(" parent %p: closing children: list:\n", wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, h2.sibling_list); + /* trigger closing of all of our http2 children first */ + lws_start_foreach_llp(struct lws **, w, + wsi->h2.child_list) { + lwsl_info(" closing child %p\n", *w); + /* disconnect from siblings */ + wsi2 = (*w)->h2.sibling_list; + (*w)->h2.sibling_list = NULL; + (*w)->socket_is_permanently_unusable = 1; + __lws_close_free_wsi(*w, reason, "h2 child recurse"); + *w = wsi2; + continue; + } lws_end_foreach_llp(w, h2.sibling_list); + } + } + + if (wsi->upgraded_to_http2) { + /* remove pps */ + struct lws_h2_protocol_send *w = wsi->h2.h2n->pps, *w1; + while (w) { + w1 = w->next; + free(w); + w = w1; + } + wsi->h2.h2n->pps = NULL; + } + + if ((wsi->client_h2_substream || wsi->http2_substream) && + wsi->h2.parent_wsi) { + lwsl_info(" %p: disentangling from siblings\n", wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->h2.parent_wsi->h2.child_list) { + /* disconnect from siblings */ + if (*w == wsi) { + wsi2 = (*w)->h2.sibling_list; + (*w)->h2.sibling_list = NULL; + *w = wsi2; + lwsl_info(" %p disentangled from sibling %p\n", + wsi, wsi2); + break; + } + } lws_end_foreach_llp(w, h2.sibling_list); + wsi->h2.parent_wsi->h2.child_count--; + wsi->h2.parent_wsi = NULL; + if (wsi->h2.pending_status_body) + lws_free_set_NULL(wsi->h2.pending_status_body); + } + + if (wsi->h2_stream_carries_ws) { + struct lws *nwsi = lws_get_network_wsi(wsi); + + nwsi->ws_over_h2_count++; + /* if no ws, then put a timeout on the parent wsi */ + if (!nwsi->ws_over_h2_count) + __lws_set_timeout(nwsi, + PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); + } + + if (wsi->upgraded_to_http2 && wsi->h2.h2n && + wsi->h2.h2n->rx_scratch) + lws_free_set_NULL(wsi->h2.h2n->rx_scratch); + + return 0; +} + +static int +rops_callback_on_writable_h2(struct lws *wsi) +{ + struct lws *network_wsi, *wsi2; + int already; + + //lwsl_notice("%s: %p (wsistate 0x%x)\n", __func__, wsi, wsi->wsistate); + +// if (!lwsi_role_h2(wsi) && !lwsi_role_h2_ENCAPSULATION(wsi)) +// return 0; + + if (wsi->h2.requested_POLLOUT +#if !defined(LWS_NO_CLIENT) + && !wsi->client_h2_alpn +#endif + ) { + lwsl_debug("already pending writable\n"); + return 1; + } + + /* is this for DATA or for control messages? */ + if (wsi->upgraded_to_http2 && !wsi->h2.h2n->pps && + !lws_h2_tx_cr_get(wsi)) { + /* + * other side is not able to cope with us sending DATA + * anything so no matter if we have POLLOUT on our side if it's + * DATA we want to send. + * + * Delay waiting for our POLLOUT until peer indicates he has + * space for more using tx window command in http2 layer + */ + lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi, + wsi->h2.tx_cr); + wsi->h2.skint = 1; + return 0; + } + + wsi->h2.skint = 0; + network_wsi = lws_get_network_wsi(wsi); + already = network_wsi->h2.requested_POLLOUT; + + /* mark everybody above him as requesting pollout */ + + wsi2 = wsi; + while (wsi2) { + wsi2->h2.requested_POLLOUT = 1; + lwsl_info("mark %p pending writable\n", wsi2); + wsi2 = wsi2->h2.parent_wsi; + } + + /* for network action, act only on the network wsi */ + + wsi = network_wsi; + if (already && !wsi->client_h2_alpn +#if !defined(LWS_NO_CLIENT) + && !wsi->client_h2_substream +#endif + ) + return 1; + + return 0; +} + +static int +rops_perform_user_POLLOUT_h2(struct lws *wsi) +{ + /* + * we are the 'network wsi' for potentially many muxed child wsi with + * no network connection of their own, who have to use us for all their + * network actions. So we use a round-robin scheme to share out the + * POLLOUT notifications to our children. + * + * But because any child could exhaust the socket's ability to take + * writes, we can only let one child get notified each time. + * + * In addition children may be closed / deleted / added between POLLOUT + * notifications, so we can't hold pointers + */ + struct lws **wsi2, *wsi2a; + int write_type = LWS_WRITE_PONG, n; + + wsi = lws_get_network_wsi(wsi); + + wsi->h2.requested_POLLOUT = 0; + if (!wsi->h2.initialized) { + lwsl_info("pollout on uninitialized http2 conn\n"); + return 0; + } + + lwsl_info("%s: %p: children waiting for POLLOUT service:\n", __func__, wsi); + wsi2a = wsi->h2.child_list; + while (wsi2a) { + if (wsi2a->h2.requested_POLLOUT) + lwsl_info(" * %p %s\n", wsi2a, wsi2a->protocol->name); + else + lwsl_info(" %p %s\n", wsi2a, wsi2a->protocol->name); + + wsi2a = wsi2a->h2.sibling_list; + } + + wsi2 = &wsi->h2.child_list; + if (!*wsi2) + return 0; + + do { + struct lws *w, **wa; + + wa = &(*wsi2)->h2.sibling_list; + if (!(*wsi2)->h2.requested_POLLOUT) + goto next_child; + + /* + * we're going to do writable callback for this child. + * move him to be the last child + */ + + lwsl_debug("servicing child %p\n", *wsi2); + + w = *wsi2; + while (w) { + if (!w->h2.sibling_list) { /* w is the current last */ + lwsl_debug("w=%p, *wsi2 = %p\n", w, *wsi2); + if (w == *wsi2) /* we are already last */ + break; + /* last points to us as new last */ + w->h2.sibling_list = *wsi2; + /* guy pointing to us until now points to + * our old next */ + *wsi2 = (*wsi2)->h2.sibling_list; + /* we point to nothing because we are last */ + w->h2.sibling_list->h2.sibling_list = NULL; + /* w becomes us */ + w = w->h2.sibling_list; + break; + } + w = w->h2.sibling_list; + } + + w->h2.requested_POLLOUT = 0; + lwsl_info("%s: child %p (state %d)\n", __func__, w, lwsi_state(w)); + + /* if we arrived here, even by looping, we checked choked */ + w->could_have_pending = 0; + wsi->could_have_pending = 0; + + if (w->h2.pending_status_body) { + w->h2.send_END_STREAM = 1; + n = lws_write(w, (uint8_t *)w->h2.pending_status_body + + LWS_PRE, + strlen(w->h2.pending_status_body + + LWS_PRE), LWS_WRITE_HTTP_FINAL); + lws_free_set_NULL(w->h2.pending_status_body); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream 1"); + wa = &wsi->h2.child_list; + goto next_child; + } + + if (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS) { + if (lws_h2_client_handshake(w)) + return -1; + + goto next_child; + } + + if (lwsi_state(w) == LRS_DEFERRING_ACTION) { + + /* + * we had to defer the http_action to the POLLOUT + * handler, because we know it will send something and + * only in the POLLOUT handler do we know for sure + * that there is no partial pending on the network wsi. + */ + + lwsi_set_state(w, LRS_ESTABLISHED); + + lwsl_info(" h2 action start...\n"); + n = lws_http_action(w); + lwsl_info(" h2 action result %d " + "(wsi->http.rx_content_remain %lld)\n", + n, w->http.rx_content_remain); + + /* + * Commonly we only managed to start a larger transfer + * that will complete asynchronously under its own wsi + * states. In those cases we will hear about + * END_STREAM going out in the POLLOUT handler. + */ + if (n || w->h2.send_END_STREAM) { + lwsl_info("closing stream after h2 action\n"); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream"); + wa = &wsi->h2.child_list; + } + + goto next_child; + } + + if (lwsi_state(w) == LRS_ISSUING_FILE) { + + ((volatile struct lws *)w)->leave_pollout_active = 0; + + /* >0 == completion, <0 == error + * + * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION + * callback when it's done. That's the case even if we + * just completed the send, so wait for that. + */ + n = lws_serve_http_file_fragment(w); + lwsl_debug("lws_serve_http_file_fragment says %d\n", n); + + /* + * We will often hear about out having sent the final + * DATA here... if so close the actual wsi + */ + if (n < 0 || w->h2.send_END_STREAM) { + lwsl_debug("Closing POLLOUT child %p\n", w); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream file"); + wa = &wsi->h2.child_list; + goto next_child; + } + if (n > 0) + if (lws_http_transaction_completed(w)) + return -1; + if (!n) { + lws_callback_on_writable(w); + (w)->h2.requested_POLLOUT = 1; + } + + goto next_child; + } + + /* Notify peer that we decided to close */ + + if (lwsi_state(w) == LRS_WAITING_TO_SEND_CLOSE) { + lwsl_debug("sending close packet\n"); + w->waiting_to_send_close_frame = 0; + n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE], + w->ws->close_in_ping_buffer_len, + LWS_WRITE_CLOSE); + if (n >= 0) { + lwsi_set_state(w, LRS_AWAITING_CLOSE_ACK); + lws_set_timeout(w, PENDING_TIMEOUT_CLOSE_ACK, 5); + lwsl_debug("sent close indication, awaiting ack\n"); + } + + goto next_child; + } + + /* Acknowledge receipt of peer's notification he closed, + * then logically close ourself */ + + if ((lwsi_role_ws(w) && w->ws->ping_pending_flag) || + (lwsi_state(w) == LRS_RETURNED_CLOSE && + w->ws->payload_is_close)) { + + if (w->ws->payload_is_close) + write_type = LWS_WRITE_CLOSE | + LWS_WRITE_H2_STREAM_END; + + n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE], + w->ws->ping_payload_len, write_type); + if (n < 0) + return -1; + + /* well he is sent, mark him done */ + w->ws->ping_pending_flag = 0; + if (w->ws->payload_is_close) { + /* oh... a close frame was it... then we are done */ + lwsl_debug("Acknowledged peer's close packet\n"); + w->ws->payload_is_close = 0; + lwsi_set_state(w, LRS_RETURNED_CLOSE); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "returned close packet"); + wa = &wsi->h2.child_list; + goto next_child; + } + + lws_callback_on_writable(w); + (w)->h2.requested_POLLOUT = 1; + + /* otherwise for PING, leave POLLOUT active either way */ + goto next_child; + } + + if (lws_callback_as_writeable(w)) { + lwsl_info("Closing POLLOUT child (end stream %d)\n", w->h2.send_END_STREAM); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 pollout handle"); + wa = &wsi->h2.child_list; + } else + if (w->h2.send_END_STREAM) + lws_h2_state(w, LWS_H2_STATE_HALF_CLOSED_LOCAL); + +next_child: + wsi2 = wa; + } while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi)); + + lwsl_info("%s: %p: children waiting for POLLOUT service: %p\n", + __func__, wsi, wsi->h2.child_list); + wsi2a = wsi->h2.child_list; + while (wsi2a) { + if (wsi2a->h2.requested_POLLOUT) + lwsl_debug(" * %p\n", wsi2a); + else + lwsl_debug(" %p\n", wsi2a); + + wsi2a = wsi2a->h2.sibling_list; + } + + + wsi2a = wsi->h2.child_list; + while (wsi2a) { + if (wsi2a->h2.requested_POLLOUT) { + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + break; + } + wsi2a = wsi2a->h2.sibling_list; + } + + return 0; +} + +static int +rops_rxflow_cache_h2(struct lws *wsi, unsigned char *buf, int n, int len) +{ + struct lws_h2_netconn *h2n; + + if (!wsi->upgraded_to_http2) + return 0; /* parent interprets as continue */ + + h2n = wsi->h2.h2n; + + assert(h2n->rx_scratch); + buf += n; + len -= n; + assert ((char *)buf >= (char *)h2n->rx_scratch && + (char *)&buf[len] <= + (char *)&h2n->rx_scratch[wsi->vhost->h2_rx_scratch_size]); + + h2n->rx_scratch_pos = lws_ptr_diff(buf, h2n->rx_scratch); + h2n->rx_scratch_len = len; + + lwsl_info("%s: %p: pausing h2 rx_scratch\n", __func__, wsi); + + return 1; /* parent interprets as return 0 */ +} + +static struct lws * +rops_encapsulation_parent_h2(struct lws *wsi) +{ + if (wsi->h2.parent_wsi) + return wsi->h2.parent_wsi; + + return NULL; +} + +struct lws_role_ops role_ops_h2 = { + "h2", + /* check_upgrades */ rops_check_upgrades_h2, + /* init_context */ rops_init_context_h2, + /* init_vhost */ rops_init_vhost_h2, + /* periodic_checks */ NULL, + /* service_flag_pending */ rops_service_flag_pending_h2, + /* handle_POLLIN */ rops_handle_POLLIN_h2, + /* handle_POLLOUT */ rops_handle_POLLOUT_h2, + /* perform_user_POLLOUT */ rops_perform_user_POLLOUT_h2, + /* callback_on_writable */ rops_callback_on_writable_h2, + /* tx_credit */ rops_tx_credit_h2, + /* write_role_protocol */ rops_write_role_protocol_h2, + /* rxflow_cache */ rops_rxflow_cache_h2, + /* encapsulation_parent */ rops_encapsulation_parent_h2, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ rops_close_kill_connection_h2, + /* destroy_role */ rops_destroy_role_h2, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_HTTP_WRITEABLE, + LWS_CALLBACK_HTTP_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLOSED_CLIENT_HTTP, + LWS_CALLBACK_CLOSED_HTTP }, +}; diff --git a/lib/http2/ssl-http2.c b/lib/roles/h2/ssl-http2.c similarity index 97% rename from lib/http2/ssl-http2.c rename to lib/roles/h2/ssl-http2.c index 41dc1127..5174b6f7 100644 --- a/lib/http2/ssl-http2.c +++ b/lib/roles/h2/ssl-http2.c @@ -50,7 +50,7 @@ #include "private-libwebsockets.h" #if !defined(LWS_NO_SERVER) -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) #if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ OPENSSL_VERSION_NUMBER >= 0x10002000L) @@ -132,7 +132,8 @@ int lws_h2_configure_if_upgraded(struct lws *wsi) ah = wsi->ah; - lws_role_transition(wsi, LWSI_ROLE_H2_SERVER, LRS_H2_AWAIT_PREFACE); + lws_role_transition(wsi, LWSIFR_SERVER, LRS_H2_AWAIT_PREFACE, + &role_ops_h2); /* http2 union member has http union struct at start */ wsi->ah = ah; diff --git a/lib/client/client-handshake.c b/lib/roles/http/client/client-handshake.c similarity index 96% rename from lib/client/client-handshake.c rename to lib/roles/http/client/client-handshake.c index 35e8075f..cbd8e740 100644 --- a/lib/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -31,7 +31,7 @@ lws_client_connect_2(struct lws *wsi) struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; const char *cce = "", *iface, *adsin, *meth; - struct lws *wsi_piggy = NULL; + struct lws *wsi_piggyback = NULL; struct addrinfo *result; struct lws_pollfd pfd; ssize_t plen = 0; @@ -48,7 +48,7 @@ lws_client_connect_2(struct lws *wsi) #endif #endif - lwsl_client("%s\n", __func__); + lwsl_client("%s: %p\n", __func__, wsi); if (!wsi->ah) { cce = "ah was NULL at cc2"; @@ -79,11 +79,13 @@ lws_client_connect_2(struct lws *wsi) struct lws *w = lws_container_of(d, struct lws, dll_active_client_conns); - if (w->client_hostname_copy && + lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin, w->client_hostname_copy, wsi->c_port, w->c_port); + + if (w != wsi && w->client_hostname_copy && !strcmp(adsin, w->client_hostname_copy) && -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) (wsi->use_ssl & (LCCSCF_NOT_H2 | LCCSCF_USE_SSL)) == - (w->use_ssl & (LCCSCF_NOT_H2 | LCCSCF_USE_SSL)) && + (w->use_ssl & (LCCSCF_NOT_H2 | LCCSCF_USE_SSL)) && #endif wsi->c_port == w->c_port) { @@ -91,7 +93,8 @@ lws_client_connect_2(struct lws *wsi) /* do we know for a fact pipelining won't fly? */ if (w->keepalive_rejected) { - lwsl_notice("defeating pipelining due to no KA on server\n"); + lwsl_info("defeating pipelining due to no " + "keepalive on server\n"); goto create_new_conn; } #if defined (LWS_WITH_HTTP2) @@ -113,8 +116,8 @@ lws_client_connect_2(struct lws *wsi) } #endif - lwsl_info("applying %p to txn queue on %p (%d)\n", wsi, w, - lwsi_state(w)); + lwsl_info("applying %p to txn queue on %p (wsistate 0x%x)\n", wsi, w, + w->wsistate); /* * ...let's add ourselves to his transaction queue... */ @@ -127,7 +130,7 @@ lws_client_connect_2(struct lws *wsi) * to take over parsing the rx. */ - wsi_piggy = w; + wsi_piggyback = w; lws_vhost_unlock(wsi->vhost); goto send_hs; @@ -149,6 +152,23 @@ create_new_conn: strdup(lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS)); + /* + * If we made our own connection, and we're doing a method that can take + * a pipeline, we are an "active client connection". + * + * Add ourselves to the vhost list of those so that others can + * piggyback on our transaction queue + */ + + if (meth && !strcmp(meth, "GET") && + lws_dll_is_null(&wsi->dll_client_transaction_queue) && + lws_dll_is_null(&wsi->dll_active_client_conns)) { + lws_vhost_lock(wsi->vhost); + lws_dll_lws_add_front(&wsi->dll_active_client_conns, + &wsi->vhost->dll_active_client_conns); + lws_vhost_unlock(wsi->vhost); + } + /* * start off allowing ipv6 on connection if vhost allows it */ @@ -383,7 +403,7 @@ create_new_conn: sa46.sa4.sin_port = htons(port); n = sizeof(struct sockaddr); } - +lwsl_notice("%s: CONNECT\n", __func__); if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || @@ -474,7 +494,7 @@ create_new_conn: #endif send_hs: - if (wsi_piggy && + if (wsi_piggyback && !lws_dll_is_null(&wsi->dll_client_transaction_queue)) { /* * We are pipelining on an already-established connection... @@ -492,9 +512,11 @@ send_hs: * If we are trying to do this too early, before the master * connection has written his own headers, */ - lws_callback_on_writable(wsi_piggy); - lwsl_debug("wsi %p: waiting to send headers\n", wsi); + lws_callback_on_writable(wsi_piggyback); + lwsl_info("wsi %p: waiting to send headers\n", wsi); } else { + lwsl_info("wsi %p: client creating own connection\n", wsi); + /* we are making our own connection */ lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE); @@ -525,29 +547,13 @@ send_hs: return NULL; } - /* - * If we made our own connection, and we're doing a method that can take - * a pipeline, we are an "active client connection". - * - * Add ourselves to the vhost list of those so that others can - * piggyback on our transaction queue - */ - - if (meth && !strcmp(meth, "GET") && - lws_dll_is_null(&wsi->dll_client_transaction_queue)) { - lws_vhost_lock(wsi->vhost); - lws_dll_lws_add_front(&wsi->dll_active_client_conns, - &wsi->vhost->dll_active_client_conns); - lws_vhost_unlock(wsi->vhost); - } - return wsi; oom4: /* we're closing, losing some rx is OK */ lws_header_table_force_to_detachable_state(wsi); - if (lwsi_role_client(wsi) && !(lwsi_state(wsi) & LWSIFS_NOTEST)) { + if (lwsi_role_client(wsi) && lwsi_state_est(wsi)) { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); @@ -619,7 +625,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, /* close the connection by hand */ -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) lws_ssl_close(wsi); #endif @@ -641,7 +647,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, __remove_wsi_socket_from_fds(wsi); -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) wsi->use_ssl = ssl; #else if (ssl) { @@ -852,7 +858,6 @@ LWS_VISIBLE struct lws * lws_client_connect_via_info(struct lws_client_connect_info *i) { struct lws *wsi; - int v = SPEC_LATEST_SUPPORTED; const struct lws_protocols *p; const char *local = i->protocol; @@ -875,7 +880,7 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) wsi->context = i->context; /* assert the mode and union status (hdr) clearly */ - lws_role_transition(wsi, LWSI_ROLE_H1_CLIENT, LRS_UNCONNECTED); + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_h1); wsi->desc.sockfd = LWS_SOCK_INVALID; /* 1) fill up the wsi with stuff from the connect_info as far as it @@ -883,21 +888,13 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) * not even be able to get ahold of an ah at this point. */ - if (!i->method) { /* ie, ws */ - /* allocate the ws struct for the wsi */ - wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct"); - if (!wsi->ws) { - lwsl_notice("OOM\n"); - goto bail; - } - - /* -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; - } + if (!i->method) /* ie, ws */ +#if defined(LWS_ROLE_WS) + if (lws_create_client_ws_object(i, wsi)) + return NULL; +#else + return NULL; +#endif wsi->user_space = NULL; wsi->pending_timeout = NO_PENDING_TIMEOUT; @@ -917,7 +914,7 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE); /* reasonable place to start */ - lwsi_set_role(wsi, LWSI_ROLE_H1_CLIENT); + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_h1); /* * 1) for http[s] connection, allow protocol selection by name @@ -947,7 +944,7 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) if (lws_ensure_user_space(wsi)) goto bail; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) wsi->use_ssl = i->ssl_connection; if (!i->method) /* !!! disallow ws for h2 right now */ diff --git a/lib/client/client.c b/lib/roles/http/client/client.c similarity index 63% rename from lib/client/client.c rename to lib/roles/http/client/client.c index 46964bfd..5ffaf894 100644 --- a/lib/client/client.c +++ b/lib/roles/http/client/client.c @@ -21,52 +21,6 @@ #include "private-libwebsockets.h" -int -lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) -{ - int m; - - 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; - - while (len) { - /* - * we were accepting input but now we stopped doing so - */ - if (lws_is_flowcontrolled(wsi)) { - lwsl_debug("%s: caching %ld\n", __func__, (long)len); - lws_rxflow_cache(wsi, *buf, 0, (int)len); - return 0; - } - if (wsi->ws->rx_draining_ext) { -#if !defined(LWS_NO_CLIENT) - if (lwsi_role_client(wsi)) - m = lws_client_rx_sm(wsi, 0); - else -#endif - m = lws_rx_sm(wsi, 0); - if (m < 0) - return -1; - continue; - } - /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) - wsi->rxflow_pos++; - - if (lws_client_rx_sm(wsi, *(*buf)++)) { - lwsl_debug("client_rx_sm exited\n"); - return -1; - } - len--; - } - lwsl_debug("%s: finished with %ld\n", __func__, (long)len); - - return 0; -} - LWS_VISIBLE LWS_EXTERN void lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) { @@ -134,7 +88,7 @@ lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; struct lws *w; -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) char ebuf[128]; #endif const char *cce = NULL; @@ -344,7 +298,7 @@ start_ws_handshake: if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) return -1; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) /* we can retry this... just cook the SSL BIO the first time */ if ((wsi->use_ssl & LCCSCF_USE_SSL) && !wsi->ssl && @@ -391,8 +345,9 @@ start_ws_handshake: lwsl_info("client connection upgraded to h2\n"); lws_h2_configure_if_upgraded(wsi); - lws_role_transition(wsi, LWSI_ROLE_H2_CLIENT, - LRS_H2_CLIENT_SEND_SETTINGS); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_H2_CLIENT_SEND_SETTINGS, + &role_ops_h2); /* send the H2 preface to legitimize the connection */ if (lws_h2_issue_preface(wsi)) { @@ -412,7 +367,8 @@ start_ws_handshake: case LRS_H1C_ISSUE_HANDSHAKE2: p = lws_generate_client_handshake(wsi, p); if (p == NULL) { - if (lwsi_role_raw(wsi)) + if (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file) return 0; lwsl_err("Failed to generate handshake for client\n"); @@ -424,8 +380,8 @@ start_ws_handshake: lws_latency_pre(context, wsi); w = lws_client_wsi_master(wsi); - lwsl_debug("%s: HANDSHAKE2: %p: sending headers on %p\n", - __func__, wsi, w); + lwsl_debug("%s: HANDSHAKE2: %p: sending headers on %p (wsistate 0x%x 0x%x)\n", + __func__, wsi, w, wsi->wsistate, w->wsistate); n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb)); lws_latency(context, wsi, "send lws_issue_raw", n, @@ -449,6 +405,22 @@ start_ws_handshake: break; } + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + wsi->hdr_parsing_completed = 0; + + if (lwsi_state(w) == LRS_IDLING) { + lwsi_set_state(w, LRS_WAITING_SERVER_REPLY); + w->hdr_parsing_completed = 0; + + w->ah->parser_state = WSI_TOKEN_NAME_PART; + w->ah->lextable_pos = 0; + /* If we're (re)starting on headers, need other implied init */ + wsi->ah->ues = URIES_IDLE; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + wsi->context->timeout_secs); + lws_callback_on_writable(w); goto client_http_body_sent; @@ -534,6 +506,8 @@ client_http_body_sent: if (wsi->ah->parser_state != WSI_PARSING_COMPLETE) break; + + /* * otherwise deal with the handshake. If there's any * packet traffic already arrived we'll trigger poll() again @@ -559,23 +533,7 @@ bail3: return 0; } -/* - * 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_WARN_UNUSED_RESULT lws_http_transaction_completed_client(struct lws *wsi) @@ -616,6 +574,9 @@ lws_http_transaction_completed_client(struct lws *wsi) /* after the first one, they can only be coming from the queue */ wsi->transaction_from_pipeline_queue = 1; + wsi->http.rx_content_length = 0; + wsi->hdr_parsing_completed = 0; + /* is there a new tail after removing that one? */ wsi_eff = lws_client_wsi_effective(wsi); @@ -630,24 +591,20 @@ lws_http_transaction_completed_client(struct lws *wsi) * in case something turns up... */ lwsl_info("%s: nothing pipelined waiting\n", __func__); - if (wsi->ah) { - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 0); - } + lwsi_set_state(wsi, LRS_IDLING); + lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5); return 0; } /* - * H1: we can serialize the queued guys into into the same ah + * H1: we can serialize the queued guys into the same ah * H2: everybody needs their own ah until their own STREAM_END */ /* otherwise set ourselves up ready to go again */ lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); - wsi->http.rx_content_length = 0; - wsi->hdr_parsing_completed = 0; wsi->ah->parser_state = WSI_TOKEN_NAME_PART; wsi->ah->lextable_pos = 0; @@ -691,37 +648,28 @@ strrchr(const char *s, int c) int lws_client_interpret_server_handshake(struct lws *wsi) { - int n, len, okay = 0, port = 0, ssl = 0; + int n, port = 0, ssl = 0; int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; - struct lws_context *context = wsi->context; - const char *pc, *prot, *ads = NULL, *path, *cce = NULL; + const char *prot, *ads = NULL, *path, *cce = NULL; struct allocated_headers *ah = NULL; struct lws *w = lws_client_wsi_effective(wsi); char *p, *q; char new_path[300]; -#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; - void *v; -#endif + lws_client_stash_destroy(wsi); ah = wsi->ah; if (!wsi->do_ws) { /* we are being an http client... */ +#if defined(LWS_ROLE_H2) if (wsi->client_h2_alpn) - lws_role_transition(wsi, LWSI_ROLE_H2_CLIENT, - LRS_ESTABLISHED); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_ESTABLISHED, &role_ops_h2); else - lws_role_transition(wsi, LWSI_ROLE_H1_CLIENT, - LRS_ESTABLISHED); +#endif + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_ESTABLISHED, &role_ops_h1); wsi->ah = ah; ah->http_response = 0; @@ -778,7 +726,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) /* Relative reference absolute path */ if (p[0] == '/') { -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) ssl = wsi->use_ssl & LCCSCF_USE_SSL; #endif ads = lws_hdr_simple_ptr(wsi, @@ -801,7 +749,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) else { /* This doesn't try to calculate an absolute path, * that will be left to the server */ -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) ssl = wsi->use_ssl & LCCSCF_USE_SSL; #endif ads = lws_hdr_simple_ptr(wsi, @@ -819,7 +767,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) path = p; } -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if ((wsi->use_ssl & LCCSCF_USE_SSL) && !ssl) { cce = "HS: Redirect attempted SSL downgrade"; goto bail3; @@ -879,9 +827,9 @@ lws_client_interpret_server_handshake(struct lws *wsi) ww->client_pipeline = 0; /* go back to "trying to connect" state */ - lws_role_transition(ww, - LWSI_ROLE_H1_CLIENT, - LRS_UNCONNECTED); + lws_role_transition(ww, LWSIFR_CLIENT, + LRS_UNCONNECTED, + &role_ops_h1); ww->user_space = NULL; } lws_end_foreach_dll_safe(d, d1); lws_vhost_unlock(wsi->vhost); @@ -972,425 +920,16 @@ lws_client_interpret_server_handshake(struct lws *wsi) return 0; } - 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 (p && !strncmp(p, "401", 3)) { - lwsl_warn( - "lws_client_handshake: got bad HTTP response '%s'\n", p); - cce = "HS: ws upgrade unauthorized"; - goto bail3; - } - - if (p && strncmp(p, "101", 3)) { - lwsl_warn( - "lws_client_handshake: got bad HTTP response '%s'\n", p); - 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"; +#if defined(LWS_ROLE_WS) + switch (lws_client_ws_upgrade(wsi, &cce)) { + case 2: 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->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->active_extensions[wsi->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->act_ext_user[wsi->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->act_ext_user[ - wsi->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->act_ext_user[wsi->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->act_ext_user[wsi->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->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->ah->initial_handshake_hash_base64)) { - lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, - wsi->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, LWSI_ROLE_H1_CLIENT, LRS_ESTABLISHED); - 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"; + case 3: goto bail3; } -#endif - - lwsi_set_role(wsi, LWSI_ROLE_WS1_CLIENT); - - 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; - } -#if !defined(LWS_WITHOUT_EXTENSIONS) - /* - * inform all extensions, not just active ones since they - * already know - */ - ext = wsi->vhost->extensions; - - while (ext && ext->callback) { - v = NULL; - for (n = 0; n < wsi->count_act_ext; n++) - if (wsi->active_extensions[n] == ext) - v = wsi->act_ext_user[n]; - - ext->callback(context, ext, wsi, - LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0); - ext++; - } -#endif return 0; +#endif bail3: close_reason = LWS_CLOSE_STATUS_NOSTATUS; @@ -1419,14 +958,8 @@ bail2: char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { - char buf[128], hash[20], key_b64[40], *p = pkt; - struct lws_context *context = wsi->context; + char *p = pkt; const char *meth; - int n; -#if !defined(LWS_WITHOUT_EXTENSIONS) - const struct lws_extension *ext; - int ext_count = 0; -#endif const char *pp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); @@ -1456,32 +989,17 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) lws_bind_protocol(wsi, pr); } - if ((wsi->protocol->callback)(wsi, - LWS_CALLBACK_RAW_ADOPT, - wsi->user_space, NULL, 0)) + if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) return NULL; lws_header_table_force_to_detachable_state(wsi); - lws_role_transition(wsi, LWSI_ROLE_RAW_SOCKET, LRS_ESTABLISHED); + lws_role_transition(wsi, 0, LRS_ESTABLISHED, &role_ops_raw_skt); lws_header_table_detach(wsi, 1); return NULL; } - if (wsi->do_ws) { - /* - * create the random key - */ - n = lws_get_random(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)); - } - /* * 04 example client handshake * @@ -1505,7 +1023,7 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { - if (lws_check_opt(context->options, + if (lws_check_opt(wsi->context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) p += sprintf(p, "Origin: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, @@ -1516,87 +1034,169 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) _WSI_TOKEN_CLIENT_ORIGIN)); } - if (wsi->do_ws) { - 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->extensions; - while (ext && ext->callback) { - n = lws_ext_cb_all_exts(context, wsi, - LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, - (char *)ext->name, 0); - if (n) { /* an extension vetos us */ - lwsl_ext("ext %s vetoed\n", (char *)ext->name); - ext++; - continue; - } - 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->ah->initial_handshake_hash_base64, - sizeof(wsi->ah->initial_handshake_hash_base64)); - } + if (wsi->do_ws) + p = lws_generate_client_ws_handshake(wsi, p); /* give userland a chance to append, eg, cookies */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, wsi->user_space, &p, - (pkt + context->pt_serv_buf_size) - p - 12)) + (pkt + wsi->context->pt_serv_buf_size) - p - 12)) return NULL; p += sprintf(p, "\x0d\x0a"); return p; } + +LWS_VISIBLE int +lws_http_client_read(struct lws *wsi, char **buf, int *len) +{ + int rlen, n; + + rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); + *len = 0; + + // lwsl_notice("%s: rlen %d\n", __func__, rlen); + + /* allow the source to signal he has data again next time */ + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + if (rlen == LWS_SSL_CAPABLE_ERROR) { + lwsl_notice("%s: SSL capable error\n", __func__); + return -1; + } + + if (rlen == 0) + return -1; + + if (rlen < 0) + return 0; + + *len = rlen; + wsi->client_rx_avail = 0; + + /* + * server may insist on transfer-encoding: chunked, + * so http client must deal with it + */ +spin_chunks: + while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { + switch (wsi->chunk_parser) { + case ELCP_HEX: + if ((*buf)[0] == '\x0d') { + wsi->chunk_parser = ELCP_CR; + break; + } + n = char_to_hex((*buf)[0]); + if (n < 0) { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_remaining <<= 4; + wsi->chunk_remaining |= n; + break; + case ELCP_CR: + if ((*buf)[0] != '\x0a') { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_parser = ELCP_CONTENT; + lwsl_info("chunk %d\n", wsi->chunk_remaining); + if (wsi->chunk_remaining) + break; + lwsl_info("final chunk\n"); + goto completed; + + case ELCP_CONTENT: + break; + + case ELCP_POST_CR: + if ((*buf)[0] != '\x0d') { + lwsl_debug("chunking failure\n"); + + return -1; + } + + wsi->chunk_parser = ELCP_POST_LF; + break; + + case ELCP_POST_LF: + if ((*buf)[0] != '\x0a') + return -1; + + wsi->chunk_parser = ELCP_HEX; + wsi->chunk_remaining = 0; + break; + } + (*buf)++; + (*len)--; + } + + if (wsi->chunked && !wsi->chunk_remaining) + return 0; + + if (wsi->http.rx_content_remain && + wsi->http.rx_content_remain < (unsigned int)*len) + n = (int)wsi->http.rx_content_remain; + else + n = *len; + + if (wsi->chunked && wsi->chunk_remaining && + wsi->chunk_remaining < n) + n = wsi->chunk_remaining; + +#ifdef LWS_WITH_HTTP_PROXY + /* hubbub */ + if (wsi->perform_rewrite) + lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n); + else +#endif + { + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, + wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi_eff->user_space, *buf, n)) { + lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n", + __func__); + + return -1; + } + } + + if (wsi->chunked && wsi->chunk_remaining) { + (*buf) += n; + wsi->chunk_remaining -= n; + *len -= n; + } + + if (wsi->chunked && !wsi->chunk_remaining) + wsi->chunk_parser = ELCP_POST_CR; + + if (wsi->chunked && *len) + goto spin_chunks; + + if (wsi->chunked) + return 0; + + /* if we know the content length, decrement the content remaining */ + if (wsi->http.rx_content_length > 0) + wsi->http.rx_content_remain -= n; + + // lwsl_notice("rx_content_remain %lld, rx_content_length %lld\n", + // wsi->http.rx_content_remain, wsi->http.rx_content_length); + + if (wsi->http.rx_content_remain || !wsi->http.rx_content_length) + return 0; + +completed: + + if (lws_http_transaction_completed_client(wsi)) { + lwsl_notice("%s: transaction completed says -1\n", __func__); + return -1; + } + + return 0; +} diff --git a/lib/header.c b/lib/roles/http/header.c similarity index 97% rename from lib/header.c rename to lib/roles/http/header.c index 1be0f7b9..5a2900d5 100644 --- a/lib/header.c +++ b/lib/roles/http/header.c @@ -38,7 +38,7 @@ lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, unsigned char **p, unsigned char *end) { #ifdef LWS_WITH_HTTP2 - if (lwsi_role_h2(wsi)) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return lws_add_http2_header_by_name(wsi, name, value, length, p, end); #else @@ -66,7 +66,7 @@ int lws_finalize_http_header(struct lws *wsi, unsigned char **p, unsigned char *end) { #ifdef LWS_WITH_HTTP2 - if (lwsi_role_h2(wsi)) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return 0; #else (void)wsi; @@ -105,7 +105,7 @@ lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, { const unsigned char *name; #ifdef LWS_WITH_HTTP2 - if (lwsi_role_h2(wsi)) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return lws_add_http2_header_by_token(wsi, token, value, length, p, end); #endif @@ -201,7 +201,7 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code, #endif #ifdef LWS_WITH_HTTP2 - if (lwsi_role_h2(wsi)) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return lws_add_http2_header_status(wsi, code, p, end); #endif if (code >= 400 && code < (400 + ARRAY_SIZE(err400))) diff --git a/lib/lextable-strings.h b/lib/roles/http/lextable-strings.h similarity index 100% rename from lib/lextable-strings.h rename to lib/roles/http/lextable-strings.h diff --git a/lib/lextable.h b/lib/roles/http/lextable.h similarity index 100% rename from lib/lextable.h rename to lib/roles/http/lextable.h diff --git a/lib/minilex.c b/lib/roles/http/minilex.c similarity index 100% rename from lib/minilex.c rename to lib/roles/http/minilex.c diff --git a/lib/server/access-log.c b/lib/roles/http/server/access-log.c similarity index 100% rename from lib/server/access-log.c rename to lib/roles/http/server/access-log.c diff --git a/lib/server/fops-zip.c b/lib/roles/http/server/fops-zip.c similarity index 100% rename from lib/server/fops-zip.c rename to lib/roles/http/server/fops-zip.c diff --git a/lib/server/lejp-conf.c b/lib/roles/http/server/lejp-conf.c similarity index 99% rename from lib/server/lejp-conf.c rename to lib/roles/http/server/lejp-conf.c index 01ddcbe3..7a5799d2 100644 --- a/lib/server/lejp-conf.c +++ b/lib/roles/http/server/lejp-conf.c @@ -349,7 +349,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->info->protocols = a->protocols; a->info->extensions = a->extensions; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES256-GCM-SHA384:" "DHE-RSA-AES256-GCM-SHA384:" @@ -449,7 +449,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) } a->any_vhosts = 1; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (a->enable_client_ssl) { const char *cert_filepath = a->info->client_ssl_cert_filepath; const char *private_key_filepath = a->info->client_ssl_private_key_filepath; @@ -593,7 +593,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_KEEPALIVE_TIMEOUT: a->info->keepalive_timeout = atoi(ctx->buf); return 0; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) case LEJPVP_CLIENT_CIPHERS: a->info->client_ssl_cipher_list = a->p; break; @@ -673,7 +673,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_ENABLE_CLIENT_SSL: a->enable_client_ssl = arg_to_bool(ctx->buf); return 0; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) case LEJPVP_CLIENT_SSL_KEY: a->info->client_ssl_private_key_filepath = a->p; break; diff --git a/lib/server/lws-spa.c b/lib/roles/http/server/lws-spa.c similarity index 100% rename from lib/server/lws-spa.c rename to lib/roles/http/server/lws-spa.c diff --git a/lib/server/parsers.c b/lib/roles/http/server/parsers.c similarity index 61% rename from lib/server/parsers.c rename to lib/roles/http/server/parsers.c index 7577d753..6adb522d 100644 --- a/lib/server/parsers.c +++ b/lib/roles/http/server/parsers.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green + * Copyright (C) 2010-2018 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,7 +22,7 @@ #include "private-libwebsockets.h" static const unsigned char lextable[] = { - #include "lextable.h" + #include "../lextable.h" }; #define FAIL_CHAR 0x08 @@ -1040,8 +1040,8 @@ nope: * If it's h1, server needs to look out for unknown * methods... */ - if (ah->lextable_pos < 0 && - lwsi_role(wsi) == LWSI_ROLE_H1_SERVER) { + if (ah->lextable_pos < 0 && lwsi_role_h1(wsi) && + lwsi_role_server(wsi)) { /* this is not a header we know about */ for (m = 0; m < ARRAY_SIZE(methods); m++) if (ah->frag_index[methods[m]]) { @@ -1197,642 +1197,3 @@ forbid: return -1; } -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) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - - if (wsi->ws->rx_draining_ext) - return; - - lwsl_ext("%s: RX EXT DRAINING: Adding to list\n", __func__); - - wsi->ws->rx_draining_ext = 1; - wsi->ws->rx_draining_ext_list = pt->rx_draining_ext_list; - pt->rx_draining_ext_list = wsi; -} - -void -lws_remove_wsi_from_draining_ext_list(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - struct lws **w = &pt->rx_draining_ext_list; - - if (!wsi->ws->rx_draining_ext) - return; - - lwsl_ext("%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; -} - -/* - * client-parser.c: lws_client_rx_sm() needs to be roughly kept in - * sync with changes here, esp related to ext draining - */ - -int -lws_rx_sm(struct lws *wsi, unsigned char c) -{ - int callback_action = LWS_CALLBACK_RECEIVE; - int ret = 0, rx_draining_ext = 0; - struct lws_tokens eff_buf; -#if !defined(LWS_WITHOUT_EXTENSIONS) - int n; -#endif - - eff_buf.token = NULL; - eff_buf.token_len = 0; - if (wsi->socket_is_permanently_unusable) - return -1; - - switch (wsi->lws_rx_parse_state) { - case LWS_RXPS_NEW: - if (wsi->ws->rx_draining_ext) { - eff_buf.token = NULL; - eff_buf.token_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; - } - 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_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); - - switch (wsi->ws->opcode) { - case LWSWSOPC_TEXT_FRAME: - case LWSWSOPC_BINARY_FRAME: - wsi->ws->rsv_first_msg = (c & 0x70); - wsi->ws->frame_is_binary = - wsi->ws->opcode == LWSWSOPC_BINARY_FRAME; - wsi->ws->first_fragment = 1; - 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; - } - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; - 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: - assert(wsi->ws->rx_ubuf); - - if (wsi->ws->rx_draining_ext) - goto drain_extension; - - if (wsi->ws->rx_ubuf_head + LWS_PRE >= - wsi->ws->rx_ubuf_alloc) { - lwsl_err("Attempted overflow \n"); - return -1; - } - 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]; - - 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: - /* - * 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: - - /* 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 (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("passing opc %x up to exts\n", - wsi->ws->opcode); - /* - * It's something special we can't understand here. - * Pass the payload up to the extension's parsing - * state machine. - */ - - eff_buf.token = &wsi->ws->rx_ubuf[LWS_PRE]; - eff_buf.token_len = wsi->ws->rx_ubuf_head; - - if (lws_ext_cb_active(wsi, - LWS_EXT_CB_EXTENDED_PAYLOAD_RX, - &eff_buf, 0) <= 0) - /* not handle or fail */ - lwsl_ext("ext opc opcode 0x%x unknown\n", - wsi->ws->opcode); - - wsi->ws->rx_ubuf_head = 0; - return 0; - } - - /* - * 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 - */ - - eff_buf.token = &wsi->ws->rx_ubuf[LWS_PRE]; - eff_buf.token_len = wsi->ws->rx_ubuf_head; - - if (wsi->ws->opcode == LWSWSOPC_PONG && !eff_buf.token_len) - goto already_done; - -drain_extension: - lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); - - if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || - lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) - goto already_done; -#if !defined(LWS_WITHOUT_EXTENSIONS) - n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); -#endif - /* - * eff_buf may be pointing somewhere completely different now, - * it's the output - */ - wsi->ws->first_fragment = 0; -#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 (rx_draining_ext && eff_buf.token_len == 0) - goto already_done; - - if ( -#if !defined(LWS_WITHOUT_EXTENSIONS) - n && -#endif - eff_buf.token_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 (eff_buf.token_len > 0 || - callback_action == LWS_CALLBACK_RECEIVE_PONG) { - eff_buf.token[eff_buf.token_len] = '\0'; - - if (wsi->protocol->callback) { - 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, - eff_buf.token, - eff_buf.token_len); - } - else - lwsl_err("No callback on payload spill!\n"); - } - -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; -} - -/* Once we reach LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED, we know how much - * to expect in that state and can deal with it in bulk more efficiently. - */ - -int -lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, - size_t *len) -{ - unsigned char *buffer = *buf, mask[4]; - int buffer_size, n; - unsigned int avail; - char *rx_ubuf; - - if (wsi->protocol->rx_buffer_size) - buffer_size = (int)wsi->protocol->rx_buffer_size; - else - buffer_size = wsi->context->pt_serv_buf_size; - avail = buffer_size - wsi->ws->rx_ubuf_head; - - /* 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); - - /* we want to leave 1 byte for the parser to handle properly */ - if (avail <= 1) - return 0; - - avail--; - rx_ubuf = wsi->ws->rx_ubuf + LWS_PRE + wsi->ws->rx_ubuf_head; - if (wsi->ws->all_zero_nonce) - memcpy(rx_ubuf, buffer, avail); - else { - - 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--) { - *(rx_ubuf++) = *(buffer++) ^ mask[0]; - *(rx_ubuf++) = *(buffer++) ^ mask[1]; - *(rx_ubuf++) = *(buffer++) ^ mask[2]; - *(rx_ubuf++) = *(buffer++) ^ mask[3]; - } - /* and the remaining bytes bytewise */ - for (n = 0; n < (int)(avail & 3); n++) - *(rx_ubuf++) = *(buffer++) ^ mask[n]; - - wsi->ws->mask_idx = (wsi->ws->mask_idx + avail) & 3; - } - - (*buf) += avail; - wsi->ws->rx_ubuf_head += avail; - wsi->ws->rx_packet_length -= avail; - *len -= avail; - - return avail; -} diff --git a/lib/server/ranges.c b/lib/roles/http/server/ranges.c similarity index 100% rename from lib/server/ranges.c rename to lib/roles/http/server/ranges.c diff --git a/lib/server/rewrite.c b/lib/roles/http/server/rewrite.c similarity index 100% rename from lib/server/rewrite.c rename to lib/roles/http/server/rewrite.c diff --git a/lib/server/server.c b/lib/roles/http/server/server.c similarity index 73% rename from lib/server/server.c rename to lib/roles/http/server/server.c index a99b1149..42398222 100644 --- a/lib/server/server.c +++ b/lib/roles/http/server/server.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green + * Copyright (C) 2010-2018 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -38,11 +38,9 @@ int lws_context_init_server(struct lws_context_creation_info *info, struct lws_vhost *vhost) { -#if LWS_POSIX int n, opt = 1, limit = 1; #if defined(__linux__) && defined(SO_REUSEPORT) int n1; -#endif #endif lws_sockfd_type sockfd; struct lws_vhost *vh; @@ -133,7 +131,6 @@ done_list: } } -#if LWS_POSIX (void)n; #if defined(__linux__) limit = vhost->context->count_threads; @@ -153,11 +150,10 @@ done_list: sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == LWS_SOCK_INVALID) { -#endif /* LWS_POSIX */ lwsl_err("ERROR opening socket\n"); return 1; } -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) #if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE) /* * only accept that we are the only listener on the port @@ -219,7 +215,6 @@ done_list: #endif lws_plat_set_socket_options(vhost, sockfd); -#if LWS_POSIX is = lws_socket_bind(vhost, sockfd, vhost->listen_port, vhost->iface); /* * There is a race where the network device may come up and then @@ -234,7 +229,6 @@ done_list: vhost->listen_port = is; lwsl_debug("%s: lws_socket_bind says %d\n", __func__, is); -#endif wsi = lws_zalloc(sizeof(struct lws), "listen wsi"); if (wsi == NULL) { @@ -243,7 +237,7 @@ done_list: } wsi->context = vhost->context; wsi->desc.sockfd = sockfd; - lwsi_set_role(wsi, LWSI_ROLE_LISTEN_SOCKET); + lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_listen); wsi->protocol = vhost->protocols; wsi->tsi = m; wsi->vhost = vhost; @@ -262,7 +256,6 @@ done_list: vhost->context->count_wsi_allocated++; vhost->lserv_wsi = wsi; -#if LWS_POSIX n = listen(wsi->desc.sockfd, LWS_SOMAXCONN); if (n < 0) { lwsl_err("listen failed with error %d\n", LWS_ERRNO); @@ -272,7 +265,7 @@ done_list: goto bail; } } /* for each thread able to independently listen */ -#endif + if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { #ifdef LWS_WITH_UNIX_SOCK if (LWS_UNIX_SOCK_ENABLED(vhost)) @@ -459,7 +452,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, char path[256], sym[512]; unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p; unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE; -#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(WIN32) && !defined(LWS_WITH_ESP32) size_t len; #endif int n; @@ -521,7 +514,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, wsi->http.fop_fd->mod_time = (uint32_t)st.st_mtime; fflags |= LWS_FOP_FLAG_MOD_TIME_VALID; -#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(WIN32) && !defined(LWS_WITH_ESP32) if ((S_IFMT & st.st_mode) == S_IFLNK) { len = readlink(path, sym, sizeof(sym) - 1); if (len) { @@ -692,7 +685,6 @@ lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) return hit; } -#if LWS_POSIX #if !defined(LWS_WITH_ESP32) static int lws_find_string_in_file(const char *filename, const char *string, int stringlen) @@ -737,7 +729,6 @@ lws_find_string_in_file(const char *filename, const char *string, int stringlen) return hit; } -#endif static int lws_unauthorised_basic_auth(struct lws *wsi) @@ -801,245 +792,6 @@ int lws_clean_url(char *p) return 0; } -static 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 LWS_POSIX && !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_OPENSSL_SUPPORT - wsi->ssl, -#else - NULL, -#endif - wsi->h2_stream_carries_ws)) - return 1; - - lwsl_debug("ws established\n"); - - return 0; -} - -static 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. - */ - - lwsl_debug("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", - __func__, wsi, wsi->ah->rxpos, wsi->ah->rxlen); - lws_pt_lock(pt, __func__); - - if (wsi->h2_stream_carries_ws) - lws_role_transition(wsi, LWSI_ROLE_WS2_SERVER, LRS_ESTABLISHED); - else - lws_role_transition(wsi, LWSI_ROLE_WS1_SERVER, LRS_ESTABLISHED); - /* - * Because rxpos/rxlen shows something in the ah, we will get - * service guaranteed next time around the event loop - */ - - lws_pt_unlock(pt); - - lws_server_init_wsi_for_ws(wsi); - lwsl_parser("accepted v%02d connection\n", - wsi->ws->ietf_spec_revision); - - /* !!! drop ah unreservedly after ESTABLISHED */ - if (wsi->ah->rxpos == wsi->ah->rxlen ) { - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 1); - } - - return 0; -} - - static const unsigned char methods[] = { WSI_TOKEN_GET_URI, WSI_TOKEN_POST_URI, @@ -1096,9 +848,6 @@ lws_http_action(struct lws *wsi) unsigned int n; char http_version_str[10]; char http_conn_str[20]; -#if defined(LWS_WITH_HTTP2) - char *p; -#endif int http_version_len; char *uri_ptr = NULL, *s; int uri_len = 0, meth; @@ -1121,36 +870,15 @@ lws_http_action(struct lws *wsi) lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], meth, uri_ptr); -#if defined(LWS_WITH_HTTP2) - /* - * with H2 there's also a way to upgrade a stream to something - * else... :method is CONNECT and :protocol says the name of - * the new protocol we want to carry. We have to have sent a - * SETTINGS saying that we support it though. - */ - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); - if (wsi->vhost->set.s[H2SET_ENABLE_CONNECT_PROTOCOL] && - wsi->http2_substream && p && !strcmp(p, "CONNECT")) { - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_COLON_PROTOCOL); - if (p && !strcmp(p, "websocket")) { - struct lws *nwsi = lws_get_network_wsi(wsi); - - wsi->vhost->conn_stats.ws_upg++; - lwsl_info("Upgrade h2 to ws\n"); - wsi->h2_stream_carries_ws = 1; - nwsi->ws_over_h2_count++; - if (lws_process_ws_upgrade(wsi)) - goto bail_nuke_ah; - - if (nwsi->ws_over_h2_count == 1) - lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0); - - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - lwsl_info("Upgraded h2 to ws OK\n"); - return 0; - } + if (wsi->role_ops && wsi->role_ops->check_upgrades) + switch (wsi->role_ops->check_upgrades(wsi)) { + case LWS_UPG_RET_DONE: + return 0; + case LWS_UPG_RET_CONTINUE: + break; + case LWS_UPG_RET_BAIL: + goto bail_nuke_ah; } -#endif if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; @@ -1222,7 +950,7 @@ lws_http_action(struct lws *wsi) */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); -#ifdef LWS_OPENSSL_SUPPORT +#ifdef LWS_WITH_TLS if (wsi->redirect_to_https) { /* * we accepted http:// only so we could redirect to @@ -1330,7 +1058,6 @@ lws_http_action(struct lws *wsi) return lws_http_transaction_completed(wsi); } -#if LWS_POSIX /* basic auth? */ if (hit->basic_auth_login_file) { @@ -1374,7 +1101,6 @@ lws_http_action(struct lws *wsi) /* accept the auth */ } -#endif #if defined(LWS_WITH_HTTP_PROXY) /* @@ -1615,12 +1341,10 @@ bail_nuke_ah: return 1; -#if LWS_POSIX transaction_result_n: lws_return_http_status(wsi, n, NULL); return lws_http_transaction_completed(wsi); -#endif } int @@ -1645,7 +1369,7 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) } while (len) { - if (!lwsi_role_http_server(wsi)) { + if (!lwsi_role_server(wsi) || !lwsi_role_http(wsi)) { lwsl_err("%s: bad wsi role 0x%x\n", __func__, lwsi_role(wsi)); goto bail_nuke_ah; @@ -1653,6 +1377,7 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) i = (int)len; m = lws_parse(wsi, *buf, &i); + lwsl_info("%s: parsed count %d\n", __func__, (int)len - i); (*buf) += (int)len - i; len = i; if (m) { @@ -1676,8 +1401,8 @@ raw_transition: goto bail_nuke_ah; lws_header_table_force_to_detachable_state(wsi); - lws_role_transition(wsi, LWSI_ROLE_RAW_SOCKET, - LRS_ESTABLISHED); + lws_role_transition(wsi, 0, LRS_ESTABLISHED, + &role_ops_raw_skt); lws_header_table_detach(wsi, 1); if (m == 2 && (wsi->protocol->callback)(wsi, @@ -1708,7 +1433,7 @@ raw_transition: } else lwsl_info("no host\n"); - if (lwsi_role(wsi) != LWSI_ROLE_H2_SERVER) { + if (!lwsi_role_h2(wsi) || !lwsi_role_server(wsi)) { wsi->vhost->conn_stats.h1_trans++; if (!wsi->conn_stat_done) { wsi->vhost->conn_stats.h1_conn++; @@ -1930,7 +1655,7 @@ lws_create_new_server_wsi(struct lws_vhost *vhost) lwsi_set_state(new_wsi, LRS_UNCONNECTED); new_wsi->hdr_parsing_completed = 0; -#ifdef LWS_OPENSSL_SUPPORT +#ifdef LWS_WITH_TLS new_wsi->use_ssl = LWS_SSL_ENABLED(vhost); #endif @@ -2021,7 +1746,7 @@ lws_http_transaction_completed(struct lws *wsi) if (wsi->ah->rxpos == wsi->ah->rxlen && !wsi->preamble_rx) { lws_header_table_force_to_detachable_state(wsi); lws_header_table_detach(wsi, 1); -#ifdef LWS_OPENSSL_SUPPORT +#ifdef LWS_WITH_TLS /* * additionally... if we are hogging an SSL instance * with no pending pipelined headers (or ah now), and @@ -2134,7 +1859,8 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, new_wsi->desc.sockfd = LWS_SOCK_INVALID; lwsl_debug("binding to %s\n", new_wsi->protocol->name); lws_bind_protocol(new_wsi, new_wsi->protocol); - lws_role_transition(new_wsi, LWSI_ROLE_WS1_SERVER, LRS_ESTABLISHED); + lws_role_transition(new_wsi, LWSIFR_SERVER, + LRS_ESTABLISHED, &role_ops_ws); /* allocate the ws struct for the wsi */ new_wsi->ws = lws_zalloc(sizeof(*new_wsi->ws), "ws struct"); if (!new_wsi->ws) { @@ -2146,14 +1872,16 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, return new_wsi; } } else - if (type & LWS_ADOPT_HTTP) /* he will transition later */ + if (type & LWS_ADOPT_HTTP) {/* he will transition later */ new_wsi->protocol = &vh->protocols[vh->default_protocol_index]; + new_wsi->role_ops = &role_ops_h1; + } else { /* this is the only time he will transition */ lws_bind_protocol(new_wsi, &vh->protocols[vh->raw_protocol_index]); - lws_role_transition(new_wsi, LWSI_ROLE_RAW_SOCKET, - LRS_ESTABLISHED); + lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, + &role_ops_raw_skt); } if (type & LWS_ADOPT_SOCKET) { /* socket desc */ @@ -2197,21 +1925,23 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, /* non-SSL */ if (!(type & LWS_ADOPT_HTTP)) { if (!(type & LWS_ADOPT_SOCKET)) - lwsi_set_role(new_wsi, LWSI_ROLE_RAW_FILE); + lws_role_transition(new_wsi, 0, LRS_UNCONNECTED, + &role_ops_raw_file); else - lwsi_set_role(new_wsi, LWSI_ROLE_RAW_SOCKET); - } else { - lwsi_set_role(new_wsi, LWSI_ROLE_H1_SERVER); - lwsi_set_state(new_wsi, LRS_HEADERS); - } + lws_role_transition(new_wsi, 0, LRS_UNCONNECTED, + &role_ops_raw_skt); + } else + lws_role_transition(new_wsi, LWSIFR_SERVER, + LRS_HEADERS, &role_ops_h1); } else { /* SSL */ if (!(type & LWS_ADOPT_HTTP)) - lwsi_set_role(new_wsi, LWSI_ROLE_RAW_SOCKET); + lws_role_transition(new_wsi, 0, LRS_SSL_INIT, + &role_ops_raw_skt); else - lwsi_set_role(new_wsi, LWSI_ROLE_H1_SERVER); + lws_role_transition(new_wsi, LWSIFR_SERVER, + LRS_SSL_INIT, &role_ops_h1); - lwsi_set_state(new_wsi, LRS_SSL_INIT); ssl = 1; } @@ -2381,468 +2111,6 @@ lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, readbuf, len); } -LWS_VISIBLE int -lws_server_socket_service(struct lws_context *context, struct lws *wsi, - struct lws_pollfd *pollfd) -{ - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - lws_sockfd_type accept_fd = LWS_SOCK_INVALID; - struct allocated_headers *ah; - lws_sock_file_fd_type fd; - int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL; -#if LWS_POSIX - struct sockaddr_storage cli_addr; - socklen_t clilen; -#endif - int n, len; - - switch (lwsi_role(wsi)) { - - case LWSI_ROLE_H1_SERVER: - case LWSI_ROLE_H2_SERVER: - case LWSI_ROLE_RAW_SOCKET: - case LWSI_ROLE_RAW_FILE: - - /* handle http headers coming in */ - - /* pending truncated sends have uber priority */ - - if (wsi->trunc_len) { - if (!(pollfd->revents & LWS_POLLOUT)) - break; - - if (lws_issue_raw(wsi, wsi->trunc_alloc + - wsi->trunc_offset, - wsi->trunc_len) < 0) - goto fail; - /* - * we can't afford to allow input processing to send - * something new, so spin around he event loop until - * he doesn't have any partials - */ - break; - } - - if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) - goto try_pollout; - - /* any incoming data ready? */ - - if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) - goto try_pollout; - - /* - * If we previously just did POLLIN when IN and OUT were - * signalled (because POLLIN processing may have used up - * the POLLOUT), don't let that happen twice in a row... - * next time we see the situation favour POLLOUT - */ - if (wsi->favoured_pollin && - (pollfd->revents & pollfd->events & LWS_POLLOUT)) { - lwsl_notice("favouring pollout\n"); - wsi->favoured_pollin = 0; - goto try_pollout; - } - - /* - * We haven't processed that the tunnel is set up yet, so - * defer reading - */ - if (lwsi_state(wsi) == LRS_SSL_ACK_PENDING) - break; - - /* these states imply we MUST have an ah attached */ - - if (!lwsi_role_raw(wsi) && - (lwsi_state(wsi) == LRS_ESTABLISHED || - lwsi_state(wsi) == LRS_ISSUING_FILE || - lwsi_state(wsi) == LRS_HEADERS)) { - if (!wsi->ah) { - /* no autoservice beacuse we will do it next */ - if (lws_header_table_attach(wsi, 0)) { - lwsl_info("wsi %p: ah get fail\n", wsi); - goto try_pollout; - } - } - ah = wsi->ah; - - assert(ah->rxpos <= ah->rxlen); - /* if nothing in ah rx buffer, get some fresh rx */ - if (ah->rxpos == ah->rxlen) { - - if (wsi->preamble_rx) { - memcpy(ah->rx, wsi->preamble_rx, wsi->preamble_rx_len); - lws_free_set_NULL(wsi->preamble_rx); - ah->rxlen = wsi->preamble_rx_len; - wsi->preamble_rx_len = 0; - } else { - ah->rxlen = lws_ssl_capable_read(wsi, ah->rx, - sizeof(ah->rx)); - } - - ah->rxpos = 0; - switch (ah->rxlen) { - case 0: - lwsl_info("%s: read 0 len a\n", - __func__); - wsi->seen_zero_length_recv = 1; - lws_change_pollfd(wsi, LWS_POLLIN, 0); - goto try_pollout; - //goto fail; - - case LWS_SSL_CAPABLE_ERROR: - goto fail; - case LWS_SSL_CAPABLE_MORE_SERVICE: - ah->rxlen = ah->rxpos = 0; - goto try_pollout; - } - } - - if (!(ah->rxpos != ah->rxlen && ah->rxlen)) { - lwsl_err("%s: assert: rxpos %d, rxlen %d\n", - __func__, ah->rxpos, ah->rxlen); - - assert(0); - } - - /* just ignore incoming if waiting for close */ - if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE || - lwsi_state(wsi) == LRS_ISSUING_FILE) - goto try_pollout; - - /* - * otherwise give it to whoever wants it - * according to the connection state - */ - - n = lws_read(wsi, ah->rx + ah->rxpos, - ah->rxlen - ah->rxpos); - if (n < 0) /* we closed wsi */ - return 1; - - if (!wsi->ah) - break; - if ( wsi->ah->rxlen) - wsi->ah->rxpos += n; - - lwsl_debug("%s: wsi %p: ah read rxpos %d, rxlen %d\n", - __func__, wsi, wsi->ah->rxpos, - wsi->ah->rxlen); - - if (lws_header_table_is_in_detachable_state(wsi) && - lwsi_role_raw(wsi)) // ??? - lws_header_table_detach(wsi, 1); - - break; - } - - if (wsi->preamble_rx && wsi->preamble_rx_len) { - memcpy(pt->serv_buf, wsi->preamble_rx, wsi->preamble_rx_len); - lws_free_set_NULL(wsi->preamble_rx); - len = wsi->preamble_rx_len; - lwsl_debug("bringing %d out of stash\n", wsi->preamble_rx_len); - wsi->preamble_rx_len = 0; - } else { - - /* - * ... in the case of pipelined HTTP, this may be - * POST data followed by next headers... - */ - - len = lws_ssl_capable_read(wsi, pt->serv_buf, - context->pt_serv_buf_size); - lwsl_debug("%s: wsi %p read %d (wsistate 0x%x)\n", - __func__, wsi, len, wsi->wsistate); - switch (len) { - case 0: - lwsl_info("%s: read 0 len b\n", __func__); - - /* fallthru */ - case LWS_SSL_CAPABLE_ERROR: - goto fail; - case LWS_SSL_CAPABLE_MORE_SERVICE: - goto try_pollout; - } - - if (len < 0) /* coverity */ - goto fail; - } - if (lwsi_role_raw(wsi)) { - n = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_RAW_RX, - wsi->user_space, - pt->serv_buf, len); - if (n < 0) { - lwsl_info("LWS_CALLBACK_RAW_RX_fail\n"); - goto fail; - } - goto try_pollout; - } - - /* just ignore incoming if waiting for close */ - if (lwsi_state(wsi) != LRS_FLUSHING_BEFORE_CLOSE && - lwsi_state(wsi) != LRS_ISSUING_FILE) { - /* - * this may want to send - * (via HTTP callback for example) - * - * returns number of bytes used - */ - - n = lws_read(wsi, pt->serv_buf, len); - if (n < 0) /* we closed wsi */ - return 1; - - if (n != len) { - if (wsi->preamble_rx) { - lwsl_err("DISCARDING %d (ah %p)\n", len - n, wsi->ah); - - goto fail; - } - assert(n < len); - wsi->preamble_rx = lws_malloc(len - n, "preamble_rx"); - if (!wsi->preamble_rx) { - lwsl_err("OOM\n"); - goto fail; - } - memcpy(wsi->preamble_rx, pt->serv_buf + n, len - n); - wsi->preamble_rx_len = (int)len - n; - lwsl_debug("stashed %d\n", (int)wsi->preamble_rx_len); - } - - /* - * he may have used up the - * writability above, if we will defer POLLOUT - * processing in favour of POLLIN, note it - */ - if (pollfd->revents & LWS_POLLOUT) - wsi->favoured_pollin = 1; - break; - } - /* - * he may have used up the - * writability above, if we will defer POLLOUT - * processing in favour of POLLIN, note it - */ - if (pollfd->revents & LWS_POLLOUT) - wsi->favoured_pollin = 1; - -try_pollout: - - /* this handles POLLOUT for http serving fragments */ - - if (!(pollfd->revents & LWS_POLLOUT)) - break; - - /* one shot */ - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_notice("%s a\n", __func__); - goto fail; - } - - /* clear back-to-back write detection */ - wsi->could_have_pending = 0; - - if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) { - lwsl_debug("%s: LRS_DEFERRING_ACTION now writable\n", - __func__); - - if (wsi->ah) - lwsl_debug(" existing ah rxpos %d / rxlen %d\n", - wsi->ah->rxpos, wsi->ah->rxlen); - lwsi_set_state(wsi, LRS_ESTABLISHED); - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - goto fail; - } - } - - if (lwsi_role_raw(wsi)) { - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_WRITEABLE_CB, 1); -#if defined(LWS_WITH_STATS) - if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - - wsi->active_writable_req_us; - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, - LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); - wsi->active_writable_req_us = 0; - } -#endif - n = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_RAW_WRITEABLE, - wsi->user_space, NULL, 0); - if (n < 0) { - lwsl_info("writeable_fail\n"); - goto fail; - } - break; - } - - if (!wsi->hdr_parsing_completed) - break; - - if (lwsi_state(wsi) != LRS_ISSUING_FILE) { - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_WRITEABLE_CB, 1); -#if defined(LWS_WITH_STATS) - if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - - wsi->active_writable_req_us; - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, - LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); - wsi->active_writable_req_us = 0; - } -#endif - - n = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_HTTP_WRITEABLE, - wsi->user_space, NULL, 0); - if (n < 0) { - lwsl_info("writeable_fail\n"); - goto fail; - } - break; - } - - /* >0 == completion, <0 == error - * - * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when - * it's done. That's the case even if we just completed the - * send, so wait for that. - */ - n = lws_serve_http_file_fragment(wsi); - if (n < 0) - goto fail; - - break; - - case LWSI_ROLE_LISTEN_SOCKET: - -#if LWS_POSIX - /* pollin means a client has connected to us then - * pollout is a hack on esp32 for background accepts signalling - * they completed - * */ - - do { - if (!(pollfd->revents & (LWS_POLLIN |LWS_POLLOUT)) || - !(pollfd->events & LWS_POLLIN)) - break; - -#ifdef LWS_OPENSSL_SUPPORT - /* - * can we really accept it, with regards to SSL limit? - * another vhost may also have had POLLIN on his - * listener this round and used it up already - */ - if (wsi->vhost->use_ssl && - context->simultaneous_ssl_restriction && - context->simultaneous_ssl == - context->simultaneous_ssl_restriction) - /* - * no... ignore it, he won't come again until - * we are below the simultaneous_ssl_restriction - * limit and POLLIN is enabled on him again - */ - break; -#endif - /* listen socket got an unencrypted connection... */ - - clilen = sizeof(cli_addr); - lws_latency_pre(context, wsi); - - /* - * We cannot identify the peer who is in the listen - * socket connect queue before we accept it; even if - * we could, not accepting it due to PEER_LIMITS would - * block the connect queue for other legit peers. - */ - accept_fd = accept((int)pollfd->fd, - (struct sockaddr *)&cli_addr, - &clilen); - lws_latency(context, wsi, "listener accept", - (int)accept_fd, accept_fd != LWS_SOCK_INVALID); - if (accept_fd == LWS_SOCK_INVALID) { - if (LWS_ERRNO == LWS_EAGAIN || - LWS_ERRNO == LWS_EWOULDBLOCK) { - break; - } - lwsl_err("ERROR on accept: %s\n", - strerror(LWS_ERRNO)); - break; - } - - lws_plat_set_socket_options(wsi->vhost, accept_fd); - -#if defined(LWS_WITH_IPV6) - lwsl_debug("accepted new conn port %u on fd=%d\n", - ((cli_addr.ss_family == AF_INET6) ? - ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : - ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), - accept_fd); -#else - lwsl_debug("accepted new conn port %u on fd=%d\n", - ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), - accept_fd); -#endif - -#else - /* not very beautiful... */ - accept_fd = (lws_sockfd_type)pollfd; -#endif - /* - * look at who we connected to and give user code a - * chance to reject based on client IP. There's no - * protocol selected yet so we issue this to - * protocols[0] - */ - if ((wsi->vhost->protocols[0].callback)(wsi, - LWS_CALLBACK_FILTER_NETWORK_CONNECTION, - NULL, - (void *)(lws_intptr_t)accept_fd, 0)) { - lwsl_debug("Callback denied net connection\n"); - compatible_close(accept_fd); - break; - } - - if (!(wsi->vhost->options & LWS_SERVER_OPTION_ONLY_RAW)) - opts |= LWS_ADOPT_HTTP; - else - opts = LWS_ADOPT_SOCKET; - - fd.sockfd = accept_fd; - if (!lws_adopt_descriptor_vhost(wsi->vhost, opts, fd, - NULL, NULL)) - /* already closed cleanly as necessary */ - return 1; - -#if LWS_POSIX - } while (pt->fds_count < context->fd_limit_per_thread - 1 && - lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0); -#endif - return 0; - - default: - break; - } - - if (!lws_server_socket_service_ssl(wsi, accept_fd)) - return 0; - -fail: - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "server socket svc fail"); - - return 1; -} - LWS_VISIBLE int lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, const char *other_headers, int other_headers_len) @@ -3069,70 +2337,6 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, return 0; } -int -lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len) -{ - int m; - - lwsl_parser("%s: received %d byte packet\n", __func__, (int)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) { - lws_rxflow_cache(wsi, *buf, 0, (int)len); - lwsl_parser("%s: cached %ld\n", __func__, (long)len); - return 1; - } - - if (wsi->ws->rx_draining_ext) { - m = lws_rx_sm(wsi, 0); - if (m < 0) - return -1; - continue; - } - - /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) { - wsi->rxflow_pos++; - if (wsi->rxflow_pos > wsi->rxflow_len) - assert(0); - } - - /* consume payload bytes efficiently */ - if (wsi->lws_rx_parse_state == - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED) { - m = lws_payload_until_length_exhausted(wsi, buf, &len); - if (wsi->rxflow_buffer) - wsi->rxflow_pos += m; - } - - /* process the byte */ - m = lws_rx_sm(wsi, *(*buf)++); - if (m < 0) - return -1; - len--; - - if (wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { - lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi); - lws_free_set_NULL(wsi->rxflow_buffer); - /* having drained the rxflow buffer, can rearm POLLIN */ -#ifdef LWS_NO_SERVER - m = -#endif - __lws_rx_flow_control(wsi); - /* m ignored, needed for NO_SERVER case */ - } - } - - lwsl_parser("%s: exit with %d unused\n", __func__, (int)len); - - return 0; -} - LWS_VISIBLE void lws_server_get_canonical_hostname(struct lws_context *context, struct lws_context_creation_info *info) @@ -3140,7 +2344,7 @@ lws_server_get_canonical_hostname(struct lws_context *context, if (lws_check_opt(info->options, LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME)) return; -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) /* find canonical hostname */ gethostname((char *)context->canonical_hostname, sizeof(context->canonical_hostname) - 1); diff --git a/lib/roles/listen/ops-listen.c b/lib/roles/listen/ops-listen.c new file mode 100644 index 00000000..e9d4d147 --- /dev/null +++ b/lib/roles/listen/ops-listen.c @@ -0,0 +1,171 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +static int +rops_handle_POLLIN_listen(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_context *context = wsi->context; + lws_sockfd_type accept_fd = LWS_SOCK_INVALID; + lws_sock_file_fd_type fd; + int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL; + struct sockaddr_storage cli_addr; + socklen_t clilen; + + /* pollin means a client has connected to us then + * + * pollout is a hack on esp32 for background accepts signalling + * they completed + */ + + do { + struct lws *cwsi; + + if (!(pollfd->revents & (LWS_POLLIN | LWS_POLLOUT)) || + !(pollfd->events & LWS_POLLIN)) + break; + +#if defined(LWS_WITH_TLS) + /* + * can we really accept it, with regards to SSL limit? + * another vhost may also have had POLLIN on his + * listener this round and used it up already + */ + if (wsi->vhost->use_ssl && + context->simultaneous_ssl_restriction && + context->simultaneous_ssl == + context->simultaneous_ssl_restriction) + /* + * no... ignore it, he won't come again until + * we are below the simultaneous_ssl_restriction + * limit and POLLIN is enabled on him again + */ + break; +#endif + /* listen socket got an unencrypted connection... */ + + clilen = sizeof(cli_addr); + lws_latency_pre(context, wsi); + + /* + * We cannot identify the peer who is in the listen + * socket connect queue before we accept it; even if + * we could, not accepting it due to PEER_LIMITS would + * block the connect queue for other legit peers. + */ + + accept_fd = accept((int)pollfd->fd, + (struct sockaddr *)&cli_addr, &clilen); + lws_latency(context, wsi, "listener accept", + (int)accept_fd, accept_fd != LWS_SOCK_INVALID); + if (accept_fd == LWS_SOCK_INVALID) { + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK) { + break; + } + lwsl_err("ERROR on accept: %s\n", + strerror(LWS_ERRNO)); + break; + } + + lws_plat_set_socket_options(wsi->vhost, accept_fd); + +#if defined(LWS_WITH_IPV6) + lwsl_debug("accepted new conn port %u on fd=%d\n", + ((cli_addr.ss_family == AF_INET6) ? + ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), + accept_fd); +#else + lwsl_debug("accepted new conn port %u on fd=%d\n", + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), + accept_fd); +#endif + + /* + * look at who we connected to and give user code a + * chance to reject based on client IP. There's no + * protocol selected yet so we issue this to + * protocols[0] + */ + if ((wsi->vhost->protocols[0].callback)(wsi, + LWS_CALLBACK_FILTER_NETWORK_CONNECTION, + NULL, + (void *)(lws_intptr_t)accept_fd, 0)) { + lwsl_debug("Callback denied net connection\n"); + compatible_close(accept_fd); + break; + } + + if (!(wsi->vhost->options & LWS_SERVER_OPTION_ONLY_RAW)) + opts |= LWS_ADOPT_HTTP; + else + opts = LWS_ADOPT_SOCKET; + + fd.sockfd = accept_fd; + cwsi = lws_adopt_descriptor_vhost(wsi->vhost, opts, fd, + NULL, NULL); + if (!cwsi) + /* already closed cleanly as necessary */ + return LWS_HPI_RET_DIE; + + if (lws_server_socket_service_ssl(cwsi, accept_fd)) + lws_close_free_wsi(cwsi, LWS_CLOSE_STATUS_NOSTATUS, + "listen svc fail"); + + lwsl_info("%s: new wsi %p: wsistate 0x%x, role_ops %s\n", + __func__, cwsi, cwsi->wsistate, cwsi->role_ops->name); + + } while (pt->fds_count < context->fd_limit_per_thread - 1 && + lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0); + + return LWS_HPI_RET_HANDLED; +} + +int rops_handle_POLLOUT_listen(struct lws *wsi) +{ + return LWS_HP_RET_USER_SERVICE; +} + +struct lws_role_ops role_ops_listen = { + "listen", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_listen, + /* handle_POLLOUT */ rops_handle_POLLOUT_listen, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, +}; diff --git a/lib/roles/pipe/ops-pipe.c b/lib/roles/pipe/ops-pipe.c new file mode 100644 index 00000000..8a573e70 --- /dev/null +++ b/lib/roles/pipe/ops-pipe.c @@ -0,0 +1,78 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +static int +rops_handle_POLLIN_pipe(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ +#if !defined(WIN32) && !defined(_WIN32) + char s[10]; + int n; + + /* + * discard the byte(s) that signaled us + * We really don't care about the number of bytes, but coverity + * thinks we should. + */ + n = read(wsi->desc.sockfd, s, sizeof(s)); + (void)n; + if (n < 0) + return LWS_HPI_RET_CLOSE_HANDLED; +#endif + /* + * the poll() wait, or the event loop for libuv etc is a + * process-wide resource that we interrupted. So let every + * protocol that may be interested in the pipe event know that + * it happened. + */ + if (lws_broadcast(wsi->context, LWS_CALLBACK_EVENT_WAIT_CANCELLED, + NULL, 0)) { + lwsl_info("closed in event cancel\n"); + return LWS_HPI_RET_CLOSE_HANDLED; + } + + return LWS_HPI_RET_HANDLED; +} + +struct lws_role_ops role_ops_pipe = { + "pipe", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_pipe, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, +}; diff --git a/lib/roles/raw/ops-raw.c b/lib/roles/raw/ops-raw.c new file mode 100644 index 00000000..1afb4b9f --- /dev/null +++ b/lib/roles/raw/ops-raw.c @@ -0,0 +1,199 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +static int +rops_handle_POLLIN_raw_skt(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + int len, n; + + /* pending truncated sends have uber priority */ + + if (wsi->trunc_len) { + if (!(pollfd->revents & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, + wsi->trunc_len) < 0) + goto fail; + /* + * we can't afford to allow input processing to send + * something new, so spin around he event loop until + * he doesn't have any partials + */ + return LWS_HPI_RET_HANDLED; + } + + if ((pollfd->revents & pollfd->events & LWS_POLLIN) && + /* any tunnel has to have been established... */ + lwsi_state(wsi) != LRS_SSL_ACK_PENDING && + !(wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT))) { + + len = lws_read_or_use_preamble(pt, wsi); + if (len < 0) + goto fail; + + if (!len) + goto try_pollout; + + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_RX, + wsi->user_space, pt->serv_buf, + len); + if (n < 0) { + lwsl_info("LWS_CALLBACK_RAW_RX_fail\n"); + goto fail; + } + } else + if (wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT)) + /* we balanced the last favouring of pollin */ + wsi->favoured_pollin = 0; + +try_pollout: + + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + /* one shot */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_notice("%s a\n", __func__); + goto fail; + } + + /* clear back-to-back write detection */ + wsi->could_have_pending = 0; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + + return LWS_HPI_RET_HANDLED; + +fail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "raw svc fail"); + + return LWS_HPI_RET_CLOSE_HANDLED; +} + + +static int +rops_handle_POLLIN_raw_file(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + int n; + + if (pollfd->revents & LWS_POLLOUT) { + n = lws_callback_as_writeable(wsi); + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return LWS_HPI_RET_DIE; + } + if (n) + return LWS_HPI_RET_CLOSE_HANDLED; + } + + if (pollfd->revents & LWS_POLLIN) { + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_RX_FILE, + wsi->user_space, NULL, 0)) { + lwsl_debug("raw rx callback closed it\n"); + return LWS_HPI_RET_CLOSE_HANDLED; + } + } + + if (pollfd->revents & LWS_POLLHUP) + return LWS_HPI_RET_CLOSE_HANDLED; + + return LWS_HPI_RET_HANDLED; +} + + +struct lws_role_ops role_ops_raw_skt = { + "raw-skt", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_raw_skt, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { LWS_CALLBACK_RAW_WRITEABLE, 0 }, + /* close cb clnt, srv */ { LWS_CALLBACK_RAW_CLOSE, 0 }, +}; + + + +struct lws_role_ops role_ops_raw_file = { + "raw-file", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_raw_file, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { LWS_CALLBACK_RAW_WRITEABLE_FILE, 0 }, + /* close cb clnt, srv */ { LWS_CALLBACK_RAW_CLOSE_FILE, 0 }, +}; diff --git a/lib/client/client-parser.c b/lib/roles/ws/client-parser.c similarity index 99% rename from lib/client/client-parser.c rename to lib/roles/ws/client-parser.c index 8c754eb7..d3485118 100644 --- a/lib/client/client-parser.c +++ b/lib/roles/ws/client-parser.c @@ -22,7 +22,7 @@ #include "private-libwebsockets.h" /* - * parsers.c: lws_rx_sm() needs to be roughly kept in + * parsers.c: lws_ws_rx_sm() needs to be roughly kept in * sync with changes here, esp related to ext draining */ diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c new file mode 100644 index 00000000..8fd29f60 --- /dev/null +++ b/lib/roles/ws/client-ws.c @@ -0,0 +1,604 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +/* + * 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; +} + +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->extensions; + while (ext && ext->callback) { + n = lws_ext_cb_all_exts(wsi->context, wsi, + LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, + (char *)ext->name, 0); + if (n) { /* an extension vetos us */ + lwsl_ext("ext %s vetoed\n", (char *)ext->name); + ext++; + continue; + } + 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->ah->initial_handshake_hash_base64, + sizeof(wsi->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; + void *v; +#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->ah->http_response != 401) { + lwsl_warn( + "lws_client_handshake: got bad HTTP response '%d'\n", + wsi->ah->http_response); + *cce = "HS: ws upgrade unauthorized"; + goto bail3; + } + + if (wsi->ah->http_response != 101) { + lwsl_warn( + "lws_client_handshake: got bad HTTP response '%d'\n", + wsi->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->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->active_extensions[wsi->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->act_ext_user[wsi->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->act_ext_user[ + wsi->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->act_ext_user[wsi->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->act_ext_user[wsi->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->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->ah->initial_handshake_hash_base64)) { + lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, + wsi->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; + } +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* + * inform all extensions, not just active ones since they + * already know + */ + ext = wsi->vhost->extensions; + + while (ext && ext->callback) { + v = NULL; + for (n = 0; n < wsi->count_act_ext; n++) + if (wsi->active_extensions[n] == ext) + v = wsi->act_ext_user[n]; + + ext->callback(context, ext, wsi, + LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0); + ext++; + } +#endif + + return 0; + +bail3: + return 3; + +bail2: + return 2; +} diff --git a/lib/ext/extension-permessage-deflate.c b/lib/roles/ws/ext/extension-permessage-deflate.c similarity index 100% rename from lib/ext/extension-permessage-deflate.c rename to lib/roles/ws/ext/extension-permessage-deflate.c diff --git a/lib/ext/extension-permessage-deflate.h b/lib/roles/ws/ext/extension-permessage-deflate.h similarity index 100% rename from lib/ext/extension-permessage-deflate.h rename to lib/roles/ws/ext/extension-permessage-deflate.h diff --git a/lib/ext/extension.c b/lib/roles/ws/ext/extension.c similarity index 100% rename from lib/ext/extension.c rename to lib/roles/ws/ext/extension.c diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c new file mode 100644 index 00000000..914b348f --- /dev/null +++ b/lib/roles/ws/ops-ws.c @@ -0,0 +1,1872 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * 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 + +#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } + +/* + * client-parser.c: lws_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, unsigned char c) +{ + int callback_action = LWS_CALLBACK_RECEIVE; + int ret = 0, rx_draining_ext = 0; + struct lws_tokens eff_buf; +#if !defined(LWS_WITHOUT_EXTENSIONS) + int n; +#endif + + eff_buf.token = NULL; + eff_buf.token_len = 0; + if (wsi->socket_is_permanently_unusable) + return -1; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + if (wsi->ws->rx_draining_ext) { + eff_buf.token = NULL; + eff_buf.token_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; + } + 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); + + switch (wsi->ws->opcode) { + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + wsi->ws->rsv_first_msg = (c & 0x70); + wsi->ws->frame_is_binary = + wsi->ws->opcode == LWSWSOPC_BINARY_FRAME; + wsi->ws->first_fragment = 1; + 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; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + 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_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + assert(wsi->ws->rx_ubuf); + + if (wsi->ws->rx_draining_ext) + goto drain_extension; + + if (wsi->ws->rx_ubuf_head + LWS_PRE >= + wsi->ws->rx_ubuf_alloc) { + lwsl_err("Attempted overflow \n"); + return -1; + } + 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]; + + 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: + /* + * 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: + + /* 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 (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("passing opc %x up to exts\n", + wsi->ws->opcode); + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->ws->rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->ws->rx_ubuf_head; + + if (lws_ext_cb_active(wsi, + LWS_EXT_CB_EXTENDED_PAYLOAD_RX, + &eff_buf, 0) <= 0) + /* not handle or fail */ + lwsl_ext("ext opc opcode 0x%x unknown\n", + wsi->ws->opcode); + + wsi->ws->rx_ubuf_head = 0; + return 0; + } + + /* + * 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 + */ + + eff_buf.token = &wsi->ws->rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->ws->rx_ubuf_head; + + if (wsi->ws->opcode == LWSWSOPC_PONG && !eff_buf.token_len) + goto already_done; + +drain_extension: + lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); + + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) + goto already_done; +#if !defined(LWS_WITHOUT_EXTENSIONS) + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); +#endif + /* + * eff_buf may be pointing somewhere completely different now, + * it's the output + */ + wsi->ws->first_fragment = 0; +#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 (rx_draining_ext && eff_buf.token_len == 0) + goto already_done; + + if ( +#if !defined(LWS_WITHOUT_EXTENSIONS) + n && +#endif + eff_buf.token_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 (eff_buf.token_len > 0 || + callback_action == LWS_CALLBACK_RECEIVE_PONG) { + eff_buf.token[eff_buf.token_len] = '\0'; + + if (wsi->protocol->callback) { + 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, + eff_buf.token, + eff_buf.token_len); + } + else + lwsl_err("No callback on payload spill!\n"); + } + +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) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (wsi->ws->rx_draining_ext) + return; + + lwsl_ext("%s: RX EXT DRAINING: Adding to list\n", __func__); + + wsi->ws->rx_draining_ext = 1; + wsi->ws->rx_draining_ext_list = pt->rx_draining_ext_list; + pt->rx_draining_ext_list = wsi; +} + +void +lws_remove_wsi_from_draining_ext_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws **w = &pt->rx_draining_ext_list; + + if (!wsi->ws->rx_draining_ext) + return; + + lwsl_ext("%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; +} + +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; +} + +/* Once we reach LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED, we know how much + * to expect in that state and can deal with it in bulk more efficiently. + */ + +int +lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, + size_t *len) +{ + unsigned char *buffer = *buf, mask[4]; + int buffer_size, n; + unsigned int avail; + char *rx_ubuf; + + if (wsi->protocol->rx_buffer_size) + buffer_size = (int)wsi->protocol->rx_buffer_size; + else + buffer_size = wsi->context->pt_serv_buf_size; + avail = buffer_size - wsi->ws->rx_ubuf_head; + + /* 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); + + /* we want to leave 1 byte for the parser to handle properly */ + if (avail <= 1) + return 0; + + avail--; + rx_ubuf = wsi->ws->rx_ubuf + LWS_PRE + wsi->ws->rx_ubuf_head; + if (wsi->ws->all_zero_nonce) + memcpy(rx_ubuf, buffer, avail); + else { + + 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--) { + *(rx_ubuf++) = *(buffer++) ^ mask[0]; + *(rx_ubuf++) = *(buffer++) ^ mask[1]; + *(rx_ubuf++) = *(buffer++) ^ mask[2]; + *(rx_ubuf++) = *(buffer++) ^ mask[3]; + } + /* and the remaining bytes bytewise */ + for (n = 0; n < (int)(avail & 3); n++) + *(rx_ubuf++) = *(buffer++) ^ mask[n]; + + wsi->ws->mask_idx = (wsi->ws->mask_idx + avail) & 3; + } + + (*buf) += avail; + wsi->ws->rx_ubuf_head += avail; + wsi->ws->rx_packet_length -= avail; + *len -= avail; + + return avail; +} + + +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->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) +{ + lwsl_info("%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; +} + +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->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 eff_buf; + unsigned int pending = 0; + char draining_flow = 0; + int n = 0, m; +#if defined(LWS_WITH_HTTP2) + struct lws *wsi1; +#endif + + // 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_CLOSE_HANDLED; + } + + 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_CLOSE_HANDLED; + } + + n = lws_client_socket_service(wsi, pollfd, NULL); + if (n) + return LWS_HPI_RET_DIE; +#endif + return LWS_HPI_RET_HANDLED; + } + + /* 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); + /* the write failed... it's had it */ + wsi->socket_is_permanently_unusable = 1; + return LWS_HPI_RET_CLOSE_HANDLED; + } + + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE || + lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) { + /* + * we stopped caring about anything except control + * packets. Force flow control off, defeat tx + * draining. + */ + lws_rx_flow_control(wsi, 1); + if (wsi->ws) + wsi->ws->tx_draining_ext = 0; + } + + if (wsi->ws && 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; + + if (lws_is_flowcontrolled(wsi)) + /* We cannot deal with any kind of new RX because we are + * RX-flowcontrolled. + */ + 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 + + /* 2: RX Extension needs to be drained + */ + + if (lwsi_role_ws(wsi) && wsi->ws && wsi->ws->rx_draining_ext) { + + lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__); +#ifndef LWS_NO_CLIENT + if (lwsi_role_client(wsi)) { + n = lws_client_rx_sm(wsi, 0); + if (n < 0) + /* we closed wsi */ + n = 0; + } else +#endif + n = lws_ws_rx_sm(wsi, 0); + + return LWS_HPI_RET_HANDLED; + } + + if (wsi->ws && 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; + + /* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained + */ + + if (wsi->rxflow_buffer) { + lwsl_info("draining rxflow (len %d)\n", + wsi->rxflow_len - wsi->rxflow_pos); + assert(wsi->rxflow_pos < wsi->rxflow_len); + /* well, drain it */ + eff_buf.token = (char *)wsi->rxflow_buffer + + wsi->rxflow_pos; + eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; + draining_flow = 1; + goto drain; + } + +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + struct lws_h2_netconn *h2n = wsi->h2.h2n; + + if (h2n->rx_scratch_len) { + lwsl_info("%s: %p: h2 rx pos = %d len = %d\n", + __func__, wsi, h2n->rx_scratch_pos, + h2n->rx_scratch_len); + eff_buf.token = (char *)h2n->rx_scratch + + h2n->rx_scratch_pos; + eff_buf.token_len = h2n->rx_scratch_len; + + h2n->rx_scratch_len = 0; + goto drain; + } + } +#endif + + /* 4: any incoming (or ah-stashed incoming rx) data ready? + * notice if rx flow going off raced poll(), rx flow wins + */ + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN) && !wsi->ah) + return LWS_HPI_RET_HANDLED; + +read: + 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 (wsi->ah && wsi->ah->rxlen == wsi->ah->rxpos) { + /* we drained the excess data in the ah */ + lwsl_info("%s: %p: dropping ah on ws post-upgrade\n", __func__, wsi); + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); + } else + if (wsi->ah) + lwsl_info("%s: %p: unable to drop yet %d vs %d\n", + __func__, wsi, wsi->ah->rxpos, wsi->ah->rxlen); + + if (wsi->ah && wsi->ah->rxlen - wsi->ah->rxpos) { + lwsl_info("%s: %p: inherited ah rx %d\n", __func__, + wsi, wsi->ah->rxlen - wsi->ah->rxpos); + eff_buf.token_len = wsi->ah->rxlen - wsi->ah->rxpos; + eff_buf.token = (char *)wsi->ah->rx + wsi->ah->rxpos; + } else { + if (!(lwsi_role_client(wsi) && + (lwsi_state(wsi) != LRS_ESTABLISHED && + lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) { + /* + * extension may not consume everything + * (eg, pmd may be constrained + * as to what it can output...) has to go in + * per-wsi rx buf area. + * Otherwise in large temp serv_buf area. + */ + +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + if (!wsi->h2.h2n->rx_scratch) { + wsi->h2.h2n->rx_scratch = + lws_malloc( + wsi->vhost->h2_rx_scratch_size, + "h2 rx scratch"); + if (!wsi->h2.h2n->rx_scratch) + return LWS_HPI_RET_CLOSE_HANDLED; + } + eff_buf.token = wsi->h2.h2n->rx_scratch; + eff_buf.token_len = wsi->vhost->h2_rx_scratch_size; + } else +#endif + { + eff_buf.token = (char *)pt->serv_buf; + if (lws_is_ws_with_ext(wsi)) { + eff_buf.token_len = + wsi->ws->rx_ubuf_alloc; + } else { + eff_buf.token_len = + wsi->context->pt_serv_buf_size; + } + + if ((unsigned int)eff_buf.token_len > + wsi->context->pt_serv_buf_size) + eff_buf.token_len = + wsi->context->pt_serv_buf_size; + } + + if ((int)pending > eff_buf.token_len) + pending = eff_buf.token_len; + + eff_buf.token_len = lws_ssl_capable_read(wsi, + (unsigned char *)eff_buf.token, + pending ? (int)pending : + eff_buf.token_len); + switch (eff_buf.token_len) { + case 0: + lwsl_info("%s: zero length read\n", + __func__); + return LWS_HPI_RET_CLOSE_HANDLED; + 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_CLOSE_HANDLED; + } + // lwsl_notice("Actual RX %d\n", eff_buf.token_len); + + /* + * coverity thinks ssl_capable_read() may read over + * 2GB. Dissuade it... + */ + eff_buf.token_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 { +#if !defined(LWS_WITHOUT_EXTENSIONS) + m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE, + &eff_buf, 0); + if (m < 0) + return LWS_HPI_RET_CLOSE_HANDLED; +#endif + + /* service incoming data */ + + if (eff_buf.token_len) { + /* + * if draining from rxflow buffer, not + * critical to track what was used since at the + * use it bumps wsi->rxflow_pos. If we come + * around again it will pick up from where it + * left off. + */ +#if defined(LWS_ROLE_H2) + if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) + n = lws_read_h2(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + else +#endif + n = lws_read_h1(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + + if (n < 0) { + /* we closed wsi */ + n = 0; + return LWS_HPI_RET_DIE; + } + } + + eff_buf.token = NULL; + eff_buf.token_len = 0; + } while (m); + + if (wsi->ah +#if !defined(LWS_NO_CLIENT) + && !wsi->client_h2_alpn +#endif + ) { + lwsl_info("%s: %p: detaching ah\n", __func__, wsi); + lws_header_table_force_to_detachable_state(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 (draining_flow && wsi->rxflow_buffer && + wsi->rxflow_pos == wsi->rxflow_len) { + lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); + lws_free_set_NULL(wsi->rxflow_buffer); + /* 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 eff_buf; + int ret, m; +#endif + int n; + + // lwsl_notice("%s: %s\n", __func__, wsi->protocol->name); + + /* 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"); + 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) { + 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; + } + + 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; + + 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 (lwsi_role_client(wsi) && !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; + + /* 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 (lwsi_role_client(wsi) && 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 !defined(LWS_WITHOUT_EXTENSIONS) + m = lws_ext_cb_active(wsi, LWS_EXT_CB_IS_WRITEABLE, NULL, 0); + if (m) + return LWS_HP_RET_BAIL_DIE; + + if (!wsi->extension_data_pending) + 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 eff_buf 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; + eff_buf.token = NULL; + eff_buf.token_len = 0; + + /* give every extension a chance to spill */ + + m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND, + &eff_buf, 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 (eff_buf.token_len) { + n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_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 != eff_buf.token_len) { + lwsl_err("Unable to spill ext %d vs %d\n", + eff_buf.token_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->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) +{ + 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->rx_draining_ext_list; + while (wsi) { + 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; +} + +static int +rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason) +{ + 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; + lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE); + __lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5); + + lws_callback_on_writable(wsi); + + return 1; +} + +static int +rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi) +{ + if (wsi->ws->rx_draining_ext) { + struct lws **w = &pt->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->tx_draining_ext_list; + + 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; + } + 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; + + return 0; +} + +static int +rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int masked7 = lwsi_role_client(wsi); + unsigned char is_masked_bit = 0; + unsigned char *dropmask = NULL; + struct lws_tokens eff_buf; + size_t orig_len = len; + int pre = 0, n; + + if (wsi->ws->tx_draining_ext) { + /* remove us from the list */ + struct lws **w = &pt->tx_draining_ext_list; + + 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; + *wp = (wsi->ws->tx_draining_stashed_wp & 0xc0) | + LWS_WRITE_CONTINUATION; + + lwsl_ext("FORCED draining wp to 0x%02X\n", *wp); + } + + 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. + */ + eff_buf.token = (char *)buf; + eff_buf.token_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_debug("LWS_EXT_CB_PAYLOAD_TX\n"); + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &eff_buf, *wp); + if (n < 0) + return -1; + + if (n && eff_buf.token_len) { + lwsl_debug("drain len %d\n", (int)eff_buf.token_len); + /* extension requires further draining */ + wsi->ws->tx_draining_ext = 1; + wsi->ws->tx_draining_ext_list = + pt->tx_draining_ext_list; + pt->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 (eff_buf.token_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 != eff_buf.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 && !eff_buf.token_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 *)eff_buf.token; + len = eff_buf.token_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 (lws_ext_cb_active(wsi, LWS_EXT_CB_REQUEST_ON_WRITEABLE, NULL, 0)) + return 1; +#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; +} + +struct lws_role_ops role_ops_ws = { + "ws", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* 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, + /* rxflow_cache */ NULL, + /* encapsulation_parent */ 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 */ NULL, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_WRITEABLE, + LWS_CALLBACK_SERVER_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLIENT_CLOSED, + LWS_CALLBACK_CLOSED }, +}; diff --git a/lib/server/server-handshake.c b/lib/roles/ws/server-ws.c similarity index 56% rename from lib/server/server-handshake.c rename to lib/roles/ws/server-ws.c index c3590cb3..d2db6825 100644 --- a/lib/server/server-handshake.c +++ b/lib/roles/ws/server-ws.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2013 Andy Green + * Copyright (C) 2010-2018 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,7 +19,7 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include #define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } @@ -226,6 +226,199 @@ lws_extension_server_handshake(struct lws *wsi, char **p, int budget) 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. + */ + + lwsl_debug("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", + __func__, wsi, wsi->ah->rxpos, wsi->ah->rxlen); + 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); + /* + * Because rxpos/rxlen shows something in the ah, we will get + * service guaranteed next time around the event loop + */ + + lws_pt_unlock(pt); + + lws_server_init_wsi_for_ws(wsi); + lwsl_parser("accepted v%02d connection\n", wsi->ws->ietf_spec_revision); + + /* !!! drop ah unreservedly after ESTABLISHED */ + if (wsi->ah->rxpos == wsi->ah->rxlen) { + lwsl_info("%s: %p: dropping ah on ws upgrade\n", __func__, wsi); + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 1); + } else + lwsl_info("%s: %p: unable to drop ah at ws upgrade %d vs %d\n", + __func__, wsi, wsi->ah->rxpos, wsi->ah->rxlen); + + return 0; +} + int handshake_0405(struct lws_context *context, struct lws *wsi) { @@ -238,7 +431,7 @@ handshake_0405(struct lws_context *context, struct lws *wsi) if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) || !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) { - lwsl_parser("handshake_04 missing pieces\n"); + lwsl_info("handshake_04 missing pieces\n"); /* completed header processing, but missing some bits */ goto bail; } @@ -328,10 +521,10 @@ handshake_0405(struct lws_context *context, struct lws *wsi) #if defined(DEBUG) fwrite(response, 1, p - response, stderr); #endif - n = lws_write(wsi, (unsigned char *)response, - p - response, LWS_WRITE_HTTP_HEADERS); + n = lws_write(wsi, (unsigned char *)response, p - response, + LWS_WRITE_HTTP_HEADERS); if (n != (p - response)) { - lwsl_debug("handshake_0405: ERROR writing to socket\n"); + lwsl_info("%s: ERROR writing to socket %d\n", __func__, n); goto bail; } @@ -362,3 +555,67 @@ bail: return -1; } + +int +lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m; + + lwsl_parser("%s: received %d byte packet\n", __func__, (int)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) { + lws_rxflow_cache(wsi, *buf, 0, (int)len); + lwsl_parser("%s: cached %ld\n", __func__, (long)len); + return 1; + } + + if (wsi->ws->rx_draining_ext) { + m = lws_ws_rx_sm(wsi, 0); + if (m < 0) + return -1; + continue; + } + + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) { + wsi->rxflow_pos++; + if (wsi->rxflow_pos > wsi->rxflow_len) + assert(0); + } + + /* consume payload bytes efficiently */ + if (wsi->lws_rx_parse_state == + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED) { + m = lws_payload_until_length_exhausted(wsi, buf, &len); + if (wsi->rxflow_buffer) + wsi->rxflow_pos += m; + } + + /* process the byte */ + m = lws_ws_rx_sm(wsi, *(*buf)++); + if (m < 0) + return -1; + len--; + + if (wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { + lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi); + lws_free_set_NULL(wsi->rxflow_buffer); + /* having drained the rxflow buffer, can rearm POLLIN */ +#ifdef LWS_NO_SERVER + m = +#endif + __lws_rx_flow_control(wsi); + /* m ignored, needed for NO_SERVER case */ + } + } + + lwsl_parser("%s: exit with %d unused\n", __func__, (int)len); + + return 0; +} diff --git a/lib/service.c b/lib/service.c index c9210a2e..74d85862 100644 --- a/lib/service.c +++ b/lib/service.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green + * Copyright (C) 2010-2018 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,8 +21,8 @@ #include "private-libwebsockets.h" -static int -lws_calllback_as_writeable(struct lws *wsi) +int +lws_callback_as_writeable(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; int n, m; @@ -41,33 +41,11 @@ lws_calllback_as_writeable(struct lws *wsi) } #endif - switch (lwsi_role(wsi)) { - case LWSI_ROLE_RAW_SOCKET: - n = LWS_CALLBACK_RAW_WRITEABLE; - break; - case LWSI_ROLE_RAW_FILE: - n = LWS_CALLBACK_RAW_WRITEABLE_FILE; - break; - case LWSI_ROLE_WS1_CLIENT: - case LWSI_ROLE_WS2_CLIENT: - n = LWS_CALLBACK_CLIENT_WRITEABLE; - break; - case LWSI_ROLE_H1_CLIENT: - case LWSI_ROLE_H2_CLIENT: - n = LWS_CALLBACK_CLIENT_HTTP_WRITEABLE; - break; - case LWSI_ROLE_WS1_SERVER: - case LWSI_ROLE_WS2_SERVER: - n = LWS_CALLBACK_SERVER_WRITEABLE; - break; - default: - n = LWS_CALLBACK_HTTP_WRITEABLE; - break; - } + n = wsi->role_ops->writeable_cb[lwsi_role_server(wsi)]; m = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, (enum lws_callback_reasons) n, - wsi->user_space, NULL, 0); + wsi, (enum lws_callback_reasons) n, + wsi->user_space, NULL, 0); return m; } @@ -75,17 +53,8 @@ lws_calllback_as_writeable(struct lws *wsi) LWS_VISIBLE int lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) { - int write_type = LWS_WRITE_PONG; -#ifdef LWS_WITH_HTTP2 - struct lws **wsi2, *wsi2a; -#endif - int n; volatile struct lws *vwsi = (volatile struct lws *)wsi; - -#if !defined(LWS_WITHOUT_EXTENSIONS) - struct lws_tokens eff_buf; - int ret, m; -#endif + int n; lwsl_info("%s: %p\n", __func__, wsi); @@ -96,17 +65,17 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) * handling_pollout is set, he will only set leave_pollout_active. * If we are going to disable POLLOUT, we will check that first. */ + wsi->could_have_pending = 0; /* clear back-to-back write detection */ /* * user callback is lowest priority to get these notifications * actually, since other pending things cannot be disordered - */ - - /* Priority 1: pending truncated sends are incomplete ws fragments + * + * Priority 1: pending truncated sends are incomplete ws fragments * If anything else sent first the protocol would be * corrupted. */ - wsi->could_have_pending = 0; /* clear back-to-back write detection */ + if (wsi->trunc_len) { //lwsl_notice("%s: completing partial\n", __func__); if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, @@ -122,35 +91,11 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) goto bail_die; /* retry closing now */ } - if (lwsi_state(wsi) == LRS_ISSUE_HTTP_BODY) - goto user_service; - -#ifdef LWS_WITH_HTTP2 - /* - * Priority 2: protocol packets - */ - if ((wsi->upgraded_to_http2 -#if !defined(LWS_NO_CLIENT) - || wsi->client_h2_alpn -#endif - ) && wsi->h2.h2n->pps) { - lwsl_info("servicing pps\n"); - if (lws_h2_do_pps_send(wsi)) { - wsi->socket_is_permanently_unusable = 1; - goto bail_die; - } - if (wsi->h2.h2n->pps) - goto bail_ok; - - /* we can resume whatever we were doing */ - lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_ENABLE | - LWS_RXFLOW_REASON_H2_PPS_PENDING); - - goto bail_ok; /* leave POLLOUT active */ - } -#endif - #ifdef LWS_WITH_CGI + /* + * A cgi master's wire protocol remains h1 or h2. He is just getting + * his data from his child cgis. + */ if (wsi->cgi) { /* also one shot */ if (pollfd) @@ -162,199 +107,30 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) } #endif - /* Priority 3: pending control packets (pong or close) - * - * 3a: close notification packet requested from close api - */ + /* if we got here, we should have wire protocol ops set on the wsi */ + assert(wsi->role_ops); - if (lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) { - lwsl_debug("sending close packet\n"); - 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) { - 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"); - - goto bail_ok; - } + if (!wsi->role_ops->handle_POLLOUT) + goto bail_ok; + switch ((wsi->role_ops->handle_POLLOUT)(wsi)) { + case LWS_HP_RET_BAIL_OK: + goto bail_ok; + case LWS_HP_RET_BAIL_DIE: goto bail_die; + case LWS_HP_RET_USER_SERVICE: + break; + default: + assert(0); } - /* 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; - - n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE], - wsi->ws->ping_payload_len, write_type); - if (n < 0) - goto 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 */ - goto bail_die; - } - - /* otherwise for PING, leave POLLOUT active either way */ - goto bail_ok; - } - - if (lwsi_role_ws_client(wsi) && !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) - goto 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); - - goto 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) - goto user_service; - - /* 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 (lwsi_role_ws_client(wsi) && wsi->ws->tx_draining_ext) { - lwsl_ext("SERVICING TX EXT DRAINING\n"); - if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0) - goto bail_die; - /* leave POLLOUT active */ - goto bail_ok; - } - - /* Priority 6: extensions - */ -#if !defined(LWS_WITHOUT_EXTENSIONS) - m = lws_ext_cb_active(wsi, LWS_EXT_CB_IS_WRITEABLE, NULL, 0); - if (m) - goto bail_die; - - if (!wsi->extension_data_pending) - goto 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 eff_buf means just spill pending - */ - - ret = 1; - if (lwsi_role_raw(wsi)) - ret = 0; - - while (ret == 1) { - - /* default to nobody has more to spill */ - - ret = 0; - eff_buf.token = NULL; - eff_buf.token_len = 0; - - /* give every extension a chance to spill */ - - m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND, - &eff_buf, 0); - if (m < 0) { - lwsl_err("ext reports fatal error\n"); - goto 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 (eff_buf.token_len) { - n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, - eff_buf.token_len); - if (n < 0) { - lwsl_info("closing from POLLOUT spill\n"); - goto bail_die; - } - /* - * Keep amount spilled small to minimize chance of this - */ - if (n != eff_buf.token_len) { - lwsl_err("Unable to spill ext %d vs %d\n", - eff_buf.token_len, n); - goto 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. - */ - - goto bail_ok; - } - - wsi->extension_data_pending = 0; -#endif - -user_service: /* one shot */ if (wsi->parent_carries_io) { vwsi->handling_pollout = 0; vwsi->leave_pollout_active = 0; - return lws_calllback_as_writeable(wsi); + return lws_callback_as_writeable(wsi); } if (pollfd) { @@ -384,8 +160,9 @@ user_service: vwsi->leave_pollout_active = 0; } - if (lwsi_role_client(wsi) && !wsi->hdr_parsing_completed && - lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS) + if (lwsi_role_client(wsi) && + !wsi->hdr_parsing_completed && + lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS) goto bail_ok; @@ -393,274 +170,20 @@ user_service: user_service_go_again: #endif -#ifdef LWS_WITH_HTTP2 - /* - * we are the 'network wsi' for potentially many muxed child wsi with - * no network connection of their own, who have to use us for all their - * network actions. So we use a round-robin scheme to share out the - * POLLOUT notifications to our children. - * - * But because any child could exhaust the socket's ability to take - * writes, we can only let one child get notified each time. - * - * In addition children may be closed / deleted / added between POLLOUT - * notifications, so we can't hold pointers - */ - - if (!lwsi_role_h2(wsi)) { - lwsl_info("%s: non http2\n", __func__); - goto notify; - } - - wsi = lws_get_network_wsi(wsi); - - wsi->h2.requested_POLLOUT = 0; - if (!wsi->h2.initialized) { - lwsl_info("pollout on uninitialized http2 conn\n"); - goto bail_ok; - } - -// if (SSL_want_read(wsi->ssl) || SSL_want_write(wsi->ssl)) { -// lws_callback_on_writable(wsi); -// goto bail_ok; -// } - - lwsl_info("%s: %p: children waiting for POLLOUT service:\n", __func__, wsi); - wsi2a = wsi->h2.child_list; - while (wsi2a) { - if (wsi2a->h2.requested_POLLOUT) - lwsl_debug(" * %p %s\n", wsi2a, wsi2a->protocol->name); + if (wsi->role_ops->perform_user_POLLOUT) { + if (wsi->role_ops->perform_user_POLLOUT(wsi) == -1) + goto bail_die; else - lwsl_debug(" %p %s\n", wsi2a, wsi2a->protocol->name); - - wsi2a = wsi2a->h2.sibling_list; + goto bail_ok; } - - wsi2 = &wsi->h2.child_list; - if (!*wsi2) - goto bail_ok; - - do { - struct lws *w, **wa; - wa = &(*wsi2)->h2.sibling_list; - if (!(*wsi2)->h2.requested_POLLOUT) - goto next_child; + lwsl_debug("%s: %p: non mux: wsistate 0x%x, ops %s\n", __func__, wsi, + wsi->wsistate, wsi->role_ops->name); - /* - * we're going to do writable callback for this child. - * move him to be the last child - */ - - lwsl_debug("servicing child %p\n", *wsi2); - - w = *wsi2; - while (w) { - if (!w->h2.sibling_list) { /* w is the current last */ - lwsl_debug("w=%p, *wsi2 = %p\n", w, *wsi2); - if (w == *wsi2) /* we are already last */ - break; - /* last points to us as new last */ - w->h2.sibling_list = *wsi2; - /* guy pointing to us until now points to - * our old next */ - *wsi2 = (*wsi2)->h2.sibling_list; - /* we point to nothing because we are last */ - w->h2.sibling_list->h2.sibling_list = NULL; - /* w becomes us */ - w = w->h2.sibling_list; - break; - } - w = w->h2.sibling_list; - } - - w->h2.requested_POLLOUT = 0; - lwsl_info("%s: child %p (state %d)\n", __func__, w, lwsi_state(w)); - - /* if we arrived here, even by looping, we checked choked */ - w->could_have_pending = 0; - wsi->could_have_pending = 0; - - if (w->h2.pending_status_body) { - w->h2.send_END_STREAM = 1; - n = lws_write(w, (uint8_t *)w->h2.pending_status_body + - LWS_PRE, - strlen(w->h2.pending_status_body + - LWS_PRE), LWS_WRITE_HTTP_FINAL); - lws_free_set_NULL(w->h2.pending_status_body); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream 1"); - wa = &wsi->h2.child_list; - goto next_child; - } - - if (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS) { - if (lws_h2_client_handshake(w)) - return -1; - - goto next_child; - } - - if (lwsi_state(w) == LRS_DEFERRING_ACTION) { - - /* - * we had to defer the http_action to the POLLOUT - * handler, because we know it will send something and - * only in the POLLOUT handler do we know for sure - * that there is no partial pending on the network wsi. - */ - - lwsi_set_state(w, LRS_ESTABLISHED); - - lwsl_info(" h2 action start...\n"); - n = lws_http_action(w); - lwsl_info(" h2 action result %d " - "(wsi->http.rx_content_remain %lld)\n", - n, w->http.rx_content_remain); - - /* - * Commonly we only managed to start a larger transfer - * that will complete asynchronously under its own wsi - * states. In those cases we will hear about - * END_STREAM going out in the POLLOUT handler. - */ - if (n || w->h2.send_END_STREAM) { - lwsl_info("closing stream after h2 action\n"); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream"); - wa = &wsi->h2.child_list; - } - - goto next_child; - } - - if (lwsi_state(w) == LRS_ISSUING_FILE) { - - ((volatile struct lws *)w)->leave_pollout_active = 0; - - /* >0 == completion, <0 == error - * - * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION - * callback when it's done. That's the case even if we - * just completed the send, so wait for that. - */ - n = lws_serve_http_file_fragment(w); - lwsl_debug("lws_serve_http_file_fragment says %d\n", n); - - /* - * We will often hear about out having sent the final - * DATA here... if so close the actual wsi - */ - if (n < 0 || w->h2.send_END_STREAM) { - lwsl_debug("Closing POLLOUT child %p\n", w); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream file"); - wa = &wsi->h2.child_list; - goto next_child; - } - if (n > 0) - if (lws_http_transaction_completed(w)) - goto bail_die; - if (!n) { - lws_callback_on_writable(w); - (w)->h2.requested_POLLOUT = 1; - } - - goto next_child; - } - - /* Notify peer that we decided to close */ - - if (lwsi_state(w) == LRS_WAITING_TO_SEND_CLOSE) { - lwsl_debug("sending close packet\n"); - w->waiting_to_send_close_frame = 0; - n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE], - w->ws->close_in_ping_buffer_len, - LWS_WRITE_CLOSE); - if (n >= 0) { - lwsi_set_state(w, LRS_AWAITING_CLOSE_ACK); - lws_set_timeout(w, PENDING_TIMEOUT_CLOSE_ACK, 5); - lwsl_debug("sent close indication, awaiting ack\n"); - } - - goto next_child; - } - - /* Acknowledge receipt of peer's notification he closed, - * then logically close ourself */ - - if ((lwsi_role_ws(w) && w->ws->ping_pending_flag) || - (lwsi_state(w) == LRS_RETURNED_CLOSE && - w->ws->payload_is_close)) { - - if (w->ws->payload_is_close) - write_type = LWS_WRITE_CLOSE | - LWS_WRITE_H2_STREAM_END; - - n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE], - w->ws->ping_payload_len, write_type); - if (n < 0) - goto bail_die; - - /* well he is sent, mark him done */ - w->ws->ping_pending_flag = 0; - if (w->ws->payload_is_close) { - /* oh... a close frame was it... then we are done */ - lwsl_debug("Acknowledged peer's close packet\n"); - w->ws->payload_is_close = 0; - lwsi_set_state(w, LRS_RETURNED_CLOSE); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "returned close packet"); - wa = &wsi->h2.child_list; - goto next_child; - } - - lws_callback_on_writable(w); - (w)->h2.requested_POLLOUT = 1; - - /* otherwise for PING, leave POLLOUT active either way */ - goto next_child; - } - - if (lws_calllback_as_writeable(w)) { - lwsl_info("Closing POLLOUT child (end stream %d)\n", w->h2.send_END_STREAM); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 pollout handle"); - wa = &wsi->h2.child_list; - } else - if (w->h2.send_END_STREAM) - lws_h2_state(w, LWS_H2_STATE_HALF_CLOSED_LOCAL); - -next_child: - wsi2 = wa; - } while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi)); - - lwsl_info("%s: %p: children waiting for POLLOUT service: %p\n", - __func__, wsi, wsi->h2.child_list); - wsi2a = wsi->h2.child_list; - while (wsi2a) { - if (wsi2a->h2.requested_POLLOUT) - lwsl_debug(" * %p\n", wsi2a); - else - lwsl_debug(" %p\n", wsi2a); - - wsi2a = wsi2a->h2.sibling_list; - } - - - wsi2a = wsi->h2.child_list; - while (wsi2a) { - if (wsi2a->h2.requested_POLLOUT) { - lws_change_pollfd(wsi, 0, LWS_POLLOUT); - break; - } - wsi2a = wsi2a->h2.sibling_list; - } - - goto bail_ok; - - -notify: -#endif vwsi = (volatile struct lws *)wsi; vwsi->leave_pollout_active = 0; - n = lws_calllback_as_writeable(wsi); + n = lws_callback_as_writeable(wsi); vwsi->handling_pollout = 0; if (vwsi->leave_pollout_active) @@ -754,25 +277,10 @@ __lws_service_timeout_check(struct lws *wsi, time_t sec) int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len) { -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2) { - struct lws_h2_netconn *h2n = wsi->h2.h2n; + if (wsi->role_ops->rxflow_cache) + if (wsi->role_ops->rxflow_cache(wsi, buf, n, len)) + return 0; - assert(h2n->rx_scratch); - buf += n; - len -= n; - assert ((char *)buf >= (char *)h2n->rx_scratch && - (char *)&buf[len] <= - (char *)&h2n->rx_scratch[wsi->vhost->h2_rx_scratch_size]); - - h2n->rx_scratch_pos = lws_ptr_diff(buf, h2n->rx_scratch); - h2n->rx_scratch_len = len; - - lwsl_info("%s: %p: pausing h2 rx_scratch\n", __func__, wsi); - - return 0; - } -#endif /* his RX is flowcontrolled, don't send remaining now */ if (wsi->rxflow_buffer) { if (buf >= wsi->rxflow_buffer && @@ -821,7 +329,7 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) if (pt->rx_draining_ext_list) return 0; -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) /* 2) if we know we have non-network pending data, do not wait in poll */ if (lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) { lwsl_info("ssl buffered read\n"); @@ -845,6 +353,49 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) return timeout_ms; } + +int +lws_read_or_use_preamble(struct lws_context_per_thread *pt, struct lws *wsi) +{ + int len; + + if (wsi->preamble_rx && wsi->preamble_rx_len) { + memcpy(pt->serv_buf, wsi->preamble_rx, wsi->preamble_rx_len); + lws_free_set_NULL(wsi->preamble_rx); + len = wsi->preamble_rx_len; + lwsl_debug("bringing %d out of stash\n", wsi->preamble_rx_len); + wsi->preamble_rx_len = 0; + + return len; + } + + /* + * ... in the case of pipelined HTTP, this may be + * POST data followed by next headers... + */ + + len = lws_ssl_capable_read(wsi, pt->serv_buf, + wsi->context->pt_serv_buf_size); + lwsl_debug("%s: wsi %p read %d (wsistate 0x%x)\n", + __func__, wsi, len, wsi->wsistate); + switch (len) { + case 0: + lwsl_info("%s: read 0 len b\n", __func__); + + /* fallthru */ + case LWS_SSL_CAPABLE_ERROR: + return -1; + case LWS_SSL_CAPABLE_MORE_SERVICE: + return 0; + } + + if (len < 0) /* coverity */ + return -1; + + return len; +} + + /* * guys that need POLLIN service again without waiting for network action * can force POLLIN here if not flowcontrolled, so they will get service. @@ -855,33 +406,19 @@ int lws_service_flag_pending(struct lws_context *context, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; - struct allocated_headers *ah; -#ifdef LWS_OPENSSL_SUPPORT - struct lws *wsi_next; + +#if defined(LWS_WITH_TLS) + struct lws *wsi, *wsi_next; #endif - struct lws *wsi; int forced = 0; lws_pt_lock(pt, __func__); - /* POLLIN faking */ +#if defined(LWS_ROLE_WS) + forced |= role_ops_ws.service_flag_pending(context, tsi); +#endif - /* - * 1) For all guys with already-available ext data to drain, if they are - * not flowcontrolled, fake their POLLIN status - */ - wsi = pt->rx_draining_ext_list; - while (wsi) { - 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; - break; - } - wsi = wsi->ws->rx_draining_ext_list; - } - -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) /* * 2) For all guys with buffered SSL read data already saved up, if they * are not flowcontrolled, fake their POLLIN status so they'll get @@ -907,210 +444,34 @@ lws_service_flag_pending(struct lws_context *context, int tsi) wsi = wsi_next; } #endif - /* - * 3) For any wsi who have an ah with pending RX who did not - * complete their current headers, and are not flowcontrolled, - * fake their POLLIN status so they will be able to drain the - * rx buffered in the ah - */ - ah = pt->ah_list; - while (ah) { - if ((ah->rxpos != ah->rxlen && - !ah->wsi->hdr_parsing_completed) || ah->wsi->preamble_rx) { - pt->fds[ah->wsi->position_in_fds_table].revents |= - pt->fds[ah->wsi->position_in_fds_table].events & - LWS_POLLIN; - if (pt->fds[ah->wsi->position_in_fds_table].revents & - LWS_POLLIN) { - forced = 1; - break; - } - } - ah = ah->next; - } + +#if defined(LWS_ROLE_H1) + forced |= role_ops_h1.service_flag_pending(context, tsi); +#else /* they do the same thing... only need one or the other if h1 and h2 */ +#if defined(LWS_ROLE_H2) + forced |= role_ops_h2.service_flag_pending(context, tsi); +#endif +#endif lws_pt_unlock(pt); return forced; } -#ifndef LWS_NO_CLIENT - -LWS_VISIBLE int -lws_http_client_read(struct lws *wsi, char **buf, int *len) -{ - int rlen, n; - - rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); - *len = 0; - - /* allow the source to signal he has data again next time */ - lws_change_pollfd(wsi, 0, LWS_POLLIN); - - if (rlen == LWS_SSL_CAPABLE_ERROR) { - lwsl_notice("%s: SSL capable error\n", __func__); - return -1; - } - - if (rlen == 0) - return -1; - - if (rlen < 0) - return 0; - - *len = rlen; - wsi->client_rx_avail = 0; - - /* - * server may insist on transfer-encoding: chunked, - * so http client must deal with it - */ -spin_chunks: - while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { - switch (wsi->chunk_parser) { - case ELCP_HEX: - if ((*buf)[0] == '\x0d') { - wsi->chunk_parser = ELCP_CR; - break; - } - n = char_to_hex((*buf)[0]); - if (n < 0) { - lwsl_debug("chunking failure\n"); - return -1; - } - wsi->chunk_remaining <<= 4; - wsi->chunk_remaining |= n; - break; - case ELCP_CR: - if ((*buf)[0] != '\x0a') { - lwsl_debug("chunking failure\n"); - return -1; - } - wsi->chunk_parser = ELCP_CONTENT; - lwsl_info("chunk %d\n", wsi->chunk_remaining); - if (wsi->chunk_remaining) - break; - lwsl_info("final chunk\n"); - goto completed; - - case ELCP_CONTENT: - break; - - case ELCP_POST_CR: - if ((*buf)[0] != '\x0d') { - lwsl_debug("chunking failure\n"); - - return -1; - } - - wsi->chunk_parser = ELCP_POST_LF; - break; - - case ELCP_POST_LF: - if ((*buf)[0] != '\x0a') - return -1; - - wsi->chunk_parser = ELCP_HEX; - wsi->chunk_remaining = 0; - break; - } - (*buf)++; - (*len)--; - } - - if (wsi->chunked && !wsi->chunk_remaining) - return 0; - - if (wsi->http.rx_content_remain && - wsi->http.rx_content_remain < (unsigned int)*len) - n = (int)wsi->http.rx_content_remain; - else - n = *len; - - if (wsi->chunked && wsi->chunk_remaining && - wsi->chunk_remaining < n) - n = wsi->chunk_remaining; - -#ifdef LWS_WITH_HTTP_PROXY - /* hubbub */ - if (wsi->perform_rewrite) - lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n); - else -#endif - { - struct lws *wsi_eff = lws_client_wsi_effective(wsi); - - if (user_callback_handle_rxflow(wsi_eff->protocol->callback, - wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, - wsi_eff->user_space, *buf, n)) { - lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n", - __func__); - - return -1; - } - } - - if (wsi->chunked && wsi->chunk_remaining) { - (*buf) += n; - wsi->chunk_remaining -= n; - *len -= n; - } - - if (wsi->chunked && !wsi->chunk_remaining) - wsi->chunk_parser = ELCP_POST_CR; - - if (wsi->chunked && *len) - goto spin_chunks; - - if (wsi->chunked) - return 0; - - /* if we know the content length, decrement the content remaining */ - if (wsi->http.rx_content_length > 0) - wsi->http.rx_content_remain -= n; - - if (wsi->http.rx_content_remain || !wsi->http.rx_content_length) - return 0; - -completed: - - if (lws_http_transaction_completed_client(wsi)) { - lwsl_notice("%s: transaction completed says -1\n", __func__); - return -1; - } - - return 0; -} -#endif - static int -lws_is_ws_with_ext(struct lws *wsi) -{ -#if defined(LWS_WITHOUT_EXTENSIONS) - return 0; -#else - return lwsi_role_ws(wsi) && !!wsi->count_act_ext; -#endif -} - -LWS_VISIBLE int -lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, - int tsi) +lws_service_periodic_checks(struct lws_context *context, + struct lws_pollfd *pollfd, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; lws_sockfd_type our_fd = 0, tmp_fd; struct allocated_headers *ah; - struct lws_tokens eff_buf; - unsigned int pending = 0; struct lws *wsi; - char draining_flow = 0; int timed_out = 0; time_t now; - int n = 0, m; - -#if defined(LWS_WITH_HTTP2) - struct lws *wsi1; +#if defined(LWS_WITH_TLS) + int n = 0; #endif + int m; if (!context->protocol_init_done) if (lws_protocol_init(context)) @@ -1149,266 +510,238 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, context->last_timeout_check_s = now - 1; } - if (lws_compare_time_t(context, context->last_timeout_check_s, now)) { - context->last_timeout_check_s = now; + if (!lws_compare_time_t(context, context->last_timeout_check_s, now)) + return 0; + + context->last_timeout_check_s = now; #if defined(LWS_WITH_STATS) - if (!tsi && now - context->last_dump > 10) { - lws_stats_log_dump(context); - context->last_dump = now; - } + if (!tsi && now - context->last_dump > 10) { + lws_stats_log_dump(context); + context->last_dump = now; + } #endif - lws_plat_service_periodic(context); - lws_check_deferred_free(context, 0); + lws_plat_service_periodic(context); + lws_check_deferred_free(context, 0); #if defined(LWS_WITH_PEER_LIMITS) - lws_peer_cull_peer_wait_list(context); + lws_peer_cull_peer_wait_list(context); #endif - /* retire unused deprecated context */ + /* retire unused deprecated context */ #if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_ESP32) -#if LWS_POSIX && !defined(_WIN32) - if (context->deprecated && !context->count_wsi_allocated) { - lwsl_notice("%s: ending deprecated context\n", __func__); - kill(getpid(), SIGINT); - return 0; +#if !defined(_WIN32) + if (context->deprecated && !context->count_wsi_allocated) { + lwsl_notice("%s: ending deprecated context\n", __func__); + kill(getpid(), SIGINT); + return 0; + } +#endif +#endif + /* global timeout check once per second */ + + if (pollfd) + our_fd = pollfd->fd; + + /* + * Phase 1: check every wsi on the timeout check list + */ + + lws_pt_lock(pt, __func__); + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + context->pt[tsi].dll_head_timeout.next) { + wsi = lws_container_of(d, struct lws, dll_timeout); + tmp_fd = wsi->desc.sockfd; + if (__lws_service_timeout_check(wsi, now)) { + /* he did time out... */ + if (tmp_fd == our_fd) + /* it was the guy we came to service! */ + timed_out = 1; + /* he's gone, no need to mark as handled */ } -#endif -#endif - /* global timeout check once per second */ + } lws_end_foreach_dll_safe(d, d1); - if (pollfd) - our_fd = pollfd->fd; + /* + * Phase 2: double-check active ah timeouts independent of wsi + * timeout status + */ + + ah = pt->ah_list; + while (ah) { + int len; + char buf[256]; + const unsigned char *c; + + if (!ah->in_use || !ah->wsi || !ah->assigned || + (ah->wsi->vhost && + lws_compare_time_t(context, now, ah->assigned) < + ah->wsi->vhost->timeout_secs_ah_idle + 360)) { + ah = ah->next; + continue; + } /* - * Phase 1: check every wsi on the timeout check list + * a single ah session somehow got held for + * an unreasonable amount of time. + * + * Dump info on the connection... */ + wsi = ah->wsi; + buf[0] = '\0'; +#if !defined(LWS_PLAT_OPTEE) + lws_get_peer_simple(wsi, buf, sizeof(buf)); +#else + buf[0] = '\0'; +#endif + lwsl_notice("ah excessive hold: wsi %p\n" + " peer address: %s\n" + " ah rxpos %u, rxlen %u, pos %u\n", + wsi, buf, ah->rxpos, ah->rxlen, + ah->pos); + buf[0] = '\0'; + m = 0; + do { + c = lws_token_to_string(m); + if (!c) + break; + if (!(*c)) + break; - lws_pt_lock(pt, __func__); - - lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, - context->pt[tsi].dll_head_timeout.next) { - wsi = lws_container_of(d, struct lws, dll_timeout); - tmp_fd = wsi->desc.sockfd; - if (__lws_service_timeout_check(wsi, now)) { - /* he did time out... */ - if (tmp_fd == our_fd) - /* it was the guy we came to service! */ - timed_out = 1; - /* he's gone, no need to mark as handled */ - } - } lws_end_foreach_dll_safe(d, d1); - - /* - * Phase 2: double-check active ah timeouts independent of wsi - * timeout status - */ - - ah = pt->ah_list; - while (ah) { - int len; - char buf[256]; - const unsigned char *c; - - if (!ah->in_use || !ah->wsi || !ah->assigned || - (ah->wsi->vhost && - lws_compare_time_t(context, now, ah->assigned) < - ah->wsi->vhost->timeout_secs_ah_idle + 360)) { - ah = ah->next; + len = lws_hdr_total_length(wsi, m); + if (!len || len > (int)sizeof(buf) - 1) { + m++; continue; } - /* - * a single ah session somehow got held for - * an unreasonable amount of time. - * - * Dump info on the connection... - */ - wsi = ah->wsi; - buf[0] = '\0'; -#if !defined(LWS_PLAT_OPTEE) - lws_get_peer_simple(wsi, buf, sizeof(buf)); -#else - buf[0] = '\0'; -#endif - lwsl_notice("ah excessive hold: wsi %p\n" - " peer address: %s\n" - " ah rxpos %u, rxlen %u, pos %u\n", - wsi, buf, ah->rxpos, ah->rxlen, - ah->pos); - buf[0] = '\0'; - m = 0; - do { - c = lws_token_to_string(m); - if (!c) - break; - if (!(*c)) - break; + if (lws_hdr_copy(wsi, buf, + sizeof buf, m) > 0) { + buf[sizeof(buf) - 1] = '\0'; - len = lws_hdr_total_length(wsi, m); - if (!len || len > (int)sizeof(buf) - 1) { - m++; - continue; - } + lwsl_notice(" %s = %s\n", + (const char *)c, buf); + } + m++; + } while (1); - if (lws_hdr_copy(wsi, buf, - sizeof buf, m) > 0) { - buf[sizeof(buf) - 1] = '\0'; + /* explicitly detach the ah */ - lwsl_notice(" %s = %s\n", - (const char *)c, buf); - } - m++; - } while (1); + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); - /* explicitly detach the ah */ + /* ... and then drop the connection */ - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 0); + m = 0; + if (wsi->desc.sockfd == our_fd) { + m = timed_out; - /* ... and then drop the connection */ - - if (wsi->desc.sockfd == our_fd) - /* it was the guy we came to service! */ - timed_out = 1; - - __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "excessive ah"); - - ah = pt->ah_list; + /* it was the guy we came to service! */ + timed_out = 1; } - lws_pt_unlock(pt); + if (!m) /* if he didn't already timeout */ + __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "excessive ah"); + + ah = pt->ah_list; + } + + lws_pt_unlock(pt); -#ifdef LWS_WITH_CGI - /* - * Phase 3: handle cgi timeouts - */ - lws_cgi_kill_terminated(pt); -#endif #if 0 - { - char s[300], *p = s; + { + char s[300], *p = s; - for (n = 0; n < context->count_threads; n++) - p += sprintf(p, " %7lu (%5d), ", - context->pt[n].count_conns, - context->pt[n].fds_count); + for (n = 0; n < context->count_threads; n++) + p += sprintf(p, " %7lu (%5d), ", + context->pt[n].count_conns, + context->pt[n].fds_count); - lwsl_notice("load: %s\n", s); - } + lwsl_notice("load: %s\n", s); + } #endif - /* - * Phase 4: vhost / protocol timer callbacks - */ - - wsi = NULL; - lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { - struct lws_timed_vh_protocol *nx; - if (v->timed_vh_protocol_list) { - lws_start_foreach_ll(struct lws_timed_vh_protocol *, - q, v->timed_vh_protocol_list) { - if (now >= q->time) { - if (!wsi) - wsi = lws_zalloc(sizeof(*wsi), "cbwsi"); - wsi->context = context; - wsi->vhost = v; - wsi->protocol = q->protocol; - lwsl_debug("timed cb: vh %s, protocol %s, reason %d\n", v->name, q->protocol->name, q->reason); - q->protocol->callback(wsi, q->reason, NULL, NULL, 0); - nx = q->next; - lws_timed_callback_remove(v, q); - q = nx; - continue; /* we pointed ourselves to the next from the now-deleted guy */ - } - } lws_end_foreach_ll(q, next); - } - } lws_end_foreach_ll(v, vhost_next); - if (wsi) - lws_free(wsi); - - /* - * Phase 5: check for unconfigured vhosts due to required - * interface missing before - */ - - lws_context_lock(context); - lws_start_foreach_llp(struct lws_vhost **, pv, - context->no_listener_vhost_list) { - struct lws_vhost *v = *pv; - lwsl_debug("deferred iface: checking if on vh %s\n", (*pv)->name); - if (lws_context_init_server(NULL, *pv) == 0) { - /* became happy */ - lwsl_notice("vh %s: became connected\n", v->name); - *pv = v->no_listener_vhost_list; - v->no_listener_vhost_list = NULL; - break; - } - } lws_end_foreach_llp(pv, no_listener_vhost_list); - lws_context_unlock(context); - } - /* - * at intervals, check for ws connections needing ping-pong checks + * Phase 3: vhost / protocol timer callbacks */ - if (context->ws_ping_pong_interval && - context->last_ws_ping_pong_check_s < now + 10) { - struct lws_vhost *vh = context->vhost_list; - context->last_ws_ping_pong_check_s = now; - - while (vh) { - - lws_vhost_lock(vh); - - for (n = 0; n < vh->count_protocols; n++) { - 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; + wsi = NULL; + lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { + struct lws_timed_vh_protocol *nx; + if (v->timed_vh_protocol_list) { + lws_start_foreach_ll(struct lws_timed_vh_protocol *, + q, v->timed_vh_protocol_list) { + if (now >= q->time) { + if (!wsi) + wsi = lws_zalloc(sizeof(*wsi), "cbwsi"); + wsi->context = context; + wsi->vhost = v; + wsi->protocol = q->protocol; + lwsl_debug("timed cb: vh %s, protocol %s, reason %d\n", v->name, q->protocol->name, q->reason); + q->protocol->callback(wsi, q->reason, NULL, NULL, 0); + nx = q->next; + lws_timed_callback_remove(v, q); + q = nx; + continue; /* we pointed ourselves to the next from the now-deleted guy */ } - } - - lws_vhost_unlock(vh); - - vh = vh->vhost_next; + } lws_end_foreach_ll(q, next); } - } + } lws_end_foreach_ll(v, vhost_next); + if (wsi) + lws_free(wsi); -#ifdef LWS_OPENSSL_SUPPORT /* - * check the remaining cert lifetime daily + * Phase 4: check for unconfigured vhosts due to required + * interface missing before */ + + lws_context_lock(context); + lws_start_foreach_llp(struct lws_vhost **, pv, + context->no_listener_vhost_list) { + struct lws_vhost *v = *pv; + lwsl_debug("deferred iface: checking if on vh %s\n", (*pv)->name); + if (lws_context_init_server(NULL, *pv) == 0) { + /* became happy */ + lwsl_notice("vh %s: became connected\n", v->name); + *pv = v->no_listener_vhost_list; + v->no_listener_vhost_list = NULL; + break; + } + } lws_end_foreach_llp(pv, no_listener_vhost_list); + lws_context_unlock(context); + + /* + * Phase 5: role periodic checks + */ +#if defined(LWS_ROLE_WS) + role_ops_ws.periodic_checks(context, tsi, now); +#endif +#if defined(LWS_ROLE_CGI) + role_ops_cgi.periodic_checks(context, tsi, now); +#endif + + /* + * Phase 6: check the remaining cert lifetime daily + */ +#if defined(LWS_WITH_TLS) n = lws_compare_time_t(context, now, context->last_cert_check_s); if ((!context->last_cert_check_s || n > (24 * 60 * 60)) && !lws_tls_check_all_cert_lifetimes(context)) context->last_cert_check_s = now; #endif - /* the socket we came to service timed out, nothing to do */ - if (timed_out) - return 0; + return timed_out; +} - /* just here for timeout management? */ - if (!pollfd) +LWS_VISIBLE int +lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, + int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct lws *wsi; + + /* the socket we came to service timed out, nothing to do */ + if (lws_service_periodic_checks(context, pollfd, tsi) || !pollfd) return 0; /* no, here to service a socket descriptor */ @@ -1422,14 +755,13 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, * zero down pollfd->revents after handling */ -#if LWS_POSIX /* handle session socket closed */ if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && (pollfd->revents & LWS_POLLHUP)) { wsi->socket_is_permanently_unusable = 1; lwsl_debug("Session Socket %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); + (void *)wsi, pollfd->fd); goto close_and_handled; } @@ -1437,8 +769,6 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, #ifdef _WIN32 if (pollfd->revents & LWS_POLLOUT) wsi->sock_send_blocking = FALSE; -#endif - #endif if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && @@ -1448,9 +778,8 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, goto close_and_handled; } -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) if (lwsi_state(wsi) == LRS_SHUTDOWN && lws_is_ssl(wsi) && wsi->ssl) { - n = 0; switch (__lws_tls_shutdown(wsi)) { case LWS_SSL_CAPABLE_DONE: case LWS_SSL_CAPABLE_ERROR: @@ -1467,501 +796,38 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, /* okay, what we came here to do... */ - // lwsl_err("x 0x%x\n", wsi->wsistate); + /* if we got here, we should have wire protocol ops set on the wsi */ + assert(wsi->role_ops); - switch (lwsi_role(wsi)) { - case LWSI_ROLE_EVENT_PIPE: - { -#if !defined(WIN32) && !defined(_WIN32) - char s[10]; + // lwsl_notice("%s: %s: wsistate 0x%x\n", __func__, wsi->role_ops->name, + // wsi->wsistate); - /* - * discard the byte(s) that signaled us - * We really don't care about the number of bytes, but coverity - * thinks we should. - */ - n = read(wsi->desc.sockfd, s, sizeof(s)); - (void)n; - if (n < 0) - goto close_and_handled; -#endif - /* - * the poll() wait, or the event loop for libuv etc is a - * process-wide resource that we interrupted. So let every - * protocol that may be interested in the pipe event know that - * it happened. - */ - if (lws_broadcast(context, LWS_CALLBACK_EVENT_WAIT_CANCELLED, - NULL, 0)) { - lwsl_info("closed in event cancel\n"); - goto close_and_handled; - } - - goto handled; - } - - case LWSI_ROLE_H1_CLIENT: - - if (lwsi_state(wsi) == LRS_ESTABLISHED) - goto handled; - - goto do_client; - - case LWSI_ROLE_H1_SERVER: - case LWSI_ROLE_LISTEN_SOCKET: - -#ifdef LWS_WITH_CGI - if (wsi->cgi && (pollfd->revents & LWS_POLLOUT)) { - n = lws_handle_POLLOUT_event(wsi, pollfd); - if (n) - goto close_and_handled; - goto handled; - } -#endif - /* fallthru */ - case LWSI_ROLE_RAW_SOCKET: - n = lws_server_socket_service(context, wsi, pollfd); - if (n) /* closed by above */ - return 1; - goto handled; - - case LWSI_ROLE_RAW_FILE: - - if (pollfd->revents & LWS_POLLOUT) { - n = lws_calllback_as_writeable(wsi); - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - return 1; - } - if (n) - goto close_and_handled; - } - n = LWS_CALLBACK_RAW_RX; - if (lwsi_role(wsi) == LWSI_ROLE_RAW_FILE) - n = LWS_CALLBACK_RAW_RX_FILE; - - if (pollfd->revents & LWS_POLLIN) { - if (user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, n, wsi->user_space, NULL, 0)) { - lwsl_debug("raw rx callback closed it\n"); - goto close_and_handled; - } - } - - if (pollfd->revents & LWS_POLLHUP) - goto close_and_handled; - n = 0; - goto handled; - - case LWSI_ROLE_WS1_SERVER: - case LWSI_ROLE_WS1_CLIENT: - case LWSI_ROLE_H2_SERVER: - case LWSI_ROLE_WS2_SERVER: - case LWSI_ROLE_H2_CLIENT: - case LWSI_ROLE_WS2_CLIENT: - - 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; - goto close_and_handled; - } - - if (lwsi_state(wsi) == LRS_WAITING_CONNECT) - goto do_client; - - /* 1: something requested a callback when it was OK to write */ - - if ((pollfd->revents & LWS_POLLOUT) && - (lwsi_state(wsi) & LWSIFS_POCB) /* ...our state cares ... */ && - lws_handle_POLLOUT_event(wsi, pollfd)) { - if (lwsi_state(wsi) == LRS_RETURNED_CLOSE) - lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE); - // lwsl_notice("lws_service_fd: closing\n"); - /* the write failed... it's had it */ - wsi->socket_is_permanently_unusable = 1; - goto close_and_handled; - } - - if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || - lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE || - lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) { - /* - * we stopped caring about anything except control - * packets. Force flow control off, defeat tx - * draining. - */ - lws_rx_flow_control(wsi, 1); - if (wsi->ws) - wsi->ws->tx_draining_ext = 0; - } - - if (wsi->ws && 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. - */ - break; - - if (lws_is_flowcontrolled(wsi)) - /* We cannot deal with any kind of new RX because we are - * RX-flowcontrolled. - */ - break; - -#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) - */ - break; - } -#endif - - /* 2: RX Extension needs to be drained - */ - - if (lwsi_role_ws(wsi) && wsi->ws && wsi->ws->rx_draining_ext) { - - lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__); -#ifndef LWS_NO_CLIENT - if (lwsi_role_ws_client(wsi)) { - n = lws_client_rx_sm(wsi, 0); - if (n < 0) - /* we closed wsi */ - n = 0; - } else -#endif - n = lws_rx_sm(wsi, 0); - - goto handled; - } - - if (wsi->ws && 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. - */ - break; - - /* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained - */ - - if (wsi->rxflow_buffer) { - lwsl_info("draining rxflow (len %d)\n", - wsi->rxflow_len - wsi->rxflow_pos); - assert(wsi->rxflow_pos < wsi->rxflow_len); - /* well, drain it */ - eff_buf.token = (char *)wsi->rxflow_buffer + - wsi->rxflow_pos; - eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; - draining_flow = 1; - goto drain; - } - -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2) { - struct lws_h2_netconn *h2n = wsi->h2.h2n; - - if (h2n->rx_scratch_len) { - lwsl_info("%s: %p: h2 rx pos = %d len = %d\n", - __func__, wsi, h2n->rx_scratch_pos, - h2n->rx_scratch_len); - eff_buf.token = (char *)h2n->rx_scratch + - h2n->rx_scratch_pos; - eff_buf.token_len = h2n->rx_scratch_len; - - h2n->rx_scratch_len = 0; - goto drain; - } - } -#endif - - /* 4: any incoming (or ah-stashed incoming rx) data ready? - * notice if rx flow going off raced poll(), rx flow wins - */ - - if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) - break; - -read: - if (lws_is_flowcontrolled(wsi)) { - lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n", - __func__, wsi, wsi->rxflow_bitmap); - break; - } - - if (wsi->ah && wsi->ah->rxlen - wsi->ah->rxpos) { - lwsl_info("%s: %p: inherited ah rx %d\n", __func__, - wsi, wsi->ah->rxlen - wsi->ah->rxpos); - eff_buf.token_len = wsi->ah->rxlen - - wsi->ah->rxpos; - eff_buf.token = (char *)wsi->ah->rx + - wsi->ah->rxpos; - } else { - if (!(lwsi_role_client(wsi) && - (lwsi_state(wsi) != LRS_ESTABLISHED && - lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) { - /* - * extension may not consume everything - * (eg, pmd may be constrained - * as to what it can output...) has to go in - * per-wsi rx buf area. - * Otherwise in large temp serv_buf area. - */ - -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2) { - if (!wsi->h2.h2n->rx_scratch) { - wsi->h2.h2n->rx_scratch = - lws_malloc( - wsi->vhost->h2_rx_scratch_size, - "h2 rx scratch"); - if (!wsi->h2.h2n->rx_scratch) - goto close_and_handled; - } - eff_buf.token = wsi->h2.h2n->rx_scratch; - eff_buf.token_len = wsi->vhost->h2_rx_scratch_size; - } else -#endif - { - eff_buf.token = (char *)pt->serv_buf; - if (lws_is_ws_with_ext(wsi)) { - eff_buf.token_len = - wsi->ws->rx_ubuf_alloc; - } else { - eff_buf.token_len = - context->pt_serv_buf_size; - } - - if ((unsigned int)eff_buf.token_len > - context->pt_serv_buf_size) - eff_buf.token_len = - context->pt_serv_buf_size; - } - - if ((int)pending > eff_buf.token_len) - pending = eff_buf.token_len; - - eff_buf.token_len = lws_ssl_capable_read(wsi, - (unsigned char *)eff_buf.token, - pending ? (int)pending : - eff_buf.token_len); - switch (eff_buf.token_len) { - case 0: - lwsl_info("%s: zero length read\n", - __func__); - goto close_and_handled; - case LWS_SSL_CAPABLE_MORE_SERVICE: - lwsl_info("SSL Capable more service\n"); - n = 0; - goto handled; - case LWS_SSL_CAPABLE_ERROR: - lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n", - __func__); - goto close_and_handled; - } - // lwsl_notice("Actual RX %d\n", eff_buf.token_len); - } - } - -drain: -#ifndef LWS_NO_CLIENT - if (lwsi_role_http_client(wsi) && - wsi->hdr_parsing_completed && - !wsi->told_user_closed) { - - /* - * In SSL mode we get POLLIN notification about - * encrypted data in. - * - * But that is not necessarily related to decrypted - * data out becoming available; in may need to perform - * other in or out before that happens. - * - * simply mark ourselves as having readable data - * and turn off our POLLIN - */ - wsi->client_rx_avail = 1; - lws_change_pollfd(wsi, LWS_POLLIN, 0); - - /* let user code know, he'll usually ask for writeable - * callback and drain / re-enable it there - */ - if (user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, - wsi->user_space, NULL, 0)) { - lwsl_info("RECEIVE_CLIENT_HTTP closed it\n"); - goto close_and_handled; - } - - n = 0; - goto handled; - } -#endif - /* - * 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 { -#if !defined(LWS_WITHOUT_EXTENSIONS) - m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE, - &eff_buf, 0); - if (m < 0) - goto close_and_handled; -#endif - - /* service incoming data */ - - if (eff_buf.token_len) { - /* - * if draining from rxflow buffer, not - * critical to track what was used since at the - * use it bumps wsi->rxflow_pos. If we come - * around again it will pick up from where it - * left off. - */ - n = lws_read(wsi, (unsigned char *)eff_buf.token, - eff_buf.token_len); - if (n < 0) { - /* we closed wsi */ - n = 0; - goto handled; - } - } - - eff_buf.token = NULL; - eff_buf.token_len = 0; - } while (m); - - if (wsi->ah -#if !defined(LWS_NO_CLIENT) - && !wsi->client_h2_alpn -#endif - ) { - lwsl_info("%s: %p: detaching ah\n", __func__, wsi); - lws_header_table_force_to_detachable_state(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 > context->pt_serv_buf_size ? - context->pt_serv_buf_size : pending; - goto read; - } - - if (draining_flow && wsi->rxflow_buffer && - wsi->rxflow_pos == wsi->rxflow_len) { - lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); - lws_free_set_NULL(wsi->rxflow_buffer); - /* 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 */ - } - - break; -#ifdef LWS_WITH_CGI - case LWSI_ROLE_CGI: /* we exist to handle a cgi's stdin/out/err data... - * do the callback on our master wsi - */ - { - struct lws_cgi_args args; - - if (wsi->cgi_channel >= LWS_STDOUT && - !(pollfd->revents & pollfd->events & LWS_POLLIN)) - break; - if (wsi->cgi_channel == LWS_STDIN && - !(pollfd->revents & pollfd->events & LWS_POLLOUT)) - break; - - if (wsi->cgi_channel == LWS_STDIN) - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - return 1; - } - - args.ch = wsi->cgi_channel; - args.stdwsi = &wsi->parent->cgi->stdwsi[0]; - args.hdr_state = wsi->hdr_state; - - lwsl_debug("CGI LWS_STDOUT %p role 0x%x state 0x%x\n", - wsi->parent, lwsi_role(wsi->parent), - lwsi_state(wsi->parent)); - - if (user_callback_handle_rxflow( - wsi->parent->protocol->callback, - wsi->parent, LWS_CALLBACK_CGI, - wsi->parent->user_space, - (void *)&args, 0)) - return 1; - - break; - } -#endif - } - - n = 0; - goto handled; - -do_client: -#if !defined(LWS_NO_CLIENT) - if ((pollfd->revents & LWS_POLLOUT) && - lws_handle_POLLOUT_event(wsi, pollfd)) { - lwsl_debug("POLLOUT event closed it\n"); - goto close_and_handled; - } - - n = lws_client_socket_service(wsi, pollfd, NULL); - if (n) + switch ((wsi->role_ops->handle_POLLIN)(pt, wsi, pollfd)) { + case LWS_HPI_RET_DIE: return 1; -#endif - goto handled; - + case LWS_HPI_RET_HANDLED: + break; + case LWS_HPI_RET_CLOSE_HANDLED: close_and_handled: - lwsl_debug("%p: Close and handled\n", wsi); - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "close_and_handled"); - /* - * pollfd may point to something else after the close - * due to pollfd swapping scheme on delete on some platforms - * we can't clear revents now because it'd be the wrong guy's revents - */ - return 1; - + lwsl_debug("%p: Close and handled\n", wsi); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "close_and_handled"); + /* + * pollfd may point to something else after the close + * due to pollfd swapping scheme on delete on some platforms + * we can't clear revents now because it'd be the wrong guy's + * revents + */ + return 1; + default: + assert(0); + } +#if defined(LWS_WITH_TLS) handled: +#endif pollfd->revents = 0; - return n; + + return 0; } LWS_VISIBLE int diff --git a/lib/client/ssl-client.c b/lib/tls/tls-client.c similarity index 100% rename from lib/client/ssl-client.c rename to lib/tls/tls-client.c diff --git a/lib/server/ssl-server.c b/lib/tls/tls-server.c similarity index 100% rename from lib/server/ssl-server.c rename to lib/tls/tls-server.c diff --git a/minimal-examples/http-client/minimal-http-client-multi/README.md b/minimal-examples/http-client/minimal-http-client-multi/README.md index b47f66c6..f31b6228 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/README.md +++ b/minimal-examples/http-client/minimal-http-client-multi/README.md @@ -13,3 +13,12 @@ same as minimal http client. However it does it for 8 client connections concurrently. +## Commandline Options + +Option|Meaning +---|--- +-s|Stagger the connections by 100ms, the last by 1s +-p|Use http/1.1 pipelining or h2 simultaneous streams +--h1|Force http/1 only +-l|Connect to server on https://localhost:7681 instead of https://warmcat.com:443 + diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index a1a213de..6e271036 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -35,7 +35,6 @@ #include #define COUNT 8 -//#define STAGGERED_CONNECTIONS struct user { int index; @@ -136,36 +135,15 @@ unsigned long long us(void) } static void -lws_try_client_connection(struct lws_context *context, int m) +lws_try_client_connection(struct lws_client_connect_info *i, int m) { - struct lws_client_connect_info i; + i->path = "/"; - memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ - i.context = context; - -#if 0 - i.port = 7681; - i.address = "localhost"; -#else - i.port = 443; - i.address = "warmcat.com"; -#endif - i.path = "/"; - i.host = i.address; - i.origin = i.address; - i.ssl_connection = LCCSCF_PIPELINE | /* enables h1 or h2 connection sharing */ - // LCCSCF_NOT_H2 | /* forces http/1 */ - LCCSCF_ALLOW_SELFSIGNED | /* allow selfsigned cert */ - LCCSCF_USE_SSL; - i.method = "GET"; - - i.protocol = protocols[0].name; - - i.pwsi = &client_wsi[m]; + i->pwsi = &client_wsi[m]; user[m].index = m; - i.userdata = &user[m]; + i->userdata = &user[m]; - if (!lws_client_connect_via_info(&i)) { + if (!lws_client_connect_via_info(i)) { failed++; if (++completed == COUNT) { lwsl_user("Done: failed: %d\n", failed); @@ -175,27 +153,45 @@ lws_try_client_connection(struct lws_context *context, int m) lwsl_user("started connection %d\n", m); } +static int commandline_option(int argc, char **argv, const char *val) +{ + int n = strlen(val); + + while (--argc > 0) { + if (!strncmp(argv[argc], val, n)) + return argc; + } + + return 0; +} + int main(int argc, char **argv) { struct lws_context_creation_info info; + struct lws_client_connect_info i; struct lws_context *context; - unsigned long long start -#if defined(STAGGERED_CONNECTIONS) - , next -#endif - ; - int n = 0, m; + unsigned long long start, next; + int n = 0, m, staggered = 0, logs = + LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; signal(SIGINT, sigint_handler); - lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */, NULL); - lwsl_user("LWS minimal http client\n"); + memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + + staggered = !!commandline_option(argc, argv, "-s"); + m = commandline_option(argc, argv, "-d"); + if (m && m + 1 < argc) + logs = atoi(argv[m + 1]); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal http client [-s (staggered)] [-p (pipeline)]\n"); + lwsl_user(" [--h1 (http/1 only)] [-l (localhost)]\n"); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; @@ -218,43 +214,67 @@ int main(int argc, char **argv) return 1; } -#if !defined(STAGGERED_CONNECTIONS) - /* - * just pile on all the connections at once, testing the queueing - */ - for (m = 0; m < (int)LWS_ARRAY_SIZE(client_wsi); m++) - lws_try_client_connection(context, m); -#else - next = -#endif - start = us(); + i.context = context; + i.ssl_connection = LCCSCF_USE_SSL; + + /* enables h1 or h2 connection sharing */ + if (commandline_option(argc, argv, "-p")) + i.ssl_connection |= LCCSCF_PIPELINE; + + /* force h1 even if h2 available */ + if (commandline_option(argc, argv, "--h1")) + i.ssl_connection |= LCCSCF_NOT_H2; + + if (commandline_option(argc, argv, "-l")) { + i.port = 7681; + i.address = "localhost"; + i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; + } else { + i.port = 443; + i.address = "warmcat.com"; + } + + i.host = i.address; + i.origin = i.address; + i.method = "GET"; + i.protocol = protocols[0].name; + + if (!staggered) + /* + * just pile on all the connections at once, testing the + * pipeline queueing before the first is connected + */ + for (m = 0; m < (int)LWS_ARRAY_SIZE(client_wsi); m++) + lws_try_client_connection(&i, m); + + next = start = us(); m = 0; while (n >= 0 && !interrupted) { -#if defined(STAGGERED_CONNECTIONS) - /* - * open the connections at 100ms intervals, with the last - * one being after 1s, testing queueing, and direct H2 stream - * addition stability - */ - if (us() > next && m < (int)LWS_ARRAY_SIZE(client_wsi)) { + if (staggered) { + /* + * open the connections at 100ms intervals, with the + * last one being after 1s, testing both queueing, and + * direct H2 stream addition stability + */ + if (us() > next && m < (int)LWS_ARRAY_SIZE(client_wsi)) { - lws_try_client_connection(context, m++); + lws_try_client_connection(&i, m++); - if (m == (int)LWS_ARRAY_SIZE(client_wsi) - 1) - next = us() + 1000000; - else - next = us() + 100000; + if (m == (int)LWS_ARRAY_SIZE(client_wsi) - 1) + next = us() + 1000000; + else + next = us() + 100000; + } } -#endif - n = lws_service(context, 1000); + n = lws_service(context, 100); } lwsl_user("Duration: %lldms\n", (us() - start) / 1000); - lws_context_destroy(context); - lwsl_user("Completed\n"); - return 0; + lwsl_user("Exiting with %d\n", failed || completed != COUNT); + + return failed || completed != COUNT; } diff --git a/minimal-examples/raw/minimal-raw-adopt-tcp/README.md b/minimal-examples/raw/minimal-raw-adopt-tcp/README.md index 6f548063..605c722a 100644 --- a/minimal-examples/raw/minimal-raw-adopt-tcp/README.md +++ b/minimal-examples/raw/minimal-raw-adopt-tcp/README.md @@ -16,6 +16,9 @@ The example connects a socket itself to libwebsockets.org:80, and then has lws adopt it as a raw wsi. The lws protocol writes "GET / HTTP/1.1" to the socket and hexdumps what was sent back. +The socket won't close until the server side times it out, since it's +a raw socket that doesn't understand it's looking at http. + ## build ``` diff --git a/minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c b/minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c index 86b82caf..b975662a 100644 --- a/minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c +++ b/minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c @@ -111,7 +111,7 @@ int main(int argc, char **argv) return 1; } - info.port = 7681; + info.port = CONTEXT_PORT_NO_LISTEN_SERVER; info.protocols = protocols; vhost = lws_create_vhost(context, &info); diff --git a/test-apps/test-client.c b/test-apps/test-client.c index 88bc8de6..69056408 100644 --- a/test-apps/test-client.c +++ b/test-apps/test-client.c @@ -54,7 +54,7 @@ static struct lws_poly_gen tx = { { 0xabcde, 0x23456789 } }, rx = { { 0xabcde, 0x23456789 } } ; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif @@ -116,7 +116,7 @@ static int callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) union lws_tls_cert_info_results ci; #endif const char *which = "http"; @@ -177,7 +177,7 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: lwsl_notice("lws_http_client_http_response %d\n", lws_http_client_http_response(wsi)); -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME, &ci, sizeof(ci.ns.name))) lwsl_notice(" Peer Cert CN : %s\n", ci.ns.name); @@ -272,7 +272,7 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, force_exit = 1; break; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) && \ +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) && \ !defined(LWS_WITH_MBEDTLS) case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: if (crl_path[0]) { @@ -419,7 +419,7 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, lws_callback_on_writable(wsi); #if !defined(_WIN32) && !defined(WIN32) - usleep(250); + usleep(50); #endif break; @@ -545,7 +545,7 @@ static struct option options[] = { { "ssl-cert", required_argument, NULL, 'C' }, { "ssl-key", required_argument, NULL, 'K' }, { "ssl-ca", required_argument, NULL, 'A' }, -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) { "ssl-crl", required_argument, NULL, 'R' }, #endif { NULL, 0, 0, 0 } @@ -650,7 +650,7 @@ int main(int argc, char **argv) lws_strncpy(ca_path, optarg, sizeof(ca_path)); break; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) case 'R': lws_strncpy(crl_path, optarg, sizeof(crl_path)); break; @@ -697,7 +697,7 @@ int main(int argc, char **argv) info.ws_ping_pong_interval = pp_secs; info.extensions = exts; -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; #endif @@ -718,7 +718,7 @@ int main(int argc, char **argv) if (ca_path[0]) info.client_ssl_ca_filepath = ca_path; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) else if (crl_path[0]) lwsl_notice("WARNING, providing a CRL requires a CA cert!\n"); #endif diff --git a/test-apps/test-server-http.c b/test-apps/test-server-http.c index d895d267..1fb9f502 100644 --- a/test-apps/test-server-http.c +++ b/test-apps/test-server-http.c @@ -34,7 +34,7 @@ * using this protocol, including the sender */ -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) /* location of the certificate revocation list */ extern char crl_path[1024]; #endif @@ -230,7 +230,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, goto try_to_reuse; } -#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT) +#if !defined(LWS_NO_CLIENT) && defined(LWS_WITH_TLS) if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; char *rootpath = "/git/"; @@ -773,7 +773,7 @@ bail: break; -#if defined(LWS_OPENSSL_SUPPORT) && !defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: /* Verify the client certificate */ if (!len || (SSL_get_verify_result((SSL*)in) != X509_V_OK)) { diff --git a/test-apps/test-server-libev.c b/test-apps/test-server-libev.c index 5766c0c7..5b86bd60 100644 --- a/test-apps/test-server-libev.c +++ b/test-apps/test-server-libev.c @@ -30,7 +30,7 @@ struct lws_plat_file_ops fops_plat; #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" char *resource_path = LOCAL_RESOURCE_PATH; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif diff --git a/test-apps/test-server-libevent.c b/test-apps/test-server-libevent.c index 932701e7..d57622d2 100644 --- a/test-apps/test-server-libevent.c +++ b/test-apps/test-server-libevent.c @@ -30,7 +30,7 @@ struct lws_plat_file_ops fops_plat; #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" char *resource_path = LOCAL_RESOURCE_PATH; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif diff --git a/test-apps/test-server-libuv.c b/test-apps/test-server-libuv.c index c4612e52..c27e779f 100644 --- a/test-apps/test-server-libuv.c +++ b/test-apps/test-server-libuv.c @@ -33,7 +33,7 @@ struct lws_plat_file_ops fops_plat; #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" char *resource_path = LOCAL_RESOURCE_PATH; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif diff --git a/test-apps/test-server-pthreads.c b/test-apps/test-server-pthreads.c index bb090a23..b7dbd42c 100644 --- a/test-apps/test-server-pthreads.c +++ b/test-apps/test-server-pthreads.c @@ -33,7 +33,7 @@ int count_pollfds; volatile int force_exit = 0; struct lws_context *context; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif diff --git a/test-apps/test-server-v2.0.c b/test-apps/test-server-v2.0.c index ee2c032e..5e1fda7c 100644 --- a/test-apps/test-server-v2.0.c +++ b/test-apps/test-server-v2.0.c @@ -46,7 +46,7 @@ uv_timer_t timeout_watcher; #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" char *resource_path = LOCAL_RESOURCE_PATH; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif @@ -296,7 +296,7 @@ static const struct option options[] = { { "ssl-cert", required_argument, NULL, 'C' }, { "ssl-key", required_argument, NULL, 'K' }, { "ssl-ca", required_argument, NULL, 'A' }, -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) { "ssl-verify-client", no_argument, NULL, 'v' }, #if defined(LWS_HAVE_SSL_CTX_set1_param) { "ssl-crl", required_argument, NULL, 'R' }, @@ -363,7 +363,7 @@ int main(int argc, char **argv) use_ssl = 1; break; case 'S': -#if defined(LWS_OPENSSL_SUPPORT) && !defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) info.ssl_info_event_mask |= SSL_CB_ALERT; #endif break; @@ -390,7 +390,7 @@ int main(int argc, char **argv) case 'A': lws_strncpy(ca_path, optarg, sizeof(ca_path)); break; -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) case 'v': use_ssl = 1; opts |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; diff --git a/test-apps/test-server.c b/test-apps/test-server.c index 9e75a0fa..9e86590c 100644 --- a/test-apps/test-server.c +++ b/test-apps/test-server.c @@ -37,7 +37,7 @@ struct lws_plat_file_ops fops_plat; /* http server gets files from this path */ #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" char *resource_path = LOCAL_RESOURCE_PATH; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) char crl_path[1024] = ""; #endif @@ -211,7 +211,7 @@ static struct option options[] = { { "ssl-cert", required_argument, NULL, 'C' }, { "ssl-key", required_argument, NULL, 'K' }, { "ssl-ca", required_argument, NULL, 'A' }, -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) { "ssl-verify-client", no_argument, NULL, 'v' }, #if defined(LWS_HAVE_SSL_CTX_set1_param) { "ssl-crl", required_argument, NULL, 'R' }, @@ -339,7 +339,7 @@ int main(int argc, char **argv) pp_secs = atoi(optarg); lwsl_notice("Setting pingpong interval to %d\n", pp_secs); break; -#if defined(LWS_OPENSSL_SUPPORT) +#if defined(LWS_WITH_TLS) case 'v': use_ssl = 1; opts |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; @@ -475,7 +475,7 @@ int main(int argc, char **argv) info.port++; -#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT) +#if !defined(LWS_NO_CLIENT) && defined(LWS_WITH_TLS) lws_init_vhost_client_ssl(&info, vhost); #endif diff --git a/test-apps/test-server.h b/test-apps/test-server.h index 892f1e01..c8775042 100644 --- a/test-apps/test-server.h +++ b/test-apps/test-server.h @@ -63,7 +63,7 @@ extern int count_pollfds; extern volatile int force_exit; extern struct lws_context *context; extern char *resource_path; -#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param) +#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) extern char crl_path[1024]; #endif