diff --git a/.travis.yml b/.travis.yml index 6960d353..099c6de8 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 e12e1889..70c079aa 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 62ec302b..da1e2a42 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 9c251db1..4482ec8b 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 +2) From your build dir: cmake .. -DLWS_WITH_MINIMAL_EXAMPLES=1 && make -3) Run tests like this +3) ../scripts/autobahn-test.sh -libwebsockets-test-echo --client localhost --port 9001 -u "/runCase?case=20&agent=libwebsockets" -v -d 65535 -n 1 +4) In a browser go to the directory you ran wstest in (eg, /projects/libwebsockets) -(this runs test 20) - -4) In a browser, go here - -http://localhost:8080/test_browser.html - -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 7f3a456c..2a46ff9d 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 1c609911..eabd4bc0 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 6e31aec9..db6f1d3c 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 c6d20f74..34530868 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 8ac106f1..ac976dbd 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 41780394..00000000 --- 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 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include - -int -lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) -{ - int m; - - if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) && - (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) && - (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) && - !lwsi_role_client(wsi)) - return 0; - - while (len) { - /* - * we were accepting input but now we stopped doing so - */ - if (lws_is_flowcontrolled(wsi)) { - lwsl_debug("%s: caching %ld\n", __func__, (long)len); - lws_rxflow_cache(wsi, *buf, 0, (int)len); - return 0; - } - if (wsi->ws->rx_draining_ext) { -#if !defined(LWS_NO_CLIENT) - if (lwsi_role_client(wsi)) - m = lws_client_rx_sm(wsi, 0); - else -#endif - m = lws_ws_rx_sm(wsi, 0); - if (m < 0) - return -1; - continue; - } - /* account for what we're using in rxflow buffer */ - if (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 c98fb950..976aa47b 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 1f8dc2f2..a61dd218 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 ed4c992c..3ae0fa82 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 1b24819d..197b28b5 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 d80dbf13..816e988a 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 a1168883..111be039 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 7c009125..e39abd65 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 556451b9..e23d80d2 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 055fce5a..162acab4 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]; - if (--wsi->ws->rx_packet_length == 0) { + --wsi->ws->rx_packet_length; + } + + if (!wsi->ws->rx_packet_length) { + lwsl_debug("%s: ws fragment length exhausted\n", __func__); /* spill because we have the whole frame */ wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; } + if (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. - */ + lwsl_parser("unknown opc %x\n", wsi->ws->opcode); - 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 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; + } - if (wsi->protocol->callback) { + /* we are ending partway through utf-8 character? */ + if (!wsi->ws->rx_packet_length && wsi->ws->final && + wsi->ws->utf8 && !n) { + lwsl_info("FINAL utf8 error\n"); + lws_close_reason(wsi, + LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"partial utf8", 12); +utf8_fail: + lwsl_notice("utf8 error\n"); + lwsl_hexdump_notice(ebuf.token, ebuf.len); + + return -1; + } + } + + if (!wsi->wsistate_pre_close && (ebuf.len >= 0 || + callback_action == LWS_CALLBACK_RECEIVE_PONG)) { + if (ebuf.len) + ebuf.token[ebuf.len] = '\0'; + + if (wsi->protocol->callback && + !(already_processed & ALREADY_PROCESSED_NO_CB)) { if (callback_action == LWS_CALLBACK_RECEIVE_PONG) lwsl_info("Doing pong callback\n"); @@ -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 21c6b656..c8cfd4d0 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 69858316..9bcb3bcf 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: } -int -lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len) + +/* + * 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) { - int m, draining_flow = 0; + 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; - if (lws_buflist_next_segment_len(&wsi->buflist, NULL)) - draining_flow = 1; + /* + * 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... + */ - lwsl_parser("%s: received %d byte packet\n", __func__, (int)len); +#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_parse_ws(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m, bulk = 0; + + lwsl_debug("%s: received %d byte packet\n", __func__, (int)len); + + //lwsl_hexdump_notice(*buf, len); /* let the rx protocol state machine have as much as it needs */ @@ -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; + + bulk = 1; + m = lws_ws_frame_rest_is_payload(wsi, buf, len); + assert((int)lws_ptr_diff(*buf, bin) <= (int)len); + len -= lws_ptr_diff(*buf, bin); + + if (!m) { + + break; + } + if (m < 0) { + lwsl_info("%s: rest_is_payload bailed\n", + __func__); + return -1; + } } - /* process the byte */ - m = lws_ws_rx_sm(wsi, *(*buf)++); - if (m < 0) + 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); + } + + if (m < 0) { + lwsl_info("%s: lws_ws_rx_sm bailed %d\n", __func__, + bulk); + return -1; - len--; - - /* 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); - - lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi); - - /* 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 */ } + + 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 a2628c74..3c44ef29 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 e769c01e..ede1b24b 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 00000000..8a8cc747 --- /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 \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 00000000..8417804b --- /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 |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 00000000..cb8ce052 --- /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 + * + * 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 +#include +#include + +#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 00000000..cdee75c4 --- /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 + * + * 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 +#endif + +#include + +#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 8781b9d9..eb33c7a3 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 00000000..0ea8692c --- /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 \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 00000000..bf65c6ea --- /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 |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 00000000..9e4ed455 --- /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 + * + * 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 +#include +#include + +#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 00000000..7708dfb4 --- /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 + * + * 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 +#endif + +#include + +#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 +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 91b91714..550c5e74 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 & +CLIE=bin/lws-minimal-ws-client-echo +SERV=bin/lws-minimal-ws-server-echo - 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 +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 c74d3738..90f38802 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 672ff646..18e005ed 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 5076b34c..00000000 --- a/test-apps/test-echo.c +++ /dev/null @@ -1,511 +0,0 @@ -/* - * libwebsockets-test-echo - * - * Copyright (C) 2010-2016 Andy Green - * - * 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 -#include -#include -#include -#include -#include - -#include "../lib/libwebsockets.h" - -#ifndef _WIN32 -#include -#include -#include -#else -#include "gettimeofday.h" -#include -#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 \n" - " --port / -p \n" - " --ssl-cert / -C \n" - " --ssl-key / -k \n" -#ifndef LWS_NO_CLIENT - " --client / -c \n" - " --ratems / -r \n" -#endif - " --ssl / -s\n" - " --passphrase / -P \n" - " --interface / -i \n" - " --uri / -u \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 \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; -}