diff --git a/CMakeLists.txt b/CMakeLists.txt
index de16b5d81a6faf070687e9a46f03d164007f5f14..260979cb6b5c98eeda2b6ea83a958f127071844b 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/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/server/cgi.c)
+		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 b9652dd06eb43ad2288705c16b6633b68b1c8066..ed99139d8bea01c0017dec2cec6996bcc35f4b5e 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 dca7e2949bffccb37b117b8dcec74348732083a1..ed8ff38cd13f91769f123061a52acb92e1e0bf12 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 ca8992ccd6bd489aac807d34297917f3eac7cc75..d7afc67158567154ed33cf050e4a3fd8770c1b5a 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 ddd9dde5859a3700c8408413b3b930f9074fda06..a3c054c7bbf3f212eacc6625244d985b8d0de964 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 15f93cc39ea76e939691ef77b584cc1e11880bf5..2420d8b940d28b98c0da3932c0f6c288854099d0 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 2a89fec0256689a40026b3097ba276b32d5df4ee..0000000000000000000000000000000000000000
--- 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 <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "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 563b594af7a9dde1fe24f9a67f06640ed46c310e..6c430b4d8b029d28af04900efe4402290a07e867 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,67 +851,23 @@ 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->role_ops->close_role)
+	    wsi->role_ops->close_role(pt, wsi);
 
-		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);
+	/* tell the user it's all over for this guy */
 
-		if (wsi->trunc_alloc)
-			/* not going to be completed... nuke it */
-			lws_free_set_NULL(wsi->trunc_alloc);
+	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;
 
-		wsi->ws->ping_payload_len = 0;
-		wsi->ws->ping_pending_flag = 0;
+		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;
 	}
 
-	/* 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);
-
 	/* deallocate any active extension contexts */
 
 	if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
@@ -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 fd3056cf17d78a5713d3f1fadf379ae995df9594..d2c254a7f328a4b8a285e3239a4ca5ada3615d5c 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 <stdint.h>
 #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 <mbedtls/sha1.h>
@@ -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 a7a3353ea9fd09b077277d9c73bbf75a4b0b5622..c51e7c5af09598d34073e9660660b73e05332ffa 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;
-
-		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;
-		}
+	assert(wsi->role_ops);
+	if (!wsi->role_ops->write_role_protocol)
+		return lws_issue_raw(wsi, buf, len);
 
-		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 c082860520e4090efa051d442a1a6dfce56f2582..3c4dfb5116d22dacfd054e3d92e838f62c4a9bee 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 41160cb8a70f311a245794c4181f8efae28384dc..4003f7c80f3c5c940ffcb7e5cd756d00d8e32d62 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 9a075f97af104a297cf75660dc9f069cff1ef2a6..6b8eff88d40a51d1f083569acb96f2a690d6dbc0 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 d8836e29e6c5780078e9e1a3a136c563cdc82a14..d9029c44670da6628ec20180f39b7e77478367ea 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;
-	}
 
-	/* 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;
+	if (wsi->role_ops->callback_on_writable) {
+		if (wsi->role_ops->callback_on_writable(wsi))
+			return 1;
+		wsi = lws_get_network_wsi(wsi);
 	}
 
-	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 5dfdb3fb45d8349f966130155bea892270fbfbe7..2edcf9d6d25e45f2f83dfff8dfb0ec3fe2b1ec65 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)
-
-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),
+#define LWSIFR_CLIENT		(0x1000 << _RS) /* client side */
+#define LWSIFR_SERVER		(0x2000 << _RS) /* server side */
 
-	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)),
+#define LWSIFR_P_ENCAP_H2	(0x0100 << _RS) /* we are encapsulated by h2 */
 
-	LWSI_ROLE_MASK					= (0xffff << _RS),
+enum lwsi_role {
+	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 e1cbc12beeeadb5f2c1d15bae8900e06e56c9cd5..1b40ea1589dddb4cacce21a1deae2ee5b94d5a55 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 0000000000000000000000000000000000000000..88210160ea3a11677cf2f8b4b61931cc806c432c
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+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 0000000000000000000000000000000000000000..1229aabb34630d462058051bebfabe6ad29b21b2
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <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_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 0000000000000000000000000000000000000000..6e6d6142d11519e0c4310cf3418918ba2545c469
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+#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 0ea20ff3d3e3ac1555559a8b1d3b9d7f749a5650..8563204a596a0b7a325c7eada67a39d222abada2 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 0000000000000000000000000000000000000000..ded3ba24e61808f8b160444206f5cbf8555b08b1
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+/*
+ * 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 41dc1127b7ead6b65ddc7634a283ff83a6060006..5174b6f748667ca04a7213ce1220ea6e8c33f19f 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 35e8075f0360ec20960ea298a59af5918e47c467..cbd8e740fd6f7484001ff17ecdbbe2fc35d6bc16 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 46964bfdf0402b8ec538a5bc18f9f64e844e7460..5ffaf894a28d86659de61c19e4c5ceab989b81a3 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";
-		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";
+#if defined(LWS_ROLE_WS)
+	switch (lws_client_ws_upgrade(wsi, &cce)) {
+	case 2:
 		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
-
-	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";
+	case 3:
 		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;
+	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 + 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 = wsi->vhost->protocols[0].callback(wsi,
-				LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
-					wsi->user_space, (char *)ext->name, 0);
+			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;
 
-			/*
-			 * zero return from callback means go ahead and allow
-			 * the extension, it's what we get if the callback is
-			 * unhandled
-			 */
+		case ELCP_CONTENT:
+			break;
+
+		case ELCP_POST_CR:
+			if ((*buf)[0] != '\x0d') {
+				lwsl_debug("chunking failure\n");
 
-			if (n) {
-				ext++;
-				continue;
+				return -1;
 			}
 
-			/* apply it */
+			wsi->chunk_parser = ELCP_POST_LF;
+			break;
 
-			if (ext_count)
-				*p++ = ',';
-			else
-				p += sprintf(p, "Sec-WebSocket-Extensions: ");
-			p += sprintf(p, "%s", ext->client_offer);
-			ext_count++;
+		case ELCP_POST_LF:
+			if ((*buf)[0] != '\x0a')
+				return -1;
 
-			ext++;
+			wsi->chunk_parser = ELCP_HEX;
+			wsi->chunk_remaining = 0;
+			break;
 		}
-		if (ext_count)
-			p += sprintf(p, "\x0d\x0a");
-#endif
+		(*buf)++;
+		(*len)--;
+	}
+
+	if (wsi->chunked && !wsi->chunk_remaining)
+		return 0;
 
-		if (wsi->ws->ietf_spec_revision)
-			p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a",
-				     wsi->ws->ietf_spec_revision);
+	if (wsi->http.rx_content_remain &&
+	    wsi->http.rx_content_remain < (unsigned int)*len)
+		n = (int)wsi->http.rx_content_remain;
+	else
+		n = *len;
 
-		/* prepare the expected server accept response */
+	if (wsi->chunked && wsi->chunk_remaining &&
+	    wsi->chunk_remaining < n)
+		n = wsi->chunk_remaining;
 
-		key_b64[39] = '\0'; /* enforce composed length below buf sizeof */
-		n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
-				  key_b64);
+#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);
 
-		lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash);
+		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__);
 
-		lws_b64_encode_string(hash, 20,
-			  wsi->ah->initial_handshake_hash_base64,
-			  sizeof(wsi->ah->initial_handshake_hash_base64));
+			return -1;
+		}
 	}
 
-	/* give userland a chance to append, eg, cookies */
+	if (wsi->chunked && wsi->chunk_remaining) {
+		(*buf) += n;
+		wsi->chunk_remaining -= n;
+		*len -= n;
+	}
 
-	if (wsi->protocol->callback(wsi,
-				LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
-				wsi->user_space, &p,
-				(pkt + context->pt_serv_buf_size) - p - 12))
-		return NULL;
+	if (wsi->chunked && !wsi->chunk_remaining)
+		wsi->chunk_parser = ELCP_POST_CR;
 
-	p += sprintf(p, "\x0d\x0a");
+	if (wsi->chunked && *len)
+		goto spin_chunks;
 
-	return p;
+	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 1be0f7b99dd8cf0299e49d3330ae49ddb50fef8d..5a2900d512a919140d8813565d74df5bd2e4af75 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 01ddcbe3bea0e83930f138a3f9e5d7bf798f58ef..7a5799d2ed409717b58fe0dcc1a62c74cad5d84f 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 7577d75308cbcfbcbb9a0063e3ab0760149ebcc4..6adb522dae37403c90404f743469453aac4ca90a 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 <andy@warmcat.com>
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -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 a99b114962ab9d80b7861234d8af4bc0df2971d9..42398222ff114f8af61575bd315d2edc816762a5 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 <andy@warmcat.com>
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -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 0000000000000000000000000000000000000000..e9d4d147a16c98b8e6ec2824fb65b329ef95d94c
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+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 0000000000000000000000000000000000000000..8a573e7054107fa136127f9ddf182b521b6473af
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+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 0000000000000000000000000000000000000000..1afb4b9f23144002e6ea51000a7d018de645fa47
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+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 8c754eb7a7573036d8a0719e030a57087718260c..d34851189ed019281d39724f094ecca45b1063a7 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 0000000000000000000000000000000000000000..8fd29f601a6a27087056c658d0348de77880b440
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+/*
+ * In-place str to lower case
+ */
+
+static void
+strtolower(char *s)
+{
+	while (*s) {
+#ifdef LWS_PLAT_OPTEE
+		int tolower_optee(int c);
+		*s = tolower_optee((int)*s);
+#else
+		*s = tolower((int)*s);
+#endif
+		s++;
+	}
+}
+
+int
+lws_create_client_ws_object(struct lws_client_connect_info *i, struct lws *wsi)
+{
+	int v = SPEC_LATEST_SUPPORTED;
+
+	/* allocate the ws struct for the wsi */
+	wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct");
+	if (!wsi->ws) {
+		lwsl_notice("OOM\n");
+		return 1;
+	}
+
+	/* -1 means just use latest supported */
+	if (i->ietf_version_or_minus_one != -1 &&
+	    i->ietf_version_or_minus_one)
+		v = i->ietf_version_or_minus_one;
+
+	wsi->ws->ietf_spec_revision = v;
+
+	return 0;
+}
+
+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 0000000000000000000000000000000000000000..914b348fceda2bdd72395bc3c40ae70217fa821c
--- /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 <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <private-libwebsockets.h>
+
+#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 c3590cb3e23d5b360fe41d6a26005b1fc48aeca2..d2db682541f6fbba8c72fa63aa2924f0410377a5 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 <andy@warmcat.com>
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -19,7 +19,7 @@
  *  MA  02110-1301  USA
  */
 
-#include "private-libwebsockets.h"
+#include <private-libwebsockets.h>
 
 #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 c9210a2ecfc565692910031942efbe94affb79a9..74d8586267de605ea334c9b72109f45f13c0e20f 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 <andy@warmcat.com>
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -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
+#ifdef LWS_WITH_CGI
 	/*
-	 * Priority 2: protocol packets
+	 * A cgi master's wire protocol remains h1 or h2.  He is just getting
+	 * his data from his child cgis.
 	 */
-	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
 	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 (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;
-		}
-
-		goto 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)
-			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);
+	/* if we got here, we should have wire protocol ops set on the wsi */
+	assert(wsi->role_ops);
 
+	if (!wsi->role_ops->handle_POLLOUT)
 		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 */
+	switch ((wsi->role_ops->handle_POLLOUT)(wsi)) {
+	case LWS_HP_RET_BAIL_OK:
 		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)
+	case LWS_HP_RET_BAIL_DIE:
 		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;
+	case LWS_HP_RET_USER_SERVICE:
+		break;
+	default:
+		assert(0);
 	}
 
-	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;
-
-		/*
-		 * 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;
-			}
+	lwsl_debug("%s: %p: non mux: wsistate 0x%x, ops %s\n", __func__, wsi,
+		   wsi->wsistate, wsi->role_ops->name);
 
-			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;
-
-		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);
+	if (wsi->role_ops->rxflow_cache)
+		if (wsi->role_ops->rxflow_cache(wsi, buf, n, len))
+			return 0;
 
-		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 */
-
-	/*
-	 * 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;
-	}
+#if defined(LWS_ROLE_WS)
+	forced |= role_ops_ws.service_flag_pending(context, tsi);
+#endif
 
-#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)
+static int
+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 */
+	/* global timeout check once per second */
 
-		if (pollfd)
-			our_fd = pollfd->fd;
+	if (pollfd)
+		our_fd = pollfd->fd;
 
-		/*
-		 * Phase 1: check every wsi on the timeout check list
-		 */
+	/*
+	 * 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 */
-			}
-		} lws_end_foreach_dll_safe(d, d1);
+	lws_pt_lock(pt, __func__);
 
-		/*
-		 * Phase 2: double-check active ah timeouts independent of wsi
-		 *	    timeout status
-		 */
+	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);
 
-		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 2: double-check active ah timeouts independent of wsi
+	 *	    timeout status
+	 */
 
-			/*
-			 * a single ah session somehow got held for
-			 * an unreasonable amount of time.
-			 *
-			 * Dump info on the connection...
-			 */
-			wsi = ah->wsi;
-			buf[0] = '\0';
+	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;
+		}
+
+		/*
+		 * 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));
+		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;
-
-				len = lws_hdr_total_length(wsi, m);
-				if (!len || len > (int)sizeof(buf) - 1) {
-					m++;
-					continue;
-				}
-
-				if (lws_hdr_copy(wsi, buf,
-						 sizeof buf, m) > 0) {
-					buf[sizeof(buf) - 1] = '\0';
+		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;
 
-					lwsl_notice("   %s = %s\n",
-						    (const char *)c, buf);
-				}
+			len = lws_hdr_total_length(wsi, m);
+			if (!len || len > (int)sizeof(buf) - 1) {
 				m++;
-			} while (1);
+				continue;
+			}
 
-			/* explicitly detach the ah */
+			if (lws_hdr_copy(wsi, buf,
+					 sizeof buf, m) > 0) {
+				buf[sizeof(buf) - 1] = '\0';
 
-			lws_header_table_force_to_detachable_state(wsi);
-			lws_header_table_detach(wsi, 0);
+				lwsl_notice("   %s = %s\n",
+					    (const char *)c, buf);
+			}
+			m++;
+		} while (1);
 
-			/* ... and then drop the connection */
+		/* explicitly detach the ah */
 
-			if (wsi->desc.sockfd == our_fd)
-				/* it was the guy we came to service! */
-				timed_out = 1;
+		lws_header_table_force_to_detachable_state(wsi);
+		lws_header_table_detach(wsi, 0);
 
-			__lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "excessive ah");
+		/* ... and then drop the connection */
 
-			ah = pt->ah_list;
-		}
+		m = 0;
+		if (wsi->desc.sockfd == our_fd) {
+			m = timed_out;
 
-		lws_pt_unlock(pt);
+			/* it was the guy we came to service! */
+			timed_out = 1;
+		}
 
-#ifdef LWS_WITH_CGI
-		/*
-		 * Phase 3: handle cgi timeouts
-		 */
-		lws_cgi_kill_terminated(pt);
-#endif
-#if 0
-		{
-			char s[300], *p = s;
+		if (!m) /* if he didn't already timeout */
+			__lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+					     "excessive ah");
 
-			for (n = 0; n < context->count_threads; n++)
-				p += sprintf(p, " %7lu (%5d), ",
-					     context->pt[n].count_conns,
-					     context->pt[n].fds_count);
+		ah = pt->ah_list;
+	}
 
-			lwsl_notice("load: %s\n", s);
-		}
-#endif
-		/*
-		 * Phase 4: vhost / protocol timer callbacks
-		 */
+	lws_pt_unlock(pt);
 
-		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);
+#if 0
+	{
+		char s[300], *p = s;
 
-		/*
-		 * Phase 5: check for unconfigured vhosts due to required
-		 *	    interface missing before
-		 */
+		for (n = 0; n < context->count_threads; n++)
+			p += sprintf(p, " %7lu (%5d), ",
+				     context->pt[n].count_conns,
+				     context->pt[n].fds_count);
 
-		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);
+		lwsl_notice("load: %s\n", s);
 	}
-
+#endif
 	/*
-	 * 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_end_foreach_ll(q, next);
+		}
+	} lws_end_foreach_ll(v, vhost_next);
+	if (wsi)
+		lws_free(wsi);
 
-			lws_vhost_unlock(vh);
+	/*
+	 * Phase 4: check for unconfigured vhosts due to required
+	 *	    interface missing before
+	 */
 
-			vh = vh->vhost_next;
+	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);
 
-#ifdef LWS_OPENSSL_SUPPORT
 	/*
-	 * check the remaining cert lifetime daily
+	 * 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;
+}
+
+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;
 
-	/* just here for timeout management? */
-	if (!pollfd)
+	/* 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);
 
+	switch ((wsi->role_ops->handle_POLLIN)(pt, wsi, pollfd)) {
+	case LWS_HPI_RET_DIE:
+		return 1;
+	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");
 		/*
-		 * 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.
+		 * 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
 		 */
-		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)
 		return 1;
-#endif
-	goto 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;
-
+	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 b47f66c6bdcf3f4b9f906cdeff25950424af27d1..f31b62285deedf55a498ef50c2317cf0b4cbef6e 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 a1a213de94f874eea21fe8b6b3dc1f0a982aa920..6e271036d4ce284ce66a08cddc4ba3bd522cad2e 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 <time.h>
 
 #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;
-
-	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->path = "/";
 
-	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();
-	m = 0;
-	while (n >= 0 && !interrupted) {
+	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 defined(STAGGERED_CONNECTIONS)
+	if (!staggered)
 		/*
-		 * open the connections at 100ms intervals, with the last
-		 * one being after 1s, testing queueing, and direct H2 stream
-		 * addition stability
+		 * just pile on all the connections at once, testing the
+		 * pipeline queueing before the first is connected
 		 */
-		if (us() > next && m < (int)LWS_ARRAY_SIZE(client_wsi)) {
+		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) {
 
-			lws_try_client_connection(context, m++);
+		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)) {
 
-			if (m == (int)LWS_ARRAY_SIZE(client_wsi) - 1)
-				next = us() + 1000000;
-			else
-				next = us() + 100000;
+				lws_try_client_connection(&i, m++);
+
+				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 6f548063b84bbb7d1695cebd1f8a7c2a40a8ab8e..605c722a1cfa59739b8f64248a574195a228c76b 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 86b82caf286af06addbc2c1399e7237c8e46d11c..b975662acd89ece6af600eb9705ad7fb7b7e3ddb 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 88bc8de6599f51cca150f8a87be56061e2fb7495..690564081cc1c3d27a39b025b096e112f953dd5c 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 d895d267f121ed98170f1bc1395c51e33b3740fc..1fb9f50292f618385c89ea67b3cb916601f2c6f6 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 5766c0c725e3c627f04f37d16e47f8bb457c5b6e..5b86bd60f24ae69252722d84016a43a6a0ee1316 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 932701e772df9cd91045903991a070788116ada8..d57622d28eddb0f47acc3ea16c3b62975d329013 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 c4612e5250ab6d548cb96bbb42283fbf3d911e57..c27e779fb1a5d5110af9fd50b7e76ea824ed82e8 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 bb090a23af70a604851e80654b35e69cc68a5eb7..b7dbd42c2968ff9c6c36ccf6ee4d5ddd53bfe131 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 ee2c032e27d3c1c40dee5f03d5a6e978af3e9322..5e1fda7c07c84d315f8262b8c89780494acf5217 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 9e75a0fa7c676529f80823b7e2ab1673c293c993..9e86590ceb3f7fdcebec86c91be3e3d0156c96f1 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 892f1e01f3f89458d9ae792193dca1cd8747e825..c8775042aecf320119905245e1e8c5f851d38a05 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