diff --git a/.travis.yml b/.travis.yml
index 6960d35310a9049665c610d3781dbaa6aca45d00..099c6de8b44d39a0a97c267c809575c8b36252dd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ env:
   global:
     - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI="
   matrix:
-    - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG"
     - LWS_METHOD=default CMAKE_ARGS="-DLWS_WITH_MINIMAL_EXAMPLES=1"
     - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
     - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
@@ -29,7 +29,7 @@ install:
 #  - Rscript -e 'covr::coveralls()'
 
 script:
-  - if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then mkdir build && cd build && cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. && cmake --build .; else if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "linux" ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build . && if [ "$LWS_METHOD" = "lwsws" ] ; then sudo make install && ../minimal-examples/selftests.sh ; fi ; fi ; fi
+  - if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then mkdir build && cd build && cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. && cmake --build .; else if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "linux" ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build . && if [ "$LWS_METHOD" = "lwsws" ] ; then sudo make install && ../minimal-examples/selftests.sh && ../test-apps/attack.sh && ../scripts/autobahn-test.sh ; fi ; fi ; fi
 sudo: required
 dist: trusty
 addons:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e12e1889b2366b258577a2dbd1154421df1de9b6..70c079aa2757a7fe8dc6e0cf76dd86563a9a4c6a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -69,7 +69,6 @@ option(LWS_WITHOUT_TESTAPPS "Don't build the libwebsocket-test-apps" OFF)
 option(LWS_WITHOUT_TEST_SERVER "Don't build the test server" OFF)
 option(LWS_WITHOUT_TEST_SERVER_EXTPOLL "Don't build the test server version that uses external poll" OFF)
 option(LWS_WITHOUT_TEST_PING "Don't build the ping test application" OFF)
-option(LWS_WITHOUT_TEST_ECHO "Don't build the echo test application" OFF)
 option(LWS_WITHOUT_TEST_CLIENT "Don't build the client test application" OFF)
 option(LWS_WITHOUT_TEST_FRAGGLE "Don't build the ping test application" OFF)
 #
@@ -702,10 +701,6 @@ set(SOURCES
 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)
@@ -997,8 +992,15 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_C_COMPILER_ID
     if (LWS_WITH_GCOV)
 	    set (GCOV_FLAGS "-fprofile-arcs -ftest-coverage -O0")
     endif()
+
+    if (CMAKE_BUILD_TYPE MATCHES "DEBUG")
+	set(CMAKE_C_FLAGS "-O0" ${CMAKE_C_FLAGS})
+    else()
+	set(CMAKE_C_FLAGS "-O3" ${CMAKE_C_FLAGS})
+    endif()
+
     if (UNIX AND NOT LWS_WITH_ESP32)
-	    set(CMAKE_C_FLAGS "-O3 -Wall -Wsign-compare -Wignored-qualifiers -Wtype-limits -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS}" )
+	    set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wignored-qualifiers -Wtype-limits -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS}" )
     else()
 	    set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wignored-qualifiers -Wtype-limits -Wuninitialized -Werror ${VISIBILITY_FLAG} ${GCOV_FLAGS} ${CMAKE_C_FLAGS}" )
     endif()
@@ -1731,12 +1733,6 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 		if (NOT LWS_WITHOUT_TEST_PING AND NOT LWS_WITHOUT_SERVER AND NOT LWS_WITHOUT_CLIENT)
 			create_test_app(test-ping "test-apps/test-ping.c" "" "" "" "" "")
 		endif()
-		#
-		# test-echo
-		#
-		if (NOT LWS_WITHOUT_TEST_ECHO AND NOT LWS_WITHOUT_SERVER AND NOT LWS_WITHOUT_CLIENT)
-			create_test_app(test-echo "test-apps/test-echo.c" "" "" "" "" "")
-		endif()
 
 	endif(NOT LWS_WITHOUT_CLIENT)
 	
@@ -2160,7 +2156,6 @@ message(" LWS_WITHOUT_TESTAPPS = ${LWS_WITHOUT_TESTAPPS}")
 message(" LWS_WITHOUT_TEST_SERVER = ${LWS_WITHOUT_TEST_SERVER}")
 message(" LWS_WITHOUT_TEST_SERVER_EXTPOLL = ${LWS_WITHOUT_TEST_SERVER_EXTPOLL}")
 message(" LWS_WITHOUT_TEST_PING = ${LWS_WITHOUT_TEST_PING}")
-message(" LWS_WITHOUT_TEST_ECHO = ${LWS_WITHOUT_TEST_ECHO}")
 message(" LWS_WITHOUT_TEST_CLIENT = ${LWS_WITHOUT_TEST_CLIENT}")
 message(" LWS_WITHOUT_TEST_FRAGGLE = ${LWS_WITHOUT_TEST_FRAGGLE}")
 message(" LWS_WITHOUT_EXTENSIONS = ${LWS_WITHOUT_EXTENSIONS}")
diff --git a/README.md b/README.md
index 62ec302b3409099fb7bb3e9f5335d521119e7a6b..da1e2a42f5c456d4624d274c6a72849192fa975b 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,26 @@
 News
 ----
 
+## Major CI improvements for QA
+
+The Travis build of lws done on every commit now runs 
+
+Tests|Count|Explanation
+---|---|---
+Build / Linux / gcc|12|-Wall -Werror
+Build / Mac / Clang|12|-Wall -Werror
+Build / Windows / MSVC|7|default
+Selftests|29|minimal examples built and run against each other and remote server
+attack.sh|225|Correctness, robustness and security tests for http parser
+Autobahn Server|480|Testing lws ws client, including permessage-deflate
+Autobahn Client|480|Testing lws ws server, including permaessage-deflate
+
+The over 1,200 tests run on every commit take 40 minutes to complete.
+If any problems are found, it breaks the travis build, generating an email.
+
+Current master passes all the tests and these new CI arrangements will help
+keep it that way.
+
 ## Lws has the first official ws-over-h2 server support
 
 ![wss-over-h2](https://libwebsockets.org/sc-wss-over-h2.png)
@@ -78,60 +98,3 @@ You can get the latest version of the library from git:
 
 Doxygen API docs for master: https://libwebsockets.org/lws-api-doc-master/html/index.html
 
-
-After libwebsockets 1.3, tags will be signed using a key corresponding to this public key
-
-```
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v1
-
-mQINBFRe35QBEADZA7snW7MoEXkT2deDYZeggVD3694dg1o5G4q36NWjC8Pn/b2V
-d+L9Nmw8ydKIv8PLJW762rnveQpPYRqCRD8X4bVTYzYz3qsOl5BrYf6cuVn0ZrPB
-13TVRg+NZwUaVxc7O+tdOvvEBdA9OCIygctPNK9Nyh53xs5gPHhghZrKVrt0xM1A
-2LYsgoHmMBCCY25SHb1nuapvhA3LvuJb4cNNVRCukCoA6yx0uhSEz2AUPJSLqnZ9
-XnNBMKq+1a9C+y7jo4O78upTTmuOmRmNEVAu7pxCSUXDrNa87T8n6vFkV/MiW8nv
-VmhppKJrKPJ0KxJF9b7uG6eKosfoK2PKyE7pAoDN1fuNyBTB0dkFAwyTCN8hmhOg
-z71QrCltotq/AxSCsKzgFkDBL7D3KUM10QR5kmznjcm8tFWHoSttPR334z/1Yepf
-ATqH/tfYydW42qeeHgKjfeegnlI65nTDtwYW6lSqZsXg+/ABg0ki9m5HA6l713ig
-gRbVHSNkiz56O+UOqBtfcJZBc8QZqqixq8rbP2Is0HBBEtD+aFMuKx/sQ3ULkQs2
-8dZ5qsGTBT/xHmqpHJsIFX/jwjY5zeEiFbnO5bMH7YLmkjynVsn5zxTyXKQJe29C
-Uq0Yd9+JpDhHnZoiz/1hIIBsr89Z4Yy6c59YNJ3yJEOast0ODERcKSaUAQARAQAB
-tC9BbmR5IEdyZWVuIChMaW5hcm8ga2V5KSA8YW5keS5ncmVlbkBsaW5hcm8ub3Jn
-PokCPQQTAQoAJwUCVF7flAIbAwUJBaOagAULCQgHAwUVCgkICwUWAwIBAAIeAQIX
-gAAKCRA8ZxoDS3lTexApD/9WT7JWy3tK33OIACYV40XwLEhRam4Xku4rhtsoIeJK
-P0k/wa7J2PpceX6gKV+QBsOx3UbUfpqZ/Mu7ff3M0J6W87XpKRROAmP43zyiBkmM
-A6v0pJXozknmCU28p3DuLC8spVDFg9N52xV7Qb+9TDHcTYiVi4swKYuDEuHBC/qa
-M69+ANgsGbrMFRypxtU7OEhls3AEo3Cq03xD8QvLjFyPpYp1f0vNRFm2Jjgm2CRe
-YLVsCGxG35Dz7DpJHekHNxje6xsZ2w9Q38M0rLQ0ICOVQ+E1Dir3hwmZQWASzMMi
-+R0P+MVYpVt5y7KtiLywJ4BzNogS7gY3wQxksJOFA1uuk5h/hO54a361mcdA0Ta5
-HHhGKRw87lVjEQSaRjFZmHFClB+Sb8MuWR51JTzVS5HtJlcNqcWhF63vZ8bZ7b6y
-Aj8cXNjH6ULXyX3QnTUWXX/QU3an3yh8iPONWOGP5d5Hi/qejHGIhP2L5H+h05CP
-aZQYFLjjebYgEHijuA28eKWsBsoBPFSLpLloHTDkiycgFdV2AkQcxZN9ZElAqURP
-xUkEIscQg3YhExGiVEtaxBp1/p/WctMxs5HNoi0Oc97ZUcKvSCz9FDGXX9wYBpRf
-gzjNn055Xn4QyxBDnp5DrYT0ft/8BEnRK0JP6z3gNfnhOxZo4XA+M6w4Hjh3tI2A
-3rkCDQRUXt+UARAA0yHmONtW3L1HpvWFR+VgVNHa1HBWWk7lMsI6ajeiUK/lN3F/
-+vNbux46bPj/sNT9twbWmYhv6c0yVzCpmv5M5ztefS7mW/zPNLJmCmH32kAvVFr1
-Z90R/X+Z1Uh8wCCU72S2pSIXQFza3LF53pbpKi5m1F2icYcx+35egAvvZVZtcrMu
-TjHUa+N9mFKxa7tb5PI8Lv93nRLwB7aKkp5PKy9Yvse0jACrAAGeIpI73H467/wO
-ujermKlyPOOv+Lpjd7kedWKdaweitva7FVI20K/afn4AwCI8HJUIqVbil0Yrg9Le
-M1TRsRydzMQQejsb/cWi3fQ3U3HxvSJijKltckPMqjJaXbqmrLz3FOA5Km0ciIOB
-WW0Qq0WREcS3rc5FHU29duS9OAieAWFYyLDieug4nQ29KQE6I0lMqLnz8vWYtbmw
-6AHk9i2GsXOZiPnztuADgt9o9Os8fm7ZiacA1LISl86P7wpFk+Gf4LRvv8Fk08NV
-b2K1BY4YC9KP+AynyYQmxmyB1YQCh/dZHiD4ikGKttHAy4ZsMW6IRL5bRP0Z97pA
-lyBtXP0cGTJtuPt2feh0zaiA7blZ/IDXkB1UqH6jnTa71d1FeNKtVFi8FhPIREN6
-Rc5imyRxubZEgsxhdjqGgdT5k6Qr42SewAN391ygutpgGizGQtTwzvmKa0UAEQEA
-AYkCJQQYAQoADwUCVF7flAIbDAUJBaOagAAKCRA8ZxoDS3lTewuBD/9/rakAMCRC
-+WmbUVpCbJSWP5ViH87Xko4ku437gq56whcGjQpxfCYt8oeVgS8fZetUOHs2gspJ
-CEc8TYLUFntfyt2AzKU29oQoizPm33W9S1u7aRGWsVVutd2sqUaQUDsl9z35+Ka9
-YcWoATJSWBgnhSAmNcM60OG0P5qrZloTlbRSlDZTSZT3RvY4JWtWCubGsjEpXO4h
-ZqbKCu3KgV/6NOuTLciriSOZ/iyva3WsCP2S8mRRvma7x04oMTEWX80zozTCK8gG
-XqqS9eDhCkRbdmMyUQbHIhc/ChYchO5+fQ1o0zMS5cv6xgkhWI3NJRUkNdXolH9a
-5F9q4CmCTcdEZkqpnjsLNiQLIENfHbgC0A5IjR6YgN6qAP8ZJ5hBgyTfyKkwB7bW
-DcCnuoC9R79hkI8nWkoRVou9tdzKxo0bGR6O4CfLj+4d3hpWkv9Rw7Xxygo5JOqN
-4cNZGtHkmIFFk9fSXul5rkjfF/XmThIwoI8aHSBZ7j3IMtmkKVkBjNjiTfbgW8RT
-XIIR+QQdVLOyJqq+NZC/SrKVQITg0ToYJutRTUJViqyz5b3psJo5o2SW6jcexQpE
-cX6tdPyGz3o0aywfJ9dcN6izleSV1gYmXmIoS0cQyezVqTUkT8C12zeRB7mtWsDa
-+AWJGq/WfB7N6pPh8S/XMW4e6ptuUodjiA==
-=HV8t
------END PGP PUBLIC KEY BLOCK-----
-```
diff --git a/READMEs/README.test-apps.md b/READMEs/README.test-apps.md
index 9c251db1c3a8c0d1c152cba460f136558312f64e..4482ec8b659c40881422ea34ef0fb69b9142e260 100644
--- a/READMEs/README.test-apps.md
+++ b/READMEs/README.test-apps.md
@@ -238,30 +238,6 @@ For those two options libuv is needed to support the protocol plugins, if
 that's not possible then the other variations with their own protocol code
 should be considered.
 
-
-@section echo Testing simple echo
-
-You can test against `echo.websockets.org` as a sanity test like
-this (the client connects to port `80` by default):
-
-```
-	$ libwebsockets-test-echo --client echo.websocket.org
-```
-
-This echo test is of limited use though because it doesn't
-negotiate any protocol.  You can run the same test app as a
-local server, by default on localhost:7681
-```
-	$ libwebsockets-test-echo
-```
-and do the echo test against the local echo server
-```
-	$ libwebsockets-test-echo --client localhost --port 7681
-```
-If you add the `--ssl` switch to both the client and server, you can also test
-with an encrypted link.
-
-
 @section tassl Testing SSL on the client side
 
 To test SSL/WSS client action, just run the client test with
@@ -436,37 +412,25 @@ treatment to the other app during that call.
 
 @section autobahn Autobahn Test Suite
 
-Lws can be tested against the autobahn websocket fuzzer.
+Lws can be tested against the autobahn websocket fuzzer in both client and
+server modes
 
 1) pip install autobahntestsuite
 
-2) wstest -m fuzzingserver
-
-3) Run tests like this
-
-libwebsockets-test-echo --client localhost --port 9001 -u "/runCase?case=20&agent=libwebsockets" -v -d 65535 -n 1
-
-(this runs test 20)
+2) From your build dir: cmake .. -DLWS_WITH_MINIMAL_EXAMPLES=1 && make
 
-4) In a browser, go here
+3) ../scripts/autobahn-test.sh
 
-http://localhost:8080/test_browser.html
+4) In a browser go to the directory you ran wstest in (eg, /projects/libwebsockets)
 
-fill in "libwebsockets" in "User Agent Identifier" and press "Update Reports (Manual)"
-
-5) In a browser go to the directory you ran wstest in (eg, /projects/libwebsockets)
-
-file:///projects/libwebsockets/reports/clients/index.html
+file:///projects/libwebsockets/build/reports/clients/index.html
 
 to see the results
 
 
 @section autobahnnotes Autobahn Test Notes
 
-1) Autobahn tests the user code + lws implementation.  So to get the same
-results, you need to follow test-echo.c in terms of user implementation.
-
-2) Two of the tests make no sense for Libwebsockets to support and we fail them.
+1) Two of the tests make no sense for Libwebsockets to support and we fail them.
 
  - Tests 2.10 + 2.11: sends multiple pings on one connection.  Lws policy is to
 only allow one active ping in flight on each connection, the rest are dropped.
@@ -474,5 +438,7 @@ The autobahn test itself admits this is not part of the standard, just someone's
 random opinion about how they think a ws server should act.  So we will fail
 this by design and it is no problem about RFC6455 compliance.
 
- 
+2) Currently two parts of autobahn are broken and we skip them
+
+https://github.com/crossbario/autobahn-testsuite/issues/71
  
diff --git a/lib/context.c b/lib/context.c
index 7f3a456cf4a02dea81bf72028ca7f5dfcf5dae19..2a46ff9da801cbdbb28f323f956fabeed446b848 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -1346,21 +1346,11 @@ lws_create_context(struct lws_context_creation_info *info)
 	if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
 		lws_plat_drop_app_privileges(info);
 
-	/*
-	 * give all extensions a chance to create any per-context
-	 * allocations they need
-	 */
-	if (info->port != CONTEXT_PORT_NO_LISTEN) {
-		if (lws_ext_cb_all_exts(context, NULL,
-			LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT, NULL, 0) < 0)
-			goto bail;
-	} else
-		if (lws_ext_cb_all_exts(context, NULL,
-			LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT, NULL, 0) < 0)
-			goto bail;
-
 	time(&context->last_cert_check_s);
 
+	/* expedite post-context init (eg, protocols) */
+	lws_cancel_service(context);
+
 #if defined(LWS_WITH_SELFTESTS)
 	lws_jws_selftest();
 #endif
@@ -1747,17 +1737,6 @@ lws_context_destroy(struct lws_context *context)
 		lws_pt_mutex_destroy(pt);
 	}
 
-	/*
-	 * give all extensions a chance to clean up any per-context
-	 * allocations they might have made
-	 */
-
-	n = lws_ext_cb_all_exts(context, NULL,
-				LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0);
-
-	n = lws_ext_cb_all_exts(context, NULL,
-				LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0);
-
 	/*
 	 * inform all the protocols that they are done and will have no more
 	 * callbacks.
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 1c609911ee8562f5c64a5bbbc13f047bf8a21773..eabd4bc0e2a72ba696a79bba3c93e082ffe7204e 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -534,8 +534,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *
 	struct lws_context_per_thread *pt;
 	struct lws *wsi1, *wsi2;
 	struct lws_context *context;
-	struct lws_tokens ebuf;
-	int n, m;
+	int n;
 
 	lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller);
 
@@ -651,6 +650,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *
 	/* we tried the polite way... */
 	case LRS_WAITING_TO_SEND_CLOSE:
 	case LRS_AWAITING_CLOSE_ACK:
+	case LRS_RETURNED_CLOSE:
 		goto just_kill_connection;
 
 	case LRS_FLUSHING_BEFORE_CLOSE:
@@ -685,45 +685,6 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *
 		}
 	}
 
-	/*
-	 * are his extensions okay with him closing?  Eg he might be a mux
-	 * parent and just his ch1 aspect is closing?
-	 */
-
-	if (lws_ext_cb_active(wsi, LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE,
-			      NULL, 0) > 0) {
-		lwsl_ext("extension vetoed close\n");
-		return;
-	}
-
-	/*
-	 * flush any tx pending from extensions, since we may send close packet
-	 * if there are problems with send, just nuke the connection
-	 */
-	do {
-		ebuf.token = NULL;
-		ebuf.len = 0;
-
-		/* show every extension the new incoming data */
-
-		m = lws_ext_cb_active(wsi,
-			  LWS_EXT_CB_FLUSH_PENDING_TX, &ebuf, 0);
-		if (m < 0) {
-			lwsl_ext("Extension reports fatal error\n");
-			goto just_kill_connection;
-		}
-
-		/* assuming they left us something to send, send it */
-
-		if (ebuf.len)
-			if (lws_issue_raw(wsi, (unsigned char *)ebuf.token,
-					  ebuf.len) !=
-			    ebuf.len) {
-				lwsl_debug("close: ext spill failed\n");
-				goto just_kill_connection;
-			}
-	} while (m);
-
 	/*
 	 * signal we are closing, lws_write will
 	 * add any necessary version-specific stuff.  If the write fails,
@@ -865,6 +826,8 @@ just_kill_connection:
 
 		if (!wsi->protocol)
 			pro = &wsi->vhost->protocols[0];
+		//lwsl_notice("%s: est %d told %d cbin %d %s\n", __func__, lwsi_state_est_PRE_CLOSE(wsi), !wsi->told_user_closed,
+		//		wsi->role_ops->close_cb[lwsi_role_server(wsi)], pro->name);
 		pro->callback(wsi,
 			      wsi->role_ops->close_cb[lwsi_role_server(wsi)],
 			      wsi->user_space, NULL, 0);
@@ -875,13 +838,6 @@ just_kill_connection:
 
 	if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
 		lwsl_warn("extension destruction failed\n");
-	/*
-	 * inform all extensions in case they tracked this guy out of band
-	 * even though not active on him specifically
-	 */
-	if (lws_ext_cb_all_exts(context, wsi,
-		       LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING, NULL, 0) < 0)
-		lwsl_warn("ext destroy wsi failed\n");
 
 async_close:
 	wsi->socket_is_permanently_unusable = 1;
@@ -1049,7 +1005,6 @@ lws_buflist_use_segment(struct lws_buflist **head, size_t len)
 {
 	assert(*head);
 	assert(len);
-
 	assert((*head)->pos + len <= (*head)->len);
 
 	(*head)->pos += len;
@@ -2065,6 +2020,12 @@ lwsl_hexdump_level(int hexdump_level, const void *vbuf, size_t len)
 	if (!lwsl_visible(hexdump_level))
 		return;
 
+	if (!len)
+		return;
+
+	if (!vbuf)
+		return;
+
 	_lws_log(hexdump_level, "\n");
 
 	for (n = 0; n < len;) {
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 6e31aec9dd333277a2eba4785ab2e57719c6db31..db6f1d3ce29caad01349f35567cfeaa6968d434f 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -345,6 +345,7 @@ lwsl_timestamp(int level, char *p, int len);
 #define lwsl_hexdump_warn(...) lwsl_hexdump_level(LLL_WARN, __VA_ARGS__)
 #define lwsl_hexdump_notice(...) lwsl_hexdump_level(LLL_NOTICE, __VA_ARGS__)
 #define lwsl_hexdump_info(...) lwsl_hexdump_level(LLL_INFO, __VA_ARGS__)
+#define lwsl_hexdump_debug(...) lwsl_hexdump_level(LLL_DEBUG, __VA_ARGS__)
 
 /**
  * lwsl_hexdump_level() - helper to hexdump a buffer at a selected debug level
@@ -2143,27 +2144,10 @@ lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max);
  * add it at where specified so existing users are unaffected.
  */
 enum lws_extension_callback_reasons {
-	LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT		=  0,
-	LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT		=  1,
-	LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT		=  2,
-	LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT		=  3,
 	LWS_EXT_CB_CONSTRUCT				=  4,
 	LWS_EXT_CB_CLIENT_CONSTRUCT			=  5,
-	LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE		=  6,
-	LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION	=  7,
 	LWS_EXT_CB_DESTROY				=  8,
-	LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING		=  9,
-	LWS_EXT_CB_ANY_WSI_ESTABLISHED			= 10,
-	LWS_EXT_CB_PACKET_RX_PREPARSE			= 11,
 	LWS_EXT_CB_PACKET_TX_PRESEND			= 12,
-	LWS_EXT_CB_PACKET_TX_DO_SEND			= 13,
-	LWS_EXT_CB_HANDSHAKE_REPLY_TX			= 14,
-	LWS_EXT_CB_FLUSH_PENDING_TX			= 15,
-	LWS_EXT_CB_EXTENDED_PAYLOAD_RX			= 16,
-	LWS_EXT_CB_UNUSED1				= 17,
-	LWS_EXT_CB_1HZ					= 18,
-	LWS_EXT_CB_REQUEST_ON_WRITEABLE			= 19,
-	LWS_EXT_CB_IS_WRITEABLE				= 20,
 	LWS_EXT_CB_PAYLOAD_TX				= 21,
 	LWS_EXT_CB_PAYLOAD_RX				= 22,
 	LWS_EXT_CB_OPTION_DEFAULT			= 23,
@@ -2241,18 +2225,6 @@ struct lws_ext_option_arg {
  *		user data is deleted.  This same callback is used whether you
  *		are in client or server instantiation context.
  *
- *	LWS_EXT_CB_PACKET_RX_PREPARSE: when this extension was active on
- *		a connection, and a packet of data arrived at the connection,
- *		it is passed to this callback to give the extension a chance to
- *		change the data, eg, decompress it.  user is pointing to the
- *		extension's private connection context data, in is pointing
- *		to an lws_tokens struct, it consists of a char * pointer called
- *		token, and an int called len.  At entry, these are
- *		set to point to the received buffer and set to the content
- *		length.  If the extension will grow the content, it should use
- *		a new buffer allocated in its private user context data and
- *		set the pointed-to lws_tokens members to point to its buffer.
- *
  *	LWS_EXT_CB_PACKET_TX_PRESEND: this works the same way as
  *		LWS_EXT_CB_PACKET_RX_PREPARSE above, except it gives the
  *		extension a chance to change websocket data just before it will
@@ -2822,8 +2794,8 @@ struct lws_context_creation_info {
 	short max_http_header_pool;
 	/**< CONTEXT: The max number of connections with http headers that
 	 * can be processed simultaneously (the corresponding memory is
-	 * allocated for the lifetime of the context).  If the pool is
-	 * busy new incoming connections must wait for accept until one
+	 * allocated and deallocated dynamically as needed).  If the pool is
+	 * fully busy new incoming connections must wait for accept until one
 	 * becomes free. */
 
 	unsigned int count_threads;
diff --git a/lib/output.c b/lib/output.c
index c6d20f743e4b0a1c0f242bec589ea51647475ab0..3453086826c7c33a3e5affd3efeb9365f1464be7 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -30,9 +30,6 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
 	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
 	size_t real_len = len;
 	unsigned int n;
-#if !defined(LWS_WITHOUT_EXTENSIONS)
-	int m;
-#endif
 
 	// lwsl_hexdump_err(buf, len);
 
@@ -74,15 +71,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
 
 		return -1;
 	}
-#if !defined(LWS_WITHOUT_EXTENSIONS)
-	m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_DO_SEND, &buf, (int)len);
-	if (m < 0)
-		return -1;
-	if (m) /* handled */ {
-		n = m;
-		goto handle_truncated_send;
-	}
-#endif
+
 	if (!wsi->http2_substream && !lws_socket_is_valid(wsi->desc.sockfd))
 		lwsl_warn("** error invalid sock but expected to send\n");
 
@@ -120,9 +109,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
 		n = 0;
 		break;
 	}
-#if !defined(LWS_WITHOUT_EXTENSIONS)
-handle_truncated_send:
-#endif
+
 	/*
 	 * we were already handling a truncated send?
 	 */
@@ -240,251 +227,6 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len,
 	return wsi->role_ops->write_role_protocol(wsi, buf, len, &wp);
 }
 
-LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
-{
-	struct lws_context *context = wsi->context;
-	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-	struct lws_process_html_args args;
-	lws_filepos_t amount, poss;
-	unsigned char *p, *pstart;
-#if defined(LWS_WITH_RANGES)
-	unsigned char finished = 0;
-#endif
-	int n, m;
-
-	lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream);
-
-	do {
-
-		if (wsi->trunc_len) {
-			if (lws_issue_raw(wsi, wsi->trunc_alloc +
-					  wsi->trunc_offset,
-					  wsi->trunc_len) < 0) {
-				lwsl_info("%s: closing\n", __func__);
-				goto file_had_it;
-			}
-			break;
-		}
-
-		if (wsi->http.filepos == wsi->http.filelen)
-			goto all_sent;
-
-		n = 0;
-
-		pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH;
-
-		p = pstart;
-
-#if defined(LWS_WITH_RANGES)
-		if (wsi->http.range.count_ranges && !wsi->http.range.inside) {
-
-			lwsl_notice("%s: doing range start %llu\n", __func__,
-				    wsi->http.range.start);
-
-			if ((long long)lws_vfs_file_seek_cur(wsi->http.fop_fd,
-						   wsi->http.range.start -
-						   wsi->http.filepos) < 0)
-				goto file_had_it;
-
-			wsi->http.filepos = wsi->http.range.start;
-
-			if (wsi->http.range.count_ranges > 1) {
-				n =  lws_snprintf((char *)p,
-						context->pt_serv_buf_size -
-						LWS_H2_FRAME_HEADER_LENGTH,
-					"_lws\x0d\x0a"
-					"Content-Type: %s\x0d\x0a"
-					"Content-Range: bytes %llu-%llu/%llu\x0d\x0a"
-					"\x0d\x0a",
-					wsi->http.multipart_content_type,
-					wsi->http.range.start,
-					wsi->http.range.end,
-					wsi->http.range.extent);
-				p += n;
-			}
-
-			wsi->http.range.budget = wsi->http.range.end -
-						   wsi->http.range.start + 1;
-			wsi->http.range.inside = 1;
-		}
-#endif
-
-		poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH;
-
-		if (wsi->http.tx_content_length)
-			if (poss > wsi->http.tx_content_remain)
-				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 (wsi->protocol->tx_packet_size &&
-		    poss > wsi->protocol->tx_packet_size)
-			poss = wsi->protocol->tx_packet_size;
-
-		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 defined(LWS_WITH_RANGES)
-		if (wsi->http.range.count_ranges) {
-			if (wsi->http.range.count_ranges > 1)
-				poss -= 7; /* allow for final boundary */
-			if (poss > wsi->http.range.budget)
-				poss = wsi->http.range.budget;
-		}
-#endif
-		if (wsi->sending_chunked) {
-			/* we need to drop the chunk size in here */
-			p += 10;
-			/* allow for the chunk to grow by 128 in translation */
-			poss -= 10 + 128;
-		}
-
-		if (lws_vfs_file_read(wsi->http.fop_fd, &amount, p, poss) < 0)
-			goto file_had_it; /* caller will close */
-
-		if (wsi->sending_chunked)
-			n = (int)amount;
-		else
-			n = lws_ptr_diff(p, pstart) + (int)amount;
-
-		lwsl_debug("%s: sending %d\n", __func__, n);
-
-		if (n) {
-			lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
-					context->timeout_secs);
-
-			if (wsi->interpreting) {
-				args.p = (char *)p;
-				args.len = n;
-				args.max_len = (unsigned int)poss + 128;
-				args.final = wsi->http.filepos + n ==
-					     wsi->http.filelen;
-				args.chunked = wsi->sending_chunked;
-				if (user_callback_handle_rxflow(
-				     wsi->vhost->protocols[
-				     (int)wsi->protocol_interpret_idx].callback,
-				     wsi, LWS_CALLBACK_PROCESS_HTML,
-				     wsi->user_space, &args, 0) < 0)
-					goto file_had_it;
-				n = args.len;
-				p = (unsigned char *)args.p;
-			} else
-				p = pstart;
-
-#if defined(LWS_WITH_RANGES)
-			if (wsi->http.range.send_ctr + 1 ==
-				wsi->http.range.count_ranges && // last range
-			    wsi->http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart)
-			    wsi->http.range.budget - amount == 0) {// final part
-				n += lws_snprintf((char *)pstart + n, 6,
-					"_lws\x0d\x0a"); // append trailing boundary
-				lwsl_debug("added trailing boundary\n");
-			}
-#endif
-			m = lws_write(wsi, p, n,
-				      wsi->http.filepos + amount == wsi->http.filelen ?
-					LWS_WRITE_HTTP_FINAL :
-					LWS_WRITE_HTTP
-				);
-			if (m < 0)
-				goto file_had_it;
-
-			wsi->http.filepos += amount;
-
-#if defined(LWS_WITH_RANGES)
-			if (wsi->http.range.count_ranges >= 1) {
-				wsi->http.range.budget -= amount;
-				if (wsi->http.range.budget == 0) {
-					lwsl_notice("range budget exhausted\n");
-					wsi->http.range.inside = 0;
-					wsi->http.range.send_ctr++;
-
-					if (lws_ranges_next(&wsi->http.range) < 1) {
-						finished = 1;
-						goto all_sent;
-					}
-				}
-			}
-#endif
-
-			if (m != n) {
-				/* adjust for what was not sent */
-				if (lws_vfs_file_seek_cur(wsi->http.fop_fd,
-							   m - n) ==
-							     (lws_fileofs_t)-1)
-					goto file_had_it;
-			}
-		}
-
-all_sent:
-		if ((!wsi->trunc_len && wsi->http.filepos >= wsi->http.filelen)
-#if defined(LWS_WITH_RANGES)
-		    || finished)
-#else
-		)
-#endif
-		     {
-			lwsi_set_state(wsi, LRS_ESTABLISHED);
-			/* we might be in keepalive, so close it off here */
-			lws_vfs_file_close(&wsi->http.fop_fd);
-			
-			lwsl_debug("file completed\n");
-
-			if (wsi->protocol->callback &&
-			    user_callback_handle_rxflow(wsi->protocol->callback,
-					    	    	wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION,
-					    	    	wsi->user_space, NULL,
-					    	    	0) < 0) {
-					/*
-					 * For http/1.x, the choices from
-					 * transaction_completed are either
-					 * 0 to use the connection for pipelined
-					 * or nonzero to hang it up.
-					 *
-					 * However for http/2. while we are
-					 * still interested in hanging up the
-					 * nwsi if there was a network-level
-					 * fatal error, simply completing the
-					 * transaction is a matter of the stream
-					 * state, not the root connection at the
-					 * network level
-					 */
-					if (wsi->http2_substream)
-						return 1;
-					else
-						return -1;
-				}
-
-			return 1;  /* >0 indicates completed */
-		}
-	} while (0); // while (!lws_send_pipe_choked(wsi))
-
-	lws_callback_on_writable(wsi);
-
-	return 0; /* indicates further processing must be done */
-
-file_had_it:
-	lws_vfs_file_close(&wsi->http.fop_fd);
-
-	return -1;
-}
-
 LWS_VISIBLE int
 lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len)
 {
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 8ac106f1293d75a2fd6cfb6e6c4818ba44232cb7..ac976dbdea65f8a723b11966117f41e407694167 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -332,7 +332,7 @@ extern "C" {
 #define LWS_MAX_PROTOCOLS 5
 #endif
 #ifndef LWS_MAX_EXTENSIONS_ACTIVE
-#define LWS_MAX_EXTENSIONS_ACTIVE 2
+#define LWS_MAX_EXTENSIONS_ACTIVE 1
 #endif
 #ifndef LWS_MAX_EXT_OFFERS
 #define LWS_MAX_EXT_OFFERS 8
@@ -491,7 +491,7 @@ enum lwsi_state {
 
 	LRS_WAITING_TO_SEND_CLOSE		= LWSIFS_POCB | 24,
 	LRS_RETURNED_CLOSE			= LWSIFS_POCB | 25,
-	LRS_AWAITING_CLOSE_ACK			= 26,
+	LRS_AWAITING_CLOSE_ACK			= LWSIFS_POCB | 26,
 	LRS_FLUSHING_BEFORE_CLOSE		= LWSIFS_POCB | 27,
 	LRS_SHUTDOWN				= 28,
 
@@ -1480,6 +1480,7 @@ struct lws {
 	unsigned int told_user_closed:1;
 	unsigned int told_event_loop_closed:1;
 	unsigned int waiting_to_send_close_frame:1;
+	unsigned int close_needs_ack:1;
 	unsigned int ipv6:1;
 	unsigned int parent_carries_io:1;
 	unsigned int parent_pending_cb_on_writable:1;
@@ -1611,7 +1612,7 @@ lws_latency(struct lws_context *context, struct lws *wsi, const char *action,
 #endif
 
 LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_client_rx_sm(struct lws *wsi, unsigned char c);
+lws_ws_client_rx_sm(struct lws *wsi, unsigned char c);
 
 LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_parse(struct lws *wsi, unsigned char *buf, int *len);
@@ -1696,10 +1697,7 @@ LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_client_interpret_server_handshake(struct lws *wsi);
 
 LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-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);
+lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c);
 
 LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len);
@@ -1755,13 +1753,13 @@ lws_change_pollfd(struct lws *wsi, int _and, int _or);
  LWS_EXTERN struct lws_vhost *
  lws_select_vhost(struct lws_context *context, int port, const char *servername);
  LWS_EXTERN int LWS_WARN_UNUSED_RESULT
- lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len);
+ lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len);
  LWS_EXTERN void
  lws_server_get_canonical_hostname(struct lws_context *context,
 				  struct lws_context_creation_info *info);
 #else
  #define lws_context_init_server(_a, _b) (0)
- #define lws_interpret_incoming_packet(_a, _b, _c) (0)
+ #define lws_parse_ws(_a, _b, _c) (0)
  #define lws_server_get_canonical_hostname(_a, _b)
 #endif
 
@@ -1939,13 +1937,10 @@ lws_http_transaction_completed_client(struct lws *wsi);
 #if !defined(LWS_WITH_TLS)
 	#define lws_context_init_client_ssl(_a, _b) (0)
 #endif
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len);
 LWS_EXTERN void
 lws_decode_ssl_error(void);
 #else
 #define lws_context_init_client_ssl(_a, _b) (0)
-#define lws_handshake_client(_a, _b, _c) (0)
 #endif
 
 LWS_EXTERN int
@@ -2150,6 +2145,9 @@ lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn);
 int
 lws_tls_server_conn_alpn(struct lws *wsi);
 
+int
+lws_ws_client_rx_sm_block(struct lws *wsi, unsigned char **buf, size_t len);
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/lib/roles/h1/client-h1.c b/lib/roles/h1/client-h1.c
deleted file mode 100644
index 41780394032706e7dc42447033d2301c03f08cf5..0000000000000000000000000000000000000000
--- a/lib/roles/h1/client-h1.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 (lws_buflist_next_segment_len(&wsi->buflist, NULL) &&
-		    !lws_buflist_use_segment(&wsi->buflist, 1)) {
-			lwsl_debug("%s: removed wsi %p from rxflow list\n", __func__, wsi);
-			lws_dll_lws_remove(&wsi->dll_buflist);
-		}
-
-		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
index c98fb9504c49f0d833c0a6bb9777d8bc2270644c..976aa47b0e697af9005195a13839ffe6974e5aa3 100644
--- a/lib/roles/h1/ops-h1.c
+++ b/lib/roles/h1/ops-h1.c
@@ -25,6 +25,54 @@
 #define min(a, b) ((a) < (b) ? (a) : (b))
 #endif
 
+#if !defined(LWS_NO_CLIENT)
+static 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;
+
+	// lwsl_notice("%s: hs client gets %d in\n", __func__, (int)len);
+
+	while (len) {
+		/*
+		 * we were accepting input but now we stopped doing so
+		 */
+		if (lws_is_flowcontrolled(wsi)) {
+			//lwsl_notice("%s: caching %ld\n", __func__, (long)len);
+			lws_rxflow_cache(wsi, *buf, 0, (int)len);
+			*buf += len;
+			return 0;
+		}
+		if (wsi->ws->rx_draining_ext) {
+			//lwsl_notice("%s: draining ext\n", __func__);
+			if (lwsi_role_client(wsi))
+				m = lws_ws_client_rx_sm(wsi, 0);
+			else
+				m = lws_ws_rx_sm(wsi, 0, 0);
+			if (m < 0)
+				return -1;
+			continue;
+		}
+		/* caller will account for buflist usage */
+
+		if (lws_ws_client_rx_sm(wsi, *(*buf)++)) {
+			lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n",
+				    __func__, (int)len);
+			return -1;
+		}
+		len--;
+	}
+	// lwsl_notice("%s: finished with %ld\n", __func__, (long)len);
+
+	return 0;
+}
+#endif
 
 /*
  * We have to take care about parsing because the headers may be split
@@ -67,10 +115,10 @@ lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
 			assert(0);
 		}
 		lwsl_parser("issuing %d bytes to parser\n", (int)len);
-
+#if !defined(LWS_NO_CLIENT)
 		if (lws_handshake_client(wsi, &buf, (size_t)len))
 			goto bail;
-
+#endif
 		last_char = buf;
 		if (lws_handshake_server(wsi, &buf, (size_t)len))
 			/* Handshake indicates this session is done. */
@@ -200,19 +248,22 @@ postbody_completion:
 	case LRS_SHUTDOWN:
 
 ws_mode:
-
+#if !defined(LWS_NO_CLIENT)
+		// lwsl_notice("%s: ws_mode\n", __func__);
 		if (lws_handshake_client(wsi, &buf, (size_t)len))
 			goto bail;
+#endif
 #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");
+		    lws_parse_ws(wsi, &buf, (size_t)len) < 0) {
+			lwsl_info("%s: lws_parse_ws bailed\n", __func__);
 			goto bail;
 		}
 #endif
+		// lwsl_notice("%s: ws_mode: buf moved on by %d\n", __func__, lws_ptr_diff(buf, oldbuf));
 		break;
 
 	case LRS_DEFERRING_ACTION:
@@ -253,8 +304,8 @@ bail:
 
 	return -1;
 }
-
-int
+#if !defined(LWS_NO_SERVER)
+static 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];
@@ -456,6 +507,7 @@ fail:
 
 	return LWS_HPI_RET_WSI_ALREADY_DIED;
 }
+#endif
 
 static int
 rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi,
diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c
index 1f8dc2f2fc9e93ba559392ed743e8b0f4e00e277..a61dd2187de100f7033d907d36a355d7352ae8b3 100644
--- a/lib/roles/h2/http2.c
+++ b/lib/roles/h2/http2.c
@@ -2185,6 +2185,7 @@ lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
 		if (lws_is_flowcontrolled(wsi)) {
 			lws_rxflow_cache(wsi, buf, 0, (int)len);
 			buf += len;
+			len = 0;
 			break;
 		}
 
diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c
index ed4c992c9da2e0141f3f648e6e3b37059f6ed680..3ae0fa827e54980aef610029ea14a88d368a020a 100644
--- a/lib/roles/h2/ops-h2.c
+++ b/lib/roles/h2/ops-h2.c
@@ -198,22 +198,24 @@ read:
 					wsi->context->pt_serv_buf_size);
 		switch (ebuf.len) {
 		case 0:
-			lwsl_info("%s: zero length read\n",
-				  __func__);
+			lwsl_info("%s: zero length read\n", __func__);
 			return LWS_HPI_RET_PLEASE_CLOSE_ME;
 		case LWS_SSL_CAPABLE_MORE_SERVICE:
 			lwsl_info("SSL Capable more service\n");
 			return LWS_HPI_RET_HANDLED;
 		case LWS_SSL_CAPABLE_ERROR:
-			lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n",
-					__func__);
+			lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n", __func__);
 			return LWS_HPI_RET_PLEASE_CLOSE_ME;
 		}
 
-		// lwsl_notice("Actual RX %d\n", ebuf.len);
-		// lwsl_hexdump_notice(ebuf.token, 64);
+		// lwsl_notice("%s: Actual RX %d\n", __func__, ebuf.len);
+		// if (ebuf.len > 0)
+		//	lwsl_hexdump_notice(ebuf.token, ebuf.len);
 	}
 
+	if (ebuf.len < 0)
+		return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
 drain:
 #ifndef LWS_NO_CLIENT
 	if (lwsi_role_http(wsi) && lwsi_role_client(wsi) &&
@@ -254,11 +256,11 @@ drain:
 		n = 0;
 		if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) {
 			n = lws_read_h2(wsi, (unsigned char *)ebuf.token,
-				     ebuf.len);
+				        ebuf.len);
 			// lwsl_notice("h2 n = %d\n", n);
 		} else {
 			n = lws_read_h1(wsi, (unsigned char *)ebuf.token,
-				     ebuf.len);
+				        ebuf.len);
 			// lwsl_notice("h1 n = %d\n", n);
 		}
 
@@ -296,8 +298,6 @@ drain:
 
 	pending = lws_ssl_pending(wsi);
 	if (pending) {
-		pending = pending > wsi->context->pt_serv_buf_size ?
-				wsi->context->pt_serv_buf_size : pending;
 		lwsl_err("going around\n");
 		goto read;
 	}
diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c
index 1b24819d0b8a32b7c037a2fa5e123aae1cd82248..197b28b5cec9172753265aabd7abb3462f1f3adc 100644
--- a/lib/roles/http/client/client-handshake.c
+++ b/lib/roles/http/client/client-handshake.c
@@ -593,7 +593,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
 		 const char *path, const char *host)
 {
 	char origin[300] = "", protocol[300] = "", method[32] = "",
-	     iface[16] = "", alpn[32], *p;
+	     iface[16] = "", alpn[32] = "", *p;
 	struct lws *wsi = *pwsi;
 
 	if (wsi->redirects == 3) {
diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c
index d80dbf1370ca3ffec413bd03636e0b6562d0f02f..816e988ac9befcbb9968b48359e3a3ee91916aa6 100644
--- a/lib/roles/http/server/server.c
+++ b/lib/roles/http/server/server.c
@@ -2335,6 +2335,252 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 	return 0;
 }
 
+LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
+{
+	struct lws_context *context = wsi->context;
+	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+	struct lws_process_html_args args;
+	lws_filepos_t amount, poss;
+	unsigned char *p, *pstart;
+#if defined(LWS_WITH_RANGES)
+	unsigned char finished = 0;
+#endif
+	int n, m;
+
+	lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream);
+
+	do {
+
+		if (wsi->trunc_len) {
+			if (lws_issue_raw(wsi, wsi->trunc_alloc +
+					  wsi->trunc_offset,
+					  wsi->trunc_len) < 0) {
+				lwsl_info("%s: closing\n", __func__);
+				goto file_had_it;
+			}
+			break;
+		}
+
+		if (wsi->http.filepos == wsi->http.filelen)
+			goto all_sent;
+
+		n = 0;
+
+		pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH;
+
+		p = pstart;
+
+#if defined(LWS_WITH_RANGES)
+		if (wsi->http.range.count_ranges && !wsi->http.range.inside) {
+
+			lwsl_notice("%s: doing range start %llu\n", __func__,
+				    wsi->http.range.start);
+
+			if ((long long)lws_vfs_file_seek_cur(wsi->http.fop_fd,
+						   wsi->http.range.start -
+						   wsi->http.filepos) < 0)
+				goto file_had_it;
+
+			wsi->http.filepos = wsi->http.range.start;
+
+			if (wsi->http.range.count_ranges > 1) {
+				n =  lws_snprintf((char *)p,
+						context->pt_serv_buf_size -
+						LWS_H2_FRAME_HEADER_LENGTH,
+					"_lws\x0d\x0a"
+					"Content-Type: %s\x0d\x0a"
+					"Content-Range: bytes %llu-%llu/%llu\x0d\x0a"
+					"\x0d\x0a",
+					wsi->http.multipart_content_type,
+					wsi->http.range.start,
+					wsi->http.range.end,
+					wsi->http.range.extent);
+				p += n;
+			}
+
+			wsi->http.range.budget = wsi->http.range.end -
+						   wsi->http.range.start + 1;
+			wsi->http.range.inside = 1;
+		}
+#endif
+
+		poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH;
+
+		if (wsi->http.tx_content_length)
+			if (poss > wsi->http.tx_content_remain)
+				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 (wsi->protocol->tx_packet_size &&
+		    poss > wsi->protocol->tx_packet_size)
+			poss = wsi->protocol->tx_packet_size;
+
+		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 defined(LWS_WITH_RANGES)
+		if (wsi->http.range.count_ranges) {
+			if (wsi->http.range.count_ranges > 1)
+				poss -= 7; /* allow for final boundary */
+			if (poss > wsi->http.range.budget)
+				poss = wsi->http.range.budget;
+		}
+#endif
+		if (wsi->sending_chunked) {
+			/* we need to drop the chunk size in here */
+			p += 10;
+			/* allow for the chunk to grow by 128 in translation */
+			poss -= 10 + 128;
+		}
+
+		if (lws_vfs_file_read(wsi->http.fop_fd, &amount, p, poss) < 0)
+			goto file_had_it; /* caller will close */
+
+		if (wsi->sending_chunked)
+			n = (int)amount;
+		else
+			n = lws_ptr_diff(p, pstart) + (int)amount;
+
+		lwsl_debug("%s: sending %d\n", __func__, n);
+
+		if (n) {
+			lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
+					context->timeout_secs);
+
+			if (wsi->interpreting) {
+				args.p = (char *)p;
+				args.len = n;
+				args.max_len = (unsigned int)poss + 128;
+				args.final = wsi->http.filepos + n ==
+					     wsi->http.filelen;
+				args.chunked = wsi->sending_chunked;
+				if (user_callback_handle_rxflow(
+				     wsi->vhost->protocols[
+				     (int)wsi->protocol_interpret_idx].callback,
+				     wsi, LWS_CALLBACK_PROCESS_HTML,
+				     wsi->user_space, &args, 0) < 0)
+					goto file_had_it;
+				n = args.len;
+				p = (unsigned char *)args.p;
+			} else
+				p = pstart;
+
+#if defined(LWS_WITH_RANGES)
+			if (wsi->http.range.send_ctr + 1 ==
+				wsi->http.range.count_ranges && // last range
+			    wsi->http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart)
+			    wsi->http.range.budget - amount == 0) {// final part
+				n += lws_snprintf((char *)pstart + n, 6,
+					"_lws\x0d\x0a"); // append trailing boundary
+				lwsl_debug("added trailing boundary\n");
+			}
+#endif
+			m = lws_write(wsi, p, n,
+				      wsi->http.filepos + amount == wsi->http.filelen ?
+					LWS_WRITE_HTTP_FINAL :
+					LWS_WRITE_HTTP
+				);
+			if (m < 0)
+				goto file_had_it;
+
+			wsi->http.filepos += amount;
+
+#if defined(LWS_WITH_RANGES)
+			if (wsi->http.range.count_ranges >= 1) {
+				wsi->http.range.budget -= amount;
+				if (wsi->http.range.budget == 0) {
+					lwsl_notice("range budget exhausted\n");
+					wsi->http.range.inside = 0;
+					wsi->http.range.send_ctr++;
+
+					if (lws_ranges_next(&wsi->http.range) < 1) {
+						finished = 1;
+						goto all_sent;
+					}
+				}
+			}
+#endif
+
+			if (m != n) {
+				/* adjust for what was not sent */
+				if (lws_vfs_file_seek_cur(wsi->http.fop_fd,
+							   m - n) ==
+							     (lws_fileofs_t)-1)
+					goto file_had_it;
+			}
+		}
+
+all_sent:
+		if ((!wsi->trunc_len && wsi->http.filepos >= wsi->http.filelen)
+#if defined(LWS_WITH_RANGES)
+		    || finished)
+#else
+		)
+#endif
+		     {
+			lwsi_set_state(wsi, LRS_ESTABLISHED);
+			/* we might be in keepalive, so close it off here */
+			lws_vfs_file_close(&wsi->http.fop_fd);
+
+			lwsl_debug("file completed\n");
+
+			if (wsi->protocol->callback &&
+			    user_callback_handle_rxflow(wsi->protocol->callback,
+							wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION,
+							wsi->user_space, NULL,
+							0) < 0) {
+					/*
+					 * For http/1.x, the choices from
+					 * transaction_completed are either
+					 * 0 to use the connection for pipelined
+					 * or nonzero to hang it up.
+					 *
+					 * However for http/2. while we are
+					 * still interested in hanging up the
+					 * nwsi if there was a network-level
+					 * fatal error, simply completing the
+					 * transaction is a matter of the stream
+					 * state, not the root connection at the
+					 * network level
+					 */
+					if (wsi->http2_substream)
+						return 1;
+					else
+						return -1;
+				}
+
+			return 1;  /* >0 indicates completed */
+		}
+	} while (0); // while (!lws_send_pipe_choked(wsi))
+
+	lws_callback_on_writable(wsi);
+
+	return 0; /* indicates further processing must be done */
+
+file_had_it:
+	lws_vfs_file_close(&wsi->http.fop_fd);
+
+	return -1;
+}
+
+
 LWS_VISIBLE void
 lws_server_get_canonical_hostname(struct lws_context *context,
 				  struct lws_context_creation_info *info)
diff --git a/lib/roles/ws/client-parser.c b/lib/roles/ws/client-parser.c
index a1168883215f4e1c0f1b9cc3e2898d5614de6135..111be0399e1cbac8762be9838a014ae7bb0134b9 100644
--- a/lib/roles/ws/client-parser.c
+++ b/lib/roles/ws/client-parser.c
@@ -26,7 +26,7 @@
  *   sync with changes here, esp related to ext draining
  */
 
-int lws_client_rx_sm(struct lws *wsi, unsigned char c)
+int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c)
 {
 	int callback_action = LWS_CALLBACK_CLIENT_RECEIVE;
 	int handled, n, m, rx_draining_ext = 0;
@@ -34,10 +34,12 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c)
 	struct lws_tokens ebuf;
 	unsigned char *pp;
 
+	ebuf.token = NULL;
+	ebuf.len = 0;
+
 	if (wsi->ws->rx_draining_ext) {
 		assert(!c);
-		ebuf.token = NULL;
-		ebuf.len = 0;
+
 		lws_remove_wsi_from_draining_ext_list(wsi);
 		rx_draining_ext = 1;
 		lwsl_debug("%s: doing draining flow\n", __func__);
@@ -65,17 +67,20 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c)
 					wsi->context->options,
 					LWS_SERVER_OPTION_VALIDATE_UTF8);
 				wsi->ws->utf8 = 0;
+				wsi->ws->first_fragment = 1;
 				break;
 			case LWSWSOPC_BINARY_FRAME:
 				wsi->ws->rsv_first_msg = (c & 0x70);
 				wsi->ws->check_utf8 = 0;
 				wsi->ws->continuation_possible = 1;
+				wsi->ws->first_fragment = 1;
 				break;
 			case LWSWSOPC_CONTINUATION:
 				if (!wsi->ws->continuation_possible) {
 					lwsl_info("disordered continuation\n");
 					return -1;
 				}
+				wsi->ws->first_fragment = 0;
 				break;
 			case LWSWSOPC_CLOSE:
 				wsi->ws->check_utf8 = 0;
@@ -164,15 +169,15 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c)
 			wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
 			break;
 		default:
-			wsi->ws->rx_packet_length = c;
+			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 (c)
+				if (wsi->ws->rx_packet_length) {
 					wsi->lws_rx_parse_state =
-					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-				else {
+					LWS_RXPS_WS_FRAME_PAYLOAD;
+				} else {
 					wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 					goto spill;
 				}
@@ -193,7 +198,7 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c)
 		else {
 			if (wsi->ws->rx_packet_length)
 				wsi->lws_rx_parse_state =
-					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+					LWS_RXPS_WS_FRAME_PAYLOAD;
 			else {
 				wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 				goto spill;
@@ -259,7 +264,7 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c)
 		else {
 			if (wsi->ws->rx_packet_length)
 				wsi->lws_rx_parse_state =
-					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+					LWS_RXPS_WS_FRAME_PAYLOAD;
 			else {
 				wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 				goto spill;
@@ -295,14 +300,14 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c)
 
 		if (wsi->ws->rx_packet_length)
 			wsi->lws_rx_parse_state =
-					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+					LWS_RXPS_WS_FRAME_PAYLOAD;
 		else {
 			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 			goto spill;
 		}
 		break;
 
-	case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
+	case LWS_RXPS_WS_FRAME_PAYLOAD:
 
 		assert(wsi->ws->rx_ubuf);
 
@@ -387,26 +392,19 @@ spill:
 					wsi->ws->rx_ubuf_head))
 				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;
-			else
-				/*
-				 * parrot the close packet payload back
-				 * we do not care about how it went, we are closing
-				 * immediately afterwards
-				 */
-				lws_write(wsi, (unsigned char *)
-					  &wsi->ws->rx_ubuf[LWS_PRE],
-					  wsi->ws->rx_ubuf_head,
-					  LWS_WRITE_CLOSE);
-			lwsi_set_state(wsi, LRS_RETURNED_CLOSE);
-			/* close the connection */
-			return -1;
+			memcpy(wsi->ws->ping_payload_buf + LWS_PRE, pp,
+			       wsi->ws->rx_ubuf_head);
+			wsi->ws->close_in_ping_buffer_len = wsi->ws->rx_ubuf_head;
+
+			lwsl_notice("%s: scheduling return close as ack\n", __func__);
+			__lws_change_pollfd(wsi, LWS_POLLIN, 0);
+			lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 3);
+			wsi->waiting_to_send_close_frame = 1;
+			wsi->close_needs_ack = 0;
+			lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE);
+			lws_callback_on_writable(wsi);
+			handled = 1;
+			break;
 
 		case LWSWSOPC_PING:
 			lwsl_info("received %d byte ping, sending pong\n",
@@ -467,30 +465,11 @@ ping_drop:
 			break;
 
 		default:
+			/* not handled or failed */
+			lwsl_ext("Unhandled ext opc 0x%x\n", wsi->ws->opcode);
+			wsi->ws->rx_ubuf_head = 0;
 
-			lwsl_parser("Reserved opc 0x%2X\n", wsi->ws->opcode);
-
-			/*
-			 * It's something special we can't understand here.
-			 * Pass the payload up to the extension's parsing
-			 * state machine.
-			 */
-
-			ebuf.token = &wsi->ws->rx_ubuf[LWS_PRE];
-			ebuf.len = wsi->ws->rx_ubuf_head;
-
-			if (lws_ext_cb_active(wsi,
-				LWS_EXT_CB_EXTENDED_PAYLOAD_RX,
-					&ebuf, 0) <= 0) {
-				/* not handled or failed */
-				lwsl_ext("Unhandled ext opc 0x%x\n",
-					 wsi->ws->opcode);
-				wsi->ws->rx_ubuf_head = 0;
-
-				return 0;
-			}
-			handled = 1;
-			break;
+			return -1;
 		}
 
 		/*
@@ -520,7 +499,7 @@ drain_extension:
 #else
 		n = 0;
 #endif
-		lwsl_ext("post inflate ebuf len %d\n", ebuf.len);
+		lwsl_notice("post inflate ebuf len %d\n", ebuf.len);
 
 		if (rx_draining_ext && !ebuf.len) {
 			lwsl_debug("   --- ending drain on 0 read result\n");
@@ -530,15 +509,24 @@ drain_extension:
 		if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
 			if (lws_check_utf8(&wsi->ws->utf8,
 					   (unsigned char *)ebuf.token,
-					   ebuf.len))
+					   ebuf.len)) {
+				lws_close_reason(wsi,
+					LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+					(uint8_t *)"bad utf8", 8);
 				goto utf8_fail;
+			}
 
 			/* we are ending partway through utf-8 character? */
 			if (!wsi->ws->rx_packet_length && wsi->ws->final &&
 			    wsi->ws->utf8 && !n) {
 				lwsl_info("FINAL utf8 error\n");
+				lws_close_reason(wsi,
+					LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+					(uint8_t *)"partial utf8", 12);
 utf8_fail:
 				lwsl_info("utf8 error\n");
+				lwsl_hexdump_info(ebuf.token, ebuf.len);
+
 				return -1;
 			}
 		}
@@ -582,6 +570,11 @@ utf8_fail:
 			(enum lws_callback_reasons)callback_action,
 			wsi->user_space, ebuf.token, ebuf.len);
 
+		wsi->ws->first_fragment = 0;
+
+		// lwsl_notice("%s: bulk ws rx: input used %d, output %d\n",
+		//	__func__, wsi->ws->rx_ubuf_head, ebuf.len);
+
 		/* if user code wants to close, let caller know */
 		if (m)
 			return 1;
diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c
index 7c009125496559b5302d3882fc6bf791541d0609..e39abd65e027d4c7e14e6dc7e1a9ab6fc745cb07 100644
--- a/lib/roles/ws/client-ws.c
+++ b/lib/roles/ws/client-ws.c
@@ -99,14 +99,7 @@ lws_generate_client_ws_handshake(struct lws *wsi, char *p)
 #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);
@@ -172,7 +165,6 @@ lws_client_ws_upgrade(struct lws *wsi, const char **cce)
 	const char *c, *a;
 	char ignore;
 	int more = 1;
-	void *v;
 #endif
 
 	if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */
@@ -575,24 +567,6 @@ check_accept:
 		*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;
 
diff --git a/lib/roles/ws/ext/extension-permessage-deflate.c b/lib/roles/ws/ext/extension-permessage-deflate.c
index 556451b9003972d4f386ccc3e55119ae3cfa862a..e23d80d251dce816e8a954c2e373b3bbfc1a1b3a 100644
--- a/lib/roles/ws/ext/extension-permessage-deflate.c
+++ b/lib/roles/ws/ext/extension-permessage-deflate.c
@@ -82,6 +82,7 @@ lws_extension_callback_pm_deflate(struct lws_context *context,
 		oa = in;
 		if (!oa->option_name)
 			break;
+		lwsl_ext("%s: named option set: %s\n", __func__, oa->option_name);
 		for (n = 0; n < (int)ARRAY_SIZE(lws_ext_pm_deflate_options); n++)
 			if (!strcmp(lws_ext_pm_deflate_options[n].name,
 				    oa->option_name))
@@ -95,8 +96,8 @@ lws_extension_callback_pm_deflate(struct lws_context *context,
 
 	case LWS_EXT_CB_OPTION_SET:
 		oa = in;
-		lwsl_notice("%s: option set: idx %d, %s, len %d\n", __func__,
-			  oa->option_index, oa->start, oa->len);
+		lwsl_ext("%s: option set: idx %d, %s, len %d\n", __func__,
+			 oa->option_index, oa->start, oa->len);
 		if (oa->start)
 			priv->args[oa->option_index] = atoi(oa->start);
 		else
@@ -179,14 +180,8 @@ lws_extension_callback_pm_deflate(struct lws_context *context,
 		if (!(wsi->ws->rsv_first_msg & 0x40))
 			return 0;
 
-#if 0
-		for (n = 0; n < ebuf->len; n++) {
-			printf("%02X ", (unsigned char)ebuf->token[n]);
-			if ((n & 15) == 15)
-				printf("\n");
-		}
-		printf("\n");
-#endif
+		// lwsl_hexdump_debug(ebuf->token, ebuf->len);
+
 		if (!priv->rx_init)
 			if (inflateInit2(&priv->rx,
 			     -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) {
@@ -250,7 +245,7 @@ lws_extension_callback_pm_deflate(struct lws_context *context,
 		case Z_STREAM_ERROR:
 		case Z_DATA_ERROR:
 		case Z_MEM_ERROR:
-			lwsl_info("zlib error inflate %d: %s\n",
+			lwsl_notice("zlib error inflate %d: %s\n",
 				  n, priv->rx.msg);
 			return -1;
 		}
@@ -320,15 +315,13 @@ lws_extension_callback_pm_deflate(struct lws_context *context,
 		if (was_fin) {
 			priv->count_rx_between_fin = 0;
 			if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) {
+				lwsl_ext("PMD_SERVER_NO_CONTEXT_TAKEOVER\n");
 				(void)inflateEnd(&priv->rx);
 				priv->rx_init = 0;
 			}
 		}
-#if 0
-		for (n = 0; n < ebuf->len; n++)
-			putchar(ebuf->token[n]);
-		puts("\n");
-#endif
+
+		// lwsl_hexdump_debug(ebuf->token, ebuf->len);
 
 		return priv->rx_held_valid;
 
diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c
index 055fce5a32cd1f2808713dcfe3071f2ce0581185..162acab44815cb19391fc2c69284da47427f847a 100644
--- a/lib/roles/ws/ops-ws.c
+++ b/lib/roles/ws/ops-ws.c
@@ -24,18 +24,21 @@
 #define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
 
 /*
- * client-parser.c: lws_client_rx_sm() needs to be roughly kept in
+ * client-parser.c: lws_ws_client_rx_sm() needs to be roughly kept in
  *   sync with changes here, esp related to ext draining
  */
 
 int
-lws_ws_rx_sm(struct lws *wsi, unsigned char c)
+lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c)
 {
 	int callback_action = LWS_CALLBACK_RECEIVE;
 	int ret = 0, rx_draining_ext = 0;
+	unsigned short close_code;
 	struct lws_tokens ebuf;
+	unsigned char *pp;
+	int n = 0;
 #if !defined(LWS_WITHOUT_EXTENSIONS)
-	int n;
+	int lin;
 #endif
 
 	ebuf.token = NULL;
@@ -131,14 +134,42 @@ handle_first:
 		wsi->ws->opcode = c & 0xf;
 		wsi->ws->rsv = c & 0x70;
 		wsi->ws->final = !!((c >> 7) & 1);
+		wsi->ws->defeat_check_utf8 = 0;
+
+		if (((wsi->ws->opcode) & 8) && !wsi->ws->final) {
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+					(uint8_t *)"frag ctl", 8);
+			return -1;
+		}
 
 		switch (wsi->ws->opcode) {
 		case LWSWSOPC_TEXT_FRAME:
+			wsi->ws->check_utf8 = lws_check_opt(
+				wsi->context->options,
+				LWS_SERVER_OPTION_VALIDATE_UTF8);
+			/* fallthru */
 		case LWSWSOPC_BINARY_FRAME:
+			if (wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)
+				wsi->ws->check_utf8 = 0;
+			if (wsi->ws->continuation_possible) {
+				lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad cont", 8);
+				return -1;
+			}
 			wsi->ws->rsv_first_msg = (c & 0x70);
 			wsi->ws->frame_is_binary =
 			     wsi->ws->opcode == LWSWSOPC_BINARY_FRAME;
 			wsi->ws->first_fragment = 1;
+			wsi->ws->continuation_possible = !wsi->ws->final;
+			break;
+		case LWSWSOPC_CONTINUATION:
+			if (!wsi->ws->continuation_possible) {
+				lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad cont", 8);
+				return -1;
+			}
+			break;
+		case LWSWSOPC_CLOSE:
+			wsi->ws->check_utf8 = 0;
+			wsi->ws->utf8 = 0;
 			break;
 		case 3:
 		case 4:
@@ -150,10 +181,37 @@ handle_first:
 		case 0xd:
 		case 0xe:
 		case 0xf:
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad opc", 7);
 			lwsl_info("illegal opcode\n");
 			return -1;
 		}
+
+		if (wsi->ws->owed_a_fin &&
+		    (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+		     wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) {
+			lwsl_info("hey you owed us a FIN\n");
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad fin", 7);
+			return -1;
+		}
+		if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) {
+			wsi->ws->continuation_possible = 0;
+			wsi->ws->owed_a_fin = 0;
+		}
+
+		if (!wsi->ws->final)
+			wsi->ws->owed_a_fin = 1;
+
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
+		if (wsi->ws->rsv &&
+		    (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+				    !wsi->count_act_ext ||
+#endif
+				    (wsi->ws->rsv & ~0x40))) {
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+					 (uint8_t *)"rsv bits", 8);
+			return -1;
+		}
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN:
@@ -177,14 +235,16 @@ handle_first:
 			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)
+				if (wsi->ws->rx_packet_length) {
 					wsi->lws_rx_parse_state =
-					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-				else {
+					LWS_RXPS_WS_FRAME_PAYLOAD;
+				} else {
 					wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 					goto spill;
 				}
@@ -202,9 +262,10 @@ handle_first:
 		if (wsi->ws->this_frame_masked)
 			wsi->lws_rx_parse_state =
 					LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-		else
+		else {
 			wsi->lws_rx_parse_state =
-				LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+				LWS_RXPS_WS_FRAME_PAYLOAD;
+		}
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_8:
@@ -263,8 +324,7 @@ handle_first:
 			wsi->lws_rx_parse_state =
 					LWS_RXPS_07_COLLECT_FRAME_KEY_1;
 		else
-			wsi->lws_rx_parse_state =
-				LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+			wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD;
 		break;
 
 	case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
@@ -292,8 +352,7 @@ handle_first:
 		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->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD;
 		wsi->ws->mask_idx = 0;
 		if (wsi->ws->rx_packet_length == 0) {
 			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
@@ -302,32 +361,36 @@ handle_first:
 		break;
 
 
-	case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
+	case LWS_RXPS_WS_FRAME_PAYLOAD:
 		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) {
+		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 (!(already_processed & ALREADY_PROCESSED_IGNORE_CHAR)) {
+			if (wsi->ws->all_zero_nonce)
+				wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] =
+				   c;
+			else
+				wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] =
+				   c ^ wsi->ws->mask[(wsi->ws->mask_idx++) & 3];
+
+			--wsi->ws->rx_packet_length;
+		}
 
-		if (--wsi->ws->rx_packet_length == 0) {
+		if (!wsi->ws->rx_packet_length) {
+			lwsl_debug("%s: ws fragment length exhausted\n", __func__);
 			/* spill because we have the whole frame */
 			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 			goto spill;
 		}
 
+		if (wsi->ws->rx_draining_ext) {
+			lwsl_debug("%s: UNTIL_EXHAUSTED draining\n", __func__);
+			goto drain_extension;
+		}
+
 		/*
 		 * if there's no protocol max frame size given, we are
 		 * supposed to default to context->pt_serv_buf_size
@@ -352,6 +415,19 @@ spill:
 		switch (wsi->ws->opcode) {
 		case LWSWSOPC_CLOSE:
 
+			if (wsi->ws->peer_has_sent_close)
+				break;
+
+			wsi->ws->peer_has_sent_close = 1;
+
+			pp = (unsigned char *)&wsi->ws->rx_ubuf[LWS_PRE];
+			if (lws_check_opt(wsi->context->options,
+					  LWS_SERVER_OPTION_VALIDATE_UTF8) &&
+			    wsi->ws->rx_ubuf_head > 2 &&
+			    lws_check_utf8(&wsi->ws->utf8, pp + 2,
+					   wsi->ws->rx_ubuf_head - 2))
+				goto utf8_fail;
+
 			/* is this an acknowledgment of our close? */
 			if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
 				/*
@@ -376,6 +452,23 @@ spill:
 				return -1;
 			}
 
+			if (wsi->ws->rx_ubuf_head >= 2) {
+				close_code = (pp[0] << 8) | pp[1];
+				if (close_code < 1000 ||
+				    close_code == 1004 ||
+				    close_code == 1005 ||
+				    close_code == 1006 ||
+				    close_code == 1012 ||
+				    close_code == 1013 ||
+				    close_code == 1014 ||
+				    close_code == 1015 ||
+				    (close_code >= 1016 && close_code < 3000)
+				) {
+					pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
+					pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
+				}
+			}
+
 			if (user_callback_handle_rxflow(
 					wsi->protocol->callback, wsi,
 					LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
@@ -445,26 +538,9 @@ ping_drop:
 			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.
-			 */
-
-			ebuf.token = &wsi->ws->rx_ubuf[LWS_PRE];
-			ebuf.len = wsi->ws->rx_ubuf_head;
+			lwsl_parser("unknown opc %x\n", wsi->ws->opcode);
 
-			if (lws_ext_cb_active(wsi,
-					      LWS_EXT_CB_EXTENDED_PAYLOAD_RX,
-					      &ebuf, 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;
+			return -1;
 		}
 
 		/*
@@ -480,19 +556,24 @@ ping_drop:
 			goto already_done;
 
 drain_extension:
-		lwsl_ext("%s: passing %d to ext\n", __func__, ebuf.len);
+		// lwsl_notice("%s: passing %d to ext\n", __func__, ebuf.len);
 
 		if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
 		    lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK)
 			goto already_done;
 #if !defined(LWS_WITHOUT_EXTENSIONS)
+		lin = ebuf.len;
+		//if (lin)
+		//	lwsl_hexdump_notice(ebuf.token, ebuf.len);
 		n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0);
+		lwsl_debug("%s: ext says %d / ebuf.len %d\n", __func__,  n, ebuf.len);
+		if (wsi->ws->rx_draining_ext)
+			already_processed &= ~ALREADY_PROCESSED_NO_CB;
 #endif
 		/*
 		 * ebuf may be pointing somewhere completely different now,
 		 * it's the output
 		 */
-		wsi->ws->first_fragment = 0;
 #if !defined(LWS_WITHOUT_EXTENSIONS)
 		if (n < 0) {
 			/*
@@ -515,11 +596,38 @@ drain_extension:
 		else
 			lws_remove_wsi_from_draining_ext_list(wsi);
 
-		if (ebuf.len > 0 ||
-		    callback_action == LWS_CALLBACK_RECEIVE_PONG) {
-			ebuf.token[ebuf.len] = '\0';
+		if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+			if (lws_check_utf8(&wsi->ws->utf8,
+					   (unsigned char *)ebuf.token,
+					   ebuf.len)) {
+				lws_close_reason(wsi,
+					LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+					(uint8_t *)"bad utf8", 8);
+				goto utf8_fail;
+			}
+
+			/* we are ending partway through utf-8 character? */
+			if (!wsi->ws->rx_packet_length && wsi->ws->final &&
+			    wsi->ws->utf8 && !n) {
+				lwsl_info("FINAL utf8 error\n");
+				lws_close_reason(wsi,
+					LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+					(uint8_t *)"partial utf8", 12);
+utf8_fail:
+				lwsl_notice("utf8 error\n");
+				lwsl_hexdump_notice(ebuf.token, ebuf.len);
+
+				return -1;
+			}
+		}
+
+		if (!wsi->wsistate_pre_close && (ebuf.len >= 0 ||
+		    callback_action == LWS_CALLBACK_RECEIVE_PONG)) {
+			if (ebuf.len)
+				ebuf.token[ebuf.len] = '\0';
 
-			if (wsi->protocol->callback) {
+			if (wsi->protocol->callback &&
+			    !(already_processed & ALREADY_PROCESSED_NO_CB)) {
 				if (callback_action == LWS_CALLBACK_RECEIVE_PONG)
 					lwsl_info("Doing pong callback\n");
 
@@ -531,10 +639,14 @@ drain_extension:
 						ebuf.token,
 						ebuf.len);
 			}
-			else
-				lwsl_err("No callback on payload spill!\n");
+			wsi->ws->first_fragment = 0;
 		}
 
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+		if (!lin)
+			break;
+#endif
+
 already_done:
 		wsi->ws->rx_ubuf_head = 0;
 		break;
@@ -569,7 +681,7 @@ lws_add_wsi_to_draining_ext_list(struct lws *wsi)
 	if (wsi->ws->rx_draining_ext)
 		return;
 
-	lwsl_ext("%s: RX EXT DRAINING: Adding to list\n", __func__);
+	lwsl_debug("%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;
@@ -585,7 +697,7 @@ lws_remove_wsi_from_draining_ext_list(struct lws *wsi)
 	if (!wsi->ws->rx_draining_ext)
 		return;
 
-	lwsl_ext("%s: RX EXT DRAINING: Removing from list\n", __func__);
+	lwsl_debug("%s: RX EXT DRAINING: Removing from list\n", __func__);
 
 	wsi->ws->rx_draining_ext = 0;
 
@@ -630,70 +742,6 @@ lws_0405_frame_mask_generate(struct lws *wsi)
 	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)
 {
@@ -753,7 +801,7 @@ lws_server_init_wsi_for_ws(struct lws *wsi)
 LWS_VISIBLE int
 lws_is_final_fragment(struct lws *wsi)
 {
-       lwsl_info("%s: final %d, rx pk length %ld, draining %ld\n", __func__,
+       lwsl_debug("%s: final %d, rx pk length %ld, draining %ld\n", __func__,
 			wsi->ws->final, (long)wsi->ws->rx_packet_length,
 			(long)wsi->ws->rx_draining_ext);
 	return wsi->ws->final && !wsi->ws->rx_packet_length &&
@@ -821,16 +869,21 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 {
 	struct lws_tokens ebuf;
 	unsigned int pending = 0;
-	char draining_flow = 0, buffered = 0;
+	char buffered = 0;
 	int n = 0, m;
 #if defined(LWS_WITH_HTTP2)
 	struct lws *wsi1;
 #endif
 
+	if (!wsi->ws) {
+		lwsl_err("ws role wsi with no ws\n");
+		return 1;
+	}
+
 	// lwsl_notice("%s: %s\n", __func__, wsi->protocol->name);
 
-	lwsl_info("%s: wsistate 0x%x, pollout %d\n", __func__,
-		   wsi->wsistate, pollfd->revents & LWS_POLLOUT);
+	//lwsl_info("%s: wsistate 0x%x, pollout %d\n", __func__,
+	//	   wsi->wsistate, pollfd->revents & LWS_POLLOUT);
 
 	/*
 	 * something went wrong with parsing the handshake, and
@@ -859,6 +912,8 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 		return LWS_HPI_RET_HANDLED;
 	}
 
+	//lwsl_notice("%s:  wsi->ws->tx_draining_ext %d revents 0x%x 0x%x %d\n", __func__,  wsi->ws->tx_draining_ext, pollfd->revents, wsi->wsistate, lwsi_state_can_handle_POLLOUT(wsi));
+
 	/* 1: something requested a callback when it was OK to write */
 
 	if ((pollfd->revents & LWS_POLLOUT) &&
@@ -866,14 +921,12 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *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_PLEASE_CLOSE_ME;
 	}
 
 	if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
-	    lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE ||
-	    lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
+	    lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) {
 		/*
 		 * we stopped caring about anything except control
 		 * packets.  Force flow control off, defeat tx
@@ -884,7 +937,7 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 			wsi->ws->tx_draining_ext = 0;
 	}
 
-	if (wsi->ws && wsi->ws->tx_draining_ext)
+	if (wsi->ws->tx_draining_ext)
 		/*
 		 * We cannot deal with new RX until the TX ext path has
 		 * been drained.  It's because new rx will, eg, crap on
@@ -895,11 +948,13 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 		 */
 		return LWS_HPI_RET_HANDLED;
 
-	if (lws_is_flowcontrolled(wsi))
+	if (lws_is_flowcontrolled(wsi)) {
 		/* We cannot deal with any kind of new RX because we are
 		 * RX-flowcontrolled.
 		 */
+		lwsl_info("flowcontrolled\n");
 		return LWS_HPI_RET_HANDLED;
+	}
 
 #if defined(LWS_WITH_HTTP2)
 	if (wsi->http2_substream || wsi->upgraded_to_http2) {
@@ -917,23 +972,23 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 	/* 2: RX Extension needs to be drained
 	 */
 
-	if (lwsi_role_ws(wsi) && wsi->ws && wsi->ws->rx_draining_ext) {
+	if (wsi->ws->rx_draining_ext) {
 
-		lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__);
+		lwsl_debug("%s: RX EXT DRAINING: Service\n", __func__);
 #ifndef LWS_NO_CLIENT
 		if (lwsi_role_client(wsi)) {
-			n = lws_client_rx_sm(wsi, 0);
+			n = lws_ws_client_rx_sm(wsi, 0);
 			if (n < 0)
 				/* we closed wsi */
-				n = 0;
+				return LWS_HPI_RET_PLEASE_CLOSE_ME;
 		} else
 #endif
-			n = lws_ws_rx_sm(wsi, 0);
+			n = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
 
 		return LWS_HPI_RET_HANDLED;
 	}
 
-	if (wsi->ws && wsi->ws->rx_draining_ext)
+	if (wsi->ws->rx_draining_ext)
 		/*
 		 * We have RX EXT content to drain, but can't do it
 		 * right now.  That means we cannot do anything lower
@@ -943,7 +998,8 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 
 	/* 3: buflist needs to be drained
 	 */
-
+read:
+	//lws_buflist_describe(&wsi->buflist, wsi);
 	ebuf.len = (int)lws_buflist_next_segment_len(&wsi->buflist,
 						     (uint8_t **)&ebuf.token);
 	if (ebuf.len) {
@@ -955,7 +1011,6 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 	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);
@@ -964,18 +1019,21 @@ read:
 
 	if (!(lwsi_role_client(wsi) &&
 	      (lwsi_state(wsi) != LRS_ESTABLISHED &&
+	       lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK &&
 	       lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) {
 		/*
-		 * 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.
+		 * In case we are going to react to this rx by scheduling
+		 * writes, we need to restrict the amount of rx to the size
+		 * the protocol reported for rx buffer.
+		 *
+		 * Otherwise we get a situation we have to absorb possibly a
+		 * lot of reads before we get a chance to drain them by writing
+		 * them, eg, with echo type tests in autobahn.
 		 */
 
 		buffered = 0;
 		ebuf.token = (char *)pt->serv_buf;
-		if (lws_is_ws_with_ext(wsi))
+		if (lwsi_role_ws(wsi))
 			ebuf.len = wsi->ws->rx_ubuf_alloc;
 		else
 			ebuf.len = wsi->context->pt_serv_buf_size;
@@ -1026,15 +1084,9 @@ drain:
 	 */
 	m = 0;
 	do {
-#if !defined(LWS_WITHOUT_EXTENSIONS)
-		m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE,
-				      &ebuf, 0);
-		if (m < 0)
-			return LWS_HPI_RET_PLEASE_CLOSE_ME;
-#endif
 
 		/* service incoming data */
-
+		//lws_buflist_describe(&wsi->buflist, wsi);
 		if (ebuf.len) {
 #if defined(LWS_ROLE_H2)
 			if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY)
@@ -1050,6 +1102,8 @@ drain:
 				n = 0;
 				return LWS_HPI_RET_WSI_ALREADY_DIED;
 			}
+			//lws_buflist_describe(&wsi->buflist, wsi);
+			//lwsl_notice("%s: consuming %d / %d\n", __func__, n, ebuf.len);
 			if (lws_buflist_aware_consume(wsi, &ebuf, n, buffered))
 				return LWS_HPI_RET_PLEASE_CLOSE_ME;
 		}
@@ -1078,7 +1132,7 @@ drain:
 		goto read;
 	}
 
-	if (draining_flow && /* were draining, now nothing left */
+	if (buffered && /* were draining, now nothing left */
 	    !lws_buflist_next_segment_len(&wsi->buflist, NULL)) {
 		lwsl_info("%s: %p flow buf: drained\n", __func__, wsi);
 		/* having drained the rxflow buffer, can rearm POLLIN */
@@ -1103,7 +1157,8 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 #endif
 	int n;
 
-	// lwsl_notice("%s: %s\n", __func__, wsi->protocol->name);
+	lwsl_debug("%s: %s: wsi->ws->tx_draining_ext %d\n", __func__,
+			wsi->protocol->name, wsi->ws->tx_draining_ext);
 
 	/* Priority 3: pending control packets (pong or close)
 	 *
@@ -1112,16 +1167,22 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 
 	if (lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) {
 		lwsl_debug("sending close packet\n");
+		lwsl_hexdump_debug(&wsi->ws->ping_payload_buf[LWS_PRE],
+				   wsi->ws->close_in_ping_buffer_len);
 		wsi->waiting_to_send_close_frame = 0;
 		n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
 			      wsi->ws->close_in_ping_buffer_len,
 			      LWS_WRITE_CLOSE);
 		if (n >= 0) {
-			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");
+			if (wsi->close_needs_ack) {
+				lwsi_set_state(wsi, LRS_AWAITING_CLOSE_ACK);
+				lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 5);
+				lwsl_debug("sent close indication, awaiting ack\n");
 
-			return LWS_HP_RET_BAIL_OK;
+				return LWS_HP_RET_BAIL_OK;
+			}
+			wsi->close_needs_ack = 0;
+			lwsi_set_state(wsi, LRS_RETURNED_CLOSE);
 		}
 
 		return LWS_HP_RET_BAIL_DIE;
@@ -1135,6 +1196,14 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 
 		if (wsi->ws->payload_is_close)
 			write_type = LWS_WRITE_CLOSE;
+		else {
+			if (wsi->wsistate_pre_close) {
+				/* we started close flow, forget pong */
+				wsi->ws->ping_pending_flag = 0;
+				return LWS_HP_RET_BAIL_OK;
+			}
+			lwsl_info("issuing pong %d on wsi %p\n", wsi->ws->ping_payload_len, wsi);
+		}
 
 		n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
 			      wsi->ws->ping_payload_len, write_type);
@@ -1199,10 +1268,6 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 	/* 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;
 
@@ -1389,6 +1454,7 @@ rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason)
 	}
 
 	wsi->waiting_to_send_close_frame = 1;
+	wsi->close_needs_ack = 1;
 	lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE);
 	__lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5);
 
@@ -1417,7 +1483,7 @@ rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
 
 	if (wsi->ws->tx_draining_ext) {
 		struct lws **w = &pt->tx_draining_ext_list;
-
+		lwsl_notice("%s: CLEARING tx_draining_ext\n", __func__);
 		wsi->ws->tx_draining_ext = 0;
 		/* remove us from context draining ext list */
 		while (*w) {
@@ -1449,14 +1515,18 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
 	int masked7 = lwsi_role_client(wsi);
 	unsigned char is_masked_bit = 0;
 	unsigned char *dropmask = NULL;
+	enum lws_write_protocol wpt;
 	struct lws_tokens ebuf;
 	size_t orig_len = len;
-	int pre = 0, n;
+	int pre = 0, n = 0;
+
+	// lwsl_err("%s: wp 0x%x len %d\n", __func__, *wp, (int)len);
 
 	if (wsi->ws->tx_draining_ext) {
 		/* remove us from the list */
 		struct lws **w = &pt->tx_draining_ext_list;
 
+		lwsl_notice("%s: CLEARING tx_draining_ext\n", __func__);
 		wsi->ws->tx_draining_ext = 0;
 		/* remove us from context draining ext list */
 		while (*w) {
@@ -1467,10 +1537,23 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
 			w = &((*w)->ws->tx_draining_ext_list);
 		}
 		wsi->ws->tx_draining_ext_list = NULL;
-		*wp = (wsi->ws->tx_draining_stashed_wp & 0xc0) |
+
+		wpt = *wp;
+		*wp = (wsi->ws->tx_draining_stashed_wp & 0xc0)|
 				LWS_WRITE_CONTINUATION;
 
-		lwsl_ext("FORCED draining wp to 0x%02X\n", *wp);
+		/*
+		 * When we are just flushing (len == 0), we can trust the
+		 * stashed wp info completely.  Otherwise adjust it to the
+		 * FIN status of the incoming packet.
+		 */
+
+		if (!(wpt & LWS_WRITE_NO_FIN) && len)
+			*wp &= ~LWS_WRITE_NO_FIN;
+
+		lwsl_notice("FORCED draining wp to 0x%02X (stashed 0x%02X, incoming 0x%02X)\n", *wp,
+				wsi->ws->tx_draining_stashed_wp, wpt);
+		// assert(0);
 	}
 
 	lws_restart_ws_ping_pong_timer(wsi);
@@ -1515,17 +1598,19 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
 		break;
 	default:
 #if !defined(LWS_WITHOUT_EXTENSIONS)
-		lwsl_debug("LWS_EXT_CB_PAYLOAD_TX\n");
+		// lwsl_notice("LWS_EXT_CB_PAYLOAD_TX\n");
+		// m = (int)ebuf.len;
+		/* returns 0 if no more tx pending, 1 if more pending */
 		n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &ebuf, *wp);
 		if (n < 0)
 			return -1;
+		// lwsl_notice("ext processed %d plaintext into %d compressed (wp 0x%x)\n", m, (int)ebuf.len, *wp);
 
 		if (n && ebuf.len) {
-			lwsl_debug("drain len %d\n", (int)ebuf.len);
+			lwsl_notice("write drain len %d (wp 0x%x) SETTING tx_draining_ext\n", (int)ebuf.len, *wp);
 			/* extension requires further draining */
 			wsi->ws->tx_draining_ext = 1;
-			wsi->ws->tx_draining_ext_list =
-					pt->tx_draining_ext_list;
+			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);
@@ -1763,8 +1848,6 @@ rops_close_kill_connection_ws(struct lws *wsi, enum lws_close_status reason)
 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 */
diff --git a/lib/roles/ws/private.h b/lib/roles/ws/private.h
index 21c6b6568a4bda0540ecd81583c150d09f29de1b..c8cfd4d0f7cf185e3bf9d56ef21c3cfa0ce8e2e1 100644
--- a/lib/roles/ws/private.h
+++ b/lib/roles/ws/private.h
@@ -50,7 +50,7 @@ enum lws_rx_parse_state {
 	LWS_RXPS_07_COLLECT_FRAME_KEY_3,
 	LWS_RXPS_07_COLLECT_FRAME_KEY_4,
 
-	LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED
+	LWS_RXPS_WS_FRAME_PAYLOAD
 };
 
 enum lws_websocket_opcodes_07 {
@@ -70,6 +70,9 @@ enum lws_websocket_opcodes_07 {
 /* this is not usable directly by user code any more, lws_close_reason() */
 #define LWS_WRITE_CLOSE 4
 
+#define ALREADY_PROCESSED_IGNORE_CHAR 1
+#define ALREADY_PROCESSED_NO_CB 2
+
 struct _lws_websocket_related {
 	char *rx_ubuf;
 	struct lws *rx_draining_ext_list;
@@ -113,6 +116,7 @@ struct _lws_websocket_related {
 	unsigned int tx_draining_ext:1;
 	unsigned int send_check_ping:1;
 	unsigned int first_fragment:1;
+	unsigned int peer_has_sent_close:1;
 };
 
 #if !defined(LWS_WITHOUT_EXTENSIONS)
diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c
index 6985831679dde79021cc4d4dba8c525439bfba73..9bcb3bcf3062c3fdb0b0eb5c8b81ebd0d398f7a2 100644
--- a/lib/roles/ws/server-ws.c
+++ b/lib/roles/ws/server-ws.c
@@ -75,10 +75,14 @@ lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
 
 	while (more) {
 
+		if (c >= (char *)pt->serv_buf + 255)
+			return -1;
+
 		if (*c && (*c != ',' && *c != '\t')) {
 			if (*c == ';') {
 				ignore = 1;
-				args = c + 1;
+				if (!args)
+					args = c + 1;
 			}
 			if (ignore || *c == ' ') {
 				c++;
@@ -173,11 +177,19 @@ lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
 			*p += lws_snprintf(*p, (end - *p), "%s", ext_name);
 
 			/*
-			 *  go through the options trying to apply the
+			 * The client may send a bunch of different option
+			 * sets for the same extension, we are supposed to
+			 * pick one we like the look of.  The option sets are
+			 * separated by comma.
+			 *
+			 * Actually we just either accept the first one or
+			 * nothing.
+			 *
+			 * Go through the options trying to apply the
 			 * recognized ones
 			 */
 
-			lwsl_debug("ext args %s", args);
+			lwsl_info("ext args %s\n", args);
 
 			while (args && *args && *args != ',') {
 				while (*args == ' ')
@@ -194,9 +206,10 @@ lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
 					oa.option_name = NULL;
 					oa.option_index = (int)(po - opts);
 					oa.start = NULL;
-					lwsl_debug("setting %s\n", po->name);
-					if (!ext->callback(
-						lws_get_context(wsi), ext, wsi,
+					oa.len = 0;
+					lwsl_info("setting '%s'\n", po->name);
+					if (!ext->callback(lws_get_context(wsi),
+							  ext, wsi,
 							  LWS_EXT_CB_OPTION_SET,
 							  wsi->act_ext_user[
 								 wsi->count_act_ext],
@@ -211,11 +224,17 @@ lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
 				}
 				while (*args && *args != ',' && *args != ';')
 					args++;
+
+				if (*args == ';')
+					args++;
 			}
 
 			wsi->count_act_ext++;
 			lwsl_parser("cnt_act_ext <- %d\n", wsi->count_act_ext);
 
+			if (args && *args == ',')
+				more = 0;
+
 			ext++;
 		}
 
@@ -454,7 +473,7 @@ handshake_0405(struct lws_context *context, struct lws *wsi)
 
 	/* make a buffer big enough for everything */
 
-	response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + LWS_PRE;
+	response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + 256 + LWS_PRE;
 	p = response;
 	LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
 		      "Upgrade: WebSocket\x0d\x0a"
@@ -499,23 +518,18 @@ handshake_0405(struct lws_context *context, struct lws *wsi)
 
 	LWS_CPYAPP(p, "\x0d\x0a");
 
-	if (!lws_any_extension_handled(wsi, LWS_EXT_CB_HANDSHAKE_REPLY_TX,
-				       response, p - response)) {
-
-		/* okay send the handshake response accepting the connection */
+	/* okay send the handshake response accepting the connection */
 
-		lwsl_parser("issuing resp pkt %d len\n",
-			    lws_ptr_diff(p, response));
+	lwsl_parser("issuing resp pkt %d len\n",
+		    lws_ptr_diff(p, response));
 #if defined(DEBUG)
-		fwrite(response, 1,  p - response, stderr);
+	fwrite(response, 1,  p - response, stderr);
 #endif
-		n = lws_write(wsi, (unsigned char *)response, p - response,
-			      LWS_WRITE_HTTP_HEADERS);
-		if (n != (p - response)) {
-			lwsl_info("%s: ERROR writing to socket %d\n", __func__, n);
-			goto bail;
-		}
-
+	n = lws_write(wsi, (unsigned char *)response, p - response,
+		      LWS_WRITE_HTTP_HEADERS);
+	if (n != (p - response)) {
+		lwsl_info("%s: ERROR writing to socket %d\n", __func__, n);
+		goto bail;
 	}
 
 	/* alright clean up and set ourselves into established state */
@@ -543,15 +557,200 @@ bail:
 }
 
 
+
+/*
+ * Once we reach LWS_RXPS_WS_FRAME_PAYLOAD, we know how much
+ * to expect in that state and can deal with it in bulk more efficiently.
+ */
+
+static int
+lws_ws_frame_rest_is_payload(struct lws *wsi, uint8_t **buf, size_t len)
+{
+	uint8_t *buffer = *buf, mask[4];
+	struct lws_tokens ebuf;
+	unsigned int avail = len;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	unsigned int old_packet_length = (int)wsi->ws->rx_packet_length;
+#endif
+	int n = 0;
+
+	/*
+	 * With zlib, we can give it as much input as we like.  The pmd
+	 * extension will draw it down in chunks (default 1024).
+	 *
+	 * If we try to restrict how much we give it, because we must go
+	 * back to the event loop each time, we will drop the remainder...
+	 */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	if (!wsi->count_act_ext)
+#endif
+	{
+		if (wsi->protocol->rx_buffer_size)
+			avail = (int)wsi->protocol->rx_buffer_size;
+		else
+			avail = wsi->context->pt_serv_buf_size;
+	}
+
+	/* do not consume more than we should */
+	if (avail > wsi->ws->rx_packet_length)
+		avail = (unsigned int)wsi->ws->rx_packet_length;
+
+	/* do not consume more than what is in the buffer */
+	if (avail > len)
+		avail = (unsigned int)len;
+
+	if (avail <= 0)
+		return 0;
+
+	ebuf.token = (char *)buffer;
+	ebuf.len = avail;
+
+	//lwsl_hexdump_notice(ebuf.token, ebuf.len);
+
+	if (!wsi->ws->all_zero_nonce) {
+
+		for (n = 0; n < 4; n++)
+			mask[n] = wsi->ws->mask[(wsi->ws->mask_idx + n) & 3];
+
+		/* deal with 4-byte chunks using unwrapped loop */
+		n = avail >> 2;
+		while (n--) {
+			*(buffer) = *(buffer) ^ mask[0];
+			buffer++;
+			*(buffer) = *(buffer) ^ mask[1];
+			buffer++;
+			*(buffer) = *(buffer) ^ mask[2];
+			buffer++;
+			*(buffer) = *(buffer) ^ mask[3];
+			buffer++;
+		}
+		/* and the remaining bytes bytewise */
+		for (n = 0; n < (int)(avail & 3); n++) {
+			*(buffer) = *(buffer) ^ mask[n];
+			buffer++;
+		}
+
+		wsi->ws->mask_idx = (wsi->ws->mask_idx + avail) & 3;
+	}
+
+	lwsl_info("%s: using %d of raw input (total %d on offer)\n", __func__,
+		    avail, (int)len);
+
+	(*buf) += avail;
+	len -= avail;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0);
+	lwsl_info("%s: ext says %d / ebuf.len %d\n", __func__,  n, ebuf.len);
+#endif
+	/*
+	 * ebuf may be pointing somewhere completely different now,
+	 * it's the output
+	 */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	if (n < 0) {
+		/*
+		 * we may rely on this to get RX, just drop connection
+		 */
+		lwsl_notice("%s: LWS_EXT_CB_PAYLOAD_RX blew out\n", __func__);
+		wsi->socket_is_permanently_unusable = 1;
+		return -1;
+	}
+#endif
+
+	wsi->ws->rx_packet_length -= avail;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	/*
+	 * if we had an rx fragment right at the last compressed byte of the
+	 * message, we can get a zero length inflated output, where no prior
+	 * rx inflated output marked themselves with FIN, since there was
+	 * raw ws payload still to drain at that time.
+	 *
+	 * Then we need to generate a zero length ws rx that can be understood
+	 * as the message completion.
+	 */
+
+	if (!ebuf.len &&		      /* zero-length inflation output */
+	    !n &&		   /* nothing left to drain from the inflator */
+	    wsi->count_act_ext &&			  /* we are using pmd */
+	    old_packet_length &&	    /* we gave the inflator new input */
+	    !wsi->ws->rx_packet_length &&   /* raw ws packet payload all gone */
+	    wsi->ws->final &&		    /* the raw ws packet is a FIN guy */
+	    wsi->protocol->callback &&
+	    !wsi->wsistate_pre_close) {
+
+		if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+						LWS_CALLBACK_RECEIVE,
+						wsi->user_space, NULL, 0))
+			return -1;
+
+		return avail;
+	}
+#endif
+
+	if (!ebuf.len)
+		return avail;
+
+	if (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	    n &&
+#endif
+	    ebuf.len)
+		/* extension had more... main loop will come back */
+		lws_add_wsi_to_draining_ext_list(wsi);
+	else
+		lws_remove_wsi_from_draining_ext_list(wsi);
+
+	if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+		if (lws_check_utf8(&wsi->ws->utf8,
+				   (unsigned char *)ebuf.token, ebuf.len)) {
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+					 (uint8_t *)"bad utf8", 8);
+			goto utf8_fail;
+		}
+
+		/* we are ending partway through utf-8 character? */
+		if (!wsi->ws->rx_packet_length && wsi->ws->final &&
+		    wsi->ws->utf8 && !n) {
+			lwsl_info("FINAL utf8 error\n");
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+					 (uint8_t *)"partial utf8", 12);
+
+utf8_fail:
+			lwsl_info("utf8 error\n");
+			lwsl_hexdump_info(ebuf.token, ebuf.len);
+
+			return -1;
+		}
+	}
+
+	if (wsi->protocol->callback && !wsi->wsistate_pre_close)
+		if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+						LWS_CALLBACK_RECEIVE,
+						wsi->user_space,
+						ebuf.token, ebuf.len))
+			return -1;
+
+	wsi->ws->first_fragment = 0;
+
+	lwsl_info("%s: input used %d, output %d, rem len %d, rx_draining_ext %d\n",
+		  __func__, avail, ebuf.len, (int)len, wsi->ws->rx_draining_ext);
+
+	return avail; /* how much we used from the input */
+}
+
+
 int
-lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
+lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len)
 {
-	int m, draining_flow = 0;
+	int m, bulk = 0;
 
-	if (lws_buflist_next_segment_len(&wsi->buflist, NULL))
-		draining_flow = 1;
+	lwsl_debug("%s: received %d byte packet\n", __func__, (int)len);
 
-	lwsl_parser("%s: received %d byte packet\n", __func__, (int)len);
+	//lwsl_hexdump_notice(*buf, len);
 
 	/* let the rx protocol state machine have as much as it needs */
 
@@ -560,51 +759,73 @@ lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
 		 * we were accepting input but now we stopped doing so
 		 */
 		if (wsi->rxflow_bitmap) {
+			lwsl_info("%s: doing rxflow\n", __func__);
 			lws_rxflow_cache(wsi, *buf, 0, (int)len);
 			lwsl_parser("%s: cached %ld\n", __func__, (long)len);
-			buf += len; /* stashing it is taking care of it */
+			*buf += len; /* stashing it is taking care of it */
 			return 1;
 		}
 
 		if (wsi->ws->rx_draining_ext) {
-			m = lws_ws_rx_sm(wsi, 0);
+			lwsl_debug("%s: draining rx ext\n", __func__);
+			m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
 			if (m < 0)
 				return -1;
 			continue;
 		}
 
 		/* 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 (draining_flow &&
-			    !lws_buflist_use_segment(&wsi->buflist, m))
-				lws_dll_lws_remove(&wsi->dll_buflist);
-		}
+		while (wsi->lws_rx_parse_state == LWS_RXPS_WS_FRAME_PAYLOAD &&
+				(wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+				 wsi->ws->opcode == LWSWSOPC_BINARY_FRAME ||
+				 wsi->ws->opcode == LWSWSOPC_CONTINUATION) &&
+		       len) {
+			uint8_t *bin = *buf;
 
-		/* process the byte */
-		m = lws_ws_rx_sm(wsi, *(*buf)++);
-		if (m < 0)
-			return -1;
-		len--;
+			bulk = 1;
+			m = lws_ws_frame_rest_is_payload(wsi, buf, len);
+			assert((int)lws_ptr_diff(*buf, bin) <= (int)len);
+			len -= lws_ptr_diff(*buf, bin);
+
+			if (!m) {
 
-		/* account for what we're using in rxflow buffer */
-		if (draining_flow &&
-		    !lws_buflist_use_segment(&wsi->buflist, 1)) {
-				lws_dll_lws_remove(&wsi->dll_buflist);
+				break;
+			}
+			if (m < 0) {
+				lwsl_info("%s: rest_is_payload bailed\n",
+					  __func__);
+				return -1;
+			}
+		}
+
+		if (!bulk) {
+			/* process the byte */
+			m = lws_ws_rx_sm(wsi, 0, *(*buf)++);
+			len--;
+		} else {
+			/*
+			 * We already handled this byte in bulk, just deal
+			 * with the ramifications
+			 */
+			lwsl_debug("%s: coming out of bulk with len %d, "
+				   "wsi->ws->rx_draining_ext %d\n",
+				   __func__, (int)len,
+				   wsi->ws->rx_draining_ext);
+			m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR |
+					 ALREADY_PROCESSED_NO_CB, 0);
+		}
 
-			lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi);
+		if (m < 0) {
+			lwsl_info("%s: lws_ws_rx_sm bailed %d\n", __func__,
+				  bulk);
 
-			/* 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 */
+			return -1;
 		}
+
+		bulk = 0;
 	}
 
-	lwsl_parser("%s: exit with %d unused\n", __func__, (int)len);
+	lwsl_debug("%s: exit with %d unused\n", __func__, (int)len);
 
 	return 0;
 }
diff --git a/lib/service.c b/lib/service.c
index a2628c74b5930de4272e36bd81eb22cd4f644958..3c44ef29a9e74744ed00b30694877f6928b2aa32 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -41,6 +41,8 @@ lws_callback_as_writeable(struct lws *wsi)
 	}
 #endif
 
+	assert(!(lwsi_role_ws(wsi) && wsi->ws->tx_draining_ext));
+
 	n = wsi->role_ops->writeable_cb[lwsi_role_server(wsi)];
 
 	m = user_callback_handle_rxflow(wsi->protocol->callback,
@@ -56,7 +58,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
 	volatile struct lws *vwsi = (volatile struct lws *)wsi;
 	int n;
 
-	lwsl_info("%s: %p\n", __func__, wsi);
+	//lwsl_notice("%s: %p\n", __func__, wsi);
 
 	vwsi->leave_pollout_active = 0;
 	vwsi->handling_pollout = 1;
@@ -220,13 +222,6 @@ __lws_service_timeout_check(struct lws *wsi, time_t sec)
 
 	(void)n;
 
-	/*
-	 * if extensions want in on it (eg, we are a mux parent)
-	 * give them a chance to service child timeouts
-	 */
-	if (lws_ext_cb_active(wsi, LWS_EXT_CB_1HZ, NULL, sec) < 0)
-		return 0;
-
 	/*
 	 * if we went beyond the allowed time, kill the
 	 * connection
@@ -358,7 +353,7 @@ int
 lws_buflist_aware_read(struct lws_context_per_thread *pt, struct lws *wsi,
 		       struct lws_tokens *ebuf)
 {
-	ebuf->len = lws_buflist_next_segment_len(&wsi->buflist,
+	ebuf->len = (int)lws_buflist_next_segment_len(&wsi->buflist,
 						 (uint8_t **)&ebuf->token);
 	if (!ebuf->len) {
 		ebuf->token = (char *)pt->serv_buf;
@@ -388,7 +383,7 @@ lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used,
 		if (m)
 			return 0;
 
-		lwsl_notice("%s: removed %p from dll_buflist\n", __func__, wsi);
+		lwsl_info("%s: removed %p from dll_buflist\n", __func__, wsi);
 		lws_dll_lws_remove(&wsi->dll_buflist);
 
 		return 0;
diff --git a/minimal-examples/ws-client/README.md b/minimal-examples/ws-client/README.md
index e769c01e608762c1f64cde0c513064837dfd615f..ede1b24b78dccc81263bdb3b9ec95d87dad2c50e 100644
--- a/minimal-examples/ws-client/README.md
+++ b/minimal-examples/ws-client/README.md
@@ -1,5 +1,6 @@
 |name|demonstrates|
 ---|---
+minimal-ws-client-echo|Simple client that connects to a ws server and echos anything the server sends
 minimal-ws-client-pmd-bulk|Client that sends bulk multifragment data to the minimal-ws-server-pmd-bulk example
 minimal-ws-client-rx|Connects to the dumb-increment-protocol wss server at https://libwebsockets.org and demonstrates receiving ws data
 minimal-ws-client-tx|Connects to the minimal-ws-broker example as a publisher, demonstrating sending ws data
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-echo/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8a8cc747b1c3444041fa4cb726472ff0fae419ec
--- /dev/null
+++ b/minimal-examples/ws-client/minimal-ws-client-echo/CMakeLists.txt
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-echo)
+set(SRCS minimal-ws-client-echo.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+	if (DEFINED ${reqconfig})
+	if (${reqconfig})
+		set (rq 1)
+	else()
+		set (rq 0)
+	endif()
+	else()
+		set(rq 0)
+	endif()
+
+	if (${_val} EQUAL ${rq})
+		set(SAME 1)
+	else()
+		set(SAME 0)
+	endif()
+
+	if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+		if (${_val})
+			message("${SAMP}: skipping as lws being built without ${reqconfig}")
+		else()
+			message("${SAMP}: skipping as lws built with ${reqconfig}")
+		endif()
+		set(${result} 0)
+	else()
+		if (LWS_WITH_MINIMAL_EXAMPLES)
+			set(MET ${SAME})
+		else()
+			CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+			if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+				set(HAS_${reqconfig} 0)
+			else()
+				set(HAS_${reqconfig} 1)
+			endif()
+			if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+				set(MET 1)
+			else()
+				set(MET 0)
+			endif()
+		endif()
+		if (NOT MET)
+			if (${_val})
+				message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+			else()
+				message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+			endif()
+		endif()
+	
+	endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+	add_executable(${SAMP} ${SRCS})
+
+	if (websockets_shared)
+		target_link_libraries(${SAMP} websockets_shared)
+		add_dependencies(${SAMP} websockets_shared)
+	else()
+		target_link_libraries(${SAMP} websockets)
+	endif()
+endif()
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/README.md b/minimal-examples/ws-client/minimal-ws-client-echo/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8417804bedbb1e04a07f28723bd7ad9e398bd52b
--- /dev/null
+++ b/minimal-examples/ws-client/minimal-ws-client-echo/README.md
@@ -0,0 +1,35 @@
+# lws minimal ws client + permessage-deflate echo
+
+This example opens a ws client connection to localhost:7681 and
+echoes back anything that comes from the server.
+
+You can use it for testing lws against Autobahn.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-p port|Port to connect to
+-u url|URL path part to connect to
+-o|Finish after one connection
+
+```
+ $ ./lws-minimal-ws-client-echo
+[2018/04/22 20:03:50:2343] USER: LWS minimal ws client echo + permessage-deflate + multifragment bulk message
+[2018/04/22 20:03:50:2344] USER:    lws-minimal-ws-client-echo [-n (no exts)] [-u url] [-o (once)]
+[2018/04/22 20:03:50:2344] USER: options 0
+[2018/04/22 20:03:50:2345] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2018/04/22 20:03:51:2356] USER: connecting to localhost:9001//runCase?case=362&agent=libwebsockets
+[2018/04/22 20:03:51:2385] NOTICE: checking client ext permessage-deflate
+[2018/04/22 20:03:51:2386] NOTICE: instantiating client ext permessage-deflate
+[2018/04/22 20:03:51:2386] USER: LWS_CALLBACK_CLIENT_ESTABLISHED
+...
+```
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c b/minimal-examples/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c
new file mode 100644
index 0000000000000000000000000000000000000000..cb8ce0524c8434d88e88f6616304b406cf174119
--- /dev/null
+++ b/minimal-examples/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c
@@ -0,0 +1,143 @@
+/*
+ * lws-minimal-ws-client-echo
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws client that echoes back what it was sent, in a
+ * way compatible with autobahn -m fuzzingserver
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_client_echo.c"
+
+static struct lws_protocols protocols[] = {
+	LWS_PLUGIN_PROTOCOL_MINIMAL_CLIENT_ECHO,
+	{ NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted, port = 7681, options = 0;
+static const char *url = "/", *ads = "localhost";
+
+/* pass pointers to shared vars to the protocol */
+
+static const struct lws_protocol_vhost_options pvo_ads = {
+	NULL,
+	NULL,
+	"ads",		/* pvo name */
+	(void *)&ads	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_url = {
+	&pvo_ads,
+	NULL,
+	"url",		/* pvo name */
+	(void *)&url	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_options = {
+	&pvo_url,
+	NULL,
+	"options",		/* pvo name */
+	(void *)&options	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_port = {
+	&pvo_options,
+	NULL,
+	"port",		/* pvo name */
+	(void *)&port	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_interrupted = {
+	&pvo_port,
+	NULL,
+	"interrupted",		/* pvo name */
+	(void *)&interrupted	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+	NULL,		/* "next" pvo linked-list */
+	&pvo_interrupted,	/* "child" pvo linked-list */
+	"lws-minimal-client-echo",	/* protocol name we belong to on this vhost */
+	""		/* ignored */
+};
+static const struct lws_extension extensions[] = {
+	{
+		"permessage-deflate",
+		lws_extension_callback_pm_deflate,
+		"permessage-deflate"
+		 "; client_no_context_takeover"
+		 "; client_max_window_bits"
+	},
+	{ NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+	interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+	struct lws_context_creation_info info;
+	struct lws_context *context;
+	const char *p;
+	int n = 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);
+
+	if ((p = lws_cmdline_option(argc, argv, "-d")))
+		logs = atoi(p);
+
+	lws_set_log_level(logs, NULL);
+	lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n");
+	lwsl_user("   lws-minimal-ws-client-echo [-n (no exts)] [-u url] [-p port] [-o (once)]\n");
+
+	if ((p = lws_cmdline_option(argc, argv, "-u")))
+		url = p;
+
+	if ((p = lws_cmdline_option(argc, argv, "-p")))
+		port = atoi(p);
+
+	if (lws_cmdline_option(argc, argv, "-o"))
+		options |= 1;
+
+	lwsl_user("options %d\n", options);
+
+	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+	info.port = CONTEXT_PORT_NO_LISTEN;
+	info.protocols = protocols;
+	info.pvo = &pvo;
+	if (!lws_cmdline_option(argc, argv, "-n"))
+		info.extensions = extensions;
+	info.pt_serv_buf_size = 32 * 1024;
+	info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;
+
+	context = lws_create_context(&info);
+	if (!context) {
+		lwsl_err("lws init failed\n");
+		return 1;
+	}
+
+	while (n >= 0 && !interrupted)
+		n = lws_service(context, 1000);
+
+	lws_context_destroy(context);
+
+	lwsl_user("Completed %s\n", interrupted == 2 ? "OK" : "failed");
+
+	return interrupted != 2;
+}
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/protocol_lws_minimal_client_echo.c b/minimal-examples/ws-client/minimal-ws-client-echo/protocol_lws_minimal_client_echo.c
new file mode 100644
index 0000000000000000000000000000000000000000..cdee75c4fc49278c3cbb0c70d5c5d144b10ff2e9
--- /dev/null
+++ b/minimal-examples/ws-client/minimal-ws-client-echo/protocol_lws_minimal_client_echo.c
@@ -0,0 +1,309 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-client-echo"
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The protocol shows how to send and receive bulk messages over a ws connection
+ * that optionally may have the permessage-deflate extension negotiated on it.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+#define RING_DEPTH 1024
+
+/* one of these created for each message */
+
+struct msg {
+	void *payload; /* is malloc'd */
+	size_t len;
+	char binary;
+	char first;
+	char final;
+};
+
+struct per_session_data__minimal_client_echo {
+	struct lws_ring *ring;
+	uint32_t tail;
+	uint8_t completed:1;
+};
+
+struct vhd_minimal_client_echo {
+	struct lws_context *context;
+	struct lws_vhost *vhost;
+	struct lws *client_wsi;
+
+	int *interrupted;
+	int *options;
+	const char **url;
+	const char **ads;
+	int *port;
+};
+
+static int
+connect_client(struct vhd_minimal_client_echo *vhd)
+{
+	struct lws_client_connect_info i;
+	char host[128];
+
+	lws_snprintf(host, sizeof(host), "%s:%u", *vhd->ads, *vhd->port);
+
+	memset(&i, 0, sizeof(i));
+
+	i.context = vhd->context;
+	i.port = *vhd->port;
+	i.address = *vhd->ads;
+	i.path = *vhd->url;
+	i.host = host;
+	i.origin = host;
+	i.ssl_connection = 0;
+	i.vhost = vhd->vhost;
+	//i.protocol = ;
+	i.pwsi = &vhd->client_wsi;
+
+	lwsl_user("connecting to %s:%d/%s\n", i.address, i.port, i.path);
+
+	return !lws_client_connect_via_info(&i);
+}
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+	struct msg *msg = _msg;
+
+	free(msg->payload);
+	msg->payload = NULL;
+	msg->len = 0;
+}
+
+static void
+schedule_callback(struct lws *wsi, int reason, int secs)
+{
+	lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+		lws_get_protocol(wsi), reason, secs);
+}
+
+static int
+callback_minimal_client_echo(struct lws *wsi, enum lws_callback_reasons reason,
+			  void *user, void *in, size_t len)
+{
+	struct per_session_data__minimal_client_echo *pss =
+			(struct per_session_data__minimal_client_echo *)user;
+	struct vhd_minimal_client_echo *vhd = (struct vhd_minimal_client_echo *)
+			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+				lws_get_protocol(wsi));
+	const struct msg *pmsg;
+	struct msg amsg;
+	int n, m, flags;
+
+	switch (reason) {
+
+	case LWS_CALLBACK_PROTOCOL_INIT:
+		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+				lws_get_protocol(wsi),
+				sizeof(struct vhd_minimal_client_echo));
+		if (!vhd)
+			return -1;
+
+		vhd->context = lws_get_context(wsi);
+		vhd->vhost = lws_get_vhost(wsi);
+
+		/* get the pointer to "interrupted" we were passed in pvo */
+		vhd->interrupted = (int *)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"interrupted")->value;
+		vhd->port = (int *)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"port")->value;
+		vhd->options = (int *)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"options")->value;
+		vhd->ads = (const char **)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"ads")->value;
+		vhd->url = (const char **)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"url")->value;
+
+		if (connect_client(vhd))
+			schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+		break;
+
+	case LWS_CALLBACK_CLIENT_ESTABLISHED:
+		lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED\n");
+		pss->ring = lws_ring_create(sizeof(struct msg), RING_DEPTH,
+					    __minimal_destroy_message);
+		if (!pss->ring)
+			return 1;
+		pss->tail = 0;
+		break;
+
+	case LWS_CALLBACK_CLIENT_WRITEABLE:
+
+		lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE\n");
+		do {
+			pmsg = lws_ring_get_element(pss->ring, &pss->tail);
+			if (!pmsg) {
+				lwsl_user(" (nothing in ring)\n");
+				break;
+			}
+
+			flags = lws_write_ws_flags(
+				    pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+				    pmsg->first, pmsg->final);
+
+			/* notice we allowed for LWS_PRE in the payload already */
+			m = lws_write(wsi, pmsg->payload + LWS_PRE, pmsg->len, flags);
+			if (m < (int)pmsg->len) {
+				lwsl_err("ERROR %d writing to ws socket\n", m);
+				return -1;
+			}
+
+			lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
+					m, flags, pmsg->first, pmsg->final);
+
+			lws_ring_consume_single_tail(pss->ring, &pss->tail, 1);
+
+		} while (lws_ring_get_element(pss->ring, &pss->tail) &&
+			 !lws_send_pipe_choked(wsi));
+
+		/* more to do for us? */
+		if (lws_ring_get_element(pss->ring, &pss->tail))
+			/* come back as soon as we can write more */
+			lws_callback_on_writable(wsi);
+
+		if ((int)lws_ring_get_count_free_elements(pss->ring) > RING_DEPTH - 5)
+			lws_rx_flow_control(wsi, 1);
+
+		if ((*vhd->options & 1) && pmsg && pmsg->final)
+			pss->completed = 1;
+
+		break;
+
+	case LWS_CALLBACK_CLIENT_RECEIVE:
+
+		lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d (rpp %5d, first %d, last %d, bin %d)\n",
+			(int)len, (int)lws_remaining_packet_payload(wsi),
+			lws_is_first_fragment(wsi),
+			lws_is_final_fragment(wsi),
+			lws_frame_is_binary(wsi));
+
+		// lwsl_hexdump_notice(in, len);
+
+		amsg.first = lws_is_first_fragment(wsi);
+		amsg.final = lws_is_final_fragment(wsi);
+		amsg.binary = lws_frame_is_binary(wsi);
+		n = (int)lws_ring_get_count_free_elements(pss->ring);
+		if (!n) {
+			lwsl_user("dropping!\n");
+			break;
+		}
+
+		amsg.len = len;
+		/* notice we over-allocate by LWS_PRE */
+		amsg.payload = malloc(LWS_PRE + len);
+		if (!amsg.payload) {
+			lwsl_user("OOM: dropping\n");
+			break;
+		}
+
+		memcpy((char *)amsg.payload + LWS_PRE, in, len);
+		if (!lws_ring_insert(pss->ring, &amsg, 1)) {
+			__minimal_destroy_message(&amsg);
+			lwsl_user("dropping!\n");
+			break;
+		}
+		lws_callback_on_writable(wsi);
+
+		if (n < 3)
+			lws_rx_flow_control(wsi, 0);
+		break;
+
+	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+			 in ? (char *)in : "(null)");
+		vhd->client_wsi = NULL;
+		schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+		if (*vhd->options & 1) {
+			if (!*vhd->interrupted)
+				*vhd->interrupted = 1;
+			lws_cancel_service(lws_get_context(wsi));
+		}
+		break;
+
+	case LWS_CALLBACK_CLIENT_CLOSED:
+		lwsl_user("LWS_CALLBACK_CLIENT_CLOSED\n");
+		lws_ring_destroy(pss->ring);
+		vhd->client_wsi = NULL;
+		// schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+		//if (*vhd->options & 1) {
+			if (!*vhd->interrupted)
+				*vhd->interrupted = 1 + pss->completed;
+			lws_cancel_service(lws_get_context(wsi));
+	//	}
+		break;
+
+	/* rate-limited client connect retries */
+
+	case LWS_CALLBACK_USER:
+		lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
+		if (connect_client(vhd))
+			schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_CLIENT_ECHO \
+	{ \
+		"lws-minimal-client-echo", \
+		callback_minimal_client_echo, \
+		sizeof(struct per_session_data__minimal_client_echo), \
+		1024, \
+		0, NULL, 0 \
+	}
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+	LWS_PLUGIN_PROTOCOL_MINIMAL_client_echo
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_client_echo(struct lws_context *context,
+			       struct lws_plugin_capability *c)
+{
+	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+			 c->api_magic);
+		return 1;
+	}
+
+	c->protocols = protocols;
+	c->count_protocols = ARRAY_SIZE(protocols);
+	c->extensions = NULL;
+	c->count_extensions = 0;
+
+	return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_client_echo(struct lws_context *context)
+{
+	return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/README.md b/minimal-examples/ws-server/README.md
index 8781b9d9c25bef109d527c50ee63c95bb1b1f953..eb33c7a3c51308cf0aadd9f503a6721300ed6708 100644
--- a/minimal-examples/ws-server/README.md
+++ b/minimal-examples/ws-server/README.md
@@ -1,6 +1,7 @@
 |Example|Demonstrates|
 ---|---
 minimal-ws-broker|Simple ws server with a publish / broker / subscribe architecture
+minimal-ws-server-echo|Simple ws server that listens and echos back anything clients send
 minimal-ws-server-pmd-bulk|Simple ws server showing how to pass bulk data with permessage-deflate
 minimal-ws-server-pmd|Simple ws server with permessage-deflate support
 minimal-ws-server-ring|Like minimal-ws-server but holds the chat in a multi-tail ringbuffer
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-echo/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0ea8692cd4afe774c4b57b91d170d1fb14c0c83e
--- /dev/null
+++ b/minimal-examples/ws-server/minimal-ws-server-echo/CMakeLists.txt
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-echo)
+set(SRCS minimal-ws-server-echo.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+	if (DEFINED ${reqconfig})
+	if (${reqconfig})
+		set (rq 1)
+	else()
+		set (rq 0)
+	endif()
+	else()
+		set(rq 0)
+	endif()
+
+	if (${_val} EQUAL ${rq})
+		set(SAME 1)
+	else()
+		set(SAME 0)
+	endif()
+
+	if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+		if (${_val})
+			message("${SAMP}: skipping as lws being built without ${reqconfig}")
+		else()
+			message("${SAMP}: skipping as lws built with ${reqconfig}")
+		endif()
+		set(${result} 0)
+	else()
+		if (LWS_WITH_MINIMAL_EXAMPLES)
+			set(MET ${SAME})
+		else()
+			CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+			if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+				set(HAS_${reqconfig} 0)
+			else()
+				set(HAS_${reqconfig} 1)
+			endif()
+			if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+				set(MET 1)
+			else()
+				set(MET 0)
+			endif()
+		endif()
+		if (NOT MET)
+			if (${_val})
+				message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+			else()
+				message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+			endif()
+		endif()
+	
+	endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+	add_executable(${SAMP} ${SRCS})
+
+	if (websockets_shared)
+		target_link_libraries(${SAMP} websockets_shared)
+		add_dependencies(${SAMP} websockets_shared)
+	else()
+		target_link_libraries(${SAMP} websockets)
+	endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/README.md b/minimal-examples/ws-server/minimal-ws-server-echo/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bf65c6ea0f544a966d90735e1c651e1957928f06
--- /dev/null
+++ b/minimal-examples/ws-server/minimal-ws-server-echo/README.md
@@ -0,0 +1,30 @@
+# lws minimal ws server + permessage-deflate echo
+
+This example serves no-protocl-name ws on localhost:7681
+and echoes back anything that comes from the client.
+
+You can use it for testing lws against Autobahn (use the
+-p option to tell it to listen on 9001 for that)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-p port|Port to connect to
+-u url|URL path part to connect to
+-o|Finish after one connection
+
+```
+ $ ./lws-minimal-ws-server-echo
+[2018/04/24 10:29:34:6212] USER: LWS minimal ws server echo + permessage-deflate + multifragment bulk message
+[2018/04/24 10:29:34:6213] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+...
+```
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c b/minimal-examples/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c
new file mode 100644
index 0000000000000000000000000000000000000000..9e4ed4554cce1134b330e259339b36e289f30472
--- /dev/null
+++ b/minimal-examples/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c
@@ -0,0 +1,117 @@
+/*
+ * lws-minimal-ws-server-echo
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws server that echoes back what it was sent, in a way
+ * compatible with autobahn -m fuzzingclient
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_server_echo.c"
+
+static struct lws_protocols protocols[] = {
+	LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO,
+	{ NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted, port = 7681, options;
+
+/* pass pointers to shared vars to the protocol */
+
+static const struct lws_protocol_vhost_options pvo_options = {
+	NULL,
+	NULL,
+	"options",		/* pvo name */
+	(void *)&options	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_interrupted = {
+	&pvo_options,
+	NULL,
+	"interrupted",		/* pvo name */
+	(void *)&interrupted	/* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+	NULL,				/* "next" pvo linked-list */
+	&pvo_interrupted,		/* "child" pvo linked-list */
+	"lws-minimal-server-echo",	/* protocol name we belong to on this vhost */
+	""				/* ignored */
+};
+static const struct lws_extension extensions[] = {
+	{
+		"permessage-deflate",
+		lws_extension_callback_pm_deflate,
+		"permessage-deflate"
+		 "; client_no_context_takeover"
+		 "; client_max_window_bits"
+	},
+	{ NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+	interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+	struct lws_context_creation_info info;
+	struct lws_context *context;
+	const char *p;
+	int n = 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);
+
+	if ((p = lws_cmdline_option(argc, argv, "-d")))
+		logs = atoi(p);
+
+	lws_set_log_level(logs, NULL);
+	lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n");
+	lwsl_user("   lws-minimal-ws-client-echo [-n (no exts)] [-p port] [-o (once)]\n");
+
+
+	if ((p = lws_cmdline_option(argc, argv, "-p")))
+		port = atoi(p);
+
+	if (lws_cmdline_option(argc, argv, "-o"))
+		options |= 1;
+
+	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+	info.port = port;
+	info.protocols = protocols;
+	info.pvo = &pvo;
+	if (!lws_cmdline_option(argc, argv, "-n"))
+		info.extensions = extensions;
+	info.pt_serv_buf_size = 32 * 1024;
+	info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;
+
+	context = lws_create_context(&info);
+	if (!context) {
+		lwsl_err("lws init failed\n");
+		return 1;
+	}
+
+	while (n >= 0 && !interrupted)
+		n = lws_service(context, 1000);
+
+	lws_context_destroy(context);
+
+	lwsl_user("Completed %s\n", interrupted == 2 ? "OK" : "failed");
+
+	return interrupted != 2;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/protocol_lws_minimal_server_echo.c b/minimal-examples/ws-server/minimal-ws-server-echo/protocol_lws_minimal_server_echo.c
new file mode 100644
index 0000000000000000000000000000000000000000..7708dfb4d2b8d7fd12d5759a0453e1378a4f15b4
--- /dev/null
+++ b/minimal-examples/ws-server/minimal-ws-server-echo/protocol_lws_minimal_server_echo.c
@@ -0,0 +1,258 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-server-echo"
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The protocol shows how to send and receive bulk messages over a ws connection
+ * that optionally may have the permessage-deflate extension negotiated on it.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+#define RING_DEPTH 4096
+
+/* one of these created for each message */
+
+struct msg {
+	void *payload; /* is malloc'd */
+	size_t len;
+	char binary;
+	char first;
+	char final;
+};
+
+struct per_session_data__minimal_server_echo {
+	struct lws_ring *ring;
+	uint32_t msglen;
+	uint32_t tail;
+	uint8_t completed:1;
+	uint8_t flow_controlled:1;
+};
+
+struct vhd_minimal_server_echo {
+	struct lws_context *context;
+	struct lws_vhost *vhost;
+
+	int *interrupted;
+	int *options;
+};
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+	struct msg *msg = _msg;
+
+	free(msg->payload);
+	msg->payload = NULL;
+	msg->len = 0;
+}
+#include <assert.h>
+static int
+callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason,
+			  void *user, void *in, size_t len)
+{
+	struct per_session_data__minimal_server_echo *pss =
+			(struct per_session_data__minimal_server_echo *)user;
+	struct vhd_minimal_server_echo *vhd = (struct vhd_minimal_server_echo *)
+			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+				lws_get_protocol(wsi));
+	const struct msg *pmsg;
+	struct msg amsg;
+	int n, m, flags;
+
+	switch (reason) {
+
+	case LWS_CALLBACK_PROTOCOL_INIT:
+		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+				lws_get_protocol(wsi),
+				sizeof(struct vhd_minimal_server_echo));
+		if (!vhd)
+			return -1;
+
+		vhd->context = lws_get_context(wsi);
+		vhd->vhost = lws_get_vhost(wsi);
+
+		/* get the pointers we were passed in pvo */
+
+		vhd->interrupted = (int *)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"interrupted")->value;
+		vhd->options = (int *)lws_pvo_search(
+			(const struct lws_protocol_vhost_options *)in,
+			"options")->value;
+		break;
+
+	case LWS_CALLBACK_ESTABLISHED:
+		lwsl_user("LWS_CALLBACK_ESTABLISHED\n");
+		pss->ring = lws_ring_create(sizeof(struct msg), RING_DEPTH,
+					    __minimal_destroy_message);
+		if (!pss->ring)
+			return 1;
+		pss->tail = 0;
+		break;
+
+	case LWS_CALLBACK_SERVER_WRITEABLE:
+
+		lwsl_user("LWS_CALLBACK_SERVER_WRITEABLE\n");
+		do {
+			pmsg = lws_ring_get_element(pss->ring, &pss->tail);
+			if (!pmsg) {
+				lwsl_user(" (nothing in ring)\n");
+				break;
+			}
+
+			flags = lws_write_ws_flags(
+				    pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+				    pmsg->first, pmsg->final);
+
+			/* notice we allowed for LWS_PRE in the payload already */
+			m = lws_write(wsi, pmsg->payload + LWS_PRE, pmsg->len, flags);
+			if (m < (int)pmsg->len) {
+				lwsl_err("ERROR %d writing to ws socket\n", m);
+				return -1;
+			}
+
+			lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
+					m, flags, pmsg->first, pmsg->final);
+
+			lws_ring_consume_single_tail(pss->ring, &pss->tail, 1);
+
+		} while (lws_ring_get_element(pss->ring, &pss->tail) &&
+			 !lws_send_pipe_choked(wsi));
+
+		/* more to do for us? */
+		if (lws_ring_get_element(pss->ring, &pss->tail))
+			/* come back as soon as we can write more */
+			lws_callback_on_writable(wsi);
+
+		if (pss->flow_controlled &&
+		    (int)lws_ring_get_count_free_elements(pss->ring) > RING_DEPTH - 5) {
+			lws_rx_flow_control(wsi, 1);
+			pss->flow_controlled = 0;
+		}
+
+		if ((*vhd->options & 1) && pmsg && pmsg->final)
+			pss->completed = 1;
+
+		break;
+
+	case LWS_CALLBACK_RECEIVE:
+
+		lwsl_user("LWS_CALLBACK_RECEIVE: %4d (rpp %5d, first %d, "
+			  "last %d, bin %d, msglen %d (+ %d = %d))\n",
+			  (int)len, (int)lws_remaining_packet_payload(wsi),
+			  lws_is_first_fragment(wsi),
+			  lws_is_final_fragment(wsi),
+			  lws_frame_is_binary(wsi), pss->msglen, (int)len,
+			  (int)pss->msglen + (int)len);
+
+		if (len) {
+			;
+			//puts((const char *)in);
+			//lwsl_hexdump_notice(in, len);
+		}
+
+		amsg.first = lws_is_first_fragment(wsi);
+		amsg.final = lws_is_final_fragment(wsi);
+		amsg.binary = lws_frame_is_binary(wsi);
+		n = (int)lws_ring_get_count_free_elements(pss->ring);
+		if (!n) {
+			lwsl_user("dropping!\n");
+			break;
+		}
+
+		if (amsg.final)
+			pss->msglen = 0;
+		else
+			pss->msglen += len;
+
+		amsg.len = len;
+		/* notice we over-allocate by LWS_PRE */
+		amsg.payload = malloc(LWS_PRE + len);
+		if (!amsg.payload) {
+			lwsl_user("OOM: dropping\n");
+			break;
+		}
+
+		memcpy((char *)amsg.payload + LWS_PRE, in, len);
+		if (!lws_ring_insert(pss->ring, &amsg, 1)) {
+			__minimal_destroy_message(&amsg);
+			lwsl_user("dropping!\n");
+			break;
+		}
+		lws_callback_on_writable(wsi);
+
+		if (n < 3 && !pss->flow_controlled) {
+			pss->flow_controlled = 1;
+			lws_rx_flow_control(wsi, 0);
+		}
+		break;
+
+	case LWS_CALLBACK_CLOSED:
+		lwsl_user("LWS_CALLBACK_CLOSED\n");
+		lws_ring_destroy(pss->ring);
+
+		if (*vhd->options & 1) {
+			if (!*vhd->interrupted)
+				*vhd->interrupted = 1 + pss->completed;
+			lws_cancel_service(lws_get_context(wsi));
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO \
+	{ \
+		"lws-minimal-server-echo", \
+		callback_minimal_server_echo, \
+		sizeof(struct per_session_data__minimal_server_echo), \
+		1024, \
+		0, NULL, 0 \
+	}
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+	LWS_PLUGIN_PROTOCOL_MINIMAL_server_echo
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_server_echo(struct lws_context *context,
+			       struct lws_plugin_capability *c)
+{
+	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+			 c->api_magic);
+		return 1;
+	}
+
+	c->protocols = protocols;
+	c->count_protocols = ARRAY_SIZE(protocols);
+	c->extensions = NULL;
+	c->count_extensions = 0;
+
+	return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_server_echo(struct lws_context *context)
+{
+	return 0;
+}
+#endif
diff --git a/scripts/autobahn-test.sh b/scripts/autobahn-test.sh
index 91b9171446bdfa958f2b93326833263044770954..550c5e744be1f1b85d44909e3302ac337ddce52c 100755
--- a/scripts/autobahn-test.sh
+++ b/scripts/autobahn-test.sh
@@ -1,25 +1,98 @@
-#!/bin/sh
+#!/bin/bash
+#
+# Requires pip install autobahntestsuite
+#
+# you should run this from ./build, after building with
+# cmake .. -DLWS_WITH_MINIMAL_EXAMPLES=1
+#
+# It will use the minimal echo client and server to run
+# autobahn ws tests as both client and server.
 
 set -u
 
+PARALLEL=8
 N=1
 OS=`uname`
 
-for i in '1.1.1' '1.1.2' '1.1.3' '1.1.4' '1.1.5' '1.1.6' '1.1.7' '1.1.8' '1.2.1' '1.2.2' '1.2.3' '1.2.4' '1.2.5' '1.2.6' '1.2.7' '1.2.8' '2.1' '2.2' '2.3' '2.4' '2.5' '2.6' '2.7' '2.8' '2.9' '2.10' '2.11' '3.1' '3.2' '3.3' '3.4' '3.5' '3.6' '3.7' '4.1.1' '4.1.2' '4.1.3' '4.1.4' '4.1.5' '4.2.1' '4.2.2' '4.2.3' '4.2.4' '4.2.5' '5.1' '5.2' '5.3' '5.4' '5.5' '5.6' '5.7' '5.8' '5.9' '5.10' '5.11' '5.12' '5.13' '5.14' '5.15' '5.16' '5.17' '5.18' '5.19' '5.20' '6.1.1' '6.1.2' '6.1.3' '6.2.1' '6.2.2' '6.2.3' '6.2.4' '6.3.1' '6.3.2' '6.4.1' '6.4.2' '6.4.3' '6.4.4' '6.5.1' '6.5.2' '6.5.3' '6.5.4' '6.5.5' '6.6.1' '6.6.2' '6.6.3' '6.6.4' '6.6.5' '6.6.6' '6.6.7' '6.6.8' '6.6.9' '6.6.10' '6.6.11' '6.7.1' '6.7.2' '6.7.3' '6.7.4' '6.8.1' '6.8.2' '6.9.1' '6.9.2' '6.9.3' '6.9.4' '6.10.1' '6.10.2' '6.10.3' '6.11.1' '6.11.2' '6.11.3' '6.11.4' '6.11.5' '6.12.1' '6.12.2' '6.12.3' '6.12.4' '6.12.5' '6.12.6' '6.12.7' '6.12.8' '6.13.1' '6.13.2' '6.13.3' '6.13.4' '6.13.5' '6.14.1' '6.14.2' '6.14.3' '6.14.4' '6.14.5' '6.14.6' '6.14.7' '6.14.8' '6.14.9' '6.14.10' '6.15.1' '6.16.1' '6.16.2' '6.16.3' '6.17.1' '6.17.2' '6.17.3' '6.17.4' '6.17.5' '6.18.1' '6.18.2' '6.18.3' '6.18.4' '6.18.5' '6.19.1' '6.19.2' '6.19.3' '6.19.4' '6.19.5' '6.20.1' '6.20.2' '6.20.3' '6.20.4' '6.20.5' '6.20.6' '6.20.7' '6.21.1' '6.21.2' '6.21.3' '6.21.4' '6.21.5' '6.21.6' '6.21.7' '6.21.8' '6.22.1' '6.22.2' '6.22.3' '6.22.4' '6.22.5' '6.22.6' '6.22.7' '6.22.8' '6.22.9' '6.22.10' '6.22.11' '6.22.12' '6.22.13' '6.22.14' '6.22.15' '6.22.16' '6.22.17' '6.22.18' '6.22.19' '6.22.20' '6.22.21' '6.22.22' '6.22.23' '6.22.24' '6.22.25' '6.22.26' '6.22.27' '6.22.28' '6.22.29' '6.22.30' '6.22.31' '6.22.32' '6.22.33' '6.22.34' '6.23.1' '6.23.2' '6.23.3' '6.23.4' '6.23.5' '6.23.6' '6.23.7' '7.1.1' '7.1.2' '7.1.3' '7.1.4' '7.1.5' '7.1.6' '7.3.1' '7.3.2' '7.3.3' '7.3.4' '7.3.5' '7.3.6' '7.5.1' '7.7.1' '7.7.2' '7.7.3' '7.7.4' '7.7.5' '7.7.6' '7.7.7' '7.7.8' '7.7.9' '7.7.10' '7.7.11' '7.7.12' '7.7.13' '7.9.1' '7.9.2' '7.9.3' '7.9.4' '7.9.5' '7.9.6' '7.9.7' '7.9.8' '7.9.9' '7.9.10' '7.9.11' '7.9.12' '7.9.13' '7.13.1' '7.13.2' '9.1.1' '9.1.2' '9.1.3' '9.1.4' '9.1.5' '9.1.6' '9.2.1' '9.2.2' '9.2.3' '9.2.4' '9.2.5' '9.2.6' '9.3.1' '9.3.2' '9.3.3' '9.3.4' '9.3.5' '9.3.6' '9.3.7' '9.3.8' '9.3.9' '9.4.1' '9.4.2' '9.4.3' '9.4.4' '9.4.5' '9.4.6' '9.4.7' '9.4.8' '9.4.9' '9.5.1' '9.5.2' '9.5.3' '9.5.4' '9.5.5' '9.5.6' '9.6.1' '9.6.2' '9.6.3' '9.6.4' '9.6.5' '9.6.6' '9.7.1' '9.7.2' '9.7.3' '9.7.4' '9.7.5' '9.7.6' '9.8.1' '9.8.2' '9.8.3' '9.8.4' '9.8.5' '9.8.6' '10.1.1' '12.1.1' '12.1.2' '12.1.3' '12.1.4' '12.1.5' '12.1.6' '12.1.7' '12.1.8' '12.1.9' '12.1.10' '12.1.11' '12.1.12' '12.1.13' '12.1.14' '12.1.15' '12.1.16' '12.1.17' '12.1.18' '12.2.1' '12.2.2' '12.2.3' '12.2.4' '12.2.5' '12.2.6' '12.2.7' '12.2.8' '12.2.9' '12.2.10' '12.2.11' '12.2.12' '12.2.13' '12.2.14' '12.2.15' '12.2.16' '12.2.17' '12.2.18' '12.3.1' '12.3.2' '12.3.3' '12.3.4' '12.3.5' '12.3.6' '12.3.7' '12.3.8' '12.3.9' '12.3.10' '12.3.11' '12.3.12' '12.3.13' '12.3.14' '12.3.15' '12.3.16' '12.3.17' '12.3.18' '12.4.1' '12.4.2' '12.4.3' '12.4.4' '12.4.5' '12.4.6' '12.4.7' '12.4.8' '12.4.9' '12.4.10' '12.4.11' '12.4.12' '12.4.13' '12.4.14' '12.4.15' '12.4.16' '12.4.17' '12.4.18' '12.5.1' '12.5.2' '12.5.3' '12.5.4' '12.5.5' '12.5.6' '12.5.7' '12.5.8' '12.5.9' '12.5.10' '12.5.11' '12.5.12' '12.5.13' '12.5.14' '12.5.15' '12.5.16' '12.5.17' '12.5.18' '13.1.1' '13.1.2' '13.1.3' '13.1.4' '13.1.5' '13.1.6' '13.1.7' '13.1.8' '13.1.9' '13.1.10' '13.1.11' '13.1.12' '13.1.13' '13.1.14' '13.1.15' '13.1.16' '13.1.17' '13.1.18' '13.2.1' '13.2.2' '13.2.3' '13.2.4' '13.2.5' '13.2.6' '13.2.7' '13.2.8' '13.2.9' '13.2.10' '13.2.11' '13.2.12' '13.2.13' '13.2.14' '13.2.15' '13.2.16' '13.2.17' '13.2.18' '13.3.1' '13.3.2' '13.3.3' '13.3.4' '13.3.5' '13.3.6' '13.3.7' '13.3.8' '13.3.9' '13.3.10' '13.3.11' '13.3.12' '13.3.13' '13.3.14' '13.3.15' '13.3.16' '13.3.17' '13.3.18' '13.4.1' '13.4.2' '13.4.3' '13.4.4' '13.4.5' '13.4.6' '13.4.7' '13.4.8' '13.4.9' '13.4.10' '13.4.11' '13.4.12' '13.4.13' '13.4.14' '13.4.15' '13.4.16' '13.4.17' '13.4.18' '13.5.1' '13.5.2' '13.5.3' '13.5.4' '13.5.5' '13.5.6' '13.5.7' '13.5.8' '13.5.9' '13.5.10' '13.5.11' '13.5.12' '13.5.13' '13.5.14' '13.5.15' '13.5.16' '13.5.17' '13.5.18' '13.6.1' '13.6.2' '13.6.3' '13.6.4' '13.6.5' '13.6.6' '13.6.7' '13.6.8' '13.6.9' '13.6.10' '13.6.11' '13.6.12' '13.6.13' '13.6.14' '13.6.15' '13.6.16' '13.6.17' '13.6.18' '13.7.1' '13.7.2' '13.7.3' '13.7.4' '13.7.5' '13.7.6' '13.7.7' '13.7.8' '13.7.9' '13.7.10' '13.7.11' '13.7.12' '13.7.13' '13.7.14' '13.7.15' '13.7.16' '13.7.17' '13.7.18' ; do
-	libwebsockets-test-echo --client 127.0.0.1 --port 9001 -u "/runCase?case=$N&agent=libwebsockets" -v -n 1 &
-
-	C=99
-	while [ $C -gt 8 ] ; do
-		if [ $OS=SunOS ] ; then
-			C=`ps -ef | grep libwebsockets-test-echo | wc -l`
-		else
-			C=`ps fax | grep libwebsockets-test-echo | wc -l`
-		fi
-		if [ $C -gt 8 ] ; then
-			sleep 1s
-		fi
-	done
+CLIE=bin/lws-minimal-ws-client-echo
+SERV=bin/lws-minimal-ws-server-echo
 
+RESULT=0
+
+which wstest 2>/dev/null
+if [ $? -ne 0 ]; then
+	echo "wstest is not installed"
+	exit 8
+fi
+
+killall wstest 2>/dev/null
+
+#
+# 2.10 / 2.11:      There is no requirement to handle multiple PING / PONG
+#                   in flight in RFC6455.  lws doesn't waste memory on it
+#                   since it is useless.
+#
+# 12.4.* / 12.5.*:  Autobahn has been broken for these tests since Aug 2017
+#                   https://github.com/crossbario/autobahn-testsuite/issues/71
+
+
+cat << EOF >fuzzingserver.json
+{
+   "url": "ws://127.0.0.1:9001",
+   "outdir": "./reports/clients",
+   "cases": ["*"],
+   "exclude-cases": [ "2.10", "2.11", "12.4.*", "12.5.*"],
+   "exclude-agent-cases": {}
+}
+EOF
+
+cat << EOF >fuzzingclient.json
+{ 
+   "outdir": "./reports/servers",
+   "servers": [
+      {
+         "url": "ws://127.0.0.1:9001"
+      }
+   ],
+   "cases": ["*"],
+   "exclude-cases": ["2.10", "2.11", "12.4.*", "12.5.*" ],
+   "exclude-agent-cases": {}
+}
+EOF
+
+
+PYTHONHASHSEED=0 wstest -m fuzzingserver &
+Q=$!
+sleep 2s
+ps -p $Q > /dev/null
+if [ $? -ne 0 ] ; then
+	echo "Problem with autobahn wstest install"
+	exit 9
+fi
+
+# 1) lws-as-client tests first
+
+for i in '1.1.1', '1.1.2', '1.1.3', '1.1.4', '1.1.5', '1.1.6', '1.1.7', '1.1.8', '1.2.1', '1.2.2', '1.2.3', '1.2.4', '1.2.5', '1.2.6', '1.2.7', '1.2.8', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '2.8', '2.9', '3.1', '3.2', '3.3', '3.4', '3.5', '3.6', '3.7', '4.1.1', '4.1.2', '4.1.3', '4.1.4', '4.1.5', '4.2.1', '4.2.2', '4.2.3', '4.2.4', '4.2.5', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '5.7', '5.8', '5.9', '5.10', '5.11', '5.12', '5.13', '5.14', '5.15', '5.16', '5.17', '5.18', '5.19', '5.20', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.2.4', '6.3.1', '6.3.2', '6.4.1', '6.4.2', '6.4.3', '6.4.4', '6.5.1', '6.5.2', '6.5.3', '6.5.4', '6.5.5', '6.6.1', '6.6.2', '6.6.3', '6.6.4', '6.6.5', '6.6.6', '6.6.7', '6.6.8', '6.6.9', '6.6.10', '6.6.11', '6.7.1', '6.7.2', '6.7.3', '6.7.4', '6.8.1', '6.8.2', '6.9.1', '6.9.2', '6.9.3', '6.9.4', '6.10.1', '6.10.2', '6.10.3', '6.11.1', '6.11.2', '6.11.3', '6.11.4', '6.11.5', '6.12.1', '6.12.2', '6.12.3', '6.12.4', '6.12.5', '6.12.6', '6.12.7', '6.12.8', '6.13.1', '6.13.2', '6.13.3', '6.13.4', '6.13.5', '6.14.1', '6.14.2', '6.14.3', '6.14.4', '6.14.5', '6.14.6', '6.14.7', '6.14.8', '6.14.9', '6.14.10', '6.15.1', '6.16.1', '6.16.2', '6.16.3', '6.17.1', '6.17.2', '6.17.3', '6.17.4', '6.17.5', '6.18.1', '6.18.2', '6.18.3', '6.18.4', '6.18.5', '6.19.1', '6.19.2', '6.19.3', '6.19.4', '6.19.5', '6.20.1', '6.20.2', '6.20.3', '6.20.4', '6.20.5', '6.20.6', '6.20.7', '6.21.1', '6.21.2', '6.21.3', '6.21.4', '6.21.5', '6.21.6', '6.21.7', '6.21.8', '6.22.1', '6.22.2', '6.22.3', '6.22.4', '6.22.5', '6.22.6', '6.22.7', '6.22.8', '6.22.9', '6.22.10', '6.22.11', '6.22.12', '6.22.13', '6.22.14', '6.22.15', '6.22.16', '6.22.17', '6.22.18', '6.22.19', '6.22.20', '6.22.21', '6.22.22', '6.22.23', '6.22.24', '6.22.25', '6.22.26', '6.22.27', '6.22.28', '6.22.29', '6.22.30', '6.22.31', '6.22.32', '6.22.33', '6.22.34', '6.23.1', '6.23.2', '6.23.3', '6.23.4', '6.23.5', '6.23.6', '6.23.7', '7.1.1', '7.1.2', '7.1.3', '7.1.4', '7.1.5', '7.1.6', '7.3.1', '7.3.2', '7.3.3', '7.3.4', '7.3.5', '7.3.6', '7.5.1', '7.7.1', '7.7.2', '7.7.3', '7.7.4', '7.7.5', '7.7.6', '7.7.7', '7.7.8', '7.7.9', '7.7.10', '7.7.11', '7.7.12', '7.7.13', '7.9.1', '7.9.2', '7.9.3', '7.9.4', '7.9.5', '7.9.6', '7.9.7', '7.9.8', '7.9.9', '7.9.10', '7.9.11', '7.13.1', '7.13.2', '9.1.1', '9.1.2', '9.1.3', '9.1.4', '9.1.5', '9.1.6', '9.2.1', '9.2.2', '9.2.3', '9.2.4', '9.2.5', '9.2.6', '9.3.1', '9.3.2', '9.3.3', '9.3.4', '9.3.5', '9.3.6', '9.3.7', '9.3.8', '9.3.9', '9.4.1', '9.4.2', '9.4.3', '9.4.4', '9.4.5', '9.4.6', '9.4.7', '9.4.8', '9.4.9', '9.5.1', '9.5.2', '9.5.3', '9.5.4', '9.5.5', '9.5.6', '9.6.1', '9.6.2', '9.6.3', '9.6.4', '9.6.5', '9.6.6', '9.7.1', '9.7.2', '9.7.3', '9.7.4', '9.7.5', '9.7.6', '9.8.1', '9.8.2', '9.8.3', '9.8.4', '9.8.5', '9.8.6', '10.1.1', '12.1.1', '12.1.2', '12.1.3', '12.1.4', '12.1.5', '12.1.6', '12.1.7', '12.1.8', '12.1.9', '12.1.10', '12.1.11', '12.1.12', '12.1.13', '12.1.14', '12.1.15', '12.1.16', '12.1.17', '12.1.18', '12.2.1', '12.2.2', '12.2.3', '12.2.4', '12.2.5', '12.2.6', '12.2.7', '12.2.8', '12.2.9', '12.2.10', '12.2.11', '12.2.12', '12.2.13', '12.2.14', '12.2.15', '12.2.16', '12.2.17', '12.2.18', '12.3.1', '12.3.2', '12.3.3', '12.3.4', '12.3.5', '12.3.6', '12.3.7', '12.3.8', '12.3.9', '12.3.10', '12.3.11', '12.3.12', '12.3.13', '12.3.14', '12.3.15', '12.3.16', '12.3.17', '12.3.18', '13.1.1', '13.1.2', '13.1.3', '13.1.4', '13.1.5', '13.1.6', '13.1.7', '13.1.8', '13.1.9', '13.1.10', '13.1.11', '13.1.12', '13.1.13', '13.1.14', '13.1.15', '13.1.16', '13.1.17', '13.1.18', '13.2.1', '13.2.2', '13.2.3', '13.2.4', '13.2.5', '13.2.6', '13.2.7', '13.2.8', '13.2.9', '13.2.10', '13.2.11', '13.2.12', '13.2.13', '13.2.14', '13.2.15', '13.2.16', '13.2.17', '13.2.18', '13.3.1', '13.3.2', '13.3.3', '13.3.4', '13.3.5', '13.3.6', '13.3.7', '13.3.8', '13.3.9', '13.3.10', '13.3.11', '13.3.12', '13.3.13', '13.3.14', '13.3.15', '13.3.16', '13.3.17', '13.3.18', '13.4.1', '13.4.2', '13.4.3', '13.4.4', '13.4.5', '13.4.6', '13.4.7', '13.4.8', '13.4.9', '13.4.10', '13.4.11', '13.4.12', '13.4.13', '13.4.14', '13.4.15', '13.4.16', '13.4.17', '13.4.18', '13.5.1', '13.5.2', '13.5.3', '13.5.4', '13.5.5', '13.5.6', '13.5.7', '13.5.8', '13.5.9', '13.5.10', '13.5.11', '13.5.12', '13.5.13', '13.5.14', '13.5.15', '13.5.16', '13.5.17', '13.5.18', '13.6.1', '13.6.2', '13.6.3', '13.6.4', '13.6.5', '13.6.6', '13.6.7', '13.6.8', '13.6.9', '13.6.10', '13.6.11', '13.6.12', '13.6.13', '13.6.14', '13.6.15', '13.6.16', '13.6.17', '13.6.18', '13.7.1', '13.7.2', '13.7.3', '13.7.4', '13.7.5', '13.7.6', '13.7.7', '13.7.8', '13.7.9', '13.7.10', '13.7.11', '13.7.12', '13.7.13', '13.7.14', '13.7.15', '13.7.16', '13.7.17', '13.7.18'; do
+
+#	if [ $N -ge 360 -a $N -le 393 ] ; then
+#		echo "skipping broken autobahn tests (broken in autobahn) $i https://github.com/crossbario/autobahn-testsuite/issues/71"
+#	else
+
+		echo $N: $i
+		$CLIE -a 127.0.0.1 -p 9001 -u "/runCase?case=$N&agent=libwebsockets" -d3 &
+
+		C=99
+		while [ $C -gt $PARALLEL ] ; do
+			if [ $OS=SunOS ] ; then
+				C=`ps -ef | grep client-echo | wc -l`
+			else
+				C=`ps fax | grep client-echo | wc -l`
+			fi
+			if [ $C -gt $PARALLEL ] ; then
+				sleep 0.1s
+			fi
+		done
+#	fi
 	N=$(( $N + 1 ))
 done
 
@@ -27,15 +100,80 @@ echo "waiting for forks to complete..."
 
 while [ 1 ] ; do
 	if [ $OS=SunOS ] ; then
-		n=`ps -ef | grep libwebsocket | grep -v grep | wc -l`
+		n=`ps -ef | grep client-echo | grep -v grep | wc -l`
 	else
-		n=`ps fax | grep libwebsocket | grep -v grep | wc -l`
+		n=`ps fax | grep client-echo | grep -v grep | wc -l`
 	fi
 	echo "$n forks running..."
 	if [ $n -eq 0 ] ; then
 		echo "Completed"
-		exit 0
+		break
 	fi
-	sleep 1s
+	sleep 2s
 done
 
+# generate the report in ./reports
+#
+$CLIE -a 127.0.0.1 -p 9001 -u "/updateReports?agent=libwebsockets" -o
+sleep 2s
+killall wstest
+sleep 1s
+
+# this squashes the results into single lines like
+#
+#  "9.8.4": { "behavior": "OK", "behaviorClose": "OK", "duration": 1312, "remoteCloseCode": 1000, "reportfile": "libwebsockets_case_9_8_4.json"
+
+cat reports/clients/index.json | tr '\n' '!' | sed "s|\},\!|\n|g" | tr '!' ' ' | tr -s ' ' > /tmp/ji
+
+echo -n "AUTOBAHN SERVER / LWS CLIENT: Total tests: " `cat /tmp/ji | wc -l` " : "
+R="`cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | wc -l`"
+if [ "$R" == "0" ] ; then
+	echo "All pass"
+else
+	RESULT=1
+	echo -n "$R FAIL : "
+	cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | cut -d\" -f2 | tr '\n' ','
+	echo
+fi
+
+# 2) lws-as-server tests
+
+$SERV -p 9001 -d7 &
+wstest -m fuzzingclient
+R=$?
+echo "Autobahn client exit $R"
+
+killall lws-minimal-ws-server-echo
+sleep 1s
+
+# repeat the client results
+
+R=`cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | wc -l`
+echo -n "AUTOBAHN SERVER / LWS CLIENT: Total tests: " `cat /tmp/ji | wc -l` " : "
+if [ "$R" == "0" ] ;then
+	echo "All pass"
+else
+	RESULT=1
+	echo -n "$R FAIL : "
+	cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | cut -d\" -f2 | tr '\n' ','
+	echo
+fi
+
+# and then the server results
+
+cat reports/servers/index.json | tr '\n' '!' | sed "s|\},\!|\n|g" | tr '!' ' ' | tr -s ' ' > /tmp/jis
+R=`cat /tmp/jis | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | wc -l`
+
+echo -n "AUTOBAHN CLIENT / LWS SERVER: Total tests: " `cat /tmp/jis | wc -l` " : "
+if [ "$R" == "0" ] ;then
+	echo "All pass"
+else
+	RESULT=$(( $RESULT + 2 ))
+	echo -n "$R FAIL : "
+	cat /tmp/jis | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | cut -d\" -f2 | tr '\n' ','
+	echo
+fi
+
+echo $RESULT
+exit $RESULT
+
diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh
index c74d373829082c97918a6514dfccb42460bdcf76..90f388021565d0934bdf78d07f2a182ce14c12c3 100755
--- a/scripts/travis_install.sh
+++ b/scripts/travis_install.sh
@@ -9,6 +9,11 @@ then
 	if [ "$LWS_METHOD" == "lwsws" ];
 	then
 		sudo apt-get install -y -qq realpath;
+		sudo apt-get remove python-six
+		sudo pip install six>=1.9
+		sudo pip install Twisted==16.0.0
+		sudo pip install pyopenssl>=0.14
+		sudo pip install autobahntestsuite
 	fi
 
 	if [ "$LWS_METHOD" == "libev" ];
diff --git a/test-apps/attack.sh b/test-apps/attack.sh
index 672ff646eb6a96d62e45fd1130a89fa1fc43865d..18e005ed5543864494d0bb4b04c34ec5767a348e 100755
--- a/test-apps/attack.sh
+++ b/test-apps/attack.sh
@@ -20,7 +20,7 @@ function check {
 		echo "(killed it) *******"
 		exit 1
 	fi
-	dd if=$LOG bs=1 skip=$LEN 2>/dev/null
+	#dd if=$LOG bs=1 skip=$LEN 2>/dev/null
 
 	if [ "$1" = "default" ] ; then
 		diff /tmp/lwscap $INSTALLED/../share/libwebsockets-test-server/test.html > /dev/null
@@ -107,7 +107,7 @@ function check {
 
 rm -rf $LOG
 killall libwebsockets-test-server 2>/dev/null
-libwebsockets-test-server -d1023 2>> $LOG &
+libwebsockets-test-server -d15 2>> $LOG >/dev/null &
 CPID=$!
 
 echo "Started server on PID $CPID"
@@ -546,13 +546,13 @@ for i in \
 
 R=`rm -f /tmp/lwscap ; echo -n -e "GET $i HTTP/1.0\r\n\r\n" | nc localhost 7681 2>/dev/null >/tmp/lwscap; head -n1 /tmp/lwscap| cut -d' ' -f2`
 
-cat /tmp/lwscap | head -n1
-echo ==== $R
+#cat /tmp/lwscap | head -n1
+#echo ==== $R
 
 
 if [ "$R" != "403" ]; then
 	U=`cat $LOG | grep lws_http_serve | tail -n 1 | cut -d':' -f6 | cut -d' ' -f2`
-	echo $U
+#	echo $U
 	echo "- \"$i\" -> $R \"$U\"" >>/tmp/results
 else
 	echo "- \"$i\" -> $R" >>/tmp/results
diff --git a/test-apps/test-echo.c b/test-apps/test-echo.c
deleted file mode 100644
index 5076b34c941c5a5548afdee04fa10fb0a82b0a5a..0000000000000000000000000000000000000000
--- a/test-apps/test-echo.c
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * libwebsockets-test-echo
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.	So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <string.h>
-#include <assert.h>
-#include <signal.h>
-
-#include "../lib/libwebsockets.h"
-
-#ifndef _WIN32
-#include <syslog.h>
-#include <sys/time.h>
-#include <unistd.h>
-#else
-#include "gettimeofday.h"
-#include <process.h>
-#endif
-
-static volatile int force_exit = 0;
-static int versa, state;
-static int times = -1;
-
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-
-#define MAX_ECHO_PAYLOAD 1024
-
-struct per_session_data__echo {
-	size_t rx, tx;
-	unsigned char buf[LWS_PRE + MAX_ECHO_PAYLOAD];
-	unsigned int len;
-	unsigned int index;
-	int final;
-	int continuation;
-	int binary;
-};
-
-static int
-callback_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user,
-	      void *in, size_t len)
-{
-	struct per_session_data__echo *pss =
-			(struct per_session_data__echo *)user;
-	int n, flags;
-
-	switch (reason) {
-
-#ifndef LWS_NO_SERVER
-
-	case LWS_CALLBACK_ESTABLISHED:
-		pss->index = 0;
-		pss->len = -1;
-		break;
-
-	case LWS_CALLBACK_SERVER_WRITEABLE:
-do_tx:
-		if ((int)pss->len == -1)
-			break;
-
-		flags = lws_write_ws_flags(pss->binary ? LWS_WRITE_BINARY :
-				LWS_WRITE_TEXT, pss->continuation, pss->final);
-
-		lwsl_info("+++ test-echo: writing %d, with final %d\n",
-			  pss->len, pss->final);
-
-		pss->tx += pss->len;
-		n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, flags);
-		if (n < 0) {
-			lwsl_err("ERROR %d writing to socket, hanging up\n", n);
-			return 1;
-		}
-		pss->len = -1;
-		if (pss->final)
-			pss->continuation = 0;
-		lws_rx_flow_control(wsi, 1);
-		break;
-
-	case LWS_CALLBACK_RECEIVE:
-do_rx:
-		pss->final = lws_is_final_fragment(wsi);
-		pss->binary = lws_frame_is_binary(wsi);
-		lwsl_info("+++ test-echo: RX len %ld final %ld, pss->len=%ld\n",
-			  (long)len, (long)pss->final, (long)pss->len);
-
-		memcpy(&pss->buf[LWS_PRE], in, len);
-		assert((int)pss->len == -1);
-		pss->len = (unsigned int)len;
-		pss->rx += len;
-
-		lws_rx_flow_control(wsi, 0);
-		lws_callback_on_writable(wsi);
-		break;
-#endif
-
-#ifndef LWS_NO_CLIENT
-	/* when the callback is used for client operations --> */
-
-	case LWS_CALLBACK_CLOSED:
-	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
-		lwsl_debug("closed\n");
-		state = 0;
-		break;
-
-	case LWS_CALLBACK_CLIENT_ESTABLISHED:
-		lwsl_debug("Client has connected\n");
-		pss->index = 0;
-		pss->len = -1;
-		state = 2;
-		break;
-
-	case LWS_CALLBACK_CLIENT_RECEIVE:
-#ifndef LWS_NO_SERVER
-		if (versa)
-			goto do_rx;
-#endif
-		lwsl_notice("Client RX: %s", (char *)in);
-		if (times == 0)
-			force_exit = 1;
-		break;
-
-	case LWS_CALLBACK_CLIENT_WRITEABLE:
-#ifndef LWS_NO_SERVER
-		if (versa) {
-			if (pss->len != (unsigned int)-1)
-				goto do_tx;
-			break;
-		}
-#endif
-		/* we will send our packet... */
-		pss->len = sprintf((char *)&pss->buf[LWS_PRE],
-				   "hello from libwebsockets-test-echo client pid %d index %d\n",
-				   getpid(), pss->index++);
-		lwsl_notice("Client TX: %s", &pss->buf[LWS_PRE]);
-		n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, LWS_WRITE_TEXT);
-		if (n < 0) {
-			lwsl_err("ERROR %d writing to socket, hanging up\n", n);
-			return -1;
-		}
-		if (n < (int)pss->len) {
-			lwsl_err("Partial write\n");
-			return -1;
-		}
-		break;
-#endif
-
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-
-
-static struct lws_protocols protocols[] = {
-	/* first protocol must always be HTTP handler */
-
-	{
-		"",		/* name - can be overridden with -e */
-		callback_echo,
-		sizeof(struct per_session_data__echo),	/* per_session_data_size */
-		MAX_ECHO_PAYLOAD,
-	},
-	{
-		NULL, NULL, 0		/* End of list */
-	}
-};
-
-static const struct lws_extension exts[] = {
-	{
-		"permessage-deflate",
-		lws_extension_callback_pm_deflate,
-		"permessage-deflate; client_no_context_takeover; client_max_window_bits"
-	},
-	{ NULL, NULL, NULL /* terminator */ }
-};
-
-
-void sighandler(int sig)
-{
-	force_exit = 1;
-}
-
-static struct option options[] = {
-	{ "help",	no_argument,		NULL, 'h' },
-	{ "debug",	required_argument,	NULL, 'd' },
-	{ "port",	required_argument,	NULL, 'p' },
-	{ "ssl-cert",	required_argument,	NULL, 'C' },
-	{ "ssl-key",	required_argument,	NULL, 'k' },
-#ifndef LWS_NO_CLIENT
-	{ "client",	required_argument,	NULL, 'c' },
-	{ "ratems",	required_argument,	NULL, 'r' },
-#endif
-	{ "ssl",	no_argument,		NULL, 's' },
-	{ "versa",	no_argument,		NULL, 'v' },
-	{ "uri",	required_argument,	NULL, 'u' },
-	{ "passphrase", required_argument,	NULL, 'P' },
-	{ "interface",	required_argument,	NULL, 'i' },
-	{ "times",	required_argument,	NULL, 'n' },
-	{ "echogen",	no_argument,		NULL, 'e' },
-#ifndef LWS_NO_DAEMONIZE
-	{ "daemonize",	no_argument,		NULL, 'D' },
-#endif
-	{ NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-	int n = 0;
-	int port = 7681;
-	int use_ssl = 0;
-	struct lws_context *context;
-	int opts = 0;
-	char interface_name[128] = "";
-	const char *_interface = NULL;
-	char ssl_cert[256] = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
-	char ssl_key[256] = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
-#ifndef _WIN32
-/* LOG_PERROR is not POSIX standard, and may not be portable */
-#ifdef __sun
-	int syslog_options = LOG_PID;
-#else
-	int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#endif
-	int client = 0;
-	int listen_port = 80;
-	struct lws_context_creation_info info;
-	char passphrase[256];
-	char uri[256] = "/";
-#ifndef LWS_NO_CLIENT
-	char address[256], ads_port[256 + 30];
-	int rate_us = 250000;
-	unsigned long long oldus;
-	struct lws *wsi;
-	int disallow_selfsigned = 0;
-	struct timeval tv;
-	const char *connect_protocol = NULL;
-	struct lws_client_connect_info i;
-#endif
-
-	int debug_level = 7;
-#ifndef LWS_NO_DAEMONIZE
-	int daemonize = 0;
-#endif
-
-	memset(&info, 0, sizeof info);
-
-#ifndef LWS_NO_CLIENT
-	lwsl_notice("Built to support client operations\n");
-#endif
-#ifndef LWS_NO_SERVER
-	lwsl_notice("Built to support server operations\n");
-#endif
-
-	while (n >= 0) {
-		n = getopt_long(argc, argv, "i:hsp:d:DC:k:P:vu:n:e"
-#ifndef LWS_NO_CLIENT
-			"c:r:"
-#endif
-				, options, NULL);
-		if (n < 0)
-			continue;
-		switch (n) {
-		case 'P':
-			lws_strncpy(passphrase, optarg, sizeof(passphrase));
-			info.ssl_private_key_password = passphrase;
-			break;
-		case 'C':
-			lws_strncpy(ssl_cert, optarg, sizeof(ssl_cert));
-			disallow_selfsigned = 1;
-			break;
-		case 'k':
-			lws_strncpy(ssl_key, optarg, sizeof(ssl_key));
-			break;
-		case 'u':
-			lws_strncpy(uri, optarg, sizeof(uri));
-			break;
-
-#ifndef LWS_NO_DAEMONIZE
-		case 'D':
-			daemonize = 1;
-#if !defined(_WIN32) && !defined(__sun)
-			syslog_options &= ~LOG_PERROR;
-#endif
-			break;
-#endif
-#ifndef LWS_NO_CLIENT
-		case 'c':
-			client = 1;
-			lws_strncpy(address, optarg, sizeof(address));
-			port = 80;
-			break;
-		case 'r':
-			rate_us = atoi(optarg) * 1000;
-			break;
-#endif
-		case 'd':
-			debug_level = atoi(optarg);
-			break;
-		case 's':
-			use_ssl = 1; /* 1 = take care about cert verification, 2 = allow anything */
-			break;
-		case 'p':
-			port = atoi(optarg);
-			break;
-		case 'v':
-			versa = 1;
-			break;
-		case 'e':
-			protocols[0].name = "lws-echogen";
-			connect_protocol = protocols[0].name;
-			lwsl_err("using lws-echogen\n");
-			break;
-		case 'i':
-			lws_strncpy(interface_name, optarg, sizeof interface_name);
-			_interface = interface_name;
-			break;
-		case 'n':
-			times = atoi(optarg) + 1;
-			break;
-		case '?':
-		case 'h':
-			fprintf(stderr, "Usage: libwebsockets-test-echo\n"
-				"  --debug	/ -d <debug bitfield>\n"
-				"  --port	/ -p <port>\n"
-				"  --ssl-cert	/ -C <cert path>\n"
-				"  --ssl-key	/ -k <key path>\n"
-#ifndef LWS_NO_CLIENT
-				"  --client	/ -c <server IP>\n"
-				"  --ratems	/ -r <rate in ms>\n"
-#endif
-				"  --ssl	/ -s\n"
-				"  --passphrase / -P <passphrase>\n"
-				"  --interface	/ -i <interface>\n"
-				"  --uri	/ -u <uri path>\n"
-				"  --times	/ -n <-1 unlimited or times to echo>\n"
-#ifndef LWS_NO_DAEMONIZE
-				"  --daemonize	/ -D\n"
-#endif
-			);
-			exit(1);
-		}
-	}
-
-#ifndef LWS_NO_DAEMONIZE
-	/*
-	 * normally lock path would be /var/lock/lwsts or similar, to
-	 * simplify getting started without having to take care about
-	 * permissions or running as root, set to /tmp/.lwsts-lock
-	 */
-#if defined(WIN32) || defined(_WIN32)
-#else
-	if (!client && daemonize && lws_daemonize("/tmp/.lwstecho-lock")) {
-		fprintf(stderr, "Failed to daemonize\n");
-		return 1;
-	}
-#endif
-#endif
-
-#ifndef _WIN32
-	/* we will only try to log things according to our debug_level */
-	setlogmask(LOG_UPTO (LOG_DEBUG));
-	openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-	/* tell the library what debug level to emit and to send it to syslog */
-	lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-	lwsl_notice("libwebsockets test server echo - license LGPL2.1+SLE\n");
-	lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-#ifndef LWS_NO_CLIENT
-	if (client) {
-		lwsl_notice("Running in client mode\n");
-		listen_port = CONTEXT_PORT_NO_LISTEN;
-		if (use_ssl && !disallow_selfsigned) {
-			lwsl_info("allowing selfsigned\n");
-			use_ssl = 2;
-		} else {
-			lwsl_info("requiring server cert validation against %s\n",
-				  ssl_cert);
-			info.ssl_ca_filepath = ssl_cert;
-		}
-	} else {
-#endif
-#ifndef LWS_NO_SERVER
-		lwsl_notice("Running in server mode\n");
-		listen_port = port;
-#endif
-#ifndef LWS_NO_CLIENT
-	}
-#endif
-
-	info.port = listen_port;
-	info.iface = _interface;
-	info.protocols = protocols;
-	if (use_ssl && !client) {
-		info.ssl_cert_filepath = ssl_cert;
-		info.ssl_private_key_filepath = ssl_key;
-	} else
-		if (use_ssl && client) {
-			info.ssl_cert_filepath = NULL;
-			info.ssl_private_key_filepath = NULL;
-		}
-	info.gid = -1;
-	info.uid = -1;
-	info.extensions = exts;
-	info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8;
-
-	if (use_ssl)
-		info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-#if !defined(LWS_WITHOUT_EXTENSIONS)
-	info.extensions = exts;
-#endif
-
-	context = lws_create_context(&info);
-	if (context == NULL) {
-		lwsl_err("libwebsocket init failed\n");
-		return -1;
-	}
-
-
-	signal(SIGINT, sighandler);
-
-#ifndef LWS_NO_CLIENT
-	gettimeofday(&tv, NULL);
-	oldus = ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
-#endif
-
-	n = 0;
-	while (n >= 0 && !force_exit) {
-#ifndef LWS_NO_CLIENT
-		if (client && !state && times) {
-			state = 1;
-			lwsl_notice("Client connecting to %s:%u....\n",
-				    address, port);
-			/* we are in client mode */
-
-			address[sizeof(address) - 1] = '\0';
-			sprintf(ads_port, "%s:%u", address, port & 65535);
-			if (times > 0)
-				times--;
-
-			memset(&i, 0, sizeof(i));
-
-			i.context = context;
-			i.address = address;
-			i.port = port;
-			i.ssl_connection = use_ssl;
-			i.path = uri;
-			i.host = ads_port;
-			i.origin = ads_port;
-			i.protocol = connect_protocol;
-
-			wsi = lws_client_connect_via_info(&i);
-			if (!wsi) {
-				lwsl_err("Client failed to connect to %s:%u\n",
-					 address, port);
-				goto bail;
-			}
-		}
-
-		if (client && !versa && times) {
-			gettimeofday(&tv, NULL);
-
-			if ((int)((((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec) - oldus) > rate_us) {
-				lws_callback_on_writable_all_protocol(context,
-						&protocols[0]);
-				oldus = ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
-				if (times > 0)
-					times--;
-			}
-		}
-
-		if (client && !state && !times)
-			break;
-#endif
-		n = lws_service(context, 10);
-	}
-#ifndef LWS_NO_CLIENT
-bail:
-#endif
-	lws_context_destroy(context);
-
-	lwsl_notice("libwebsockets-test-echo exited cleanly\n");
-#ifndef _WIN32
-	closelog();
-#endif
-
-	return 0;
-}