diff --git a/CMakeLists.txt b/CMakeLists.txt index 7149959b7..89372d9a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ project(libwebsockets C) set(PACKAGE "libwebsockets") set(CPACK_PACKAGE_NAME "${PACKAGE}") set(CPACK_PACKAGE_VERSION_MAJOR "2") -set(CPACK_PACKAGE_VERSION_MINOR "3") +set(CPACK_PACKAGE_VERSION_MINOR "4") set(CPACK_PACKAGE_VERSION_PATCH "0") set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") set(CPACK_PACKAGE_VENDOR "andy@warmcat.com") @@ -96,7 +96,7 @@ option(LWS_WITH_LATENCY "Build latency measuring code into the library" OFF) option(LWS_WITHOUT_DAEMONIZE "Don't build the daemonization api" ON) option(LWS_IPV6 "Compile with support for ipv6" OFF) option(LWS_UNIX_SOCK "Compile with support for UNIX domain socket" OFF) -#option(LWS_WITH_HTTP2 "Compile with support for http2" OFF) +option(LWS_WITH_HTTP2 "Compile with server support for HTTP/2" OFF) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying (requires libhubbub)" OFF) @@ -628,7 +628,7 @@ if (NOT LWS_WITHOUT_CLIENT) lib/client-parser.c) endif() -if (LWS_WITH_MBEDTLS AND NOT LWS_WITH_ESP32) +if (LWS_WITH_MBEDTLS) set(LWS_WITH_SSL ON) list(APPEND HDR_PRIVATE @@ -1474,6 +1474,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) "${PROJECT_SOURCE_DIR}/test-apps/leaf.jpg" "${PROJECT_SOURCE_DIR}/test-apps/candide.zip" "${PROJECT_SOURCE_DIR}/test-apps/libwebsockets.org-logo.png" + "${PROJECT_SOURCE_DIR}/test-apps/http2.png" "${PROJECT_SOURCE_DIR}/test-apps/lws-common.js" "${PROJECT_SOURCE_DIR}/test-apps/test.html") diff --git a/READMEs/README.build.md b/READMEs/README.build.md index 811083d9a..d065eb61b 100644 --- a/READMEs/README.build.md +++ b/READMEs/README.build.md @@ -251,6 +251,31 @@ deleting build/CMakeCache.txt may be enough. $ make install ``` +@section ssllib Choosing Your TLS Poison + + - If you are really restricted on memory, code size, or don't care about TLS + speed, mbedTLS is a good choice: `cmake .. -DLWS_WITH_MBEDTLS=1` + + - If cpu and memory is not super restricted and you care about TLS speed, + OpenSSL or a directly compatible variant like Boring SSL is a good choice. + +Just building lws against stock Fedora OpenSSL or stock Fedora mbedTLS, for +SSL handhake mbedTLS takes ~36ms and OpenSSL takes ~1ms on the same x86_64 +build machine here, with everything else the same. Over the 144 connections of +h2spec compliance testing for example, this ends up completing in 400ms for +OpenSSL and 5.5sec for mbedTLS on x86_64. In other words mbedTLS is very slow +compared to OpenSSL under the (fairly typical) conditions I tested it. + +This isn't an inefficiency in the mbedtls interface implementation, it's just +mbedTLS doing the crypto much slower than OpenSSL, which has accelerated +versions of common crypto operations it automatically uses for platforms +supporting it. As of Oct 2017 mbedTLS itself has no such optimizations for any +platform that I could find. It's just pure C running on the CPU. + +Lws supports both almost the same, so instead of taking my word for it you are +invited to try it both ways and see which the results (including, eg, binary +size and memory usage as well as speed) suggest you use. + @section optee Building for OP-TEE OP-TEE is a "Secure World" Trusted Execution Environment. @@ -376,11 +401,14 @@ additionally, discovered plugins are not enabled automatically for security reasons. You do this using info->pvo or for lwsws, in the JSON config. -@section http2rp Reproducing HTTP2.0 tests +@section http2rp Reproducing HTTP/2 tests + +Enable `-DLWS_WITH_HTTP2=1` in cmake to build with http/2 support enabled. You must have built and be running lws against a version of openssl that has -ALPN / NPN. Most distros still have older versions. You'll know it's right by -seeing +ALPN. At the time of writing, recent distros have started upgrading to OpenSSL +1.1+ that supports this already. You'll know it's right by seeing + ``` lwsts[4752]: Compiled with OpenSSL support lwsts[4752]: Using SSL mode @@ -388,15 +416,30 @@ seeing ``` at lws startup. -For non-SSL HTTP2.0 upgrade -``` - $ nghttp -nvasu http://localhost:7681/test.htm -``` -For SSL / ALPN HTTP2.0 upgrade +Recent Firefox and Chrome also support HTTP/2 by ALPN, so these should just work +with the test server running in -s / ssl mode. + +For testing with nghttp client: + ``` $ nghttp -nvas https://localhost:7681/test.html ``` +Testing with h2spec (https://github.com/summerwind/h2spec) + +``` + $ h2spec -h 127.0.0.1 -p 7681 -t -k -v -o 1 +``` + +At the time of writing, http/2 support is not fully complete; however all the +h2spec tests pass. + +``` +145 tests, 144 passed, 1 skipped, 0 failed + +``` + + @section cross Cross compiling To enable cross-compiling **libwebsockets** using CMake you need to create @@ -537,7 +580,7 @@ chmod 644 /tmp/cross/include/zlib.h /tmp/cross/include/zconf.h 3) `cd mbedtls ; mkdir build ; cd build` -3) `cmake .. -DCMAKE_TOOLCHAIN_FILE=/tmp/mytoolchainfile -DCMAKE_INSTALL_PREFIX:PATH=/tmp/cross -DUSE_SHARED_MBEDTLS_LIBRARY=1` mbedtls also uses cmake, so you can simply reuse the toolchain file you used for libwebsockets. That is why you shouldn't put project-specific options in the toolchain file, it should just describe the toolchain. +3) `cmake .. -DCMAKE_TOOLCHAIN_FILE=/tmp/mytoolchainfile -DCMAKE_INSTALL_PREFIX:PATH=/tmp/cross -DCMAKE_BUILD_TYPE=RELEASE -DUSE_SHARED_MBEDTLS_LIBRARY=1` mbedtls also uses cmake, so you can simply reuse the toolchain file you used for libwebsockets. That is why you shouldn't put project-specific options in the toolchain file, it should just describe the toolchain. 4) `make && make install` diff --git a/READMEs/README.coding.md b/READMEs/README.coding.md index e6afe3fa4..7e934ba52 100644 --- a/READMEs/README.coding.md +++ b/READMEs/README.coding.md @@ -332,6 +332,75 @@ isn't processed by user code before then should be copied out for later. For HTTP connections that don't upgrade, header info remains available the whole time. +@section http2compat Code Requirements for HTTP/2 compatibility + +Websocket connections only work over http/1, so there is nothing special to do +when you want to enable -DLWS_WITH_HTTP2=1. + +The internal http apis already follow these requirements and are compatible with +http/2 already. So if you use stuff like mounts and serve stuff out of the +filesystem, there's also nothing special to do. + +However if you are getting your hands dirty with writing response headers, or +writing bulk data over http/2, you need to observe these rules so that it will +work over both http/1.x and http/2 the same. + +1) LWS_PRE requirement applies on ALL lws_write(). For http/1, you don't have +to take care of LWS_PRE for http data, since it is just sent straight out. +For http/2, it will write up to LWS_PRE bytes behind the buffer start to create +the http/2 frame header. + +This has implications if you treated the input buffer to lws_write() as const... +it isn't any more with http/2, up to 9 bytes behind the buffer will be trashed. + +2) Headers are encoded using a sophisticated scheme in http/2. The existing +header access apis are already made compatible for incoming headers, +for outgoing headers you must: + + - observe the LWS_PRE buffer requirement mentioned above + + - Use `lws_add_http_header_status()` to add the transaction status (200 etc) + + - use lws apis `lws_add_http_header_by_name()` and `lws_add_http_header_by_token()` + to put the headers into the buffer (these will translate what is actually + written to the buffer depending on if the connection is in http/2 mode or not) + + - use the `lws api lws_finalize_http_header()` api after adding the last + response header + + - write the header using lws_write(..., `LWS_WRITE_HTTP_HEADERS`); + + 3) http/2 introduces per-stream transmit credit... how much more you can send + on a stream is decided by the peer. You start off with some amount, as the + stream sends stuff lws will reduce your credit accordingly, when it reaches + zero, you must not send anything further until lws receives "more credit" for + that stream the peer. Lws will suppress writable callbacks if you hit 0 until + more credit for the stream appears, and lws built-in file serving (via mounts + etc) already takes care of observing the tx credit restrictions. However if + you write your own code that wants to send http data, you must consult the + `lws_get_peer_write_allowance()` api to find out the state of your tx credit. + For http/1, it will always return (size_t)-1, ie, no limit. + + This is orthogonal to the question of how much space your local side's kernel + will make to buffer your send data on that connection. So although the result + from `lws_get_peer_write_allowance()` is "how much you can send" logically, + and may be megabytes if the peer allows it, you should restrict what you send + at one time to whatever your machine will generally accept in one go, and + further reduce that amount if `lws_get_peer_write_allowance()` returns + something smaller. If it returns 0, you should not consume or send anything + and return having asked for callback on writable, it will only come back when + more tx credit has arrived for your stream. + + 4) Header names with captital letters are illegal in http/2. Header names in + http/1 are case insensitive. So if you generate headers by name, change all + your header name strings to lower-case to be compatible both ways. + + 5) Chunked Transfer-encoding is illegal in http/2, http/2 peers will actively + reject it. Lws takes care of removing the header and converting CGIs that + emit chunked into unchunked automatically for http/2 connections. + +If you follow these rules, your code will automatically work with both http/1.x +and http/2. @section ka TCP Keepalive diff --git a/READMEs/mainpage.md b/READMEs/mainpage.md index 9a427b39a..33b1348cf 100644 --- a/READMEs/mainpage.md +++ b/READMEs/mainpage.md @@ -2,13 +2,17 @@ Libwebsockets covers a lot of interesting features for people making embedded servers or clients - - http(s) serving and client operation - - ws(s) serving and client operation - - http(s) apis for file transfer and upload - - http POST form handling (including multipart) + - HTTP(S) serving and client operation + - HTTP/2 support for serving + - WS(S) serving and client operation + - HTTP(S) apis for file transfer and upload + - HTTP 1 + 2 POST form handling (including multipart / file upload) - cookie-based sessions - account management (including registration, email verification, lost pw etc) - - strong ssl PFS support (A+ on SSLlabs test) + - strong SSL / TLS PFS support (A+ on SSLlabs test) + - ssh server integration + - serving gzipped files directly from inside zip files, without conversion + - support for linux, bsd, windows etc... and very small nonlinux targets like ESP32 You can browse by api category here diff --git a/component.mk b/component.mk index 236395a4d..8baf3d6da 100644 --- a/component.mk +++ b/component.mk @@ -19,7 +19,7 @@ CROSS_PATH:= $(shell dirname $(CROSS_PATH1) )/.. build: cd $(COMPONENT_BUILD_DIR) ; \ echo "doing lws cmake" ; \ - cmake $(COMPONENT_PATH) -DLWS_C_FLAGS="$(CFLAGS) -DNDEBUG=1 " \ + cmake $(COMPONENT_PATH) -DLWS_C_FLAGS="$(CFLAGS) " \ -DIDF_PATH=$(IDF_PATH) \ -DCROSS_PATH=$(CROSS_PATH) \ -DBUILD_DIR_BASE=$(BUILD_DIR_BASE) \ @@ -27,6 +27,7 @@ build: -DCMAKE_BUILD_TYPE=RELEASE \ -DLWS_MBEDTLS_INCLUDE_DIRS="${IDF_PATH}/components/openssl/include;${IDF_PATH}/components/mbedtls/include;${IDF_PATH}/components/mbedtls/port/include" \ -DLWS_WITH_STATS=0 \ + -DLWS_WITH_HTTP2=1 \ -DZLIB_LIBRARY=$(BUILD_DIR_BASE)/zlib/libzlib.a \ -DZLIB_INCLUDE_DIR=$(COMPONENT_PATH)/../zlib \ -DLWS_WITH_ESP32=1 ;\ diff --git a/lib/alloc.c b/lib/alloc.c index e53c356ca..898db1246 100644 --- a/lib/alloc.c +++ b/lib/alloc.c @@ -51,7 +51,11 @@ void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason)) static void *_realloc(void *ptr, size_t size, const char *reason) { if (size) { +#if defined(LWS_PLAT_ESP32) + lwsl_notice("%s: size %lu: %s\n", __func__, (unsigned long)size, reason); +#else lwsl_debug("%s: size %lu: %s\n", __func__, (unsigned long)size, reason); +#endif #if defined(LWS_PLAT_OPTEE) return (void *)TEE_Realloc(ptr, size); #else diff --git a/lib/client.c b/lib/client.c index 54ffb59ba..b2a4c12f5 100755 --- a/lib/client.c +++ b/lib/client.c @@ -36,7 +36,7 @@ lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) /* * we were accepting input but now we stopped doing so */ - if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { + if (lws_is_flowcontrolled(wsi)) { lwsl_debug("%s: caching %ld\n", __func__, (long)len); lws_rxflow_cache(wsi, *buf, 0, len); return 0; @@ -308,7 +308,6 @@ start_ws_handshake: } /* send our request to the server */ - lws_latency_pre(context, wsi); n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb); @@ -350,10 +349,12 @@ client_http_body_sent: break; case LWSCM_WSCL_WAITING_SERVER_REPLY: - - /* handle server hung up on us */ - - if (pollfd->revents & LWS_POLLHUP) { + /* + * handle server hanging up on us... + * but if there is POLLIN waiting, handle that first + */ + if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == + LWS_POLLHUP) { lwsl_debug("Server connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); @@ -364,18 +365,15 @@ client_http_body_sent: if (!(pollfd->revents & LWS_POLLIN)) break; - /* interpret the server response */ - - /* + /* interpret the server response + * * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== * Sec-WebSocket-Protocol: chat - */ - - /* + * * we have to take some care here to only take from the * socket bytewise. The browser may (and has been seen to * in the case that onopen() performs websocket traffic) @@ -409,7 +407,6 @@ client_http_body_sent: * libwebsocket timeout still active here too, so if parsing did * not complete just wait for next packet coming in this state */ - if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) break; @@ -418,7 +415,6 @@ client_http_body_sent: * packet traffic already arrived we'll trigger poll() again * right away and deal with it that way */ - return lws_client_interpret_server_handshake(wsi); bail3: @@ -480,7 +476,7 @@ lws_http_transaction_completed_client(struct lws *wsi) /* otherwise set ourselves up ready to go again */ wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; wsi->mode = LWSCM_HTTP_CLIENT_ACCEPTED; - wsi->u.http.content_length = 0; + wsi->u.http.rx_content_length = 0; wsi->hdr_parsing_completed = 0; /* He asked for it to stay alive indefinitely */ @@ -694,12 +690,12 @@ lws_client_interpret_server_handshake(struct lws *wsi) } if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { - wsi->u.http.content_length = + wsi->u.http.rx_content_length = atoll(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)); lwsl_notice("%s: incoming content length %llu\n", __func__, - (unsigned long long)wsi->u.http.content_length); - wsi->u.http.content_remain = wsi->u.http.content_length; + (unsigned long long)wsi->u.http.rx_content_length); + wsi->u.http.rx_content_remain = wsi->u.http.rx_content_length; } else /* can't do 1.1 without a content length or chunked */ if (!wsi->chunked) wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; diff --git a/lib/context.c b/lib/context.c index 8b5512b2a..ae96ddfea 100644 --- a/lib/context.c +++ b/lib/context.c @@ -50,6 +50,64 @@ static const char * const mount_protocols[] = { "callback://" }; +#if defined(LWS_WITH_HTTP2) +/* + * These are the standardized defaults. + * Override what actually goes in the vhost settings in platform or user code. + * Leave these alone because they are used to determine "what is different + * from the protocol defaults". + */ +const struct http2_settings lws_h2_defaults = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 4096, + /* *** This controls how many entries in the dynamic table *** + * Allows the sender to inform the remote endpoint of the maximum + * size of the header compression table used to decode header + * blocks, in octets. The encoder can select any size equal to or + * less than this value by using signaling specific to the header + * compression format inside a header block (see [COMPRESSION]). + * The initial value is 4,096 octets. + */ + /* H2SET_ENABLE_PUSH */ 1, + /* H2SET_MAX_CONCURRENT_STREAMS */ 0x7fffffff, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 0x7fffffff, + /*< This advisory setting informs a peer of the maximum size of + * header list that the sender is prepared to accept, in octets. + * The value is based on the uncompressed size of header fields, + * including the length of the name and value in octets plus an + * overhead of 32 octets for each header field. + */ + +}}; + +const struct http2_settings lws_h2_stock_settings = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 512, + /* *** This controls how many entries in the dynamic table *** + * Allows the sender to inform the remote endpoint of the maximum + * size of the header compression table used to decode header + * blocks, in octets. The encoder can select any size equal to or + * less than this value by using signaling specific to the header + * compression format inside a header block (see [COMPRESSION]). + * The initial value is 4,096 octets. + */ + /* H2SET_ENABLE_PUSH */ 1, + /* H2SET_MAX_CONCURRENT_STREAMS */ 24, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 4096, + /*< This advisory setting informs a peer of the maximum size of + * header list that the sender is prepared to accept, in octets. + * The value is based on the uncompressed size of header fields, + * including the length of the name and value in octets plus an + * overhead of 32 octets for each header field. + */ + +}}; +#endif + LWS_VISIBLE void * lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot, int size) @@ -268,9 +326,15 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { - lwsl_debug("writing chunk terminator and exiting\n"); - n = lws_write(wsi, (unsigned char *)"0\x0d\x0a\x0d\x0a", - 5, LWS_WRITE_HTTP); + if (!wsi->http2_substream) { + memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); + lwsl_debug("writing chunk terminator and exiting\n"); + n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, + 5, LWS_WRITE_HTTP); + } else + n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, + 0, LWS_WRITE_HTTP_FINAL); + /* always close after sending it */ return -1; } @@ -392,7 +456,8 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", wsi->cgi->explicitly_chunked, (uint64_t)wsi->cgi->content_length); - if (!wsi->cgi->explicitly_chunked && !wsi->cgi->content_length) { + if (!wsi->cgi->explicitly_chunked && + !wsi->cgi->content_length) { /* send terminating chunk */ lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; @@ -485,6 +550,13 @@ lws_create_vhost(struct lws_context *context, if (info->options & LWS_SERVER_OPTION_ONLY_RAW) lwsl_info("%s set to only support RAW\n", vh->name); +#if defined(LWS_WITH_HTTP2) + vh->set = context->set; + if (info->http2_settings[0]) + for (n = 1; n < LWS_H2_SETTINGS_LEN; n++) + vh->set.s[n] = info->http2_settings[n]; +#endif + vh->iface = info->iface; #if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) vh->bind_iface = info->bind_iface; @@ -790,6 +862,11 @@ lws_create_context(struct lws_context_creation_info *info) #endif #if LWS_POSIX lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH); +#endif +#if defined(LWS_WITH_HTTP2) + lwsl_info(" HTTP2 support : available\n"); +#else + lwsl_info(" HTTP2 support : not configured"); #endif if (lws_plat_context_early_init()) return NULL; @@ -804,6 +881,10 @@ lws_create_context(struct lws_context_creation_info *info) else context->pt_serv_buf_size = 4096; +#if defined(LWS_WITH_HTTP2) + context->set = lws_h2_stock_settings; +#endif + #if LWS_MAX_SMP > 1 pthread_mutex_init(&context->lock, NULL); #endif @@ -1023,6 +1104,15 @@ lws_create_context(struct lws_context_creation_info *info) if (lws_plat_init(context, info)) goto bail; +#if defined(LWS_WITH_HTTP2) + /* + * let the user code see what the platform default SETTINGS were, he + * can modify them when he creates the vhosts. + */ + for (n = 1; n < LWS_H2_SETTINGS_LEN; n++) + info->http2_settings[n] = context->set.s[n]; +#endif + lws_context_init_ssl_library(info); context->user_space = info->user; diff --git a/lib/handshake.c b/lib/handshake.c index e837a18ae..bc7609d92 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -72,24 +72,33 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: case LWSS_HTTP2_ESTABLISHED: n = 0; + //lwsl_debug("%s: starting new block of %d\n", __func__, (int)len); + /* + * wsi here is always the network connection wsi, not a stream + * wsi. + */ while (n < len) { /* * we were accepting input but now we stopped doing so */ - if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { + if (lws_is_flowcontrolled(wsi)) { lws_rxflow_cache(wsi, buf, n, len); return 1; } /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) + if (wsi->rxflow_buffer) { wsi->rxflow_pos++; - if (lws_http2_parser(wsi, buf[n++])) { + assert(wsi->rxflow_pos <= wsi->rxflow_len); + } + + if (lws_h2_parser(wsi, buf[n++])) { lwsl_debug("%s: http2_parser bailed\n", __func__); goto bail; } } + lwsl_debug("%s: used up block of %d\n", __func__, (int)len); break; #endif @@ -111,6 +120,8 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) } lwsl_parser("issuing %d bytes to parser\n", (int)len); + lwsl_hexdump(buf, (size_t)len); + if (lws_handshake_client(wsi, &buf, (size_t)len)) goto bail; @@ -145,9 +156,9 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) case LWSS_HTTP_ISSUING_FILE: goto read_ok; case LWSS_HTTP_BODY: - wsi->u.http.content_remain = - wsi->u.http.content_length; - if (wsi->u.http.content_remain) + wsi->u.http.rx_content_remain = + wsi->u.http.rx_content_length; + if (wsi->u.http.rx_content_remain) goto http_postbody; /* there is no POST content */ @@ -159,13 +170,14 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) case LWSS_HTTP_BODY: http_postbody: - while (len && wsi->u.http.content_remain) { + //lwsl_notice("http post body\n"); + while (len && wsi->u.http.rx_content_remain) { /* Copy as much as possible, up to the limit of: * what we have in the read buffer (len) * remaining portion of the POST body (content_remain) */ - body_chunk_len = min(wsi->u.http.content_remain,len); - wsi->u.http.content_remain -= body_chunk_len; + body_chunk_len = min(wsi->u.http.rx_content_remain, len); + wsi->u.http.rx_content_remain -= body_chunk_len; len -= body_chunk_len; #ifdef LWS_WITH_CGI if (wsi->cgi) { @@ -197,7 +209,7 @@ http_postbody: #endif buf += n; - if (wsi->u.http.content_remain) { + if (wsi->u.http.rx_content_remain) { lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); break; @@ -219,11 +231,15 @@ postbody_completion: if (!wsi->cgi) #endif { + lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BODY_COMPLETION, wsi->user_space, NULL, 0); if (n) goto bail; + + if (wsi->http2_substream) + wsi->state = LWSS_HTTP2_ESTABLISHED; } break; diff --git a/lib/header.c b/lib/header.c index 872da2061..e2562cd6e 100644 --- a/lib/header.c +++ b/lib/header.c @@ -107,8 +107,8 @@ int lws_add_http_header_content_length(struct lws *wsi, if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b, n, p, end)) return 1; - wsi->u.http.content_length = content_length; - wsi->u.http.content_remain = content_length; + wsi->u.http.tx_content_length = content_length; + wsi->u.http.tx_content_remain = content_length; return 0; } @@ -228,7 +228,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, unsigned char *p = pt->serv_buf + LWS_PRE; unsigned char *start = p; unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; - int n = 0, m, len; + int n = 0, m = 0, len; char slen[20]; if (!html_body) @@ -254,29 +254,65 @@ lws_return_http_status(struct lws *wsi, unsigned int code, return 1; #if defined(LWS_WITH_HTTP2) - { + if (wsi->http2_substream) { unsigned char *body = p + 512; + /* + * for HTTP/2, the headers must be sent separately, since they + * go out in their own frame. That puts us in a bind that + * we won't always be able to get away with two lws_write()s in + * sequence, since the first may use up the writability due to + * the pipe being choked or SSL_WANT_. + * + * However we do need to send the human-readable body, and the + * END_STREAM. + * + * Solve it by writing the headers now... + */ m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (m != (int)(p - start)) return 1; - len = sprintf((char *)body, "

%u

%s", - code, html_body); + /* + * ... but stash the body and send it as a priority next + * handle_POLLOUT + */ - n = len; - m = lws_write(wsi, body, len, LWS_WRITE_HTTP); - } -#else - p += lws_snprintf((char *)p, end - p - 1, - "

%u

%s", - code, html_body); + len = sprintf((char *)body, + "

%u

%s", + code, html_body); + wsi->u.http.tx_content_length = len; + wsi->u.http.tx_content_remain = len; - n = (int)(p - start); - m = lws_write(wsi, start, n, LWS_WRITE_HTTP); - if (m != n) - return 1; + wsi->u.h2.pending_status_body = lws_malloc(len + LWS_PRE + 1, + "pending status body"); + if (!wsi->u.h2.pending_status_body) + return -1; + + strcpy(wsi->u.h2.pending_status_body + LWS_PRE, + (const char *)body); + lws_callback_on_writable(wsi); + + return 0; + } else #endif + { + /* + * for http/1, we can just append the body after the finalized + * headers and send it all in one go. + */ + p += lws_snprintf((char *)p, end - p - 1, + "

%u

%s", + code, html_body); + + n = (int)(p - start); + + m = lws_write(wsi, start, n, LWS_WRITE_HTTP); + if (m != n) + return 1; + } + + lwsl_notice("%s: return\n", __func__); return m != n; } @@ -313,7 +349,7 @@ lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, if (lws_finalize_http_header(wsi, p, end)) return -1; - n = lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS); + n = lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); return n; } diff --git a/lib/hpack.c b/lib/hpack.c index a07d13e47..f7d916f93 100644 --- a/lib/hpack.c +++ b/lib/hpack.c @@ -1,7 +1,7 @@ /* * lib/hpack.c * - * Copyright (C) 2014 Andy Green + * Copyright (C) 2014-2017 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -88,6 +88,17 @@ +-------+-----------------------------+---------------+ */ +static const uint8_t static_hdr_len[] = { + 0, /* starts at 1 */ + 10, 7, 7, 5, 5, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 14, 15, 15, 13, 6, 27, + 3, 5, 13, 13, 19, 16, 16, 14, 16, 13, + 12, 6, 4, 4, 6, 7, 4, 4, 8, 17, + 13, 8, 19, 13, 4, 8, 12, 18, 19, + 5, 7, 7, 11, 6, 10, 25, 17, 10, 4, + 3, 16 +}; + static const unsigned char static_token[] = { 0, WSI_TOKEN_HTTP_COLON_AUTHORITY, @@ -189,15 +200,19 @@ static int huftable_decode(int pos, char c) return pos + (lextable[q] << 1); } -static int lws_hpack_update_table_size(struct lws *wsi, int idx) -{ - lwsl_info("hpack set table size %d\n", idx); - return 0; -} - static int lws_frag_start(struct lws *wsi, int hdr_token_idx) { - struct allocated_headers * ah = wsi->u.http2.http.ah; + struct allocated_headers *ah = wsi->u.h2.http.ah; + + if (!ah) { + lwsl_notice("%s: no ah\n", __func__); + return 1; + } + + ah->hdr_token_idx = -1; + + lwsl_header("%s: token %d ah->pos = %d, ah->nfrag = %d\n", + __func__, hdr_token_idx, ah->pos, ah->nfrag); if (!hdr_token_idx) { lwsl_err("%s: zero hdr_token_idx\n", __func__); @@ -209,9 +224,28 @@ static int lws_frag_start(struct lws *wsi, int hdr_token_idx) return 1; } + if ((hdr_token_idx == WSI_TOKEN_HTTP_COLON_AUTHORITY || + hdr_token_idx == WSI_TOKEN_HTTP_COLON_METHOD || + hdr_token_idx == WSI_TOKEN_HTTP_COLON_PATH || + hdr_token_idx == WSI_TOKEN_HTTP_COLON_SCHEME) && + ah->frag_index[hdr_token_idx]) { + if (!(ah->frags[ah->frag_index[hdr_token_idx]].flags & 1)) { + lws_h2_goaway(lws_get_network_wsi(wsi), + H2_ERR_PROTOCOL_ERROR, + "Duplicated pseudoheader"); + return 1; + } + } + + if (ah->nfrag == 0) + ah->nfrag = 1; + ah->frags[ah->nfrag].offset = ah->pos; ah->frags[ah->nfrag].len = 0; ah->frags[ah->nfrag].nfrag = 0; + ah->frags[ah->nfrag].flags = 2; /* we had reason to set it */ + + ah->hdr_token_idx = hdr_token_idx; ah->frag_index[hdr_token_idx] = ah->nfrag; @@ -220,7 +254,7 @@ static int lws_frag_start(struct lws *wsi, int hdr_token_idx) static int lws_frag_append(struct lws *wsi, unsigned char c) { - struct allocated_headers * ah = wsi->u.http2.http.ah; + struct allocated_headers * ah = wsi->u.h2.http.ah; ah->data[ah->pos++] = c; ah->frags[ah->nfrag].len++; @@ -230,121 +264,435 @@ static int lws_frag_append(struct lws *wsi, unsigned char c) static int lws_frag_end(struct lws *wsi) { + lwsl_header("%s\n", __func__); if (lws_frag_append(wsi, 0)) return 1; - wsi->u.http2.http.ah->nfrag++; + /* don't account for the terminating NUL in the logical length */ + wsi->u.h2.http.ah->frags[wsi->u.h2.http.ah->nfrag].len--; + + wsi->u.h2.http.ah->nfrag++; return 0; } +int +lws_hdr_extant(struct lws *wsi, enum lws_token_indexes h) +{ + struct allocated_headers *ah = wsi->u.h2.http.ah; + int n; + + if (!ah) + return 0; + + n = ah->frag_index[h]; + if (!n) + return 0; + + return !!(ah->frags[n].flags & 2); +} + static void lws_dump_header(struct lws *wsi, int hdr) { char s[200]; - int len = lws_hdr_copy(wsi, s, sizeof(s) - 1, hdr); - s[len] = '\0'; - lwsl_info(" hdr tok %d (%s) = '%s'\n", hdr, lws_token_to_string(hdr), s); + const unsigned char *p; + int len; + + if (hdr == LWS_HPACK_IGNORE_ENTRY) { + lwsl_notice("hdr tok ignored\n"); + return; + } + + (void)p; + + len = lws_hdr_copy(wsi, s, sizeof(s) - 1, hdr); + if (len < 0) + strcpy(s, "(too big to show)"); + else + s[len] = '\0'; + p = lws_token_to_string(hdr); + lwsl_header(" hdr tok %d (%s) = '%s' (len %d)\n", hdr, + p ? (char *)p : (char *)"null", s, len); } +/* + * dynamic table + * + * [ 0 .... num_entries - 1] + * + * Starts filling at 0+ + * + * #62 is *most recently entered* + * + * Number of entries is not restricted, but aggregated size of the entry + * payloads is. Unfortunately the way HPACK does this is specific to an + * imagined implementation, and lws implementation is much more efficient + * (ignoring unknown headers and using the lws token index for the header + * name part). + */ + +/* + * returns 0 if dynamic entry (arg and len are filled) + * returns -1 if failure + * returns nonzero token index if actually static token + */ static int -lws_token_from_index(struct lws *wsi, int index, char **arg, int *len) +lws_token_from_index(struct lws *wsi, int index, const char **arg, int *len, + uint32_t *hdr_len) { struct hpack_dynamic_table *dyn; + if (index == LWS_HPACK_IGNORE_ENTRY) + return LWS_HPACK_IGNORE_ENTRY; + /* dynamic table only belongs to network wsi */ + wsi = lws_get_network_wsi(wsi); + if (!wsi->u.h2.h2n) + return -1; - wsi = lws_http2_get_network_wsi(wsi); + dyn = &wsi->u.h2.h2n->hpack_dyn_table; - dyn = wsi->u.http2.hpack_dyn_table; + if (index < 0) + return -1; + + if (index < ARRAY_SIZE(static_token)) { + if (arg && index < ARRAY_SIZE(http2_canned)) { + *arg = http2_canned[index]; + *len = strlen(http2_canned[index]); + } + if (hdr_len) + *hdr_len = static_hdr_len[index]; - if (index < ARRAY_SIZE(static_token)) return static_token[index]; - - if (!dyn) - return 0; - - index -= ARRAY_SIZE(static_token); - if (index >= dyn->num_entries) - return 0; - - if (arg && len) { - *arg = dyn->args + dyn->entries[index].arg_offset; - *len = dyn->entries[index].arg_len; } - return dyn->entries[index].token; + if (!dyn) { + lwsl_notice("no dynamic table\n"); + return -1; + } + + if (index < ARRAY_SIZE(static_token) || + index >= ARRAY_SIZE(static_token) + dyn->used_entries) { + lwsl_err(" %s: adjusted index %d >= %d\n", __func__, index, + dyn->used_entries); + lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR, + "index out of range"); + return -1; + } + + index -= ARRAY_SIZE(static_token); + index = (dyn->pos - 1 - index) % dyn->num_entries; + if (index < 0) + index += dyn->num_entries; + + lwsl_header("%s: dyn index %d, tok %d\n", __func__, index, dyn->entries[index].lws_hdr_idx); + + if (arg && len) { + *arg = dyn->entries[index].value; + *len = dyn->entries[index].value_len; + } + + if (hdr_len) + *hdr_len = dyn->entries[index].hdr_len; + + return dyn->entries[index].lws_hdr_idx; } static int -lws_hpack_add_dynamic_header(struct lws *wsi, int token, char *arg, int len) +lws_h2_dynamic_table_dump(struct lws *wsi) +{ +#if 0 + struct lws *nwsi = lws_get_network_wsi(wsi); + struct hpack_dynamic_table *dyn; + int n, m; + const char *p; + + if (!nwsi->u.h2.h2n) + return 1; + dyn = &nwsi->u.h2.h2n->hpack_dyn_table; + + lwsl_header("Dump dyn table for nwsi %p (%d / %d members, pos = %d, start index %d, virt used %d / %d)\n", nwsi, + dyn->used_entries, dyn->num_entries, dyn->pos, (uint32_t)ARRAY_SIZE(static_token), + dyn->virtual_payload_usage, dyn->virtual_payload_max); + + for (n = 0; n < dyn->used_entries; n++) { + m = (dyn->pos - 1 - n) % dyn->num_entries; + if (m < 0) + m += dyn->num_entries; + if (dyn->entries[m].lws_hdr_idx != LWS_HPACK_IGNORE_ENTRY) + p = (const char *)lws_token_to_string( + dyn->entries[m].lws_hdr_idx); + else + p = "(ignored)"; + lwsl_header(" %3d: tok %s: (len %d) val '%s'\n", + (int)(n + ARRAY_SIZE(static_token)), p, dyn->entries[m].hdr_len, + dyn->entries[m].value ? dyn->entries[m].value : "null"); + } +#endif + return 0; +} + +static void +lws_dynamic_free(struct hpack_dynamic_table *dyn, int idx) +{ + lwsl_header("freeing %d for reuse\n", idx); + dyn->virtual_payload_usage -= dyn->entries[idx].value_len + + dyn->entries[idx].hdr_len; + lws_free_set_NULL(dyn->entries[idx].value); + dyn->entries[idx].value = NULL; + dyn->entries[idx].value_len = 0; + dyn->entries[idx].hdr_len = 0; + dyn->entries[idx].lws_hdr_idx = LWS_HPACK_IGNORE_ENTRY; + dyn->used_entries--; +} + +/* + * There are two address spaces, 1) internal ringbuffer and 2) HPACK indexes. + * + * Internal ringbuffer: + * + * The internal ringbuffer wraps as we keep filling it, dyn->pos points to + * the next index to be written. + * + * HPACK indexes: + * + * The last-written entry becomes entry 0, the previously-last-written entry + * becomes entry 1 etc. + */ + +static int +lws_dynamic_token_insert(struct lws *wsi, int hdr_len, + int lws_hdr_index, char *arg, int len) { struct hpack_dynamic_table *dyn; - int ret = 1; + int new_index, n; - wsi = lws_http2_get_network_wsi(wsi); - dyn = wsi->u.http2.hpack_dyn_table; + /* dynamic table only belongs to network wsi */ + wsi = lws_get_network_wsi(wsi); + if (!wsi->u.h2.h2n) + return 1; + dyn = &wsi->u.h2.h2n->hpack_dyn_table; - if (!dyn) { - dyn = lws_zalloc(sizeof(*dyn), "hpack dyn"); - if (!dyn) - return 1; - wsi->u.http2.hpack_dyn_table = dyn; + if (!dyn->entries) { + lwsl_err("%s: unsized dyn table\n", __func__); - dyn->args = lws_malloc(1024, "hpack"); - if (!dyn->args) - goto bail1; - dyn->args_length = 1024; - dyn->entries = lws_malloc(sizeof(dyn->entries[0]) * 20, "hpack dyn entries"); - if (!dyn->entries) - goto bail2; - dyn->num_entries = 20; + return 1; + } + lws_h2_dynamic_table_dump(wsi); + + new_index = (dyn->pos) % dyn->num_entries; + if (dyn->num_entries && dyn->used_entries == dyn->num_entries) { + if (dyn->virtual_payload_usage < dyn->virtual_payload_max) + lwsl_err("Dropping header content before limit!\n"); + /* we have to drop the oldest to make space */ + lws_dynamic_free(dyn, new_index); } - if (dyn->next == dyn->num_entries) - return 1; + /* + * evict guys to make room, allowing for some overage. We have to + * take care about getting a single huge header, and evicting + * everything + */ - if (dyn->args_length - dyn->pos < len) - return 1; + while (dyn->virtual_payload_usage && + dyn->used_entries && + dyn->virtual_payload_usage + hdr_len + len > + dyn->virtual_payload_max + 1024) { + n = (dyn->pos - dyn->used_entries) % dyn->num_entries; + if (n < 0) + n += dyn->num_entries; + lws_dynamic_free(dyn, n); + } - dyn->entries[dyn->next].token = token; - dyn->entries[dyn->next].arg_offset = dyn->pos; - if (len) - memcpy(dyn->args + dyn->pos, arg, len); - dyn->entries[dyn->next].arg_len = len; + if (dyn->used_entries < dyn->num_entries) + dyn->used_entries++; - lwsl_info("%s: added dynamic hdr %d, token %d (%s), len %d\n", - __func__, dyn->next, token, lws_token_to_string(token), len); + dyn->entries[new_index].value_len = 0; - dyn->pos += len; - dyn->next++; + if (lws_hdr_index != LWS_HPACK_IGNORE_ENTRY) { + dyn->entries[new_index].value = lws_malloc(len + 1, "hpack dyn"); + if (!dyn->entries[new_index].value) + return 1; + + memcpy(dyn->entries[new_index].value, arg, len); + dyn->entries[new_index].value[len] = '\0'; + dyn->entries[new_index].value_len = len; + } else + dyn->entries[new_index].value = NULL; + + dyn->entries[new_index].lws_hdr_idx = lws_hdr_index; + dyn->entries[new_index].hdr_len = hdr_len; + + dyn->virtual_payload_usage += hdr_len + len; + + lwsl_info("%s: index %ld: lws_hdr_index 0x%x, hdr len %d, '%s' len %d\n", + __func__, (long)ARRAY_SIZE(static_token), + lws_hdr_index, hdr_len, dyn->entries[new_index].value ? + dyn->entries[new_index].value : "null", len); + + dyn->pos = (dyn->pos + 1) % dyn->num_entries; + + lws_h2_dynamic_table_dump(wsi); + + return 0; +} + +int +lws_hpack_dynamic_size(struct lws *wsi, int size) +{ + struct hpack_dynamic_table *dyn; + struct hpack_dt_entry *dte; + struct lws *nwsi; + int min, n = 0, m; + + /* + * "size" here is coming from the http/2 SETTING + * SETTINGS_HEADER_TABLE_SIZE. This is a (virtual, in our case) + * linear buffer containing dynamic header names and values... when it + * is full, old entries are evicted. + * + * We encode the header as an lws_hdr_idx, which is all the rest of + * lws cares about; if there is no matching header we store an empty + * entry in the dyn table as a placeholder. + * + * So to make the two systems work together we keep an accounting of + * what we are using to decide when to evict... we must only evict + * things when the remote peer's accounting also makes him feel he + * should evict something. + */ + + nwsi = lws_get_network_wsi(wsi); + if (!nwsi->u.h2.h2n) + goto bail; + + dyn = &nwsi->u.h2.h2n->hpack_dyn_table; + lwsl_info("%s: from %d to %d\n", __func__, (int)dyn->num_entries, size); + + if (size > nwsi->u.h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]) { + lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR, + "Asked for header table bigger than we told"); + goto bail; + } + + dyn->virtual_payload_max = size; + + size = size / 8; + min = size; + if (min > dyn->used_entries) + min = dyn->used_entries; + + if (size == dyn->num_entries) + return 0; + + if (dyn->num_entries < min) + min = dyn->num_entries; + + dte = lws_zalloc(sizeof(*dte) * (size + 1), "dynamic table entries"); + if (!dte) + goto bail; + + while (dyn->virtual_payload_usage && dyn->used_entries && + dyn->virtual_payload_usage > dyn->virtual_payload_max) { + n = (dyn->pos - dyn->used_entries) % dyn->num_entries; + if (n < 0) + n += dyn->num_entries; + lws_dynamic_free(dyn, n); + } + + if (min > dyn->used_entries) + min = dyn->used_entries; + + if (dyn->entries) { + for (n = 0; n < min; n++) { + m = (dyn->pos - dyn->used_entries + n) % dyn->num_entries; + if (m < 0) + m += dyn->num_entries; + dte[n] = dyn->entries[m]; + } + + lws_free(dyn->entries); + } + + dyn->entries = dte; + dyn->num_entries = size; + dyn->used_entries = min; + dyn->pos = min % size; + + lws_h2_dynamic_table_dump(wsi); return 0; -bail2: - lws_free(dyn->args); -bail1: - lws_free(dyn); - wsi->u.http2.hpack_dyn_table = NULL; +bail: + lwsl_info("%s: failed to resize to %d\n", __func__, size); - return ret; + return 1; } -static int lws_write_indexed_hdr(struct lws *wsi, int idx) +void +lws_hpack_destroy_dynamic_header(struct lws *wsi) { - const char *p; - int tok = lws_token_from_index(wsi, idx, NULL, 0); + struct hpack_dynamic_table *dyn; + int n; - lwsl_info("writing indexed hdr %d (tok %d '%s')\n", idx, tok, - lws_token_to_string(tok)); + if (!wsi->u.h2.h2n) + return; + + dyn = &wsi->u.h2.h2n->hpack_dyn_table; + + if (!dyn->entries) + return; + + for (n = 0; n < dyn->num_entries; n++) + if (dyn->entries[n].value) + lws_free_set_NULL(dyn->entries[n].value); + + lws_free_set_NULL(dyn->entries); +} + +static int +lws_hpack_use_idx_hdr(struct lws *wsi, int idx, int known_token) +{ + const char *arg = NULL; + int len = 0; + const char *p = NULL; + int tok = lws_token_from_index(wsi, idx, &arg, &len, NULL); + + if (tok == LWS_HPACK_IGNORE_ENTRY) { + lwsl_header("%s: lws_token says ignore, returning\n", __func__); + return 0; + } + + if (tok == -1) { + lwsl_info("%s: idx %d mapped to tok %d\n", __func__, idx, tok); + return 1; + } + + if (arg) { + /* dynamic result */ + if (known_token > 0) + tok = known_token; + lwsl_header("%s: dyn: idx %d '%s' tok %d\n", __func__, idx, arg, + tok); + } else + lwsl_header("writing indexed hdr %d (tok %d '%s')\n", idx, tok, + lws_token_to_string(tok)); + + if (tok == LWS_HPACK_IGNORE_ENTRY) + return 0; + + if (arg) + p = arg; + + if (idx < ARRAY_SIZE(http2_canned)) + p = http2_canned[idx]; if (lws_frag_start(wsi, tok)) return 1; - if (idx < ARRAY_SIZE(http2_canned)) { - p = http2_canned[idx]; - while (*p) + if (p) + while (*p && len--) if (lws_frag_append(wsi, *p++)) return 1; - } + if (lws_frag_end(wsi)) return 1; @@ -355,320 +703,590 @@ static int lws_write_indexed_hdr(struct lws *wsi, int idx) int lws_hpack_interpret(struct lws *wsi, unsigned char c) { + struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws_h2_netconn *h2n = nwsi->u.h2.h2n; + struct allocated_headers *ah = wsi->u.h2.http.ah; unsigned int prev; unsigned char c1; - int n; + int n, m; - lwsl_debug(" state %d\n", wsi->u.http2.hpack); + if (!h2n) + return -1; - switch (wsi->u.http2.hpack) { - case HPKS_OPT_PADDING: - wsi->u.http2.padding = c; - lwsl_info("padding %d\n", c); - if (wsi->u.http2.flags & LWS_HTTP2_FLAG_PRIORITY) { - wsi->u.http2.hpack = HKPS_OPT_E_DEPENDENCY; - wsi->u.http2.hpack_m = 4; - } else - wsi->u.http2.hpack = HPKS_TYPE; - break; - case HKPS_OPT_E_DEPENDENCY: - wsi->u.http2.hpack_e_dep <<= 8; - wsi->u.http2.hpack_e_dep |= c; - if (! --wsi->u.http2.hpack_m) { - lwsl_info("hpack_e_dep = 0x%x\n", wsi->u.http2.hpack_e_dep); - wsi->u.http2.hpack = HKPS_OPT_WEIGHT; - } - break; - case HKPS_OPT_WEIGHT: - /* weight */ - wsi->u.http2.hpack = HPKS_TYPE; - break; + /* + * HPKT_INDEXED_HDR_7 1xxxxxxx: just "header field" + * HPKT_INDEXED_HDR_6_VALUE_INCR 01xxxxxx: NEW indexed hdr + val + * HPKT_LITERAL_HDR_VALUE_INCR 01000000: NEW literal hdr + val + * HPKT_INDEXED_HDR_4_VALUE 0000xxxx: indexed hdr + val + * HPKT_INDEXED_HDR_4_VALUE_NEVER 0001xxxx: NEVER NEW indexed hdr + val + * HPKT_LITERAL_HDR_VALUE 00000000: literal hdr + val + * HPKT_LITERAL_HDR_VALUE_NEVER 00010000: NEVER NEW literal hdr + val + */ + switch (h2n->hpack) { case HPKS_TYPE: + h2n->is_first_header_char = 1; + h2n->huff_pad = 0; + h2n->zero_huff_padding = 0; + h2n->last_action_dyntable_resize = 0; + h2n->ext_count = 0; + h2n->hpack_hdr_len = 0; + h2n->unknown_header = 0; + h2n->seen_nonpseudoheader = 0; - if (wsi->u.http2.count > (wsi->u.http2.length - wsi->u.http2.padding)) { - lwsl_info("padding eat\n"); - break; - } - - if (c & 0x80) { /* indexed header field only */ + if (c & 0x80) { /* 1.... indexed header field only */ /* just a possibly-extended integer */ - wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_7; - lwsl_debug("HKPS_TYPE setting header_index %d\n", c & 0x7f); - wsi->u.http2.header_index = c & 0x7f; + h2n->hpack_type = HPKT_INDEXED_HDR_7; + lwsl_header("HPKT_INDEXED_HDR_7 hdr %d\n", c & 0x7f); + lws_h2_dynamic_table_dump(wsi); + + h2n->hdr_idx = c & 0x7f; if ((c & 0x7f) == 0x7f) { - wsi->u.http2.hpack_len = c & 0x7f; - wsi->u.http2.hpack_m = 0; - wsi->u.http2.hpack = HPKS_IDX_EXT; + h2n->hpack_len = 0; + h2n->hpack_m = 0x7f; + h2n->hpack = HPKS_IDX_EXT; break; } - lwsl_debug("HKPS_TYPE: %d\n", c & 0x7f); - if (lws_write_indexed_hdr(wsi, c & 0x7f)) + if (!h2n->hdr_idx) { + lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR, + "hdr index 0 seen"); + return 1; + } + lwsl_header("HPKT_INDEXED_HDR_7: hdr %d\n", c & 0x7f); + if (lws_hpack_use_idx_hdr(wsi, c & 0x7f, -1)) { + lwsl_header("%s: idx hdr wr fail\n", __func__); return 1; + } /* stay at same state */ break; } - if (c & 0x40) { /* literal header incr idx */ + if (c & 0x40) { /* 01.... indexed or literal header incr idx */ /* - * [possibly-extended hdr idx (6) | new literal hdr name] - * H + possibly-extended value length + * [possibly-ext hdr idx (6) | new literal hdr name] + * H + possibly-ext value length * literal value */ - lwsl_debug("HKPS_TYPE 2 setting header_index %d\n", 0); - wsi->u.http2.header_index = 0; - if (c == 0x40) { /* literal name */ - wsi->u.http2.hpack_type = HPKT_LITERAL_HDR_VALUE_INCR; - wsi->u.http2.value = 0; - wsi->u.http2.hpack = HPKS_HLEN; + h2n->hdr_idx = 0; + if (c == 0x40) { /* literal header */ + lwsl_header(" HPKT_LITERAL_HDR_VALUE_INCR\n"); + h2n->hpack_type = HPKT_LITERAL_HDR_VALUE_INCR; + h2n->value = 0; + h2n->hpack_len = 0; + h2n->hpack = HPKS_HLEN; break; } - /* indexed name */ - wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_6_VALUE_INCR; + /* indexed header */ + h2n->hpack_type = HPKT_INDEXED_HDR_6_VALUE_INCR; + lwsl_header(" HPKT_INDEXED_HDR_6_VALUE_INCR (hdr %d)\n", + c & 0x3f); + h2n->hdr_idx = c & 0x3f; if ((c & 0x3f) == 0x3f) { - wsi->u.http2.hpack_len = c & 0x3f; - wsi->u.http2.hpack_m = 0; - wsi->u.http2.hpack = HPKS_IDX_EXT; + h2n->hpack_m = 0x3f; + h2n->hpack_len = 0; + h2n->hpack = HPKS_IDX_EXT; break; } - lwsl_debug("HKPS_TYPE 3 setting header_index %d\n", c & 0x3f); - wsi->u.http2.header_index = c & 0x3f; - wsi->u.http2.value = 1; - wsi->u.http2.hpack = HPKS_HLEN; + + h2n->value = 1; + h2n->hpack = HPKS_HLEN; + if (!h2n->hdr_idx) { + lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR, + "hdr index 0 seen"); + return 1; + } break; } switch(c & 0xf0) { case 0x10: /* literal header never index */ - case 0: /* literal header without indexing */ + case 0: /* literal header without indexing */ /* * follows 0x40 except 4-bit hdr idx * and don't add to index */ if (c == 0) { /* literal name */ - wsi->u.http2.hpack_type = HPKT_LITERAL_HDR_VALUE; - wsi->u.http2.hpack = HPKS_HLEN; - wsi->u.http2.value = 0; + h2n->hpack_type = HPKT_LITERAL_HDR_VALUE; + lwsl_header(" HPKT_LITERAL_HDR_VALUE\n"); + h2n->hpack = HPKS_HLEN; + h2n->value = 0; break; } - //lwsl_debug("indexed\n"); + if (c == 0x10) { /* literal name NEVER */ + h2n->hpack_type = HPKT_LITERAL_HDR_VALUE_NEVER; + lwsl_header(" HPKT_LITERAL_HDR_VALUE_NEVER\n"); + h2n->hpack = HPKS_HLEN; + h2n->value = 0; + break; + } + lwsl_header("indexed\n"); /* indexed name */ - wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_4_VALUE; - wsi->u.http2.header_index = 0; + if (c & 0x10) { + h2n->hpack_type = HPKT_INDEXED_HDR_4_VALUE_NEVER; + lwsl_header(" HPKT_LITERAL_HDR_4_VALUE_NEVER\n"); + } else { + h2n->hpack_type = HPKT_INDEXED_HDR_4_VALUE; + lwsl_header(" HPKT_INDEXED_HDR_4_VALUE\n"); + } + h2n->hdr_idx = 0; if ((c & 0xf) == 0xf) { - wsi->u.http2.hpack_len = c & 0xf; - wsi->u.http2.hpack_m = 0; - wsi->u.http2.hpack = HPKS_IDX_EXT; + h2n->hpack_len = c & 0xf; + h2n->hpack_m = 0xf; + h2n->hpack_len = 0; + h2n->hpack = HPKS_IDX_EXT; break; } - //lwsl_err("HKPS_TYPE 5 setting header_index %d\n", c & 0xf); - wsi->u.http2.header_index = c & 0xf; - wsi->u.http2.value = 1; - wsi->u.http2.hpack = HPKS_HLEN; + h2n->hdr_idx = c & 0xf; + h2n->value = 1; + h2n->hpack = HPKS_HLEN; break; case 0x20: case 0x30: /* header table size update */ /* possibly-extended size value (5) */ - wsi->u.http2.hpack_type = HPKT_SIZE_5; - if ((c & 0x1f) == 0x1f) { - wsi->u.http2.hpack_len = c & 0x1f; - wsi->u.http2.hpack_m = 0; - wsi->u.http2.hpack = HPKS_IDX_EXT; + lwsl_header("HPKT_SIZE_5 %x\n", c &0x1f); + h2n->hpack_type = HPKT_SIZE_5; + h2n->hpack_len = c & 0x1f; + if (h2n->hpack_len == 0x1f) { + h2n->hpack_m = 0x1f; + h2n->hpack_len = 0; + h2n->hpack = HPKS_IDX_EXT; break; } - lws_hpack_update_table_size(wsi, c & 0x1f); - /* stay at HPKS_TYPE state */ + h2n->last_action_dyntable_resize = 1; + if (lws_hpack_dynamic_size(wsi, h2n->hpack_len)) + return 1; break; } break; case HPKS_IDX_EXT: - wsi->u.http2.hpack_len += (c & 0x7f) << wsi->u.http2.hpack_m; - wsi->u.http2.hpack_m += 7; - if (!(c & 0x80)) { - switch (wsi->u.http2.hpack_type) { - case HPKT_INDEXED_HDR_7: - //lwsl_err("HKPS_IDX_EXT hdr idx %d\n", wsi->u.http2.hpack_len); - if (lws_write_indexed_hdr(wsi, wsi->u.http2.hpack_len)) - return 1; - wsi->u.http2.hpack = HPKS_TYPE; - break; - default: - // lwsl_err("HKPS_IDX_EXT setting header_index %d\n", - // wsi->u.http2.hpack_len); - wsi->u.http2.header_index = wsi->u.http2.hpack_len; - wsi->u.http2.value = 1; - wsi->u.http2.hpack = HPKS_HLEN; - break; + h2n->hpack_len = h2n->hpack_len | + ((c & 0x7f) << h2n->ext_count); + h2n->ext_count += 7; + if (c & 0x80) /* extended int not complete yet */ + break; + + /* extended integer done */ + h2n->hpack_len += h2n->hpack_m; + lwsl_header("HPKS_IDX_EXT: hpack_len %d\n", h2n->hpack_len); + + switch (h2n->hpack_type) { + case HPKT_INDEXED_HDR_7: + if (lws_hpack_use_idx_hdr(wsi, h2n->hpack_len, + h2n->hdr_idx)) { + lwsl_notice("%s: hd7 use fail\n", __func__); + return 1; } + h2n->hpack = HPKS_TYPE; + break; + + case HPKT_SIZE_5: + h2n->last_action_dyntable_resize = 1; + if (lws_hpack_dynamic_size(wsi, h2n->hpack_len)) + return 1; + h2n->hpack = HPKS_TYPE; + break; + + default: + h2n->hdr_idx = h2n->hpack_len; + if (!h2n->hdr_idx) { + lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR, + "extended header index was 0"); + return 1; + } + h2n->value = 1; + h2n->hpack = HPKS_HLEN; + break; } break; case HPKS_HLEN: /* [ H | 7+ ] */ - wsi->u.http2.huff = !!(c & 0x80); - wsi->u.http2.hpack_pos = 0; - wsi->u.http2.hpack_len = c & 0x7f; - if (wsi->u.http2.hpack_len < 0x7f) { -pre_data: - if (wsi->u.http2.value) { - if (wsi->u.http2.header_index) - if (lws_frag_start(wsi, lws_token_from_index(wsi, - wsi->u.http2.header_index, - NULL, NULL))) { - // lwsl_notice("%s: hlen failed\n", __func__); - return 1; - } - } else - wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; - wsi->u.http2.hpack = HPKS_DATA; + h2n->huff = !!(c & 0x80); + h2n->hpack_pos = 0; + h2n->hpack_len = c & 0x7f; + + if (h2n->hpack_len == 0x7f) { + h2n->hpack_m = 0x7f; + h2n->hpack_len = 0; + h2n->ext_count = 0; + h2n->hpack = HPKS_HLEN_EXT; + break; + } +pre_data: + h2n->hpack = HPKS_DATA; + if (!h2n->value || !h2n->hdr_idx) { + wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; + wsi->u.hdr.lextable_pos = 0; + h2n->unknown_header = 0; + break; + } + + if (h2n->hpack_type == HPKT_LITERAL_HDR_VALUE || + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE_INCR || + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE_NEVER) { + n = wsi->u.hdr.parser_state; + if (n == 255) { + n = -1; + h2n->hdr_idx = -1; + } else + h2n->hdr_idx = 1; + } else { + n = lws_token_from_index(wsi, h2n->hdr_idx, NULL, NULL, NULL); + lwsl_header(" lws_tok_from_idx(%d) says %d\n", + h2n->hdr_idx, n); + } + + if (n == LWS_HPACK_IGNORE_ENTRY || n == -1) + h2n->hdr_idx = LWS_HPACK_IGNORE_ENTRY; + + switch (h2n->hpack_type) { + /* + * hpack types with literal headers were parsed by the lws + * header SM... on recognition of a known lws header, it does + * the correct lws_frag_start() for us already. Other types + * (ie, indexed header) need us to do it here. + */ + case HPKT_LITERAL_HDR_VALUE_INCR: + case HPKT_LITERAL_HDR_VALUE: + case HPKT_LITERAL_HDR_VALUE_NEVER: + break; + default: + if (n != -1 && n != LWS_HPACK_IGNORE_ENTRY && + lws_frag_start(wsi, n)) { + lwsl_header("%s: frag start failed\n", __func__); + return 1; + } break; } - wsi->u.http2.hpack_m = 0; - wsi->u.http2.hpack = HPKS_HLEN_EXT; break; case HPKS_HLEN_EXT: - wsi->u.http2.hpack_len += (c & 0x7f) << - wsi->u.http2.hpack_m; - wsi->u.http2.hpack_m += 7; - if (!(c & 0x80)) - goto pre_data; + h2n->hpack_len = h2n->hpack_len | + ((c & 0x7f) << h2n->ext_count); + h2n->ext_count += 7; + if (c & 0x80) /* extended integer not complete yet */ + break; - break; + h2n->hpack_len += h2n->hpack_m; + goto pre_data; case HPKS_DATA: + //lwsl_header(" 0x%02X huff %d\n", c, h2n->huff); + c1 = c; + for (n = 0; n < 8; n++) { - if (wsi->u.http2.huff) { - prev = wsi->u.http2.hpack_pos; - wsi->u.http2.hpack_pos = huftable_decode( - wsi->u.http2.hpack_pos, - (c >> 7) & 1); + if (h2n->huff) { + char b = (c >> 7) & 1; + prev = h2n->hpack_pos; + h2n->hpack_pos = huftable_decode( + h2n->hpack_pos, b); c <<= 1; - if (wsi->u.http2.hpack_pos == 0xffff) + if (h2n->hpack_pos == 0xffff) { + lwsl_notice("Huffman err\n"); return 1; - if (!(wsi->u.http2.hpack_pos & 0x8000)) + } + if (!(h2n->hpack_pos & 0x8000)) { + if (!b) + h2n->zero_huff_padding = 1; + h2n->huff_pad++; continue; - c1 = wsi->u.http2.hpack_pos & 0x7fff; - wsi->u.http2.hpack_pos = 0; + } + c1 = h2n->hpack_pos & 0x7fff; + h2n->hpack_pos = 0; + h2n->huff_pad = 0; + h2n->zero_huff_padding = 0; - if (!c1 && prev == HUFTABLE_0x100_PREV) - ; /* EOT */ - } else { + /* EOS |11111111|11111111|11111111|111111 */ + if (!c1 && prev == HUFTABLE_0x100_PREV) { + lws_h2_goaway(nwsi, + H2_ERR_COMPRESSION_ERROR, + "Huffman EOT seen"); + return 1; + } + } else n = 8; - c1 = c; - } - if (wsi->u.http2.value) { /* value */ - if (wsi->u.http2.header_index) - if (lws_frag_append(wsi, c1)) + + if (h2n->value) { /* value */ + + if (h2n->hdr_idx && + h2n->hdr_idx != LWS_HPACK_IGNORE_ENTRY) { + + if (ah->hdr_token_idx == WSI_TOKEN_HTTP_COLON_PATH) { + + switch (lws_parse_urldecode(wsi, &c1)) { + case LPUR_CONTINUE: + break; + case LPUR_SWALLOW: + goto swallow; + case LPUR_EXCESSIVE: + case LPUR_FORBID: + lws_h2_goaway(nwsi, + H2_ERR_PROTOCOL_ERROR, + "Evil URI"); + return 1; + + default: + return -1; + } + } + if (lws_frag_append(wsi, c1)) { + lwsl_notice("%s: frag app fail\n", + __func__); return 1; - } else { /* name */ - if (lws_parse(wsi, c1)) + } + } else + lwsl_header("ignoring %c\n", c1); + } else { + /* + * Convert name using existing parser, + * If h2n->unknown_header == 0, result is + * in wsi->u.hdr.parser_state + * using WSI_TOKEN_GET_URI. + * + * If unknown header h2n->unknown_header + * will be set. + */ + h2n->hpack_hdr_len++; + if (h2n->is_first_header_char) { + h2n->is_first_header_char = 0; + h2n->first_hdr_char = c1; + } + lwsl_header("parser: %c\n", c1); + /* uppercase header names illegal */ + if (c1 >= 'A' && c1 <= 'Z') { + lws_h2_goaway(nwsi, + H2_ERR_COMPRESSION_ERROR, + "Uppercase literal hpack hdr"); return 1; + } + if (!h2n->unknown_header && lws_parse(wsi, c1)) + h2n->unknown_header = 1; + } +swallow: + (void)n; + } // for n + if (--h2n->hpack_len) + break; + + /* + * The header (h2n->value = 0) or the payload (h2n->value = 1) + * is complete. + */ + + if (h2n->huff && (h2n->huff_pad > 7 || + (h2n->zero_huff_padding && h2n->huff_pad))) { + lwsl_notice("zero_huff_padding: %d huff_pad: %d\n", + h2n->zero_huff_padding, h2n->huff_pad); + lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR, + "Huffman padding excessive or wrong"); + return 1; + } + + if (!h2n->value && ( + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE || + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE_INCR || + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE_NEVER)) { + h2n->hdr_idx = LWS_HPACK_IGNORE_ENTRY; + lwsl_header("wsi->u.hdr.parser_state: %d\n", + wsi->u.hdr.parser_state); + + if (wsi->u.hdr.parser_state == WSI_TOKEN_NAME_PART) { + /* h2 headers come without the colon */ + n = lws_parse(wsi, ':'); + (void)n; + } + + if (wsi->u.hdr.parser_state == WSI_TOKEN_NAME_PART || + wsi->u.hdr.parser_state == WSI_TOKEN_SKIPPING) { + h2n->unknown_header = 1; + wsi->u.hdr.parser_state = -1; } } - if (--wsi->u.http2.hpack_len == 0) { - switch (wsi->u.http2.hpack_type) { - case HPKT_LITERAL_HDR_VALUE_INCR: - case HPKT_INDEXED_HDR_6_VALUE_INCR: // !!! - if (lws_hpack_add_dynamic_header(wsi, - lws_token_from_index(wsi, - wsi->u.http2.header_index, - NULL, NULL), NULL, 0)) - return 1; - break; - default: - break; - } + n = 8; - n = 8; - if (wsi->u.http2.value) { - if (lws_frag_end(wsi)) - return 1; - // lwsl_err("data\n"); - lws_dump_header(wsi, lws_token_from_index( - wsi, wsi->u.http2.header_index, - NULL, NULL)); - if (wsi->u.http2.count + wsi->u.http2.padding == - wsi->u.http2.length) - wsi->u.http2.hpack = HKPS_OPT_DISCARD_PADDING; - else - wsi->u.http2.hpack = HPKS_TYPE; - } else { /* name */ - //if (wsi->u.hdr.parser_state < WSI_TOKEN_COUNT) - - wsi->u.http2.value = 1; - wsi->u.http2.hpack = HPKS_HLEN; - } + /* we have the header */ + if (!h2n->value) { + h2n->value = 1; + h2n->hpack = HPKS_HLEN; + h2n->huff_pad = 0; + h2n->zero_huff_padding = 0; + h2n->ext_count = 0; + break; } - break; - case HKPS_OPT_DISCARD_PADDING: - lwsl_info("eating padding %x\n", c); - if (! --wsi->u.http2.padding) - wsi->u.http2.hpack = HPKS_TYPE; + + /* + * we have got both the header and value + */ + + switch (h2n->hpack_type) { + /* + * These are the only two that insert to the dyntable + */ + /* NEW indexed hdr with value */ + case HPKT_INDEXED_HDR_6_VALUE_INCR: + /* header length is determined by known index */ + m = lws_token_from_index(wsi, h2n->hdr_idx, NULL, NULL, + &h2n->hpack_hdr_len); + goto add_it; + /* NEW literal hdr with value */ + case HPKT_LITERAL_HDR_VALUE_INCR: + /* + * hdr is a new literal, so length is already in + * h2n->hpack_hdr_len + */ + m = wsi->u.hdr.parser_state; + if (h2n->unknown_header || + wsi->u.hdr.parser_state == WSI_TOKEN_NAME_PART || + wsi->u.hdr.parser_state == WSI_TOKEN_SKIPPING) { + if (h2n->first_hdr_char == ':') { + lwsl_info("HPKT_LITERAL_HDR_VALUE_INCR: end parser state %d unk hdr %d\n", + wsi->u.hdr.parser_state, + h2n->unknown_header); + /* unknown pseudoheaders are illegal */ + lws_h2_goaway(nwsi, + H2_ERR_PROTOCOL_ERROR, + "Unknown pseudoheader"); + return 1; + } + m = LWS_HPACK_IGNORE_ENTRY; + } +add_it: + /* + * mark us as having been set at the time of dynamic + * token insertion. + */ + ah->frags[ah->nfrag].flags |= 1; + + if (lws_dynamic_token_insert(wsi, h2n->hpack_hdr_len, m, + &ah->data[ah->frags[ah->nfrag].offset], + ah->frags[ah->nfrag].len)) { + lwsl_notice("%s: tok_insert fail\n", __func__); + return 1; + } + break; + + default: + break; + } + + if (h2n->hdr_idx != LWS_HPACK_IGNORE_ENTRY && lws_frag_end(wsi)) + return 1; + + if (h2n->hpack_type == HPKT_LITERAL_HDR_VALUE || + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE_INCR || + h2n->hpack_type == HPKT_LITERAL_HDR_VALUE_NEVER) { + m = wsi->u.hdr.parser_state; + if (m == 255) + m = -1; + } else + m = lws_token_from_index(wsi, h2n->hdr_idx, NULL, NULL, NULL); + if (m != -1 && m != LWS_HPACK_IGNORE_ENTRY) + lws_dump_header(wsi, m); + + if (h2n->seen_nonpseudoheader && ( + m == WSI_TOKEN_HTTP_COLON_AUTHORITY || + m == WSI_TOKEN_HTTP_COLON_METHOD || + m == WSI_TOKEN_HTTP_COLON_PATH || + m == WSI_TOKEN_HTTP_COLON_SCHEME)) { + /* + * it's not legal to see a + * pseudoheader after normal + * headers + */ + lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, + "Pseudoheader after normal hdrs"); + return 1; + } + + if (m != WSI_TOKEN_HTTP_COLON_AUTHORITY && + m != WSI_TOKEN_HTTP_COLON_METHOD && + m != WSI_TOKEN_HTTP_COLON_PATH && + m != WSI_TOKEN_HTTP_COLON_SCHEME) + h2n->seen_nonpseudoheader = 1; + + h2n->is_first_header_char = 1; + h2n->hpack = HPKS_TYPE; break; } return 0; } -static int lws_http2_num(int starting_bits, unsigned long num, +static int +lws_h2_num_start(int starting_bits, unsigned long num) +{ + int mask = (1 << starting_bits) - 1; + + if (num < mask) + return (int)num; + + return mask; +} + +static int +lws_h2_num(int starting_bits, unsigned long num, unsigned char **p, unsigned char *end) { int mask = (1 << starting_bits) - 1; - if (num < mask) { - *((*p)++) |= num; - return *p >= end; - } - - *((*p)++) |= mask; - if (*p >= end) - return 1; + if (num < mask) + return 0; num -= mask; - while (num >= 128) { - *((*p)++) = 0x80 | (num & 0x7f); + do { + if (num > 127) + *((*p)++) = 0x80 | (num & 0x7f); + else + *((*p)++) = 0x00 | (num & 0x7f); if (*p >= end) return 1; num >>= 7; - } + } while (num); return 0; } -int lws_add_http2_header_by_name(struct lws *wsi, - const unsigned char *name, +int lws_add_http2_header_by_name(struct lws *wsi, const unsigned char *name, const unsigned char *value, int length, unsigned char **p, unsigned char *end) { int len; - lwsl_info("%s: %p %s:%s\n", __func__, *p, name, value); + lwsl_header("%s: %p %s:%s\n", __func__, *p, name, value); len = strlen((char *)name); if (len) if (name[len - 1] == ':') len--; + if (wsi->http2_substream && !strncmp((const char *)name, + "transfer-encoding", len)) { + lwsl_header("rejecting %s\n", name); + + return 0; + } + if (end - *p < len + length + 8) return 1; - *((*p)++) = 0; /* not indexed, literal name */ + *((*p)++) = 0; /* literal hdr, literal name, */ - **p = 0; /* non-HUF */ - if (lws_http2_num(7, len, p, end)) + *((*p)++) = 0 | lws_h2_num_start(7, len); /* non-HUF */ + if (lws_h2_num(7, len, p, end)) return 1; memcpy(*p, name, len); *p += len; - *(*p) = 0; /* non-HUF */ - if (lws_http2_num(7, length, p, end)) + *((*p)++) = 0 | lws_h2_num_start(7, length); /* non-HUF */ + if (lws_h2_num(7, length, p, end)) return 1; memcpy(*p, value, length); *p += length; + //lwsl_hexdump(op, *p -op); + return 0; } @@ -685,14 +1303,13 @@ int lws_add_http2_header_by_token(struct lws *wsi, enum lws_token_indexes token, return lws_add_http2_header_by_name(wsi, name, value, length, p, end); } -int lws_add_http2_header_status(struct lws *wsi, - unsigned int code, unsigned char **p, - unsigned char *end) +int lws_add_http2_header_status(struct lws *wsi, unsigned int code, + unsigned char **p, unsigned char *end) { unsigned char status[10]; int n; - wsi->u.http2.send_END_STREAM = !!(code >= 400); + wsi->u.h2.send_END_STREAM = 0; // !!(code >= 400); n = sprintf((char *)status, "%u", code); if (lws_add_http2_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_STATUS, diff --git a/lib/http2.c b/lib/http2.c index 0bde70040..560f414ba 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2013 Andy Green + * Copyright (C) 2010-2017 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,141 +22,443 @@ #include "private-libwebsockets.h" -const struct http2_settings lws_http2_default_settings = { { - 0, - /* LWS_HTTP2_SETTINGS__HEADER_TABLE_SIZE */ 4096, - /* LWS_HTTP2_SETTINGS__ENABLE_PUSH */ 1, - /* LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS */ 100, - /* LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE */ 65535, - /* LWS_HTTP2_SETTINGS__MAX_FRAME_SIZE */ 16384, - /* LWS_HTTP2_SETTINGS__MAX_HEADER_LIST_SIZE */ ~0, -}}; +/* + * bitmap of control messages that are valid to receive for each http2 state + */ +static const uint16_t http2_rx_validity[] = { + /* LWS_H2S_IDLE */ + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY) | +// (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE)| /* ignore */ + (1 << LWS_H2_FRAME_TYPE_HEADERS) | + (1 << LWS_H2_FRAME_TYPE_CONTINUATION), + /* LWS_H2S_RESERVED_LOCAL */ + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_RST_STREAM) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY) | + (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE), + /* LWS_H2S_RESERVED_REMOTE */ + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_HEADERS) | + (1 << LWS_H2_FRAME_TYPE_CONTINUATION) | + (1 << LWS_H2_FRAME_TYPE_RST_STREAM) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY), + /* LWS_H2S_OPEN */ + (1 << LWS_H2_FRAME_TYPE_DATA) | + (1 << LWS_H2_FRAME_TYPE_HEADERS) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY) | + (1 << LWS_H2_FRAME_TYPE_RST_STREAM) | + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_PUSH_PROMISE) | + (1 << LWS_H2_FRAME_TYPE_PING) | + (1 << LWS_H2_FRAME_TYPE_GOAWAY) | + (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) | + (1 << LWS_H2_FRAME_TYPE_CONTINUATION), + /* LWS_H2S_HALF_CLOSED_REMOTE */ + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY) | + (1 << LWS_H2_FRAME_TYPE_RST_STREAM), + /* LWS_H2S_HALF_CLOSED_LOCAL */ + (1 << LWS_H2_FRAME_TYPE_DATA) | + (1 << LWS_H2_FRAME_TYPE_HEADERS) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY) | + (1 << LWS_H2_FRAME_TYPE_RST_STREAM) | + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_PUSH_PROMISE) | + (1 << LWS_H2_FRAME_TYPE_PING) | + (1 << LWS_H2_FRAME_TYPE_GOAWAY) | + (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) | + (1 << LWS_H2_FRAME_TYPE_CONTINUATION), + /* LWS_H2S_CLOSED */ + (1 << LWS_H2_FRAME_TYPE_SETTINGS) | + (1 << LWS_H2_FRAME_TYPE_PRIORITY) | + (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) | + (1 << LWS_H2_FRAME_TYPE_RST_STREAM), +}; -void lws_http2_init(struct http2_settings *settings) +static const char *preface = "PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a"; + +static const char * const h2_state_names[] = { + "LWS_H2S_IDLE", + "LWS_H2S_RESERVED_LOCAL", + "LWS_H2S_RESERVED_REMOTE", + "LWS_H2S_OPEN", + "LWS_H2S_HALF_CLOSED_REMOTE", + "LWS_H2S_HALF_CLOSED_LOCAL", + "LWS_H2S_CLOSED", +}; + +#if 0 +static const char * const h2_setting_names[] = { + "", + "H2SET_HEADER_TABLE_SIZE", + "H2SET_ENABLE_PUSH", + "H2SET_MAX_CONCURRENT_STREAMS", + "H2SET_INITIAL_WINDOW_SIZE", + "H2SET_MAX_FRAME_SIZE", + "H2SET_MAX_HEADER_LIST_SIZE", +}; + +void +lws_h2_dump_settings(struct http2_settings *set) { - memcpy(settings, lws_http2_default_settings.setting, sizeof(*settings)); + int n; + + for (n = 1; n < H2SET_COUNT; n++) + lwsl_notice(" %30s: %10d\n", h2_setting_names[n], set->s[n]); +} +#else +void +lws_h2_dump_settings(struct http2_settings *set) +{ +} +#endif + +void lws_h2_init(struct lws *wsi) +{ + wsi->u.h2.h2n->set = wsi->vhost->set; +} + +static void +lws_h2_state(struct lws *wsi, enum lws_h2_states s) +{ + if (!wsi) + return; + lwsl_info("%s: wsi %p: state %s -> %s\n", __func__, wsi, + h2_state_names[wsi->u.h2.h2_state], + h2_state_names[s]); + wsi->u.h2.h2_state = (uint8_t)s; } struct lws * -lws_http2_wsi_from_id(struct lws *wsi, unsigned int sid) +lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, + unsigned int sid) { - do { - if (wsi->u.http2.my_stream_id == sid) - return wsi; + struct lws *wsi; + struct lws *nwsi = lws_get_network_wsi(parent_wsi); + struct lws_h2_netconn *h2n = nwsi->u.h2.h2n; - wsi = wsi->u.http2.next_child_wsi; - } while (wsi); + /* + * The identifier of a newly established stream MUST be numerically + * greater than all streams that the initiating endpoint has opened or + * reserved. This governs streams that are opened using a HEADERS frame + * and streams that are reserved using PUSH_PROMISE. An endpoint that + * receives an unexpected stream identifier MUST respond with a + * connection error (Section 5.4.1) of type PROTOCOL_ERROR. + */ + if (sid <= h2n->highest_sid_opened) { + lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, "Bad sid"); + return NULL; + } + + /* no more children allowed by parent */ + if (parent_wsi->u.h2.child_count + 1 > + parent_wsi->u.h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) { + lwsl_notice("reached concurrent stream limit\n"); + return NULL; + } + wsi = lws_create_new_server_wsi(vh); + if (!wsi) { + lwsl_notice("new server wsi failed (vh %p)\n", vh); + return NULL; + } + + h2n->highest_sid_opened = sid; + wsi->u.h2.my_sid = sid; + wsi->http2_substream = 1; + + wsi->u.h2.parent_wsi = parent_wsi; + /* new guy's sibling is whoever was the first child before */ + wsi->u.h2.sibling_list = parent_wsi->u.h2.child_list; + /* first child is now the new guy */ + parent_wsi->u.h2.child_list = wsi; + parent_wsi->u.h2.child_count++; + + wsi->u.h2.my_priority = 16; + wsi->u.h2.tx_cr = nwsi->u.h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE]; + wsi->u.h2.peer_tx_cr_est = nwsi->vhost->set.s[H2SET_INITIAL_WINDOW_SIZE]; + + wsi->state = LWSS_HTTP2_ESTABLISHED; + wsi->mode = parent_wsi->mode; + + wsi->protocol = &vh->protocols[0]; + if (lws_ensure_user_space(wsi)) + goto bail1; + + wsi->vhost->conn_stats.h2_subs++; + + lwsl_info("%s: %p new ch %p, sid %d, usersp=%p, tx cr %d, peer_credit %d (nwsi tx_cr %d)\n", + __func__, parent_wsi, wsi, sid, wsi->user_space, + wsi->u.h2.tx_cr, wsi->u.h2.peer_tx_cr_est, nwsi->u.h2.tx_cr); + + return wsi; + +bail1: + /* undo the insert */ + parent_wsi->u.h2.child_list = wsi->u.h2.sibling_list; + parent_wsi->u.h2.child_count--; + + if (wsi->user_space) + lws_free_set_NULL(wsi->user_space); + vh->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0); + lws_free(wsi); return NULL; } struct lws * -lws_create_server_child_wsi(struct lws_vhost *vhost, struct lws *parent_wsi, - unsigned int sid) +lws_h2_wsi_from_id(struct lws *parent_wsi, unsigned int sid) { - struct lws *wsi = lws_create_new_server_wsi(vhost); - - if (!wsi) - return NULL; - - /* no more children allowed by parent */ - if (parent_wsi->u.http2.child_count + 1 == - parent_wsi->u.http2.peer_settings.setting[ - LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS]) - goto bail; - lws_http2_init(&wsi->u.http2.peer_settings); - lws_http2_init(&wsi->u.http2.my_settings); - wsi->u.http2.stream_id = sid; - wsi->u.http2.my_stream_id = sid; - - wsi->u.http2.parent_wsi = parent_wsi; - wsi->u.http2.next_child_wsi = parent_wsi->u.http2.next_child_wsi; - parent_wsi->u.http2.next_child_wsi = wsi; - parent_wsi->u.http2.child_count++; - - wsi->u.http2.my_priority = 16; - wsi->u.http2.tx_credit = 65535; - - wsi->state = LWSS_HTTP2_ESTABLISHED; - wsi->mode = parent_wsi->mode; - - wsi->protocol = &vhost->protocols[0]; - if (lws_ensure_user_space(wsi)) - goto bail; - - lwsl_info("%s: %p new child %p, sid %d, user_space=%p\n", __func__, - parent_wsi, wsi, sid, wsi->user_space); - - return wsi; - -bail: - vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, - NULL, NULL, 0); - lws_free(wsi); + lws_start_foreach_ll(struct lws *, wsi, parent_wsi->u.h2.child_list) { + if (wsi->u.h2.my_sid == sid) + return wsi; + } lws_end_foreach_ll(wsi, u.h2.sibling_list); return NULL; } int lws_remove_server_child_wsi(struct lws_context *context, struct lws *wsi) { - struct lws **w = &wsi->u.http2.parent_wsi; - do { + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { if (*w == wsi) { - *w = wsi->u.http2.next_child_wsi; - (wsi->u.http2.parent_wsi)->u.http2.child_count--; + *w = wsi->u.h2.sibling_list; + (wsi->u.h2.parent_wsi)->u.h2.child_count--; return 0; } - - w = &((*w)->u.http2.next_child_wsi); - } while (*w); + } lws_end_foreach_llp(w, u.h2.sibling_list); lwsl_err("%s: can't find %p\n", __func__, wsi); + return 1; } -int -lws_http2_interpret_settings_payload(struct http2_settings *settings, - unsigned char *buf, int len) +void +lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pps) { + struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws_h2_netconn *h2n = nwsi->u.h2.h2n; + + pps->next = h2n->pps; + h2n->pps = pps; + lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_DISABLE | + LWS_RXFLOW_REASON_H2_PPS_PENDING); + lws_callback_on_writable(wsi); +} + +static struct lws_h2_protocol_send * +lws_h2_new_pps(enum lws_h2_protocol_send_type type) +{ + struct lws_h2_protocol_send *pps = lws_malloc(sizeof(*pps), "pps"); + + if (pps) + pps->type = type; + + return pps; +} + +int +lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason) +{ + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + struct lws_h2_protocol_send *pps; + + if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) + return 0; + + pps = lws_h2_new_pps(LWS_H2_PPS_GOAWAY); + if (!pps) + return 1; + + lwsl_info("%s: %p: ERR 0x%x, '%s'\n", __func__, wsi, err, reason); + + pps->u.ga.err = err; + strncpy(pps->u.ga.str, reason, sizeof(pps->u.ga.str) - 1); + pps->u.ga.str[sizeof(pps->u.ga.str) - 1] = '\0'; + lws_pps_schedule(wsi, pps); + + h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */ + + return 0; +} + +int +lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason) +{ + struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws_h2_netconn *h2n = nwsi->u.h2.h2n; + struct lws_h2_protocol_send *pps; + + if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) + return 0; + + pps = lws_h2_new_pps(LWS_H2_PPS_RST_STREAM); + if (!pps) + return 1; + + lwsl_info("%s: RST_STREAM 0x%x, REASON '%s'\n", __func__, err, reason); + + pps->u.rs.sid = h2n->sid; + pps->u.rs.err = err; + lws_pps_schedule(wsi, pps); + + h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */ + lws_h2_state(wsi, LWS_H2_STATE_CLOSED); + + return 0; +} + +int +lws_h2_settings(struct lws *wsi, struct http2_settings *settings, + unsigned char *buf, int len) +{ + struct lws *nwsi = lws_get_network_wsi(wsi); unsigned int a, b; if (!len) return 0; - if (len < LWS_HTTP2_SETTINGS_LENGTH) + if (len < LWS_H2_SETTINGS_LEN) return 1; - while (len >= LWS_HTTP2_SETTINGS_LENGTH) { + while (len >= LWS_H2_SETTINGS_LEN) { a = (buf[0] << 8) | buf[1]; - if (a < LWS_HTTP2_SETTINGS__COUNT) { - b = buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5]; - settings->setting[a] = b; - lwsl_info("http2 settings %d <- 0x%x\n", a, b); + if (!a || a >= H2SET_COUNT) + goto skip; + b = buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5]; + + switch (a) { + case H2SET_HEADER_TABLE_SIZE: + break; + case H2SET_ENABLE_PUSH: + if (b > 1) { + lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, + "ENABLE_PUSH invalid arg"); + return 1; + } + break; + case H2SET_MAX_CONCURRENT_STREAMS: + break; + case H2SET_INITIAL_WINDOW_SIZE: + if (b > 0x7fffffff) { + lws_h2_goaway(nwsi, H2_ERR_FLOW_CONTROL_ERROR, + "Inital Window beyond max"); + return 1; + } + /* + * In addition to changing the flow-control window for + * streams that are not yet active, a SETTINGS frame + * can alter the initial flow-control window size for + * streams with active flow-control windows (that is, + * streams in the "open" or "half-closed (remote)" + * state). When the value of + * SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver + * MUST adjust the size of all stream flow-control + * windows that it maintains by the difference between + * the new value and the old value. + */ + + lws_start_foreach_ll(struct lws *, w, + nwsi->u.h2.child_list) { + lwsl_info("%s: adi child tc cr %d +%d -> %d", + __func__, + w->u.h2.tx_cr, b - settings->s[a], + w->u.h2.tx_cr + b - settings->s[a]); + w->u.h2.tx_cr += b - settings->s[a]; + if (w->u.h2.tx_cr > 0 && + w->u.h2.tx_cr <= b - settings->s[a]) + lws_callback_on_writable(w); + } lws_end_foreach_ll(w, u.h2.sibling_list); + + break; + case H2SET_MAX_FRAME_SIZE: + if (b < wsi->vhost->set.s[H2SET_MAX_FRAME_SIZE]) { + lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, + "Frame size < initial"); + return 1; + } + if (b > 0x007fffff) { + lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, + "Settings Frame size above max"); + return 1; + } + break; + case H2SET_MAX_HEADER_LIST_SIZE: + break; } - len -= LWS_HTTP2_SETTINGS_LENGTH; - buf += LWS_HTTP2_SETTINGS_LENGTH; + settings->s[a] = b; + lwsl_info("http2 settings %d <- 0x%x\n", a, b); +skip: + len -= LWS_H2_SETTINGS_LEN; + buf += LWS_H2_SETTINGS_LEN; } if (len) return 1; + lws_h2_dump_settings(settings); + return 0; } -struct lws *lws_http2_get_network_wsi(struct lws *wsi) -{ - while (wsi->u.http2.parent_wsi) - wsi = wsi->u.http2.parent_wsi; +/* RFC7640 Sect 6.9 + * + * The WINDOW_UPDATE frame can be specific to a stream or to the entire + * connection. In the former case, the frame's stream identifier + * indicates the affected stream; in the latter, the value "0" indicates + * that the entire connection is the subject of the frame. + * + * ... + * + * Two flow-control windows are applicable: the stream flow-control + * window and the connection flow-control window. The sender MUST NOT + * send a flow-controlled frame with a length that exceeds the space + * available in either of the flow-control windows advertised by the + * receiver. Frames with zero length with the END_STREAM flag set (that + * is, an empty DATA frame) MAY be sent if there is no available space + * in either flow-control window. + */ - return wsi; +int +lws_h2_tx_cr_get(struct lws *wsi) +{ + int c = wsi->u.h2.tx_cr; + struct lws *nwsi; + + if (!wsi->http2_substream && !wsi->upgraded_to_http2) + return ~0x80000000; + + nwsi = lws_get_network_wsi(wsi); + + lwsl_info ("%s: %p: own tx credit %d: nwsi credit %d\n", + __func__, wsi, c, nwsi->u.h2.tx_cr); + + if (nwsi->u.h2.tx_cr < c) + c = nwsi->u.h2.tx_cr; + + if (c < 0) + return 0; + + return c; } -int lws_http2_frame_write(struct lws *wsi, int type, int flags, - unsigned int sid, unsigned int len, unsigned char *buf) +void +lws_h2_tx_cr_consume(struct lws *wsi, int consumed) { - struct lws *wsi_eff = lws_http2_get_network_wsi(wsi); - unsigned char *p = &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH]; + struct lws *nwsi = lws_get_network_wsi(wsi); + + wsi->u.h2.tx_cr -= consumed; + + if (nwsi != wsi) + nwsi->u.h2.tx_cr -= consumed; +} + +int lws_h2_frame_write(struct lws *wsi, int type, int flags, + unsigned int sid, unsigned int len, unsigned char *buf) +{ + struct lws *nwsi = lws_get_network_wsi(wsi); + unsigned char *p = &buf[-LWS_H2_FRAME_HEADER_LENGTH]; int n; *p++ = len >> 16; @@ -169,368 +471,1103 @@ int lws_http2_frame_write(struct lws *wsi, int type, int flags, *p++ = sid >> 8; *p++ = sid; - lwsl_info("%s: %p (eff %p). type %d, flags 0x%x, sid=%d, len=%d, tx_credit=%d\n", - __func__, wsi, wsi_eff, type, flags, sid, len, - wsi->u.http2.tx_credit); + lwsl_debug("%s: %p (eff %p). typ %d, fl 0x%x, sid=%d, len=%d, " + "txcr=%d, nwsi->txcr=%d\n", __func__, wsi, nwsi, type, flags, + sid, len, wsi->u.h2.tx_cr, nwsi->u.h2.tx_cr); - if (type == LWS_HTTP2_FRAME_TYPE_DATA) { - if (wsi->u.http2.tx_credit < len) + if (type == LWS_H2_FRAME_TYPE_DATA) { + if (wsi->u.h2.tx_cr < len) lwsl_err("%s: %p: sending payload len %d" - " but tx_credit only %d!\n", __func__, wsi, len, - wsi->u.http2.tx_credit); - wsi->u.http2.tx_credit -= len; + " but tx_cr only %d!\n", __func__, wsi, + len, wsi->u.h2.tx_cr); + lws_h2_tx_cr_consume(wsi, len); } - n = lws_issue_raw(wsi_eff, &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH], - len + LWS_HTTP2_FRAME_HEADER_LENGTH); - if (n >= LWS_HTTP2_FRAME_HEADER_LENGTH) - return n - LWS_HTTP2_FRAME_HEADER_LENGTH; + n = lws_issue_raw(nwsi, &buf[-LWS_H2_FRAME_HEADER_LENGTH], + len + LWS_H2_FRAME_HEADER_LENGTH); + if (n < 0) + return n; + + if (n >= LWS_H2_FRAME_HEADER_LENGTH) + return n - LWS_H2_FRAME_HEADER_LENGTH; return n; } -static void lws_http2_settings_write(struct lws *wsi, int n, unsigned char *buf) +static void lws_h2_set_bin(struct lws *wsi, int n, unsigned char *buf) { *buf++ = n >> 8; *buf++ = n; - *buf++ = wsi->u.http2.my_settings.setting[n] >> 24; - *buf++ = wsi->u.http2.my_settings.setting[n] >> 16; - *buf++ = wsi->u.http2.my_settings.setting[n] >> 8; - *buf = wsi->u.http2.my_settings.setting[n]; + *buf++ = wsi->u.h2.h2n->set.s[n] >> 24; + *buf++ = wsi->u.h2.h2n->set.s[n] >> 16; + *buf++ = wsi->u.h2.h2n->set.s[n] >> 8; + *buf = wsi->u.h2.h2n->set.s[n]; } -static const char * https_client_preface = - "PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a"; - -int -lws_http2_parser(struct lws *wsi, unsigned char c) +int lws_h2_do_pps_send(struct lws *wsi) { - struct lws *swsi; - int n; - - switch (wsi->state) { - case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: - if (https_client_preface[wsi->u.http2.count++] != c) - return 1; - - if (!https_client_preface[wsi->u.http2.count]) { - lwsl_info("http2: %p: established\n", wsi); - wsi->state = LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS; - wsi->u.http2.count = 0; - wsi->u.http2.tx_credit = 65535; - - /* - * we must send a settings frame -- empty one is OK... - * that must be the first thing sent by server - * and the peer must send a SETTINGS with ACK flag... - */ - - lws_set_protocol_write_pending(wsi, - LWS_PPS_HTTP2_MY_SETTINGS); - } - break; - - case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: - case LWSS_HTTP2_ESTABLISHED: - if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { // payload - wsi->u.http2.count++; - wsi->u.http2.stream_wsi->u.http2.count = wsi->u.http2.count; - /* applies to wsi->u.http2.stream_wsi which may be wsi*/ - switch(wsi->u.http2.type) { - case LWS_HTTP2_FRAME_TYPE_SETTINGS: - wsi->u.http2.stream_wsi->u.http2.one_setting[wsi->u.http2.count % LWS_HTTP2_SETTINGS_LENGTH] = c; - if (wsi->u.http2.count % LWS_HTTP2_SETTINGS_LENGTH == LWS_HTTP2_SETTINGS_LENGTH - 1) - if (lws_http2_interpret_settings_payload( - &wsi->u.http2.stream_wsi->u.http2.peer_settings, - wsi->u.http2.one_setting, - LWS_HTTP2_SETTINGS_LENGTH)) - return 1; - break; - case LWS_HTTP2_FRAME_TYPE_CONTINUATION: - case LWS_HTTP2_FRAME_TYPE_HEADERS: - lwsl_info(" %02X\n", c); - if (!wsi->u.http2.stream_wsi->u.hdr.ah) - if (lws_header_table_attach(wsi->u.http2.stream_wsi, 0)) { - lwsl_err("%s: Failed to get ah\n", __func__); - return 1; - } - if (lws_hpack_interpret(wsi->u.http2.stream_wsi, c)) { - lwsl_notice("%s: lws_hpack_interpret failed\n", __func__); - return 1; - } - break; - case LWS_HTTP2_FRAME_TYPE_GOAWAY: - if (wsi->u.http2.count >= 5 && wsi->u.http2.count <= 8) { - wsi->u.http2.hpack_e_dep <<= 8; - wsi->u.http2.hpack_e_dep |= c; - if (wsi->u.http2.count == 8) { - lwsl_info("goaway err 0x%x\n", wsi->u.http2.hpack_e_dep); - } - } - wsi->u.http2.GOING_AWAY = 1; - break; - case LWS_HTTP2_FRAME_TYPE_DATA: - break; - case LWS_HTTP2_FRAME_TYPE_PRIORITY: - break; - case LWS_HTTP2_FRAME_TYPE_RST_STREAM: - break; - case LWS_HTTP2_FRAME_TYPE_PUSH_PROMISE: - break; - case LWS_HTTP2_FRAME_TYPE_PING: - if (wsi->u.http2.flags & LWS_HTTP2_FLAG_SETTINGS_ACK) { // ack - } else { /* they're sending us a ping request */ - if (wsi->u.http2.count > 8) - return 1; - wsi->u.http2.ping_payload[wsi->u.http2.count - 1] = c; - } - break; - case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE: - wsi->u.http2.hpack_e_dep <<= 8; - wsi->u.http2.hpack_e_dep |= c; - break; - } - if (wsi->u.http2.count != wsi->u.http2.length) - break; - - /* end of frame */ - - wsi->u.http2.frame_state = 0; - wsi->u.http2.count = 0; - swsi = wsi->u.http2.stream_wsi; - /* set our initial window size */ - if (!wsi->u.http2.initialized) { - wsi->u.http2.tx_credit = wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE]; - lwsl_info("initial tx credit on master conn %p: %d\n", wsi, wsi->u.http2.tx_credit); - wsi->u.http2.initialized = 1; - } - switch (wsi->u.http2.type) { - case LWS_HTTP2_FRAME_TYPE_HEADERS: - /* service the http request itself */ - lwsl_info("servicing initial http request, wsi=%p, stream wsi=%p\n", wsi, wsi->u.http2.stream_wsi); - n = lws_http_action(swsi); - (void)n; - lwsl_info(" action result %d\n", n); - break; - case LWS_HTTP2_FRAME_TYPE_PING: - if (wsi->u.http2.flags & LWS_HTTP2_FLAG_SETTINGS_ACK) { // ack - } else { /* they're sending us a ping request */ - lws_set_protocol_write_pending(wsi, LWS_PPS_HTTP2_PONG); - } - break; - case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE: - wsi->u.http2.hpack_e_dep &= ~(1 << 31); - if ((lws_intptr_t)swsi->u.http2.tx_credit + (lws_intptr_t)wsi->u.http2.hpack_e_dep > (~(1 << 31))) - return 1; /* actually need to close swsi not the whole show */ - swsi->u.http2.tx_credit += wsi->u.http2.hpack_e_dep; - if (swsi->u.http2.waiting_tx_credit && swsi->u.http2.tx_credit > 0) { - lwsl_info("%s: %p: waiting_tx_credit -> wait on writeable\n", __func__, wsi); - swsi->u.http2.waiting_tx_credit = 0; - lws_callback_on_writable(swsi); - } - break; - } - break; - } - switch (wsi->u.http2.frame_state++) { - case 0: - wsi->u.http2.length = c; - break; - case 1: - case 2: - wsi->u.http2.length <<= 8; - wsi->u.http2.length |= c; - break; - case 3: - wsi->u.http2.type = c; - break; - case 4: - wsi->u.http2.flags = c; - break; - case 5: - case 6: - case 7: - case 8: - wsi->u.http2.stream_id <<= 8; - wsi->u.http2.stream_id |= c; - break; - } - if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { /* frame header complete */ - lwsl_info("frame: type 0x%x, flags 0x%x, sid 0x%x, len 0x%x\n", - wsi->u.http2.type, wsi->u.http2.flags, wsi->u.http2.stream_id, wsi->u.http2.length); - wsi->u.http2.count = 0; - - wsi->u.http2.stream_wsi = wsi; - if (wsi->u.http2.stream_id) - wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id); - - switch (wsi->u.http2.type) { - case LWS_HTTP2_FRAME_TYPE_SETTINGS: - /* nonzero sid on settings is illegal */ - if (wsi->u.http2.stream_id) - return 1; - - if (wsi->u.http2.flags & LWS_HTTP2_FLAG_SETTINGS_ACK) { // ack - } else - /* non-ACK coming in means we must ACK it */ - lws_set_protocol_write_pending(wsi, LWS_PPS_HTTP2_ACK_SETTINGS); - break; - case LWS_HTTP2_FRAME_TYPE_PING: - if (wsi->u.http2.stream_id) - return 1; - if (wsi->u.http2.length != 8) - return 1; - break; - case LWS_HTTP2_FRAME_TYPE_CONTINUATION: - if (wsi->u.http2.END_HEADERS) - return 1; - goto update_end_headers; - - case LWS_HTTP2_FRAME_TYPE_HEADERS: - lwsl_info("LWS_HTTP2_FRAME_TYPE_HEADERS: stream_id = %d\n", wsi->u.http2.stream_id); - if (!wsi->u.http2.stream_id) - return 1; - if (!wsi->u.http2.stream_wsi) { - wsi->u.http2.stream_wsi = - lws_create_server_child_wsi(wsi->vhost, wsi, wsi->u.http2.stream_id); - wsi->u.http2.stream_wsi->http2_substream = 1; - } - - /* END_STREAM means after servicing this, close the stream */ - wsi->u.http2.END_STREAM = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_STREAM); - lwsl_info("%s: headers END_STREAM = %d\n",__func__, wsi->u.http2.END_STREAM); -update_end_headers: - /* no END_HEADERS means CONTINUATION must come */ - wsi->u.http2.END_HEADERS = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_HEADERS); - - swsi = wsi->u.http2.stream_wsi; - if (!swsi) - return 1; - - - /* prepare the hpack parser at the right start */ - - swsi->u.http2.flags = wsi->u.http2.flags; - swsi->u.http2.length = wsi->u.http2.length; - swsi->u.http2.END_STREAM = wsi->u.http2.END_STREAM; - - if (swsi->u.http2.flags & LWS_HTTP2_FLAG_PADDED) - swsi->u.http2.hpack = HPKS_OPT_PADDING; - else - if (swsi->u.http2.flags & LWS_HTTP2_FLAG_PRIORITY) { - swsi->u.http2.hpack = HKPS_OPT_E_DEPENDENCY; - swsi->u.http2.hpack_m = 4; - } else - swsi->u.http2.hpack = HPKS_TYPE; - lwsl_info("initial hpack state %d\n", swsi->u.http2.hpack); - break; - case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE: - if (wsi->u.http2.length != 4) - return 1; - break; - } - if (wsi->u.http2.length == 0) - wsi->u.http2.frame_state = 0; - - } - break; - } - - return 0; -} - -int lws_http2_do_pps_send(struct lws_context *context, struct lws *wsi) -{ - unsigned char settings[LWS_PRE + 6 * LWS_HTTP2_SETTINGS__COUNT]; - struct lws *swsi; + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + struct lws_h2_protocol_send *pps = NULL; + struct lws *cwsi; + uint8_t set[LWS_PRE + 64], *p = &set[LWS_PRE], *q; int n, m = 0; - lwsl_debug("%s: %p: %d\n", __func__, wsi, wsi->pps); + if (!h2n) + return 1; - switch (wsi->pps) { - case LWS_PPS_HTTP2_MY_SETTINGS: - for (n = 1; n < LWS_HTTP2_SETTINGS__COUNT; n++) - if (wsi->u.http2.my_settings.setting[n] != lws_http2_default_settings.setting[n]) { - lws_http2_settings_write(wsi, n, - &settings[LWS_PRE + m]); - m += sizeof(wsi->u.http2.one_setting); + /* get the oldest pps */ + + lws_start_foreach_llp(struct lws_h2_protocol_send **, pps1, h2n->pps) { + if ((*pps1)->next == NULL) { /* we are the oldest in the list */ + pps = *pps1; /* remove us from the list */ + *pps1 = NULL; + continue; + } + } lws_end_foreach_llp(pps1, next); + + if (!pps) + return 1; + + lwsl_info("%s: %p: %d\n", __func__, wsi, pps->type); + + switch (pps->type) { + + case LWS_H2_PPS_MY_SETTINGS: + /* + * if any of our settings varies from h2 "default defaults" + * then we must inform the perr + */ + for (n = 1; n < H2SET_COUNT; n++) + if (h2n->set.s[n] != lws_h2_defaults.s[n]) { + lwsl_debug("sending SETTING %d 0x%x\n", n, + wsi->u.h2.h2n->set.s[n]); + lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]); + m += sizeof(h2n->one_setting); } - n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS, - 0, LWS_HTTP2_STREAM_ID_MASTER, m, - &settings[LWS_PRE]); + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS, + 0, LWS_H2_STREAM_ID_MASTER, m, + &set[LWS_PRE]); if (n != m) { lwsl_info("send %d %d\n", n, m); - return 1; + goto bail; } break; - case LWS_PPS_HTTP2_ACK_SETTINGS: + + case LWS_H2_PPS_ACK_SETTINGS: /* send ack ... always empty */ - n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS, - 1, LWS_HTTP2_STREAM_ID_MASTER, 0, - &settings[LWS_PRE]); + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS, 1, + LWS_H2_STREAM_ID_MASTER, 0, &set[LWS_PRE]); if (n) { lwsl_err("ack tells %d\n", n); - return 1; + goto bail; } /* this is the end of the preface dance then? */ if (wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS) { wsi->state = LWSS_HTTP2_ESTABLISHED; - wsi->u.http.fop_fd = NULL; - - if (lws_is_ssl(lws_http2_get_network_wsi(wsi))) { - lwsl_info("skipping nonexistent ssl upgrade headers\n"); + if (lws_is_ssl(lws_get_network_wsi(wsi))) break; - } - /* - * we need to treat the headers from this upgrade - * as the first job. These need to get - * shifted to stream ID 1 + * we need to treat the headers from the upgrade as the + * first job. So these need to get shifted to sid 1. */ - lwsl_info("%s: setting up sid 1\n", __func__); + h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, 1); + if (!h2n->swsi) + goto bail; - swsi = wsi->u.http2.stream_wsi = - lws_create_server_child_wsi(wsi->vhost, wsi, 1); /* pass on the initial headers to SID 1 */ - swsi->u.http.ah = wsi->u.http.ah; + h2n->swsi->u.http.ah = wsi->u.http.ah; wsi->u.http.ah = NULL; - lwsl_info("%s: inherited headers %p\n", __func__, swsi->u.http.ah); - swsi->u.http2.tx_credit = wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE]; - lwsl_info("initial tx credit on conn %p: %d\n", swsi, swsi->u.http2.tx_credit); - swsi->u.http2.initialized = 1; + lwsl_info("%s: inherited headers %p\n", __func__, + h2n->swsi->u.http.ah); + h2n->swsi->u.h2.tx_cr = + h2n->set.s[H2SET_INITIAL_WINDOW_SIZE]; + lwsl_info("initial tx credit on conn %p: %d\n", + h2n->swsi, h2n->swsi->u.h2.tx_cr); + h2n->swsi->u.h2.initialized = 1; /* demanded by HTTP2 */ - swsi->u.http2.END_STREAM = 1; + h2n->swsi->u.h2.END_STREAM = 1; lwsl_info("servicing initial http request\n"); - return lws_http_action(swsi); + + wsi->vhost->conn_stats.h2_trans++; + + if (lws_http_action(h2n->swsi)) + goto bail; + + break; } break; - case LWS_PPS_HTTP2_PONG: - memcpy(&settings[LWS_PRE], wsi->u.http2.ping_payload, 8); - n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_PING, - LWS_HTTP2_FLAG_SETTINGS_ACK, - LWS_HTTP2_STREAM_ID_MASTER, 8, - &settings[LWS_PRE]); + case LWS_H2_PPS_PONG: + lwsl_debug("sending PONG\n"); + memcpy(&set[LWS_PRE], pps->u.ping.ping_payload, 8); + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_PING, + LWS_H2_FLAG_SETTINGS_ACK, + LWS_H2_STREAM_ID_MASTER, 8, + &set[LWS_PRE]); if (n != 8) { lwsl_info("send %d %d\n", n, m); - return 1; + goto bail; } break; + + case LWS_H2_PPS_GOAWAY: + lwsl_info("LWS_H2_PPS_GOAWAY\n"); + *p++ = pps->u.ga.highest_sid >> 24; + *p++ = pps->u.ga.highest_sid >> 16; + *p++ = pps->u.ga.highest_sid >> 8; + *p++ = pps->u.ga.highest_sid; + *p++ = pps->u.ga.err >> 24; + *p++ = pps->u.ga.err >> 16; + *p++ = pps->u.ga.err >> 8; + *p++ = pps->u.ga.err; + q = (unsigned char *)h2n->goaway_str; + n = 0; + while (*q && n++ < sizeof(h2n->goaway_str)) + *p++ = *q++; + h2n->we_told_goaway = 1; + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_GOAWAY, 0, + LWS_H2_STREAM_ID_MASTER, + p - &set[LWS_PRE], &set[LWS_PRE]); + if (n != 4) { + lwsl_info("send %d %d\n", n, m); + goto bail; + } + goto bail; + + case LWS_H2_PPS_RST_STREAM: + lwsl_info("LWS_H2_PPS_RST_STREAM\n"); + *p++ = pps->u.rs.err >> 24; + *p++ = pps->u.rs.err >> 16; + *p++ = pps->u.rs.err >> 8; + *p++ = pps->u.rs.err; + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_RST_STREAM, + 0, pps->u.rs.sid, 4, &set[LWS_PRE]); + if (n != 4) { + lwsl_info("send %d %d\n", n, m); + goto bail; + } + cwsi = lws_h2_wsi_from_id(wsi, pps->u.rs.sid); + if (cwsi) + lws_close_free_wsi(cwsi, 0); + break; + + case LWS_H2_PPS_UPDATE_WINDOW: + lwsl_notice("LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n", pps->u.update_window.sid, pps->u.update_window.credit); + *p++ = pps->u.update_window.credit >> 24; + *p++ = pps->u.update_window.credit >> 16; + *p++ = pps->u.update_window.credit >> 8; + *p++ = pps->u.update_window.credit; + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_WINDOW_UPDATE, + 0, pps->u.update_window.sid, 4, &set[LWS_PRE]); + if (n != 4) { + lwsl_info("send %d %d\n", n, m); + goto bail; + } + break; + default: break; } + lws_free(pps); + + return 0; + +bail: + lws_free(pps); + + return 1; +} + +/* + * The frame header part has just completely arrived. + * Perform actions for frame completion. + */ +static int +lws_h2_parse_frame_header(struct lws *wsi) +{ + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + struct lws_h2_protocol_send *pps; + int n; + + /* + * We just got the frame header + */ + h2n->count = 0; + h2n->swsi = wsi; + /* b31 is a reserved bit */ + h2n->sid = h2n->sid & 0x7fffffff; + + if (h2n->sid && !(h2n->sid & 1)) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "Even Stream ID"); + + return 0; + } + + /* let the network wsi live a bit longer if subs are active */ + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 10); + + /* let the network wsi live a bit longer if subs are active */ + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 10); + + if (h2n->sid) + h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid); + + lwsl_info("%p (%p): fr hdr: typ 0x%x, flags 0x%x, sid 0x%x, len 0x%x\n", + wsi, h2n->swsi, h2n->type, h2n->flags, h2n->sid, + h2n->length); + + if (h2n->we_told_goaway && h2n->sid > h2n->highest_sid) + h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */ + + if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) + return 0; + + if (h2n->length > h2n->set.s[H2SET_MAX_FRAME_SIZE]) { + /* + * peer sent us something bigger than we told + * it we would allow + */ + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "Peer ignored our frame size setting"); + return 0; + } + + if (h2n->swsi) + lwsl_info("%s: wsi %p, State: %s, received cmd %d\n", + __func__, h2n->swsi, + h2_state_names[h2n->swsi->u.h2.h2_state], h2n->type); + else { + /* if it's data, either way no swsi means CLOSED state */ + if (h2n->type == LWS_H2_FRAME_TYPE_DATA) { + lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, + "Data for nonexistant sid"); + return 0; + } + /* if the sid is credible, treat as wsi for it closed */ + if (h2n->sid > h2n->highest_sid_opened && + h2n->type != LWS_H2_FRAME_TYPE_HEADERS && + h2n->type != LWS_H2_FRAME_TYPE_PRIORITY) { + /* if not credible, reject it */ + lwsl_info("%s: wsi %p, No child for sid %d, rx cmd %d\n", + __func__, h2n->swsi, h2n->sid, h2n->type); + lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, + "Data for nonexistant sid"); + return 0; + } + } + + if (h2n->swsi && h2n->sid && + !(http2_rx_validity[h2n->swsi->u.h2.h2_state] & (1 << h2n->type))) { + lwsl_info("%s: wsi %p, State: %s, ILLEGAL cmdrx %d (OK 0x%x)\n", + __func__, h2n->swsi, + h2_state_names[h2n->swsi->u.h2.h2_state], h2n->type, + http2_rx_validity[h2n->swsi->u.h2.h2_state]); + + if (h2n->swsi->u.h2.h2_state == LWS_H2_STATE_CLOSED || + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE) + n = H2_ERR_STREAM_CLOSED; + else + n = H2_ERR_PROTOCOL_ERROR; + lws_h2_goaway(wsi, n, "invalid rx for state"); + + return 0; + } + + if (h2n->cont_exp && (h2n->cont_exp_sid != h2n->sid || + h2n->type != LWS_H2_FRAME_TYPE_CONTINUATION)) { + lwsl_info("%s: expected cont on sid %d (got %d on sid %d)\n", + __func__, h2n->cont_exp_sid, h2n->type, h2n->sid); + h2n->cont_exp = 0; + if (h2n->cont_exp_headers) + n = H2_ERR_COMPRESSION_ERROR; + else + n = H2_ERR_PROTOCOL_ERROR; + lws_h2_goaway(wsi, n, "Continuation hdrs State"); + + return 0; + } + + switch (h2n->type) { + case LWS_H2_FRAME_TYPE_DATA: + lwsl_info("seen incoming LWS_H2_FRAME_TYPE_DATA start\n"); + if (!h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "DATA 0 sid"); + break; + } + lwsl_info("Frame header DATA: sid %d\n", h2n->sid); + + if (!h2n->swsi) + break; + + h2n->swsi->u.h2.peer_tx_cr_est -= h2n->length; + lwsl_debug(" peer_tx_cr_est %d\n", h2n->swsi->u.h2.peer_tx_cr_est); + if (h2n->swsi->u.h2.peer_tx_cr_est < 32768) { + h2n->swsi->u.h2.peer_tx_cr_est += 65536; + pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); + if (!pps) + return 1; + pps->u.update_window.sid = h2n->sid; + pps->u.update_window.credit = 65536; + lws_pps_schedule(wsi, pps); + pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); + if (!pps) + return 1; + pps->u.update_window.sid = 0; + pps->u.update_window.credit = 65536; + lws_pps_schedule(wsi, pps); + } + + if (( + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE || + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_CLOSED)) { + lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, "conn closed"); + break; + } + break; + case LWS_H2_FRAME_TYPE_PRIORITY: + lwsl_info("LWS_H2_FRAME_TYPE_PRIORITY complete frame\n"); + if (!h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Priority has 0 sid"); + break; + } + if (h2n->length != 5) { + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "Priority has length other than 5"); + break; + } + break; + case LWS_H2_FRAME_TYPE_PUSH_PROMISE: + lwsl_info("LWS_H2_FRAME_TYPE_PUSH_PROMISE complete frame\n"); + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "Server only"); + break; + + case LWS_H2_FRAME_TYPE_GOAWAY: + break; + + case LWS_H2_FRAME_TYPE_RST_STREAM: + if (!h2n->sid) + return 1; + if (!h2n->swsi) { + if (h2n->sid <= h2n->highest_sid_opened) + break; + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "crazy sid on RST_STREAM"); + return 1; + } + if (h2n->length != 4) { + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "RST_STREAM can only be length 4"); + break; + } + lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED); + break; + + case LWS_H2_FRAME_TYPE_SETTINGS: + lwsl_info("LWS_H2_FRAME_TYPE_SETTINGS complete frame\n"); + /* nonzero sid on settings is illegal */ + if (h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Settings has nonzero sid"); + break; + } + + if (!(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) { + if ((!h2n->length) || h2n->length % 6) { + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "Settings length error"); + break; + } + lwsl_info("scheduled settings ack PPS\n"); + /* non-ACK coming in means we must ACK it */ + + + if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) + return 0; + + pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS); + if (!pps) + return 1; + lws_pps_schedule(wsi, pps); + break; + } + /* came to us with ACK set... not allowed to have payload */ + + if (h2n->length) { + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "Settings with ACK not allowed payload"); + break; + } + break; + case LWS_H2_FRAME_TYPE_PING: + if (h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Ping has nonzero sid"); + break; + } + if (h2n->length != 8) { + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "Ping payload can only be 8"); + break; + } + break; + case LWS_H2_FRAME_TYPE_CONTINUATION: + lwsl_info("LWS_H2_FRAME_TYPE_CONTINUATION: sid = %d\n", + h2n->sid); + + if (!h2n->cont_exp || + h2n->cont_exp_sid != h2n->sid || + !h2n->sid || + !h2n->swsi) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "unexpected CONTINUATION"); + break; + } + if (h2n->swsi->u.h2.END_HEADERS) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "END_HEADERS already seen"); + break; + } + /* END_STREAM is in HEADERS, skip resetting it */ + goto update_end_headers; + + case LWS_H2_FRAME_TYPE_HEADERS: + lwsl_info("HEADERS: frame header: sid = %d\n", h2n->sid); + if (!h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "sid 0"); + return 1; + } + + if (!h2n->swsi) { + /* no more children allowed by parent */ + if (wsi->u.h2.child_count + 1 > + wsi->u.h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Another stream not allowed"); + + return 1; + } + + h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, h2n->sid); + if (!h2n->swsi) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "OOM"); + + return 1; + } + } + + /* + * ah needs attaching to child wsi, even though + * we only fill it from network wsi + */ + if (!h2n->swsi->u.hdr.ah) + if (lws_header_table_attach(h2n->swsi, 0)) { + lwsl_err("%s: Failed to get ah\n", __func__); + return 1; + } + + /* + * The first use of a new stream identifier implicitly closes + * all streams in the "idle" state that might have been + * initiated by that peer with a lower-valued stream identifier. + * + * For example, if a client sends a HEADERS frame on stream 7 + * without ever sending a frame on stream 5, then stream 5 + * transitions to the "closed" state when the first frame for + * stream 7 is sent or received. + */ + lws_start_foreach_ll(struct lws *, w, wsi->u.h2.child_list) { + if (w->u.h2.my_sid < h2n->sid && + w->u.h2.h2_state == LWS_H2_STATE_IDLE) + lws_close_free_wsi(w, 0); + } lws_end_foreach_ll(w, u.h2.sibling_list); + + + /* END_STREAM means after servicing this, close the stream */ + h2n->swsi->u.h2.END_STREAM = !!(h2n->flags & LWS_H2_FLAG_END_STREAM); + lwsl_info("%s: hdr END_STREAM = %d\n",__func__, + h2n->swsi->u.h2.END_STREAM); + + h2n->cont_exp = !(h2n->flags & LWS_H2_FLAG_END_HEADERS); + h2n->cont_exp_sid = h2n->sid; + h2n->cont_exp_headers = 1; + h2n->seen_nonpseudoheader = 0; + lws_header_table_reset(h2n->swsi, 0); + +update_end_headers: + /* no END_HEADERS means CONTINUATION must come */ + h2n->swsi->u.h2.END_HEADERS = + !!(h2n->flags & LWS_H2_FLAG_END_HEADERS); + if (h2n->swsi->u.h2.END_HEADERS) + h2n->cont_exp = 0; + lwsl_debug("END_HEADERS %d\n", h2n->swsi->u.h2.END_HEADERS); + break; + + case LWS_H2_FRAME_TYPE_WINDOW_UPDATE: + if (h2n->length != 4) { + lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, + "window update frame not 4"); + break; + } + lwsl_info("LWS_H2_FRAME_TYPE_WINDOW_UPDATE\n"); + break; + default: + lwsl_info("%s: ILLEGAL FRAME TYPE %d\n", __func__, h2n->type); + h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */ + break; + } + if (h2n->length == 0) + h2n->frame_state = 0; + + return 0; +} + +/* + * The last byte of the whole frame has been handled. + * Perform actions for frame completion. + */ +static int +lws_h2_parse_end_of_frame(struct lws *wsi) +{ + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + struct lws_h2_protocol_send *pps; + struct lws *eff_wsi = wsi; + const char *p; + int n; + + h2n->frame_state = 0; + h2n->count = 0; + + if (h2n->sid) + h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid); + + if (h2n->sid > h2n->highest_sid) + h2n->highest_sid = h2n->sid; + + /* set our initial window size */ + if (!wsi->u.h2.initialized) { + wsi->u.h2.tx_cr = h2n->set.s[H2SET_INITIAL_WINDOW_SIZE]; + lwsl_info("initial tx credit on master %p: %d\n", wsi, + wsi->u.h2.tx_cr); + wsi->u.h2.initialized = 1; + } + + if (h2n->collected_priority && (h2n->dep & ~(1 << 31)) == h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "depends on own sid"); + return 0; + } + + switch (h2n->type) { + case LWS_H2_FRAME_TYPE_CONTINUATION: + case LWS_H2_FRAME_TYPE_HEADERS: + + /* service the http request itself */ + + if (h2n->last_action_dyntable_resize) { + lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR, + "dyntable resize last in headers"); + break; + } + + if (!h2n->swsi->u.h2.END_HEADERS) { + /* we are not finished yet */ + lwsl_info("witholding http action for continuation\n"); + break; + } + + /* confirm the hpack stream state is reasonable for finishing */ + + if (h2n->hpack != HPKS_TYPE) { + /* hpack incomplete */ + lwsl_info("hpack incomplete %d (type %d, len %d)\n", + h2n->hpack, h2n->type, h2n->hpack_len); + lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR, + "hpack incomplete"); + break; + } + + /* this is the last part of HEADERS */ + switch (h2n->swsi->u.h2.h2_state) { + case LWS_H2_STATE_IDLE: + lws_h2_state(h2n->swsi, LWS_H2_STATE_OPEN); + break; + case LWS_H2_STATE_RESERVED_REMOTE: + lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_LOCAL); + break; + } + + lwsl_info("http req, wsi=%p, h2n->swsi=%p\n", wsi, h2n->swsi); + h2n->swsi->hdr_parsing_completed = 1; + + if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + h2n->swsi->u.http.rx_content_length = atoll( + lws_hdr_simple_ptr(h2n->swsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH)); + h2n->swsi->u.http.rx_content_remain = + h2n->swsi->u.http.rx_content_length; + lwsl_info("setting rx_content_length %lld\n", + (long long)h2n->swsi->u.http.rx_content_length); + } + + { + int n = 0, len; + char buf[256]; + const unsigned char *c; + + do { + c = lws_token_to_string(n); + if (!c) { + n++; + continue; + } + + len = lws_hdr_total_length(h2n->swsi, n); + if (!len || len > sizeof(buf) - 1) { + n++; + continue; + } + + lws_hdr_copy(h2n->swsi, buf, sizeof buf, n); + buf[sizeof(buf) - 1] = '\0'; + + lwsl_info(" %s = %s\n", (char *)c, buf); + n++; + } while (c); + } + + if (h2n->swsi->u.h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE || + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_CLOSED) { + lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, + "Banning service on CLOSED_REMOTE"); + break; + } + + switch (h2n->swsi->u.h2.h2_state) { + case LWS_H2_STATE_OPEN: + if (h2n->swsi->u.h2.END_STREAM) + lws_h2_state(h2n->swsi, + LWS_H2_STATE_HALF_CLOSED_REMOTE); + break; + case LWS_H2_STATE_HALF_CLOSED_LOCAL: + if (h2n->swsi->u.h2.END_STREAM) + lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED); + break; + } + + if (!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_PATH) || + !lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD) || + !lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_SCHEME) || + lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_STATUS) || + lws_hdr_extant(h2n->swsi, WSI_TOKEN_CONNECTION)) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Pseudoheader checks"); + break; + } + + if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_TE)) { + n = lws_hdr_total_length(h2n->swsi, WSI_TOKEN_TE); + + if (n != 8 || + strncmp(lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_TE), + "trailers", n)) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Illegal transfer-encoding"); + break; + } + } + + p = lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD); + if (!strcmp(p, "POST")) + h2n->swsi->u.hdr.ah->frag_index[WSI_TOKEN_POST_URI] = + h2n->swsi->u.hdr.ah->frag_index[WSI_TOKEN_HTTP_COLON_PATH]; + + wsi->vhost->conn_stats.h2_trans++; + + lwsl_info(" action start...\n"); + n = lws_http_action(h2n->swsi); + lwsl_info(" action result %d (wsi->u.http.rx_content_remain %lld)\n", n, h2n->swsi->u.http.rx_content_remain); + + /* + * Commonly we only managed to start a larger transfer that will + * complete asynchronously. In those cases we will hear about + * END_STREAM going out in the POLLOUT handler. + */ + if (n || h2n->swsi->u.h2.send_END_STREAM) { + lws_close_free_wsi(h2n->swsi, 0); + h2n->swsi = NULL; + break; + } + break; + + case LWS_H2_FRAME_TYPE_DATA: + if (!h2n->swsi) + break; + + if (lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) && + h2n->swsi->u.h2.END_STREAM && h2n->swsi->u.http.rx_content_length && + h2n->swsi->u.http.rx_content_remain) { + lws_h2_rst_stream(h2n->swsi, H2_ERR_PROTOCOL_ERROR, + "Not enough rx content"); + break; + } + + if (h2n->swsi->u.h2.END_STREAM && + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_OPEN) + lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_REMOTE); + + if (h2n->swsi->u.h2.END_STREAM && + h2n->swsi->u.h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL) + lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED); + break; + + case LWS_H2_FRAME_TYPE_PING: + if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack + } else {/* they're sending us a ping request */ + lwsl_info("rx ping, preparing pong\n"); + pps = lws_h2_new_pps(LWS_H2_PPS_PONG); + if (!pps) + return 1; + memcpy(pps->u.ping.ping_payload, h2n->ping_payload, 8); + lws_pps_schedule(wsi, pps); + } + + break; + + case LWS_H2_FRAME_TYPE_WINDOW_UPDATE: + h2n->hpack_e_dep &= ~(1 << 31); + lwsl_info("WINDOW_UPDATE: sid %d %u (0x%x)\n", h2n->sid, + h2n->hpack_e_dep, h2n->hpack_e_dep); + + if (h2n->sid) + eff_wsi = h2n->swsi; + + if (!eff_wsi) { + if (h2n->sid > h2n->highest_sid_opened) + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "alien sid"); + break; /* ignore */ + } + + if ((uint64_t)eff_wsi->u.h2.tx_cr + (uint64_t)h2n->hpack_e_dep > + (uint64_t)0x7fffffff) { + if (h2n->sid) + lws_h2_rst_stream(h2n->swsi, + H2_ERR_FLOW_CONTROL_ERROR, + "Flow control exceeded max"); + else + lws_h2_goaway(wsi, H2_ERR_FLOW_CONTROL_ERROR, + "Flow control exceeded max"); + break; + } + + if (!h2n->hpack_e_dep) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "Zero length window update"); + break; + } + n = eff_wsi->u.h2.tx_cr; + eff_wsi->u.h2.tx_cr += h2n->hpack_e_dep; + + if (n <= 0 && eff_wsi->u.h2.tx_cr <= 0) + /* it helps, but won't change sendability for anyone */ + break; + + /* + * It did change sendability... for us and any children waiting + * on us... reassess blockage for all children first + */ + lws_start_foreach_ll(struct lws *, w, wsi->u.h2.child_list) { + lws_callback_on_writable(w); + } lws_end_foreach_ll(w, u.h2.sibling_list); + + if (eff_wsi->u.h2.skint && lws_h2_tx_cr_get(eff_wsi)) { + lwsl_info("%s: %p: skint\n", __func__, wsi); + eff_wsi->u.h2.skint = 0; + lws_callback_on_writable(eff_wsi); + } + break; + + case LWS_H2_FRAME_TYPE_GOAWAY: + lwsl_info("GOAWAY: last sid %d, error 0x%08X, string '%s'\n", + h2n->goaway_last_sid, h2n->goaway_err, h2n->goaway_str); + wsi->u.h2.GOING_AWAY = 1; + + return 1; + + case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */ + break; + } + return 0; } -struct lws * lws_http2_get_nth_child(struct lws *wsi, int n) +int +lws_h2_parser(struct lws *wsi, unsigned char c) { - do { - wsi = wsi->u.http2.next_child_wsi; - if (!wsi) - return NULL; - } while (n--); + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + struct lws_h2_protocol_send *pps; + int n; - return wsi; + if (!h2n) + return 1; + + switch (wsi->state) { + case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: + if (preface[h2n->count++] != c) + return 1; + + if (preface[h2n->count]) + break; + + lwsl_info("http2: %p: established\n", wsi); + wsi->state = LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS; + h2n->count = 0; + wsi->u.h2.tx_cr = 65535; + + /* + * we must send a settings frame -- empty one is OK... + * that must be the first thing sent by server + * and the peer must send a SETTINGS with ACK flag... + */ + pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS); + if (!pps) + return 1; + lws_pps_schedule(wsi, pps); + break; + + case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: + case LWSS_HTTP2_ESTABLISHED: + if (h2n->frame_state != LWS_H2_FRAME_HEADER_LENGTH) + goto try_frame_start; + + /* + * post-header, preamble / payload / padding part + */ + h2n->count++; + + if (h2n->flags & LWS_H2_FLAG_PADDED && !h2n->pad_length) { + /* + * Get the padding count... actual padding is + * at the end of the frame. + */ + h2n->padding = c; + h2n->pad_length = 1; + h2n->preamble++; + + if (h2n->padding > h2n->length - 1) + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "execssive padding"); + break; /* we consumed this */ + } + + if (h2n->flags & LWS_H2_FLAG_PRIORITY && + !h2n->collected_priority) { + /* going to be 5 preamble bytes */ + + lwsl_debug("PRIORITY FLAG: 0x%x\n", c); + + if (h2n->preamble++ - h2n->pad_length < 4) { + h2n->dep = ((h2n->dep) << 8) | c; + break; /* we consumed this */ + } + h2n->weight_temp = c; + h2n->collected_priority = 1; + lwsl_debug("PRI FL: dep 0x%x, weight 0x%02X\n", + h2n->dep, h2n->weight_temp); + break; /* we consumed this */ + } + if (h2n->padding && h2n->count > (h2n->length - h2n->padding)) { + if (c) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "nonzero padding"); + break; + } + goto frame_end; + } + + /* applies to wsi->u.h2.swsi which may be wsi */ + switch(h2n->type) { + + case LWS_H2_FRAME_TYPE_SETTINGS: + n = (h2n->count - 1 - h2n->preamble) % + LWS_H2_SETTINGS_LEN; + h2n->one_setting[n] = c; + if (n != LWS_H2_SETTINGS_LEN - 1) + break; + lws_h2_settings(wsi, &h2n->set, h2n->one_setting, + LWS_H2_SETTINGS_LEN); + break; + + case LWS_H2_FRAME_TYPE_CONTINUATION: + case LWS_H2_FRAME_TYPE_HEADERS: + if (!h2n->swsi) + break; + if (lws_hpack_interpret(h2n->swsi, c)) { + lwsl_info("%s: hpack failed\n", __func__); + return 1; + } + break; + + case LWS_H2_FRAME_TYPE_GOAWAY: + switch (h2n->inside++) { + case 0: + case 1: + case 2: + case 3: + h2n->goaway_last_sid <<= 8; + h2n->goaway_last_sid |= c; + h2n->goaway_str[0] = '\0'; + break; + + case 4: + case 5: + case 6: + case 7: + h2n->goaway_err <<= 8; + h2n->goaway_err |= c; + break; + + default: + if (h2n->inside - 9 < + sizeof(h2n->goaway_str) - 1) + h2n->goaway_str[h2n->inside - 9] = c; + h2n->goaway_str[sizeof(h2n->goaway_str) - 1] = '\0'; + break; + } + break; + + case LWS_H2_FRAME_TYPE_DATA: + //lwsl_notice("incoming LWS_H2_FRAME_TYPE_DATA content\n"); + if (!h2n->swsi) { + //lwsl_notice("data sid %d has no swsi\n", h2n->sid); + break; + } + + h2n->swsi->state = LWSS_HTTP_BODY; + + h2n->inside++; + /* because the HTTP_BODY stuff will handle it */ + //h2n->swsi->u.http.rx_content_remain--; + //lwsl_info("remain %lld, %d / %d", + // (long long)h2n->swsi->u.http.rx_content_remain, + // h2n->inside, h2n->length); + if (lws_hdr_total_length(h2n->swsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH) && + h2n->swsi->u.http.rx_content_length && + h2n->swsi->u.http.rx_content_remain == 1 && /* last byte */ + h2n->inside < h2n->length) { /* unread data in frame */ + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "More rx than content_length told"); + break; + } + + n = lws_read(h2n->swsi, &c, 1); + if (n < 0) { + // lws_h2_rst_stream(wsi, LWS_H2_PPS_RST_STREAM, + // "post body done"); + break; + } + break; + + case LWS_H2_FRAME_TYPE_PRIORITY: + if (h2n->count <= 4) { + h2n->dep <<= 8; + h2n->dep |= c; + } else { + h2n->weight_temp = c; + lwsl_info("PRIORITY: dep 0x%x, weight 0x%02X\n", + h2n->dep, h2n->weight_temp); + + if ((h2n->dep & ~(1 << 31)) == h2n->sid) { + lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, + "cant depend on own sid"); + break; + } + } + break; + + case LWS_H2_FRAME_TYPE_RST_STREAM: + break; + + case LWS_H2_FRAME_TYPE_PUSH_PROMISE: + break; + + case LWS_H2_FRAME_TYPE_PING: + if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack + } else { /* they're sending us a ping request */ + if (h2n->count > 8) + return 1; + h2n->ping_payload[h2n->count - 1] = c; + } + break; + + case LWS_H2_FRAME_TYPE_WINDOW_UPDATE: + h2n->hpack_e_dep <<= 8; + h2n->hpack_e_dep |= c; + break; + + case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */ + break; + + default: + lwsl_notice("%s: unhandled frame type %d\n", + __func__, h2n->type); + + return 1; + } + +frame_end: + if (h2n->count != h2n->length) + break; + + /* + * end of frame just happened + */ + if (lws_h2_parse_end_of_frame(wsi)) + return 1; + break; + +try_frame_start: + if (h2n->frame_state <= 8) { + + switch (h2n->frame_state++) { + case 0: + h2n->pad_length = 0; + h2n->collected_priority = 0; + h2n->padding = 0; + h2n->preamble = 0; + h2n->length = c; + h2n->inside = 0; + break; + case 1: + case 2: + h2n->length <<= 8; + h2n->length |= c; + break; + case 3: + h2n->type = c; + break; + case 4: + h2n->flags = c; + break; + + case 5: + case 6: + case 7: + case 8: + h2n->sid <<= 8; + h2n->sid |= c; + break; + } + } + if (h2n->frame_state == LWS_H2_FRAME_HEADER_LENGTH) + if (lws_h2_parse_frame_header(wsi)) + return 1; + break; + } + + return 0; } + diff --git a/lib/lejp-conf.c b/lib/lejp-conf.c index c2f4793d1..e84f4b24c 100644 --- a/lib/lejp-conf.c +++ b/lib/lejp-conf.c @@ -477,7 +477,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) for (n = 0; n < ARRAY_SIZE(mount_protocols); n++) if (!strncmp(a->m.origin, mount_protocols[n], strlen(mount_protocols[n]))) { - lwsl_err("----%s\n", a->m.origin); + lwsl_info("----%s\n", a->m.origin); m->origin_protocol = n; m->origin = a->m.origin + strlen(mount_protocols[n]); diff --git a/lib/lextable-strings.h b/lib/lextable-strings.h index c9fe6ff3a..ab42c3e47 100644 --- a/lib/lextable-strings.h +++ b/lib/lextable-strings.h @@ -96,6 +96,8 @@ STORE_IN_ROM static const char * const set[] = { "x-forwarded-for", "connect ", + "head ", + "te:", /* http/2 wants it to reject it */ "", /* not matchable */ diff --git a/lib/lextable.h b/lib/lextable.h index 2f4f079ac..f940afd25 100644 --- a/lib/lextable.h +++ b/lib/lextable.h @@ -2,39 +2,39 @@ 0x70 /* 'p' */, 0x42, 0x00 /* (to 0x0045 state 5) */, 0x6F /* 'o' */, 0x51, 0x00 /* (to 0x0057 state 10) */, 0x68 /* 'h' */, 0x5D, 0x00 /* (to 0x0066 state 18) */, - 0x63 /* 'c' */, 0x66, 0x00 /* (to 0x0072 state 23) */, - 0x75 /* 'u' */, 0x87, 0x00 /* (to 0x0096 state 34) */, - 0x73 /* 's' */, 0x9D, 0x00 /* (to 0x00AF state 48) */, - 0x0D /* '.' */, 0xD6, 0x00 /* (to 0x00EB state 68) */, - 0x61 /* 'a' */, 0x2E, 0x01 /* (to 0x0146 state 129) */, - 0x69 /* 'i' */, 0x6D, 0x01 /* (to 0x0188 state 163) */, - 0x64 /* 'd' */, 0x16, 0x02 /* (to 0x0234 state 265) */, - 0x72 /* 'r' */, 0x1F, 0x02 /* (to 0x0240 state 270) */, - 0x3A /* ':' */, 0x50, 0x02 /* (to 0x0274 state 299) */, - 0x65 /* 'e' */, 0xDC, 0x02 /* (to 0x0303 state 409) */, - 0x66 /* 'f' */, 0xF8, 0x02 /* (to 0x0322 state 425) */, - 0x6C /* 'l' */, 0x1A, 0x03 /* (to 0x0347 state 458) */, - 0x6D /* 'm' */, 0x3D, 0x03 /* (to 0x036D state 484) */, - 0x74 /* 't' */, 0xAC, 0x03 /* (to 0x03DF state 578) */, - 0x76 /* 'v' */, 0xC7, 0x03 /* (to 0x03FD state 606) */, - 0x77 /* 'w' */, 0xD4, 0x03 /* (to 0x040D state 614) */, - 0x78 /* 'x' */, 0xFB, 0x03 /* (to 0x0437 state 650) */, + 0x63 /* 'c' */, 0x69, 0x00 /* (to 0x0075 state 23) */, + 0x75 /* 'u' */, 0x8A, 0x00 /* (to 0x0099 state 34) */, + 0x73 /* 's' */, 0xA0, 0x00 /* (to 0x00B2 state 48) */, + 0x0D /* '.' */, 0xD9, 0x00 /* (to 0x00EE state 68) */, + 0x61 /* 'a' */, 0x31, 0x01 /* (to 0x0149 state 129) */, + 0x69 /* 'i' */, 0x70, 0x01 /* (to 0x018B state 163) */, + 0x64 /* 'd' */, 0x19, 0x02 /* (to 0x0237 state 265) */, + 0x72 /* 'r' */, 0x22, 0x02 /* (to 0x0243 state 270) */, + 0x3A /* ':' */, 0x53, 0x02 /* (to 0x0277 state 299) */, + 0x65 /* 'e' */, 0xDF, 0x02 /* (to 0x0306 state 409) */, + 0x66 /* 'f' */, 0xFB, 0x02 /* (to 0x0325 state 425) */, + 0x6C /* 'l' */, 0x1D, 0x03 /* (to 0x034A state 458) */, + 0x6D /* 'm' */, 0x40, 0x03 /* (to 0x0370 state 484) */, + 0x74 /* 't' */, 0xAF, 0x03 /* (to 0x03E2 state 578) */, + 0x76 /* 'v' */, 0xD0, 0x03 /* (to 0x0406 state 606) */, + 0x77 /* 'w' */, 0xDD, 0x03 /* (to 0x0416 state 614) */, + 0x78 /* 'x' */, 0x04, 0x04 /* (to 0x0440 state 650) */, 0x08, /* fail */ /* pos 0040: 1 */ 0xE5 /* 'e' -> */, /* pos 0041: 2 */ 0xF4 /* 't' -> */, /* pos 0042: 3 */ 0xA0 /* ' ' -> */, /* pos 0043: 4 */ 0x00, 0x00 /* - terminal marker 0 - */, /* pos 0045: 5 */ 0x6F /* 'o' */, 0x0D, 0x00 /* (to 0x0052 state 6) */, - 0x72 /* 'r' */, 0x92, 0x01 /* (to 0x01DA state 211) */, - 0x61 /* 'a' */, 0xD4, 0x03 /* (to 0x041F state 631) */, - 0x75 /* 'u' */, 0xD6, 0x03 /* (to 0x0424 state 635) */, + 0x72 /* 'r' */, 0x95, 0x01 /* (to 0x01DD state 211) */, + 0x61 /* 'a' */, 0xDD, 0x03 /* (to 0x0428 state 631) */, + 0x75 /* 'u' */, 0xDF, 0x03 /* (to 0x042D state 635) */, 0x08, /* fail */ /* pos 0052: 6 */ 0xF3 /* 's' -> */, /* pos 0053: 7 */ 0xF4 /* 't' -> */, /* pos 0054: 8 */ 0xA0 /* ' ' -> */, /* pos 0055: 9 */ 0x00, 0x01 /* - terminal marker 1 - */, /* pos 0057: 10 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x005E state 11) */, - 0x72 /* 'r' */, 0x4E, 0x00 /* (to 0x00A8 state 42) */, + 0x72 /* 'r' */, 0x51, 0x00 /* (to 0x00AB state 42) */, 0x08, /* fail */ /* pos 005e: 11 */ 0xF4 /* 't' -> */, /* pos 005f: 12 */ 0xE9 /* 'i' -> */, @@ -43,754 +43,763 @@ /* pos 0062: 15 */ 0xF3 /* 's' -> */, /* pos 0063: 16 */ 0xA0 /* ' ' -> */, /* pos 0064: 17 */ 0x00, 0x02 /* - terminal marker 2 - */, -/* pos 0066: 18 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x006D state 19) */, - 0x74 /* 't' */, 0xBC, 0x00 /* (to 0x0125 state 110) */, +/* pos 0066: 18 */ 0x6F /* 'o' */, 0x0A, 0x00 /* (to 0x0070 state 19) */, + 0x74 /* 't' */, 0xBF, 0x00 /* (to 0x0128 state 110) */, + 0x65 /* 'e' */, 0xF8, 0x03 /* (to 0x0464 state 676) */, 0x08, /* fail */ -/* pos 006d: 19 */ 0xF3 /* 's' -> */, -/* pos 006e: 20 */ 0xF4 /* 't' -> */, -/* pos 006f: 21 */ 0xBA /* ':' -> */, -/* pos 0070: 22 */ 0x00, 0x03 /* - terminal marker 3 - */, -/* pos 0072: 23 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x0079 state 24) */, - 0x61 /* 'a' */, 0x72, 0x01 /* (to 0x01E7 state 217) */, +/* pos 0070: 19 */ 0xF3 /* 's' -> */, +/* pos 0071: 20 */ 0xF4 /* 't' -> */, +/* pos 0072: 21 */ 0xBA /* ':' -> */, +/* pos 0073: 22 */ 0x00, 0x03 /* - terminal marker 3 - */, +/* pos 0075: 23 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x007C state 24) */, + 0x61 /* 'a' */, 0x72, 0x01 /* (to 0x01EA state 217) */, 0x08, /* fail */ -/* pos 0079: 24 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x0080 state 25) */, - 0x6F /* 'o' */, 0x87, 0x01 /* (to 0x0203 state 243) */, +/* pos 007c: 24 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x0083 state 25) */, + 0x6F /* 'o' */, 0x87, 0x01 /* (to 0x0206 state 243) */, 0x08, /* fail */ -/* pos 0080: 25 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x0087 state 26) */, - 0x74 /* 't' */, 0x86, 0x01 /* (to 0x0209 state 248) */, +/* pos 0083: 25 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x008A state 26) */, + 0x74 /* 't' */, 0x86, 0x01 /* (to 0x020C state 248) */, 0x08, /* fail */ -/* pos 0087: 26 */ 0xE5 /* 'e' -> */, -/* pos 0088: 27 */ 0xE3 /* 'c' -> */, -/* pos 0089: 28 */ 0xF4 /* 't' -> */, -/* pos 008a: 29 */ 0x69 /* 'i' */, 0x07, 0x00 /* (to 0x0091 state 30) */, - 0x20 /* ' ' */, 0xCC, 0x03 /* (to 0x0459 state 675) */, +/* pos 008a: 26 */ 0xE5 /* 'e' -> */, +/* pos 008b: 27 */ 0xE3 /* 'c' -> */, +/* pos 008c: 28 */ 0xF4 /* 't' -> */, +/* pos 008d: 29 */ 0x69 /* 'i' */, 0x07, 0x00 /* (to 0x0094 state 30) */, + 0x20 /* ' ' */, 0xD2, 0x03 /* (to 0x0462 state 675) */, 0x08, /* fail */ -/* pos 0091: 30 */ 0xEF /* 'o' -> */, -/* pos 0092: 31 */ 0xEE /* 'n' -> */, -/* pos 0093: 32 */ 0xBA /* ':' -> */, -/* pos 0094: 33 */ 0x00, 0x04 /* - terminal marker 4 - */, -/* pos 0096: 34 */ 0x70 /* 'p' */, 0x0A, 0x00 /* (to 0x00A0 state 35) */, - 0x73 /* 's' */, 0x59, 0x03 /* (to 0x03F2 state 596) */, - 0x72 /* 'r' */, 0x91, 0x03 /* (to 0x042D state 642) */, +/* pos 0094: 30 */ 0xEF /* 'o' -> */, +/* pos 0095: 31 */ 0xEE /* 'n' -> */, +/* pos 0096: 32 */ 0xBA /* ':' -> */, +/* pos 0097: 33 */ 0x00, 0x04 /* - terminal marker 4 - */, +/* pos 0099: 34 */ 0x70 /* 'p' */, 0x0A, 0x00 /* (to 0x00A3 state 35) */, + 0x73 /* 's' */, 0x5F, 0x03 /* (to 0x03FB state 596) */, + 0x72 /* 'r' */, 0x97, 0x03 /* (to 0x0436 state 642) */, 0x08, /* fail */ -/* pos 00a0: 35 */ 0xE7 /* 'g' -> */, -/* pos 00a1: 36 */ 0xF2 /* 'r' -> */, -/* pos 00a2: 37 */ 0xE1 /* 'a' -> */, -/* pos 00a3: 38 */ 0xE4 /* 'd' -> */, -/* pos 00a4: 39 */ 0xE5 /* 'e' -> */, -/* pos 00a5: 40 */ 0xBA /* ':' -> */, -/* pos 00a6: 41 */ 0x00, 0x05 /* - terminal marker 5 - */, -/* pos 00a8: 42 */ 0xE9 /* 'i' -> */, -/* pos 00a9: 43 */ 0xE7 /* 'g' -> */, -/* pos 00aa: 44 */ 0xE9 /* 'i' -> */, -/* pos 00ab: 45 */ 0xEE /* 'n' -> */, -/* pos 00ac: 46 */ 0xBA /* ':' -> */, -/* pos 00ad: 47 */ 0x00, 0x06 /* - terminal marker 6 - */, -/* pos 00af: 48 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x00B6 state 49) */, - 0x74 /* 't' */, 0x13, 0x03 /* (to 0x03C5 state 553) */, +/* pos 00a3: 35 */ 0xE7 /* 'g' -> */, +/* pos 00a4: 36 */ 0xF2 /* 'r' -> */, +/* pos 00a5: 37 */ 0xE1 /* 'a' -> */, +/* pos 00a6: 38 */ 0xE4 /* 'd' -> */, +/* pos 00a7: 39 */ 0xE5 /* 'e' -> */, +/* pos 00a8: 40 */ 0xBA /* ':' -> */, +/* pos 00a9: 41 */ 0x00, 0x05 /* - terminal marker 5 - */, +/* pos 00ab: 42 */ 0xE9 /* 'i' -> */, +/* pos 00ac: 43 */ 0xE7 /* 'g' -> */, +/* pos 00ad: 44 */ 0xE9 /* 'i' -> */, +/* pos 00ae: 45 */ 0xEE /* 'n' -> */, +/* pos 00af: 46 */ 0xBA /* ':' -> */, +/* pos 00b0: 47 */ 0x00, 0x06 /* - terminal marker 6 - */, +/* pos 00b2: 48 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x00B9 state 49) */, + 0x74 /* 't' */, 0x13, 0x03 /* (to 0x03C8 state 553) */, 0x08, /* fail */ -/* pos 00b6: 49 */ 0x63 /* 'c' */, 0x0A, 0x00 /* (to 0x00C0 state 50) */, - 0x72 /* 'r' */, 0xFC, 0x02 /* (to 0x03B5 state 539) */, - 0x74 /* 't' */, 0xFF, 0x02 /* (to 0x03BB state 544) */, +/* pos 00b9: 49 */ 0x63 /* 'c' */, 0x0A, 0x00 /* (to 0x00C3 state 50) */, + 0x72 /* 'r' */, 0xFC, 0x02 /* (to 0x03B8 state 539) */, + 0x74 /* 't' */, 0xFF, 0x02 /* (to 0x03BE state 544) */, 0x08, /* fail */ -/* pos 00c0: 50 */ 0xAD /* '-' -> */, -/* pos 00c1: 51 */ 0xF7 /* 'w' -> */, -/* pos 00c2: 52 */ 0xE5 /* 'e' -> */, -/* pos 00c3: 53 */ 0xE2 /* 'b' -> */, -/* pos 00c4: 54 */ 0xF3 /* 's' -> */, -/* pos 00c5: 55 */ 0xEF /* 'o' -> */, -/* pos 00c6: 56 */ 0xE3 /* 'c' -> */, -/* pos 00c7: 57 */ 0xEB /* 'k' -> */, -/* pos 00c8: 58 */ 0xE5 /* 'e' -> */, -/* pos 00c9: 59 */ 0xF4 /* 't' -> */, -/* pos 00ca: 60 */ 0xAD /* '-' -> */, -/* pos 00cb: 61 */ 0x64 /* 'd' */, 0x19, 0x00 /* (to 0x00E4 state 62) */, - 0x65 /* 'e' */, 0x20, 0x00 /* (to 0x00EE state 70) */, - 0x6B /* 'k' */, 0x29, 0x00 /* (to 0x00FA state 81) */, - 0x70 /* 'p' */, 0x38, 0x00 /* (to 0x010C state 88) */, - 0x61 /* 'a' */, 0x3F, 0x00 /* (to 0x0116 state 97) */, - 0x6E /* 'n' */, 0x44, 0x00 /* (to 0x011E state 104) */, - 0x76 /* 'v' */, 0x86, 0x01 /* (to 0x0263 state 284) */, - 0x6F /* 'o' */, 0x8C, 0x01 /* (to 0x026C state 292) */, +/* pos 00c3: 50 */ 0xAD /* '-' -> */, +/* pos 00c4: 51 */ 0xF7 /* 'w' -> */, +/* pos 00c5: 52 */ 0xE5 /* 'e' -> */, +/* pos 00c6: 53 */ 0xE2 /* 'b' -> */, +/* pos 00c7: 54 */ 0xF3 /* 's' -> */, +/* pos 00c8: 55 */ 0xEF /* 'o' -> */, +/* pos 00c9: 56 */ 0xE3 /* 'c' -> */, +/* pos 00ca: 57 */ 0xEB /* 'k' -> */, +/* pos 00cb: 58 */ 0xE5 /* 'e' -> */, +/* pos 00cc: 59 */ 0xF4 /* 't' -> */, +/* pos 00cd: 60 */ 0xAD /* '-' -> */, +/* pos 00ce: 61 */ 0x64 /* 'd' */, 0x19, 0x00 /* (to 0x00E7 state 62) */, + 0x65 /* 'e' */, 0x20, 0x00 /* (to 0x00F1 state 70) */, + 0x6B /* 'k' */, 0x29, 0x00 /* (to 0x00FD state 81) */, + 0x70 /* 'p' */, 0x38, 0x00 /* (to 0x010F state 88) */, + 0x61 /* 'a' */, 0x3F, 0x00 /* (to 0x0119 state 97) */, + 0x6E /* 'n' */, 0x44, 0x00 /* (to 0x0121 state 104) */, + 0x76 /* 'v' */, 0x86, 0x01 /* (to 0x0266 state 284) */, + 0x6F /* 'o' */, 0x8C, 0x01 /* (to 0x026F state 292) */, 0x08, /* fail */ -/* pos 00e4: 62 */ 0xF2 /* 'r' -> */, -/* pos 00e5: 63 */ 0xE1 /* 'a' -> */, -/* pos 00e6: 64 */ 0xE6 /* 'f' -> */, -/* pos 00e7: 65 */ 0xF4 /* 't' -> */, -/* pos 00e8: 66 */ 0xBA /* ':' -> */, -/* pos 00e9: 67 */ 0x00, 0x07 /* - terminal marker 7 - */, -/* pos 00eb: 68 */ 0x8A /* '.' -> */, -/* pos 00ec: 69 */ 0x00, 0x08 /* - terminal marker 8 - */, -/* pos 00ee: 70 */ 0xF8 /* 'x' -> */, -/* pos 00ef: 71 */ 0xF4 /* 't' -> */, -/* pos 00f0: 72 */ 0xE5 /* 'e' -> */, -/* pos 00f1: 73 */ 0xEE /* 'n' -> */, -/* pos 00f2: 74 */ 0xF3 /* 's' -> */, -/* pos 00f3: 75 */ 0xE9 /* 'i' -> */, -/* pos 00f4: 76 */ 0xEF /* 'o' -> */, -/* pos 00f5: 77 */ 0xEE /* 'n' -> */, -/* pos 00f6: 78 */ 0xF3 /* 's' -> */, -/* pos 00f7: 79 */ 0xBA /* ':' -> */, -/* pos 00f8: 80 */ 0x00, 0x09 /* - terminal marker 9 - */, -/* pos 00fa: 81 */ 0xE5 /* 'e' -> */, -/* pos 00fb: 82 */ 0xF9 /* 'y' -> */, -/* pos 00fc: 83 */ 0x31 /* '1' */, 0x0A, 0x00 /* (to 0x0106 state 84) */, - 0x32 /* '2' */, 0x0A, 0x00 /* (to 0x0109 state 86) */, - 0x3A /* ':' */, 0x5F, 0x01 /* (to 0x0261 state 283) */, +/* pos 00e7: 62 */ 0xF2 /* 'r' -> */, +/* pos 00e8: 63 */ 0xE1 /* 'a' -> */, +/* pos 00e9: 64 */ 0xE6 /* 'f' -> */, +/* pos 00ea: 65 */ 0xF4 /* 't' -> */, +/* pos 00eb: 66 */ 0xBA /* ':' -> */, +/* pos 00ec: 67 */ 0x00, 0x07 /* - terminal marker 7 - */, +/* pos 00ee: 68 */ 0x8A /* '.' -> */, +/* pos 00ef: 69 */ 0x00, 0x08 /* - terminal marker 8 - */, +/* pos 00f1: 70 */ 0xF8 /* 'x' -> */, +/* pos 00f2: 71 */ 0xF4 /* 't' -> */, +/* pos 00f3: 72 */ 0xE5 /* 'e' -> */, +/* pos 00f4: 73 */ 0xEE /* 'n' -> */, +/* pos 00f5: 74 */ 0xF3 /* 's' -> */, +/* pos 00f6: 75 */ 0xE9 /* 'i' -> */, +/* pos 00f7: 76 */ 0xEF /* 'o' -> */, +/* pos 00f8: 77 */ 0xEE /* 'n' -> */, +/* pos 00f9: 78 */ 0xF3 /* 's' -> */, +/* pos 00fa: 79 */ 0xBA /* ':' -> */, +/* pos 00fb: 80 */ 0x00, 0x09 /* - terminal marker 9 - */, +/* pos 00fd: 81 */ 0xE5 /* 'e' -> */, +/* pos 00fe: 82 */ 0xF9 /* 'y' -> */, +/* pos 00ff: 83 */ 0x31 /* '1' */, 0x0A, 0x00 /* (to 0x0109 state 84) */, + 0x32 /* '2' */, 0x0A, 0x00 /* (to 0x010C state 86) */, + 0x3A /* ':' */, 0x5F, 0x01 /* (to 0x0264 state 283) */, 0x08, /* fail */ -/* pos 0106: 84 */ 0xBA /* ':' -> */, -/* pos 0107: 85 */ 0x00, 0x0A /* - terminal marker 10 - */, -/* pos 0109: 86 */ 0xBA /* ':' -> */, -/* pos 010a: 87 */ 0x00, 0x0B /* - terminal marker 11 - */, -/* pos 010c: 88 */ 0xF2 /* 'r' -> */, -/* pos 010d: 89 */ 0xEF /* 'o' -> */, -/* pos 010e: 90 */ 0xF4 /* 't' -> */, -/* pos 010f: 91 */ 0xEF /* 'o' -> */, -/* pos 0110: 92 */ 0xE3 /* 'c' -> */, -/* pos 0111: 93 */ 0xEF /* 'o' -> */, -/* pos 0112: 94 */ 0xEC /* 'l' -> */, -/* pos 0113: 95 */ 0xBA /* ':' -> */, -/* pos 0114: 96 */ 0x00, 0x0C /* - terminal marker 12 - */, -/* pos 0116: 97 */ 0xE3 /* 'c' -> */, -/* pos 0117: 98 */ 0xE3 /* 'c' -> */, -/* pos 0118: 99 */ 0xE5 /* 'e' -> */, -/* pos 0119: 100 */ 0xF0 /* 'p' -> */, -/* pos 011a: 101 */ 0xF4 /* 't' -> */, -/* pos 011b: 102 */ 0xBA /* ':' -> */, -/* pos 011c: 103 */ 0x00, 0x0D /* - terminal marker 13 - */, -/* pos 011e: 104 */ 0xEF /* 'o' -> */, -/* pos 011f: 105 */ 0xEE /* 'n' -> */, -/* pos 0120: 106 */ 0xE3 /* 'c' -> */, -/* pos 0121: 107 */ 0xE5 /* 'e' -> */, -/* pos 0122: 108 */ 0xBA /* ':' -> */, -/* pos 0123: 109 */ 0x00, 0x0E /* - terminal marker 14 - */, -/* pos 0125: 110 */ 0xF4 /* 't' -> */, -/* pos 0126: 111 */ 0xF0 /* 'p' -> */, -/* pos 0127: 112 */ 0x2F /* '/' */, 0x07, 0x00 /* (to 0x012E state 113) */, - 0x32 /* '2' */, 0x10, 0x00 /* (to 0x013A state 118) */, +/* pos 0109: 84 */ 0xBA /* ':' -> */, +/* pos 010a: 85 */ 0x00, 0x0A /* - terminal marker 10 - */, +/* pos 010c: 86 */ 0xBA /* ':' -> */, +/* pos 010d: 87 */ 0x00, 0x0B /* - terminal marker 11 - */, +/* pos 010f: 88 */ 0xF2 /* 'r' -> */, +/* pos 0110: 89 */ 0xEF /* 'o' -> */, +/* pos 0111: 90 */ 0xF4 /* 't' -> */, +/* pos 0112: 91 */ 0xEF /* 'o' -> */, +/* pos 0113: 92 */ 0xE3 /* 'c' -> */, +/* pos 0114: 93 */ 0xEF /* 'o' -> */, +/* pos 0115: 94 */ 0xEC /* 'l' -> */, +/* pos 0116: 95 */ 0xBA /* ':' -> */, +/* pos 0117: 96 */ 0x00, 0x0C /* - terminal marker 12 - */, +/* pos 0119: 97 */ 0xE3 /* 'c' -> */, +/* pos 011a: 98 */ 0xE3 /* 'c' -> */, +/* pos 011b: 99 */ 0xE5 /* 'e' -> */, +/* pos 011c: 100 */ 0xF0 /* 'p' -> */, +/* pos 011d: 101 */ 0xF4 /* 't' -> */, +/* pos 011e: 102 */ 0xBA /* ':' -> */, +/* pos 011f: 103 */ 0x00, 0x0D /* - terminal marker 13 - */, +/* pos 0121: 104 */ 0xEF /* 'o' -> */, +/* pos 0122: 105 */ 0xEE /* 'n' -> */, +/* pos 0123: 106 */ 0xE3 /* 'c' -> */, +/* pos 0124: 107 */ 0xE5 /* 'e' -> */, +/* pos 0125: 108 */ 0xBA /* ':' -> */, +/* pos 0126: 109 */ 0x00, 0x0E /* - terminal marker 14 - */, +/* pos 0128: 110 */ 0xF4 /* 't' -> */, +/* pos 0129: 111 */ 0xF0 /* 'p' -> */, +/* pos 012a: 112 */ 0x2F /* '/' */, 0x07, 0x00 /* (to 0x0131 state 113) */, + 0x32 /* '2' */, 0x10, 0x00 /* (to 0x013D state 118) */, 0x08, /* fail */ -/* pos 012e: 113 */ 0xB1 /* '1' -> */, -/* pos 012f: 114 */ 0xAE /* '.' -> */, -/* pos 0130: 115 */ 0x31 /* '1' */, 0x07, 0x00 /* (to 0x0137 state 116) */, - 0x30 /* '0' */, 0x15, 0x03 /* (to 0x0448 state 660) */, +/* pos 0131: 113 */ 0xB1 /* '1' -> */, +/* pos 0132: 114 */ 0xAE /* '.' -> */, +/* pos 0133: 115 */ 0x31 /* '1' */, 0x07, 0x00 /* (to 0x013A state 116) */, + 0x30 /* '0' */, 0x1B, 0x03 /* (to 0x0451 state 660) */, 0x08, /* fail */ -/* pos 0137: 116 */ 0xA0 /* ' ' -> */, -/* pos 0138: 117 */ 0x00, 0x0F /* - terminal marker 15 - */, -/* pos 013a: 118 */ 0xAD /* '-' -> */, -/* pos 013b: 119 */ 0xF3 /* 's' -> */, -/* pos 013c: 120 */ 0xE5 /* 'e' -> */, -/* pos 013d: 121 */ 0xF4 /* 't' -> */, -/* pos 013e: 122 */ 0xF4 /* 't' -> */, -/* pos 013f: 123 */ 0xE9 /* 'i' -> */, -/* pos 0140: 124 */ 0xEE /* 'n' -> */, -/* pos 0141: 125 */ 0xE7 /* 'g' -> */, -/* pos 0142: 126 */ 0xF3 /* 's' -> */, -/* pos 0143: 127 */ 0xBA /* ':' -> */, -/* pos 0144: 128 */ 0x00, 0x10 /* - terminal marker 16 - */, -/* pos 0146: 129 */ 0x63 /* 'c' */, 0x0D, 0x00 /* (to 0x0153 state 130) */, - 0x75 /* 'u' */, 0xAC, 0x00 /* (to 0x01F5 state 230) */, - 0x67 /* 'g' */, 0x7D, 0x01 /* (to 0x02C9 state 358) */, - 0x6C /* 'l' */, 0x7E, 0x01 /* (to 0x02CD state 361) */, +/* pos 013a: 116 */ 0xA0 /* ' ' -> */, +/* pos 013b: 117 */ 0x00, 0x0F /* - terminal marker 15 - */, +/* pos 013d: 118 */ 0xAD /* '-' -> */, +/* pos 013e: 119 */ 0xF3 /* 's' -> */, +/* pos 013f: 120 */ 0xE5 /* 'e' -> */, +/* pos 0140: 121 */ 0xF4 /* 't' -> */, +/* pos 0141: 122 */ 0xF4 /* 't' -> */, +/* pos 0142: 123 */ 0xE9 /* 'i' -> */, +/* pos 0143: 124 */ 0xEE /* 'n' -> */, +/* pos 0144: 125 */ 0xE7 /* 'g' -> */, +/* pos 0145: 126 */ 0xF3 /* 's' -> */, +/* pos 0146: 127 */ 0xBA /* ':' -> */, +/* pos 0147: 128 */ 0x00, 0x10 /* - terminal marker 16 - */, +/* pos 0149: 129 */ 0x63 /* 'c' */, 0x0D, 0x00 /* (to 0x0156 state 130) */, + 0x75 /* 'u' */, 0xAC, 0x00 /* (to 0x01F8 state 230) */, + 0x67 /* 'g' */, 0x7D, 0x01 /* (to 0x02CC state 358) */, + 0x6C /* 'l' */, 0x7E, 0x01 /* (to 0x02D0 state 361) */, 0x08, /* fail */ -/* pos 0153: 130 */ 0xE3 /* 'c' -> */, -/* pos 0154: 131 */ 0xE5 /* 'e' -> */, -/* pos 0155: 132 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x015C state 133) */, - 0x73 /* 's' */, 0x0E, 0x00 /* (to 0x0166 state 136) */, +/* pos 0156: 130 */ 0xE3 /* 'c' -> */, +/* pos 0157: 131 */ 0xE5 /* 'e' -> */, +/* pos 0158: 132 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x015F state 133) */, + 0x73 /* 's' */, 0x0E, 0x00 /* (to 0x0169 state 136) */, 0x08, /* fail */ -/* pos 015c: 133 */ 0xF4 /* 't' -> */, -/* pos 015d: 134 */ 0x3A /* ':' */, 0x07, 0x00 /* (to 0x0164 state 135) */, - 0x2D /* '-' */, 0x59, 0x00 /* (to 0x01B9 state 192) */, +/* pos 015f: 133 */ 0xF4 /* 't' -> */, +/* pos 0160: 134 */ 0x3A /* ':' */, 0x07, 0x00 /* (to 0x0167 state 135) */, + 0x2D /* '-' */, 0x59, 0x00 /* (to 0x01BC state 192) */, 0x08, /* fail */ -/* pos 0164: 135 */ 0x00, 0x11 /* - terminal marker 17 - */, -/* pos 0166: 136 */ 0xF3 /* 's' -> */, -/* pos 0167: 137 */ 0xAD /* '-' -> */, -/* pos 0168: 138 */ 0xE3 /* 'c' -> */, -/* pos 0169: 139 */ 0xEF /* 'o' -> */, -/* pos 016a: 140 */ 0xEE /* 'n' -> */, -/* pos 016b: 141 */ 0xF4 /* 't' -> */, -/* pos 016c: 142 */ 0xF2 /* 'r' -> */, -/* pos 016d: 143 */ 0xEF /* 'o' -> */, -/* pos 016e: 144 */ 0xEC /* 'l' -> */, -/* pos 016f: 145 */ 0xAD /* '-' -> */, -/* pos 0170: 146 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x0177 state 147) */, - 0x61 /* 'a' */, 0x48, 0x01 /* (to 0x02BB state 345) */, +/* pos 0167: 135 */ 0x00, 0x11 /* - terminal marker 17 - */, +/* pos 0169: 136 */ 0xF3 /* 's' -> */, +/* pos 016a: 137 */ 0xAD /* '-' -> */, +/* pos 016b: 138 */ 0xE3 /* 'c' -> */, +/* pos 016c: 139 */ 0xEF /* 'o' -> */, +/* pos 016d: 140 */ 0xEE /* 'n' -> */, +/* pos 016e: 141 */ 0xF4 /* 't' -> */, +/* pos 016f: 142 */ 0xF2 /* 'r' -> */, +/* pos 0170: 143 */ 0xEF /* 'o' -> */, +/* pos 0171: 144 */ 0xEC /* 'l' -> */, +/* pos 0172: 145 */ 0xAD /* '-' -> */, +/* pos 0173: 146 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x017A state 147) */, + 0x61 /* 'a' */, 0x48, 0x01 /* (to 0x02BE state 345) */, 0x08, /* fail */ -/* pos 0177: 147 */ 0xE5 /* 'e' -> */, -/* pos 0178: 148 */ 0xF1 /* 'q' -> */, -/* pos 0179: 149 */ 0xF5 /* 'u' -> */, -/* pos 017a: 150 */ 0xE5 /* 'e' -> */, -/* pos 017b: 151 */ 0xF3 /* 's' -> */, -/* pos 017c: 152 */ 0xF4 /* 't' -> */, -/* pos 017d: 153 */ 0xAD /* '-' -> */, -/* pos 017e: 154 */ 0xE8 /* 'h' -> */, -/* pos 017f: 155 */ 0xE5 /* 'e' -> */, -/* pos 0180: 156 */ 0xE1 /* 'a' -> */, -/* pos 0181: 157 */ 0xE4 /* 'd' -> */, -/* pos 0182: 158 */ 0xE5 /* 'e' -> */, -/* pos 0183: 159 */ 0xF2 /* 'r' -> */, -/* pos 0184: 160 */ 0xF3 /* 's' -> */, -/* pos 0185: 161 */ 0xBA /* ':' -> */, -/* pos 0186: 162 */ 0x00, 0x12 /* - terminal marker 18 - */, -/* pos 0188: 163 */ 0xE6 /* 'f' -> */, -/* pos 0189: 164 */ 0xAD /* '-' -> */, -/* pos 018a: 165 */ 0x6D /* 'm' */, 0x0D, 0x00 /* (to 0x0197 state 166) */, - 0x6E /* 'n' */, 0x20, 0x00 /* (to 0x01AD state 181) */, - 0x72 /* 'r' */, 0x9E, 0x01 /* (to 0x032E state 435) */, - 0x75 /* 'u' */, 0xA2, 0x01 /* (to 0x0335 state 441) */, +/* pos 017a: 147 */ 0xE5 /* 'e' -> */, +/* pos 017b: 148 */ 0xF1 /* 'q' -> */, +/* pos 017c: 149 */ 0xF5 /* 'u' -> */, +/* pos 017d: 150 */ 0xE5 /* 'e' -> */, +/* pos 017e: 151 */ 0xF3 /* 's' -> */, +/* pos 017f: 152 */ 0xF4 /* 't' -> */, +/* pos 0180: 153 */ 0xAD /* '-' -> */, +/* pos 0181: 154 */ 0xE8 /* 'h' -> */, +/* pos 0182: 155 */ 0xE5 /* 'e' -> */, +/* pos 0183: 156 */ 0xE1 /* 'a' -> */, +/* pos 0184: 157 */ 0xE4 /* 'd' -> */, +/* pos 0185: 158 */ 0xE5 /* 'e' -> */, +/* pos 0186: 159 */ 0xF2 /* 'r' -> */, +/* pos 0187: 160 */ 0xF3 /* 's' -> */, +/* pos 0188: 161 */ 0xBA /* ':' -> */, +/* pos 0189: 162 */ 0x00, 0x12 /* - terminal marker 18 - */, +/* pos 018b: 163 */ 0xE6 /* 'f' -> */, +/* pos 018c: 164 */ 0xAD /* '-' -> */, +/* pos 018d: 165 */ 0x6D /* 'm' */, 0x0D, 0x00 /* (to 0x019A state 166) */, + 0x6E /* 'n' */, 0x20, 0x00 /* (to 0x01B0 state 181) */, + 0x72 /* 'r' */, 0x9E, 0x01 /* (to 0x0331 state 435) */, + 0x75 /* 'u' */, 0xA2, 0x01 /* (to 0x0338 state 441) */, 0x08, /* fail */ -/* pos 0197: 166 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x019E state 167) */, - 0x61 /* 'a' */, 0x8E, 0x01 /* (to 0x0328 state 430) */, +/* pos 019a: 166 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x01A1 state 167) */, + 0x61 /* 'a' */, 0x8E, 0x01 /* (to 0x032B state 430) */, 0x08, /* fail */ -/* pos 019e: 167 */ 0xE4 /* 'd' -> */, -/* pos 019f: 168 */ 0xE9 /* 'i' -> */, -/* pos 01a0: 169 */ 0xE6 /* 'f' -> */, -/* pos 01a1: 170 */ 0xE9 /* 'i' -> */, -/* pos 01a2: 171 */ 0xE5 /* 'e' -> */, -/* pos 01a3: 172 */ 0xE4 /* 'd' -> */, -/* pos 01a4: 173 */ 0xAD /* '-' -> */, -/* pos 01a5: 174 */ 0xF3 /* 's' -> */, -/* pos 01a6: 175 */ 0xE9 /* 'i' -> */, -/* pos 01a7: 176 */ 0xEE /* 'n' -> */, -/* pos 01a8: 177 */ 0xE3 /* 'c' -> */, -/* pos 01a9: 178 */ 0xE5 /* 'e' -> */, -/* pos 01aa: 179 */ 0xBA /* ':' -> */, -/* pos 01ab: 180 */ 0x00, 0x13 /* - terminal marker 19 - */, -/* pos 01ad: 181 */ 0xEF /* 'o' -> */, -/* pos 01ae: 182 */ 0xEE /* 'n' -> */, -/* pos 01af: 183 */ 0xE5 /* 'e' -> */, -/* pos 01b0: 184 */ 0xAD /* '-' -> */, -/* pos 01b1: 185 */ 0xED /* 'm' -> */, -/* pos 01b2: 186 */ 0xE1 /* 'a' -> */, -/* pos 01b3: 187 */ 0xF4 /* 't' -> */, -/* pos 01b4: 188 */ 0xE3 /* 'c' -> */, -/* pos 01b5: 189 */ 0xE8 /* 'h' -> */, -/* pos 01b6: 190 */ 0xBA /* ':' -> */, -/* pos 01b7: 191 */ 0x00, 0x14 /* - terminal marker 20 - */, -/* pos 01b9: 192 */ 0x65 /* 'e' */, 0x0D, 0x00 /* (to 0x01C6 state 193) */, - 0x6C /* 'l' */, 0x14, 0x00 /* (to 0x01D0 state 202) */, - 0x63 /* 'c' */, 0xEB, 0x00 /* (to 0x02AA state 330) */, - 0x72 /* 'r' */, 0xF1, 0x00 /* (to 0x02B3 state 338) */, +/* pos 01a1: 167 */ 0xE4 /* 'd' -> */, +/* pos 01a2: 168 */ 0xE9 /* 'i' -> */, +/* pos 01a3: 169 */ 0xE6 /* 'f' -> */, +/* pos 01a4: 170 */ 0xE9 /* 'i' -> */, +/* pos 01a5: 171 */ 0xE5 /* 'e' -> */, +/* pos 01a6: 172 */ 0xE4 /* 'd' -> */, +/* pos 01a7: 173 */ 0xAD /* '-' -> */, +/* pos 01a8: 174 */ 0xF3 /* 's' -> */, +/* pos 01a9: 175 */ 0xE9 /* 'i' -> */, +/* pos 01aa: 176 */ 0xEE /* 'n' -> */, +/* pos 01ab: 177 */ 0xE3 /* 'c' -> */, +/* pos 01ac: 178 */ 0xE5 /* 'e' -> */, +/* pos 01ad: 179 */ 0xBA /* ':' -> */, +/* pos 01ae: 180 */ 0x00, 0x13 /* - terminal marker 19 - */, +/* pos 01b0: 181 */ 0xEF /* 'o' -> */, +/* pos 01b1: 182 */ 0xEE /* 'n' -> */, +/* pos 01b2: 183 */ 0xE5 /* 'e' -> */, +/* pos 01b3: 184 */ 0xAD /* '-' -> */, +/* pos 01b4: 185 */ 0xED /* 'm' -> */, +/* pos 01b5: 186 */ 0xE1 /* 'a' -> */, +/* pos 01b6: 187 */ 0xF4 /* 't' -> */, +/* pos 01b7: 188 */ 0xE3 /* 'c' -> */, +/* pos 01b8: 189 */ 0xE8 /* 'h' -> */, +/* pos 01b9: 190 */ 0xBA /* ':' -> */, +/* pos 01ba: 191 */ 0x00, 0x14 /* - terminal marker 20 - */, +/* pos 01bc: 192 */ 0x65 /* 'e' */, 0x0D, 0x00 /* (to 0x01C9 state 193) */, + 0x6C /* 'l' */, 0x14, 0x00 /* (to 0x01D3 state 202) */, + 0x63 /* 'c' */, 0xEB, 0x00 /* (to 0x02AD state 330) */, + 0x72 /* 'r' */, 0xF1, 0x00 /* (to 0x02B6 state 338) */, 0x08, /* fail */ -/* pos 01c6: 193 */ 0xEE /* 'n' -> */, -/* pos 01c7: 194 */ 0xE3 /* 'c' -> */, -/* pos 01c8: 195 */ 0xEF /* 'o' -> */, -/* pos 01c9: 196 */ 0xE4 /* 'd' -> */, -/* pos 01ca: 197 */ 0xE9 /* 'i' -> */, -/* pos 01cb: 198 */ 0xEE /* 'n' -> */, -/* pos 01cc: 199 */ 0xE7 /* 'g' -> */, -/* pos 01cd: 200 */ 0xBA /* ':' -> */, -/* pos 01ce: 201 */ 0x00, 0x15 /* - terminal marker 21 - */, -/* pos 01d0: 202 */ 0xE1 /* 'a' -> */, -/* pos 01d1: 203 */ 0xEE /* 'n' -> */, -/* pos 01d2: 204 */ 0xE7 /* 'g' -> */, -/* pos 01d3: 205 */ 0xF5 /* 'u' -> */, -/* pos 01d4: 206 */ 0xE1 /* 'a' -> */, -/* pos 01d5: 207 */ 0xE7 /* 'g' -> */, -/* pos 01d6: 208 */ 0xE5 /* 'e' -> */, -/* pos 01d7: 209 */ 0xBA /* ':' -> */, -/* pos 01d8: 210 */ 0x00, 0x16 /* - terminal marker 22 - */, -/* pos 01da: 211 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x01E1 state 212) */, - 0x6F /* 'o' */, 0x9E, 0x01 /* (to 0x037B state 497) */, +/* pos 01c9: 193 */ 0xEE /* 'n' -> */, +/* pos 01ca: 194 */ 0xE3 /* 'c' -> */, +/* pos 01cb: 195 */ 0xEF /* 'o' -> */, +/* pos 01cc: 196 */ 0xE4 /* 'd' -> */, +/* pos 01cd: 197 */ 0xE9 /* 'i' -> */, +/* pos 01ce: 198 */ 0xEE /* 'n' -> */, +/* pos 01cf: 199 */ 0xE7 /* 'g' -> */, +/* pos 01d0: 200 */ 0xBA /* ':' -> */, +/* pos 01d1: 201 */ 0x00, 0x15 /* - terminal marker 21 - */, +/* pos 01d3: 202 */ 0xE1 /* 'a' -> */, +/* pos 01d4: 203 */ 0xEE /* 'n' -> */, +/* pos 01d5: 204 */ 0xE7 /* 'g' -> */, +/* pos 01d6: 205 */ 0xF5 /* 'u' -> */, +/* pos 01d7: 206 */ 0xE1 /* 'a' -> */, +/* pos 01d8: 207 */ 0xE7 /* 'g' -> */, +/* pos 01d9: 208 */ 0xE5 /* 'e' -> */, +/* pos 01da: 209 */ 0xBA /* ':' -> */, +/* pos 01db: 210 */ 0x00, 0x16 /* - terminal marker 22 - */, +/* pos 01dd: 211 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x01E4 state 212) */, + 0x6F /* 'o' */, 0x9E, 0x01 /* (to 0x037E state 497) */, 0x08, /* fail */ -/* pos 01e1: 212 */ 0xE7 /* 'g' -> */, -/* pos 01e2: 213 */ 0xED /* 'm' -> */, -/* pos 01e3: 214 */ 0xE1 /* 'a' -> */, -/* pos 01e4: 215 */ 0xBA /* ':' -> */, -/* pos 01e5: 216 */ 0x00, 0x17 /* - terminal marker 23 - */, -/* pos 01e7: 217 */ 0xE3 /* 'c' -> */, -/* pos 01e8: 218 */ 0xE8 /* 'h' -> */, -/* pos 01e9: 219 */ 0xE5 /* 'e' -> */, -/* pos 01ea: 220 */ 0xAD /* '-' -> */, -/* pos 01eb: 221 */ 0xE3 /* 'c' -> */, -/* pos 01ec: 222 */ 0xEF /* 'o' -> */, -/* pos 01ed: 223 */ 0xEE /* 'n' -> */, -/* pos 01ee: 224 */ 0xF4 /* 't' -> */, -/* pos 01ef: 225 */ 0xF2 /* 'r' -> */, -/* pos 01f0: 226 */ 0xEF /* 'o' -> */, -/* pos 01f1: 227 */ 0xEC /* 'l' -> */, -/* pos 01f2: 228 */ 0xBA /* ':' -> */, -/* pos 01f3: 229 */ 0x00, 0x18 /* - terminal marker 24 - */, -/* pos 01f5: 230 */ 0xF4 /* 't' -> */, -/* pos 01f6: 231 */ 0xE8 /* 'h' -> */, -/* pos 01f7: 232 */ 0xEF /* 'o' -> */, -/* pos 01f8: 233 */ 0xF2 /* 'r' -> */, -/* pos 01f9: 234 */ 0xE9 /* 'i' -> */, -/* pos 01fa: 235 */ 0xFA /* 'z' -> */, -/* pos 01fb: 236 */ 0xE1 /* 'a' -> */, -/* pos 01fc: 237 */ 0xF4 /* 't' -> */, -/* pos 01fd: 238 */ 0xE9 /* 'i' -> */, -/* pos 01fe: 239 */ 0xEF /* 'o' -> */, -/* pos 01ff: 240 */ 0xEE /* 'n' -> */, -/* pos 0200: 241 */ 0xBA /* ':' -> */, -/* pos 0201: 242 */ 0x00, 0x19 /* - terminal marker 25 - */, -/* pos 0203: 243 */ 0xEB /* 'k' -> */, -/* pos 0204: 244 */ 0xE9 /* 'i' -> */, -/* pos 0205: 245 */ 0xE5 /* 'e' -> */, -/* pos 0206: 246 */ 0xBA /* ':' -> */, -/* pos 0207: 247 */ 0x00, 0x1A /* - terminal marker 26 - */, -/* pos 0209: 248 */ 0xE5 /* 'e' -> */, -/* pos 020a: 249 */ 0xEE /* 'n' -> */, -/* pos 020b: 250 */ 0xF4 /* 't' -> */, -/* pos 020c: 251 */ 0xAD /* '-' -> */, -/* pos 020d: 252 */ 0x6C /* 'l' */, 0x10, 0x00 /* (to 0x021D state 253) */, - 0x74 /* 't' */, 0x1E, 0x00 /* (to 0x022E state 260) */, - 0x64 /* 'd' */, 0xC0, 0x00 /* (to 0x02D3 state 366) */, - 0x65 /* 'e' */, 0xCA, 0x00 /* (to 0x02E0 state 378) */, - 0x72 /* 'r' */, 0xE3, 0x00 /* (to 0x02FC state 403) */, +/* pos 01e4: 212 */ 0xE7 /* 'g' -> */, +/* pos 01e5: 213 */ 0xED /* 'm' -> */, +/* pos 01e6: 214 */ 0xE1 /* 'a' -> */, +/* pos 01e7: 215 */ 0xBA /* ':' -> */, +/* pos 01e8: 216 */ 0x00, 0x17 /* - terminal marker 23 - */, +/* pos 01ea: 217 */ 0xE3 /* 'c' -> */, +/* pos 01eb: 218 */ 0xE8 /* 'h' -> */, +/* pos 01ec: 219 */ 0xE5 /* 'e' -> */, +/* pos 01ed: 220 */ 0xAD /* '-' -> */, +/* pos 01ee: 221 */ 0xE3 /* 'c' -> */, +/* pos 01ef: 222 */ 0xEF /* 'o' -> */, +/* pos 01f0: 223 */ 0xEE /* 'n' -> */, +/* pos 01f1: 224 */ 0xF4 /* 't' -> */, +/* pos 01f2: 225 */ 0xF2 /* 'r' -> */, +/* pos 01f3: 226 */ 0xEF /* 'o' -> */, +/* pos 01f4: 227 */ 0xEC /* 'l' -> */, +/* pos 01f5: 228 */ 0xBA /* ':' -> */, +/* pos 01f6: 229 */ 0x00, 0x18 /* - terminal marker 24 - */, +/* pos 01f8: 230 */ 0xF4 /* 't' -> */, +/* pos 01f9: 231 */ 0xE8 /* 'h' -> */, +/* pos 01fa: 232 */ 0xEF /* 'o' -> */, +/* pos 01fb: 233 */ 0xF2 /* 'r' -> */, +/* pos 01fc: 234 */ 0xE9 /* 'i' -> */, +/* pos 01fd: 235 */ 0xFA /* 'z' -> */, +/* pos 01fe: 236 */ 0xE1 /* 'a' -> */, +/* pos 01ff: 237 */ 0xF4 /* 't' -> */, +/* pos 0200: 238 */ 0xE9 /* 'i' -> */, +/* pos 0201: 239 */ 0xEF /* 'o' -> */, +/* pos 0202: 240 */ 0xEE /* 'n' -> */, +/* pos 0203: 241 */ 0xBA /* ':' -> */, +/* pos 0204: 242 */ 0x00, 0x19 /* - terminal marker 25 - */, +/* pos 0206: 243 */ 0xEB /* 'k' -> */, +/* pos 0207: 244 */ 0xE9 /* 'i' -> */, +/* pos 0208: 245 */ 0xE5 /* 'e' -> */, +/* pos 0209: 246 */ 0xBA /* ':' -> */, +/* pos 020a: 247 */ 0x00, 0x1A /* - terminal marker 26 - */, +/* pos 020c: 248 */ 0xE5 /* 'e' -> */, +/* pos 020d: 249 */ 0xEE /* 'n' -> */, +/* pos 020e: 250 */ 0xF4 /* 't' -> */, +/* pos 020f: 251 */ 0xAD /* '-' -> */, +/* pos 0210: 252 */ 0x6C /* 'l' */, 0x10, 0x00 /* (to 0x0220 state 253) */, + 0x74 /* 't' */, 0x1E, 0x00 /* (to 0x0231 state 260) */, + 0x64 /* 'd' */, 0xC0, 0x00 /* (to 0x02D6 state 366) */, + 0x65 /* 'e' */, 0xCA, 0x00 /* (to 0x02E3 state 378) */, + 0x72 /* 'r' */, 0xE3, 0x00 /* (to 0x02FF state 403) */, 0x08, /* fail */ -/* pos 021d: 253 */ 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x0227 state 254) */, - 0x61 /* 'a' */, 0xCA, 0x00 /* (to 0x02EA state 387) */, - 0x6F /* 'o' */, 0xD0, 0x00 /* (to 0x02F3 state 395) */, +/* pos 0220: 253 */ 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x022A state 254) */, + 0x61 /* 'a' */, 0xCA, 0x00 /* (to 0x02ED state 387) */, + 0x6F /* 'o' */, 0xD0, 0x00 /* (to 0x02F6 state 395) */, 0x08, /* fail */ -/* pos 0227: 254 */ 0xEE /* 'n' -> */, -/* pos 0228: 255 */ 0xE7 /* 'g' -> */, -/* pos 0229: 256 */ 0xF4 /* 't' -> */, -/* pos 022a: 257 */ 0xE8 /* 'h' -> */, -/* pos 022b: 258 */ 0xBA /* ':' -> */, -/* pos 022c: 259 */ 0x00, 0x1B /* - terminal marker 27 - */, -/* pos 022e: 260 */ 0xF9 /* 'y' -> */, -/* pos 022f: 261 */ 0xF0 /* 'p' -> */, -/* pos 0230: 262 */ 0xE5 /* 'e' -> */, -/* pos 0231: 263 */ 0xBA /* ':' -> */, -/* pos 0232: 264 */ 0x00, 0x1C /* - terminal marker 28 - */, -/* pos 0234: 265 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x023B state 266) */, - 0x65 /* 'e' */, 0xF0, 0x01 /* (to 0x0427 state 637) */, +/* pos 022a: 254 */ 0xEE /* 'n' -> */, +/* pos 022b: 255 */ 0xE7 /* 'g' -> */, +/* pos 022c: 256 */ 0xF4 /* 't' -> */, +/* pos 022d: 257 */ 0xE8 /* 'h' -> */, +/* pos 022e: 258 */ 0xBA /* ':' -> */, +/* pos 022f: 259 */ 0x00, 0x1B /* - terminal marker 27 - */, +/* pos 0231: 260 */ 0xF9 /* 'y' -> */, +/* pos 0232: 261 */ 0xF0 /* 'p' -> */, +/* pos 0233: 262 */ 0xE5 /* 'e' -> */, +/* pos 0234: 263 */ 0xBA /* ':' -> */, +/* pos 0235: 264 */ 0x00, 0x1C /* - terminal marker 28 - */, +/* pos 0237: 265 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x023E state 266) */, + 0x65 /* 'e' */, 0xF6, 0x01 /* (to 0x0430 state 637) */, 0x08, /* fail */ -/* pos 023b: 266 */ 0xF4 /* 't' -> */, -/* pos 023c: 267 */ 0xE5 /* 'e' -> */, -/* pos 023d: 268 */ 0xBA /* ':' -> */, -/* pos 023e: 269 */ 0x00, 0x1D /* - terminal marker 29 - */, -/* pos 0240: 270 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x0247 state 271) */, - 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x024D state 276) */, +/* pos 023e: 266 */ 0xF4 /* 't' -> */, +/* pos 023f: 267 */ 0xE5 /* 'e' -> */, +/* pos 0240: 268 */ 0xBA /* ':' -> */, +/* pos 0241: 269 */ 0x00, 0x1D /* - terminal marker 29 - */, +/* pos 0243: 270 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x024A state 271) */, + 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x0250 state 276) */, 0x08, /* fail */ -/* pos 0247: 271 */ 0xEE /* 'n' -> */, -/* pos 0248: 272 */ 0xE7 /* 'g' -> */, -/* pos 0249: 273 */ 0xE5 /* 'e' -> */, -/* pos 024a: 274 */ 0xBA /* ':' -> */, -/* pos 024b: 275 */ 0x00, 0x1E /* - terminal marker 30 - */, -/* pos 024d: 276 */ 0x66 /* 'f' */, 0x07, 0x00 /* (to 0x0254 state 277) */, - 0x74 /* 't' */, 0x5A, 0x01 /* (to 0x03AA state 529) */, +/* pos 024a: 271 */ 0xEE /* 'n' -> */, +/* pos 024b: 272 */ 0xE7 /* 'g' -> */, +/* pos 024c: 273 */ 0xE5 /* 'e' -> */, +/* pos 024d: 274 */ 0xBA /* ':' -> */, +/* pos 024e: 275 */ 0x00, 0x1E /* - terminal marker 30 - */, +/* pos 0250: 276 */ 0x66 /* 'f' */, 0x07, 0x00 /* (to 0x0257 state 277) */, + 0x74 /* 't' */, 0x5A, 0x01 /* (to 0x03AD state 529) */, 0x08, /* fail */ -/* pos 0254: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x025B state 278) */, - 0x72 /* 'r' */, 0x4D, 0x01 /* (to 0x03A4 state 524) */, +/* pos 0257: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x025E state 278) */, + 0x72 /* 'r' */, 0x4D, 0x01 /* (to 0x03A7 state 524) */, 0x08, /* fail */ -/* pos 025b: 278 */ 0xF2 /* 'r' -> */, -/* pos 025c: 279 */ 0xE5 /* 'e' -> */, -/* pos 025d: 280 */ 0xF2 /* 'r' -> */, -/* pos 025e: 281 */ 0xBA /* ':' -> */, -/* pos 025f: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, -/* pos 0261: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, -/* pos 0263: 284 */ 0xE5 /* 'e' -> */, -/* pos 0264: 285 */ 0xF2 /* 'r' -> */, -/* pos 0265: 286 */ 0xF3 /* 's' -> */, -/* pos 0266: 287 */ 0xE9 /* 'i' -> */, -/* pos 0267: 288 */ 0xEF /* 'o' -> */, -/* pos 0268: 289 */ 0xEE /* 'n' -> */, -/* pos 0269: 290 */ 0xBA /* ':' -> */, -/* pos 026a: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, -/* pos 026c: 292 */ 0xF2 /* 'r' -> */, -/* pos 026d: 293 */ 0xE9 /* 'i' -> */, -/* pos 026e: 294 */ 0xE7 /* 'g' -> */, -/* pos 026f: 295 */ 0xE9 /* 'i' -> */, -/* pos 0270: 296 */ 0xEE /* 'n' -> */, -/* pos 0271: 297 */ 0xBA /* ':' -> */, -/* pos 0272: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, -/* pos 0274: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0281 state 300) */, - 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x028B state 309) */, - 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0292 state 315) */, - 0x73 /* 's' */, 0x1A, 0x00 /* (to 0x0297 state 319) */, +/* pos 025e: 278 */ 0xF2 /* 'r' -> */, +/* pos 025f: 279 */ 0xE5 /* 'e' -> */, +/* pos 0260: 280 */ 0xF2 /* 'r' -> */, +/* pos 0261: 281 */ 0xBA /* ':' -> */, +/* pos 0262: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, +/* pos 0264: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, +/* pos 0266: 284 */ 0xE5 /* 'e' -> */, +/* pos 0267: 285 */ 0xF2 /* 'r' -> */, +/* pos 0268: 286 */ 0xF3 /* 's' -> */, +/* pos 0269: 287 */ 0xE9 /* 'i' -> */, +/* pos 026a: 288 */ 0xEF /* 'o' -> */, +/* pos 026b: 289 */ 0xEE /* 'n' -> */, +/* pos 026c: 290 */ 0xBA /* ':' -> */, +/* pos 026d: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, +/* pos 026f: 292 */ 0xF2 /* 'r' -> */, +/* pos 0270: 293 */ 0xE9 /* 'i' -> */, +/* pos 0271: 294 */ 0xE7 /* 'g' -> */, +/* pos 0272: 295 */ 0xE9 /* 'i' -> */, +/* pos 0273: 296 */ 0xEE /* 'n' -> */, +/* pos 0274: 297 */ 0xBA /* ':' -> */, +/* pos 0275: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, +/* pos 0277: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0284 state 300) */, + 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x028E state 309) */, + 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0295 state 315) */, + 0x73 /* 's' */, 0x1A, 0x00 /* (to 0x029A state 319) */, 0x08, /* fail */ -/* pos 0281: 300 */ 0xF5 /* 'u' -> */, -/* pos 0282: 301 */ 0xF4 /* 't' -> */, -/* pos 0283: 302 */ 0xE8 /* 'h' -> */, -/* pos 0284: 303 */ 0xEF /* 'o' -> */, -/* pos 0285: 304 */ 0xF2 /* 'r' -> */, -/* pos 0286: 305 */ 0xE9 /* 'i' -> */, -/* pos 0287: 306 */ 0xF4 /* 't' -> */, -/* pos 0288: 307 */ 0xF9 /* 'y' -> */, -/* pos 0289: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, -/* pos 028b: 309 */ 0xE5 /* 'e' -> */, -/* pos 028c: 310 */ 0xF4 /* 't' -> */, -/* pos 028d: 311 */ 0xE8 /* 'h' -> */, -/* pos 028e: 312 */ 0xEF /* 'o' -> */, -/* pos 028f: 313 */ 0xE4 /* 'd' -> */, -/* pos 0290: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, -/* pos 0292: 315 */ 0xE1 /* 'a' -> */, -/* pos 0293: 316 */ 0xF4 /* 't' -> */, -/* pos 0294: 317 */ 0xE8 /* 'h' -> */, -/* pos 0295: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, -/* pos 0297: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x029E state 320) */, - 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02A4 state 325) */, +/* pos 0284: 300 */ 0xF5 /* 'u' -> */, +/* pos 0285: 301 */ 0xF4 /* 't' -> */, +/* pos 0286: 302 */ 0xE8 /* 'h' -> */, +/* pos 0287: 303 */ 0xEF /* 'o' -> */, +/* pos 0288: 304 */ 0xF2 /* 'r' -> */, +/* pos 0289: 305 */ 0xE9 /* 'i' -> */, +/* pos 028a: 306 */ 0xF4 /* 't' -> */, +/* pos 028b: 307 */ 0xF9 /* 'y' -> */, +/* pos 028c: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, +/* pos 028e: 309 */ 0xE5 /* 'e' -> */, +/* pos 028f: 310 */ 0xF4 /* 't' -> */, +/* pos 0290: 311 */ 0xE8 /* 'h' -> */, +/* pos 0291: 312 */ 0xEF /* 'o' -> */, +/* pos 0292: 313 */ 0xE4 /* 'd' -> */, +/* pos 0293: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, +/* pos 0295: 315 */ 0xE1 /* 'a' -> */, +/* pos 0296: 316 */ 0xF4 /* 't' -> */, +/* pos 0297: 317 */ 0xE8 /* 'h' -> */, +/* pos 0298: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, +/* pos 029a: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x02A1 state 320) */, + 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02A7 state 325) */, 0x08, /* fail */ -/* pos 029e: 320 */ 0xE8 /* 'h' -> */, -/* pos 029f: 321 */ 0xE5 /* 'e' -> */, -/* pos 02a0: 322 */ 0xED /* 'm' -> */, -/* pos 02a1: 323 */ 0xE5 /* 'e' -> */, -/* pos 02a2: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, -/* pos 02a4: 325 */ 0xE1 /* 'a' -> */, -/* pos 02a5: 326 */ 0xF4 /* 't' -> */, -/* pos 02a6: 327 */ 0xF5 /* 'u' -> */, -/* pos 02a7: 328 */ 0xF3 /* 's' -> */, -/* pos 02a8: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, -/* pos 02aa: 330 */ 0xE8 /* 'h' -> */, -/* pos 02ab: 331 */ 0xE1 /* 'a' -> */, -/* pos 02ac: 332 */ 0xF2 /* 'r' -> */, -/* pos 02ad: 333 */ 0xF3 /* 's' -> */, -/* pos 02ae: 334 */ 0xE5 /* 'e' -> */, -/* pos 02af: 335 */ 0xF4 /* 't' -> */, -/* pos 02b0: 336 */ 0xBA /* ':' -> */, -/* pos 02b1: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, -/* pos 02b3: 338 */ 0xE1 /* 'a' -> */, -/* pos 02b4: 339 */ 0xEE /* 'n' -> */, -/* pos 02b5: 340 */ 0xE7 /* 'g' -> */, -/* pos 02b6: 341 */ 0xE5 /* 'e' -> */, -/* pos 02b7: 342 */ 0xF3 /* 's' -> */, -/* pos 02b8: 343 */ 0xBA /* ':' -> */, -/* pos 02b9: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, -/* pos 02bb: 345 */ 0xEC /* 'l' -> */, -/* pos 02bc: 346 */ 0xEC /* 'l' -> */, -/* pos 02bd: 347 */ 0xEF /* 'o' -> */, -/* pos 02be: 348 */ 0xF7 /* 'w' -> */, -/* pos 02bf: 349 */ 0xAD /* '-' -> */, -/* pos 02c0: 350 */ 0xEF /* 'o' -> */, -/* pos 02c1: 351 */ 0xF2 /* 'r' -> */, -/* pos 02c2: 352 */ 0xE9 /* 'i' -> */, -/* pos 02c3: 353 */ 0xE7 /* 'g' -> */, -/* pos 02c4: 354 */ 0xE9 /* 'i' -> */, -/* pos 02c5: 355 */ 0xEE /* 'n' -> */, -/* pos 02c6: 356 */ 0xBA /* ':' -> */, -/* pos 02c7: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, -/* pos 02c9: 358 */ 0xE5 /* 'e' -> */, -/* pos 02ca: 359 */ 0xBA /* ':' -> */, -/* pos 02cb: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, -/* pos 02cd: 361 */ 0xEC /* 'l' -> */, -/* pos 02ce: 362 */ 0xEF /* 'o' -> */, -/* pos 02cf: 363 */ 0xF7 /* 'w' -> */, -/* pos 02d0: 364 */ 0xBA /* ':' -> */, -/* pos 02d1: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, -/* pos 02d3: 366 */ 0xE9 /* 'i' -> */, -/* pos 02d4: 367 */ 0xF3 /* 's' -> */, -/* pos 02d5: 368 */ 0xF0 /* 'p' -> */, -/* pos 02d6: 369 */ 0xEF /* 'o' -> */, -/* pos 02d7: 370 */ 0xF3 /* 's' -> */, -/* pos 02d8: 371 */ 0xE9 /* 'i' -> */, -/* pos 02d9: 372 */ 0xF4 /* 't' -> */, -/* pos 02da: 373 */ 0xE9 /* 'i' -> */, -/* pos 02db: 374 */ 0xEF /* 'o' -> */, -/* pos 02dc: 375 */ 0xEE /* 'n' -> */, -/* pos 02dd: 376 */ 0xBA /* ':' -> */, -/* pos 02de: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, -/* pos 02e0: 378 */ 0xEE /* 'n' -> */, -/* pos 02e1: 379 */ 0xE3 /* 'c' -> */, -/* pos 02e2: 380 */ 0xEF /* 'o' -> */, -/* pos 02e3: 381 */ 0xE4 /* 'd' -> */, -/* pos 02e4: 382 */ 0xE9 /* 'i' -> */, -/* pos 02e5: 383 */ 0xEE /* 'n' -> */, -/* pos 02e6: 384 */ 0xE7 /* 'g' -> */, -/* pos 02e7: 385 */ 0xBA /* ':' -> */, -/* pos 02e8: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, -/* pos 02ea: 387 */ 0xEE /* 'n' -> */, -/* pos 02eb: 388 */ 0xE7 /* 'g' -> */, -/* pos 02ec: 389 */ 0xF5 /* 'u' -> */, -/* pos 02ed: 390 */ 0xE1 /* 'a' -> */, -/* pos 02ee: 391 */ 0xE7 /* 'g' -> */, -/* pos 02ef: 392 */ 0xE5 /* 'e' -> */, -/* pos 02f0: 393 */ 0xBA /* ':' -> */, -/* pos 02f1: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, -/* pos 02f3: 395 */ 0xE3 /* 'c' -> */, -/* pos 02f4: 396 */ 0xE1 /* 'a' -> */, -/* pos 02f5: 397 */ 0xF4 /* 't' -> */, -/* pos 02f6: 398 */ 0xE9 /* 'i' -> */, -/* pos 02f7: 399 */ 0xEF /* 'o' -> */, -/* pos 02f8: 400 */ 0xEE /* 'n' -> */, -/* pos 02f9: 401 */ 0xBA /* ':' -> */, -/* pos 02fa: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, -/* pos 02fc: 403 */ 0xE1 /* 'a' -> */, -/* pos 02fd: 404 */ 0xEE /* 'n' -> */, -/* pos 02fe: 405 */ 0xE7 /* 'g' -> */, -/* pos 02ff: 406 */ 0xE5 /* 'e' -> */, -/* pos 0300: 407 */ 0xBA /* ':' -> */, -/* pos 0301: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, -/* pos 0303: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x030A state 410) */, - 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x030F state 414) */, +/* pos 02a1: 320 */ 0xE8 /* 'h' -> */, +/* pos 02a2: 321 */ 0xE5 /* 'e' -> */, +/* pos 02a3: 322 */ 0xED /* 'm' -> */, +/* pos 02a4: 323 */ 0xE5 /* 'e' -> */, +/* pos 02a5: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, +/* pos 02a7: 325 */ 0xE1 /* 'a' -> */, +/* pos 02a8: 326 */ 0xF4 /* 't' -> */, +/* pos 02a9: 327 */ 0xF5 /* 'u' -> */, +/* pos 02aa: 328 */ 0xF3 /* 's' -> */, +/* pos 02ab: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, +/* pos 02ad: 330 */ 0xE8 /* 'h' -> */, +/* pos 02ae: 331 */ 0xE1 /* 'a' -> */, +/* pos 02af: 332 */ 0xF2 /* 'r' -> */, +/* pos 02b0: 333 */ 0xF3 /* 's' -> */, +/* pos 02b1: 334 */ 0xE5 /* 'e' -> */, +/* pos 02b2: 335 */ 0xF4 /* 't' -> */, +/* pos 02b3: 336 */ 0xBA /* ':' -> */, +/* pos 02b4: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, +/* pos 02b6: 338 */ 0xE1 /* 'a' -> */, +/* pos 02b7: 339 */ 0xEE /* 'n' -> */, +/* pos 02b8: 340 */ 0xE7 /* 'g' -> */, +/* pos 02b9: 341 */ 0xE5 /* 'e' -> */, +/* pos 02ba: 342 */ 0xF3 /* 's' -> */, +/* pos 02bb: 343 */ 0xBA /* ':' -> */, +/* pos 02bc: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, +/* pos 02be: 345 */ 0xEC /* 'l' -> */, +/* pos 02bf: 346 */ 0xEC /* 'l' -> */, +/* pos 02c0: 347 */ 0xEF /* 'o' -> */, +/* pos 02c1: 348 */ 0xF7 /* 'w' -> */, +/* pos 02c2: 349 */ 0xAD /* '-' -> */, +/* pos 02c3: 350 */ 0xEF /* 'o' -> */, +/* pos 02c4: 351 */ 0xF2 /* 'r' -> */, +/* pos 02c5: 352 */ 0xE9 /* 'i' -> */, +/* pos 02c6: 353 */ 0xE7 /* 'g' -> */, +/* pos 02c7: 354 */ 0xE9 /* 'i' -> */, +/* pos 02c8: 355 */ 0xEE /* 'n' -> */, +/* pos 02c9: 356 */ 0xBA /* ':' -> */, +/* pos 02ca: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, +/* pos 02cc: 358 */ 0xE5 /* 'e' -> */, +/* pos 02cd: 359 */ 0xBA /* ':' -> */, +/* pos 02ce: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, +/* pos 02d0: 361 */ 0xEC /* 'l' -> */, +/* pos 02d1: 362 */ 0xEF /* 'o' -> */, +/* pos 02d2: 363 */ 0xF7 /* 'w' -> */, +/* pos 02d3: 364 */ 0xBA /* ':' -> */, +/* pos 02d4: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, +/* pos 02d6: 366 */ 0xE9 /* 'i' -> */, +/* pos 02d7: 367 */ 0xF3 /* 's' -> */, +/* pos 02d8: 368 */ 0xF0 /* 'p' -> */, +/* pos 02d9: 369 */ 0xEF /* 'o' -> */, +/* pos 02da: 370 */ 0xF3 /* 's' -> */, +/* pos 02db: 371 */ 0xE9 /* 'i' -> */, +/* pos 02dc: 372 */ 0xF4 /* 't' -> */, +/* pos 02dd: 373 */ 0xE9 /* 'i' -> */, +/* pos 02de: 374 */ 0xEF /* 'o' -> */, +/* pos 02df: 375 */ 0xEE /* 'n' -> */, +/* pos 02e0: 376 */ 0xBA /* ':' -> */, +/* pos 02e1: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, +/* pos 02e3: 378 */ 0xEE /* 'n' -> */, +/* pos 02e4: 379 */ 0xE3 /* 'c' -> */, +/* pos 02e5: 380 */ 0xEF /* 'o' -> */, +/* pos 02e6: 381 */ 0xE4 /* 'd' -> */, +/* pos 02e7: 382 */ 0xE9 /* 'i' -> */, +/* pos 02e8: 383 */ 0xEE /* 'n' -> */, +/* pos 02e9: 384 */ 0xE7 /* 'g' -> */, +/* pos 02ea: 385 */ 0xBA /* ':' -> */, +/* pos 02eb: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, +/* pos 02ed: 387 */ 0xEE /* 'n' -> */, +/* pos 02ee: 388 */ 0xE7 /* 'g' -> */, +/* pos 02ef: 389 */ 0xF5 /* 'u' -> */, +/* pos 02f0: 390 */ 0xE1 /* 'a' -> */, +/* pos 02f1: 391 */ 0xE7 /* 'g' -> */, +/* pos 02f2: 392 */ 0xE5 /* 'e' -> */, +/* pos 02f3: 393 */ 0xBA /* ':' -> */, +/* pos 02f4: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, +/* pos 02f6: 395 */ 0xE3 /* 'c' -> */, +/* pos 02f7: 396 */ 0xE1 /* 'a' -> */, +/* pos 02f8: 397 */ 0xF4 /* 't' -> */, +/* pos 02f9: 398 */ 0xE9 /* 'i' -> */, +/* pos 02fa: 399 */ 0xEF /* 'o' -> */, +/* pos 02fb: 400 */ 0xEE /* 'n' -> */, +/* pos 02fc: 401 */ 0xBA /* ':' -> */, +/* pos 02fd: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, +/* pos 02ff: 403 */ 0xE1 /* 'a' -> */, +/* pos 0300: 404 */ 0xEE /* 'n' -> */, +/* pos 0301: 405 */ 0xE7 /* 'g' -> */, +/* pos 0302: 406 */ 0xE5 /* 'e' -> */, +/* pos 0303: 407 */ 0xBA /* ':' -> */, +/* pos 0304: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, +/* pos 0306: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x030D state 410) */, + 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x0312 state 414) */, 0x08, /* fail */ -/* pos 030a: 410 */ 0xE1 /* 'a' -> */, -/* pos 030b: 411 */ 0xE7 /* 'g' -> */, -/* pos 030c: 412 */ 0xBA /* ':' -> */, -/* pos 030d: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, -/* pos 030f: 414 */ 0xF0 /* 'p' -> */, -/* pos 0310: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0317 state 416) */, - 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x031C state 420) */, +/* pos 030d: 410 */ 0xE1 /* 'a' -> */, +/* pos 030e: 411 */ 0xE7 /* 'g' -> */, +/* pos 030f: 412 */ 0xBA /* ':' -> */, +/* pos 0310: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, +/* pos 0312: 414 */ 0xF0 /* 'p' -> */, +/* pos 0313: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x031A state 416) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x031F state 420) */, 0x08, /* fail */ -/* pos 0317: 416 */ 0xE3 /* 'c' -> */, -/* pos 0318: 417 */ 0xF4 /* 't' -> */, -/* pos 0319: 418 */ 0xBA /* ':' -> */, -/* pos 031a: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, -/* pos 031c: 420 */ 0xF2 /* 'r' -> */, -/* pos 031d: 421 */ 0xE5 /* 'e' -> */, -/* pos 031e: 422 */ 0xF3 /* 's' -> */, -/* pos 031f: 423 */ 0xBA /* ':' -> */, -/* pos 0320: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, -/* pos 0322: 425 */ 0xF2 /* 'r' -> */, -/* pos 0323: 426 */ 0xEF /* 'o' -> */, -/* pos 0324: 427 */ 0xED /* 'm' -> */, -/* pos 0325: 428 */ 0xBA /* ':' -> */, -/* pos 0326: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, -/* pos 0328: 430 */ 0xF4 /* 't' -> */, -/* pos 0329: 431 */ 0xE3 /* 'c' -> */, -/* pos 032a: 432 */ 0xE8 /* 'h' -> */, -/* pos 032b: 433 */ 0xBA /* ':' -> */, -/* pos 032c: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, -/* pos 032e: 435 */ 0xE1 /* 'a' -> */, -/* pos 032f: 436 */ 0xEE /* 'n' -> */, -/* pos 0330: 437 */ 0xE7 /* 'g' -> */, -/* pos 0331: 438 */ 0xE5 /* 'e' -> */, -/* pos 0332: 439 */ 0xBA /* ':' -> */, -/* pos 0333: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, -/* pos 0335: 441 */ 0xEE /* 'n' -> */, -/* pos 0336: 442 */ 0xED /* 'm' -> */, -/* pos 0337: 443 */ 0xEF /* 'o' -> */, -/* pos 0338: 444 */ 0xE4 /* 'd' -> */, -/* pos 0339: 445 */ 0xE9 /* 'i' -> */, -/* pos 033a: 446 */ 0xE6 /* 'f' -> */, -/* pos 033b: 447 */ 0xE9 /* 'i' -> */, -/* pos 033c: 448 */ 0xE5 /* 'e' -> */, -/* pos 033d: 449 */ 0xE4 /* 'd' -> */, -/* pos 033e: 450 */ 0xAD /* '-' -> */, -/* pos 033f: 451 */ 0xF3 /* 's' -> */, -/* pos 0340: 452 */ 0xE9 /* 'i' -> */, -/* pos 0341: 453 */ 0xEE /* 'n' -> */, -/* pos 0342: 454 */ 0xE3 /* 'c' -> */, -/* pos 0343: 455 */ 0xE5 /* 'e' -> */, -/* pos 0344: 456 */ 0xBA /* ':' -> */, -/* pos 0345: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, -/* pos 0347: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x0351 state 459) */, - 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x035F state 472) */, - 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x0364 state 476) */, +/* pos 031a: 416 */ 0xE3 /* 'c' -> */, +/* pos 031b: 417 */ 0xF4 /* 't' -> */, +/* pos 031c: 418 */ 0xBA /* ':' -> */, +/* pos 031d: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, +/* pos 031f: 420 */ 0xF2 /* 'r' -> */, +/* pos 0320: 421 */ 0xE5 /* 'e' -> */, +/* pos 0321: 422 */ 0xF3 /* 's' -> */, +/* pos 0322: 423 */ 0xBA /* ':' -> */, +/* pos 0323: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, +/* pos 0325: 425 */ 0xF2 /* 'r' -> */, +/* pos 0326: 426 */ 0xEF /* 'o' -> */, +/* pos 0327: 427 */ 0xED /* 'm' -> */, +/* pos 0328: 428 */ 0xBA /* ':' -> */, +/* pos 0329: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, +/* pos 032b: 430 */ 0xF4 /* 't' -> */, +/* pos 032c: 431 */ 0xE3 /* 'c' -> */, +/* pos 032d: 432 */ 0xE8 /* 'h' -> */, +/* pos 032e: 433 */ 0xBA /* ':' -> */, +/* pos 032f: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, +/* pos 0331: 435 */ 0xE1 /* 'a' -> */, +/* pos 0332: 436 */ 0xEE /* 'n' -> */, +/* pos 0333: 437 */ 0xE7 /* 'g' -> */, +/* pos 0334: 438 */ 0xE5 /* 'e' -> */, +/* pos 0335: 439 */ 0xBA /* ':' -> */, +/* pos 0336: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, +/* pos 0338: 441 */ 0xEE /* 'n' -> */, +/* pos 0339: 442 */ 0xED /* 'm' -> */, +/* pos 033a: 443 */ 0xEF /* 'o' -> */, +/* pos 033b: 444 */ 0xE4 /* 'd' -> */, +/* pos 033c: 445 */ 0xE9 /* 'i' -> */, +/* pos 033d: 446 */ 0xE6 /* 'f' -> */, +/* pos 033e: 447 */ 0xE9 /* 'i' -> */, +/* pos 033f: 448 */ 0xE5 /* 'e' -> */, +/* pos 0340: 449 */ 0xE4 /* 'd' -> */, +/* pos 0341: 450 */ 0xAD /* '-' -> */, +/* pos 0342: 451 */ 0xF3 /* 's' -> */, +/* pos 0343: 452 */ 0xE9 /* 'i' -> */, +/* pos 0344: 453 */ 0xEE /* 'n' -> */, +/* pos 0345: 454 */ 0xE3 /* 'c' -> */, +/* pos 0346: 455 */ 0xE5 /* 'e' -> */, +/* pos 0347: 456 */ 0xBA /* ':' -> */, +/* pos 0348: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, +/* pos 034a: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x0354 state 459) */, + 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x0362 state 472) */, + 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x0367 state 476) */, 0x08, /* fail */ -/* pos 0351: 459 */ 0xF3 /* 's' -> */, -/* pos 0352: 460 */ 0xF4 /* 't' -> */, -/* pos 0353: 461 */ 0xAD /* '-' -> */, -/* pos 0354: 462 */ 0xED /* 'm' -> */, -/* pos 0355: 463 */ 0xEF /* 'o' -> */, -/* pos 0356: 464 */ 0xE4 /* 'd' -> */, -/* pos 0357: 465 */ 0xE9 /* 'i' -> */, -/* pos 0358: 466 */ 0xE6 /* 'f' -> */, -/* pos 0359: 467 */ 0xE9 /* 'i' -> */, -/* pos 035a: 468 */ 0xE5 /* 'e' -> */, -/* pos 035b: 469 */ 0xE4 /* 'd' -> */, -/* pos 035c: 470 */ 0xBA /* ':' -> */, -/* pos 035d: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, -/* pos 035f: 472 */ 0xEE /* 'n' -> */, -/* pos 0360: 473 */ 0xEB /* 'k' -> */, -/* pos 0361: 474 */ 0xBA /* ':' -> */, -/* pos 0362: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, -/* pos 0364: 476 */ 0xE3 /* 'c' -> */, -/* pos 0365: 477 */ 0xE1 /* 'a' -> */, -/* pos 0366: 478 */ 0xF4 /* 't' -> */, -/* pos 0367: 479 */ 0xE9 /* 'i' -> */, -/* pos 0368: 480 */ 0xEF /* 'o' -> */, -/* pos 0369: 481 */ 0xEE /* 'n' -> */, -/* pos 036a: 482 */ 0xBA /* ':' -> */, -/* pos 036b: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, -/* pos 036d: 484 */ 0xE1 /* 'a' -> */, -/* pos 036e: 485 */ 0xF8 /* 'x' -> */, -/* pos 036f: 486 */ 0xAD /* '-' -> */, -/* pos 0370: 487 */ 0xE6 /* 'f' -> */, -/* pos 0371: 488 */ 0xEF /* 'o' -> */, -/* pos 0372: 489 */ 0xF2 /* 'r' -> */, -/* pos 0373: 490 */ 0xF7 /* 'w' -> */, -/* pos 0374: 491 */ 0xE1 /* 'a' -> */, -/* pos 0375: 492 */ 0xF2 /* 'r' -> */, -/* pos 0376: 493 */ 0xE4 /* 'd' -> */, -/* pos 0377: 494 */ 0xF3 /* 's' -> */, -/* pos 0378: 495 */ 0xBA /* ':' -> */, -/* pos 0379: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, -/* pos 037b: 497 */ 0xF8 /* 'x' -> */, -/* pos 037c: 498 */ 0xF9 /* 'y' -> */, -/* pos 037d: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x0384 state 500) */, - 0x20 /* ' ' */, 0xB5, 0x00 /* (to 0x0435 state 649) */, +/* pos 0354: 459 */ 0xF3 /* 's' -> */, +/* pos 0355: 460 */ 0xF4 /* 't' -> */, +/* pos 0356: 461 */ 0xAD /* '-' -> */, +/* pos 0357: 462 */ 0xED /* 'm' -> */, +/* pos 0358: 463 */ 0xEF /* 'o' -> */, +/* pos 0359: 464 */ 0xE4 /* 'd' -> */, +/* pos 035a: 465 */ 0xE9 /* 'i' -> */, +/* pos 035b: 466 */ 0xE6 /* 'f' -> */, +/* pos 035c: 467 */ 0xE9 /* 'i' -> */, +/* pos 035d: 468 */ 0xE5 /* 'e' -> */, +/* pos 035e: 469 */ 0xE4 /* 'd' -> */, +/* pos 035f: 470 */ 0xBA /* ':' -> */, +/* pos 0360: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, +/* pos 0362: 472 */ 0xEE /* 'n' -> */, +/* pos 0363: 473 */ 0xEB /* 'k' -> */, +/* pos 0364: 474 */ 0xBA /* ':' -> */, +/* pos 0365: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, +/* pos 0367: 476 */ 0xE3 /* 'c' -> */, +/* pos 0368: 477 */ 0xE1 /* 'a' -> */, +/* pos 0369: 478 */ 0xF4 /* 't' -> */, +/* pos 036a: 479 */ 0xE9 /* 'i' -> */, +/* pos 036b: 480 */ 0xEF /* 'o' -> */, +/* pos 036c: 481 */ 0xEE /* 'n' -> */, +/* pos 036d: 482 */ 0xBA /* ':' -> */, +/* pos 036e: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, +/* pos 0370: 484 */ 0xE1 /* 'a' -> */, +/* pos 0371: 485 */ 0xF8 /* 'x' -> */, +/* pos 0372: 486 */ 0xAD /* '-' -> */, +/* pos 0373: 487 */ 0xE6 /* 'f' -> */, +/* pos 0374: 488 */ 0xEF /* 'o' -> */, +/* pos 0375: 489 */ 0xF2 /* 'r' -> */, +/* pos 0376: 490 */ 0xF7 /* 'w' -> */, +/* pos 0377: 491 */ 0xE1 /* 'a' -> */, +/* pos 0378: 492 */ 0xF2 /* 'r' -> */, +/* pos 0379: 493 */ 0xE4 /* 'd' -> */, +/* pos 037a: 494 */ 0xF3 /* 's' -> */, +/* pos 037b: 495 */ 0xBA /* ':' -> */, +/* pos 037c: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, +/* pos 037e: 497 */ 0xF8 /* 'x' -> */, +/* pos 037f: 498 */ 0xF9 /* 'y' -> */, +/* pos 0380: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x0387 state 500) */, + 0x20 /* ' ' */, 0xBB, 0x00 /* (to 0x043E state 649) */, 0x08, /* fail */ -/* pos 0384: 500 */ 0xE1 /* 'a' -> */, -/* pos 0385: 501 */ 0xF5 /* 'u' -> */, -/* pos 0386: 502 */ 0xF4 /* 't' -> */, -/* pos 0387: 503 */ 0xE8 /* 'h' -> */, -/* pos 0388: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x038F state 505) */, - 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x0399 state 514) */, +/* pos 0387: 500 */ 0xE1 /* 'a' -> */, +/* pos 0388: 501 */ 0xF5 /* 'u' -> */, +/* pos 0389: 502 */ 0xF4 /* 't' -> */, +/* pos 038a: 503 */ 0xE8 /* 'h' -> */, +/* pos 038b: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0392 state 505) */, + 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x039C state 514) */, 0x08, /* fail */ -/* pos 038f: 505 */ 0xEE /* 'n' -> */, -/* pos 0390: 506 */ 0xF4 /* 't' -> */, -/* pos 0391: 507 */ 0xE9 /* 'i' -> */, -/* pos 0392: 508 */ 0xE3 /* 'c' -> */, -/* pos 0393: 509 */ 0xE1 /* 'a' -> */, -/* pos 0394: 510 */ 0xF4 /* 't' -> */, -/* pos 0395: 511 */ 0xE5 /* 'e' -> */, -/* pos 0396: 512 */ 0xBA /* ':' -> */, -/* pos 0397: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, -/* pos 0399: 514 */ 0xF2 /* 'r' -> */, -/* pos 039a: 515 */ 0xE9 /* 'i' -> */, -/* pos 039b: 516 */ 0xFA /* 'z' -> */, -/* pos 039c: 517 */ 0xE1 /* 'a' -> */, -/* pos 039d: 518 */ 0xF4 /* 't' -> */, -/* pos 039e: 519 */ 0xE9 /* 'i' -> */, -/* pos 039f: 520 */ 0xEF /* 'o' -> */, -/* pos 03a0: 521 */ 0xEE /* 'n' -> */, -/* pos 03a1: 522 */ 0xBA /* ':' -> */, -/* pos 03a2: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, -/* pos 03a4: 524 */ 0xE5 /* 'e' -> */, -/* pos 03a5: 525 */ 0xF3 /* 's' -> */, -/* pos 03a6: 526 */ 0xE8 /* 'h' -> */, -/* pos 03a7: 527 */ 0xBA /* ':' -> */, -/* pos 03a8: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, -/* pos 03aa: 529 */ 0xF2 /* 'r' -> */, -/* pos 03ab: 530 */ 0xF9 /* 'y' -> */, -/* pos 03ac: 531 */ 0xAD /* '-' -> */, -/* pos 03ad: 532 */ 0xE1 /* 'a' -> */, -/* pos 03ae: 533 */ 0xE6 /* 'f' -> */, -/* pos 03af: 534 */ 0xF4 /* 't' -> */, -/* pos 03b0: 535 */ 0xE5 /* 'e' -> */, -/* pos 03b1: 536 */ 0xF2 /* 'r' -> */, -/* pos 03b2: 537 */ 0xBA /* ':' -> */, -/* pos 03b3: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, -/* pos 03b5: 539 */ 0xF6 /* 'v' -> */, -/* pos 03b6: 540 */ 0xE5 /* 'e' -> */, -/* pos 03b7: 541 */ 0xF2 /* 'r' -> */, -/* pos 03b8: 542 */ 0xBA /* ':' -> */, -/* pos 03b9: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, -/* pos 03bb: 544 */ 0xAD /* '-' -> */, -/* pos 03bc: 545 */ 0xE3 /* 'c' -> */, -/* pos 03bd: 546 */ 0xEF /* 'o' -> */, -/* pos 03be: 547 */ 0xEF /* 'o' -> */, -/* pos 03bf: 548 */ 0xEB /* 'k' -> */, -/* pos 03c0: 549 */ 0xE9 /* 'i' -> */, -/* pos 03c1: 550 */ 0xE5 /* 'e' -> */, -/* pos 03c2: 551 */ 0xBA /* ':' -> */, -/* pos 03c3: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, -/* pos 03c5: 553 */ 0xF2 /* 'r' -> */, -/* pos 03c6: 554 */ 0xE9 /* 'i' -> */, -/* pos 03c7: 555 */ 0xE3 /* 'c' -> */, -/* pos 03c8: 556 */ 0xF4 /* 't' -> */, -/* pos 03c9: 557 */ 0xAD /* '-' -> */, -/* pos 03ca: 558 */ 0xF4 /* 't' -> */, -/* pos 03cb: 559 */ 0xF2 /* 'r' -> */, -/* pos 03cc: 560 */ 0xE1 /* 'a' -> */, -/* pos 03cd: 561 */ 0xEE /* 'n' -> */, -/* pos 03ce: 562 */ 0xF3 /* 's' -> */, -/* pos 03cf: 563 */ 0xF0 /* 'p' -> */, -/* pos 03d0: 564 */ 0xEF /* 'o' -> */, -/* pos 03d1: 565 */ 0xF2 /* 'r' -> */, -/* pos 03d2: 566 */ 0xF4 /* 't' -> */, -/* pos 03d3: 567 */ 0xAD /* '-' -> */, -/* pos 03d4: 568 */ 0xF3 /* 's' -> */, -/* pos 03d5: 569 */ 0xE5 /* 'e' -> */, -/* pos 03d6: 570 */ 0xE3 /* 'c' -> */, -/* pos 03d7: 571 */ 0xF5 /* 'u' -> */, -/* pos 03d8: 572 */ 0xF2 /* 'r' -> */, -/* pos 03d9: 573 */ 0xE9 /* 'i' -> */, -/* pos 03da: 574 */ 0xF4 /* 't' -> */, -/* pos 03db: 575 */ 0xF9 /* 'y' -> */, -/* pos 03dc: 576 */ 0xBA /* ':' -> */, -/* pos 03dd: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, -/* pos 03df: 578 */ 0xF2 /* 'r' -> */, -/* pos 03e0: 579 */ 0xE1 /* 'a' -> */, -/* pos 03e1: 580 */ 0xEE /* 'n' -> */, -/* pos 03e2: 581 */ 0xF3 /* 's' -> */, -/* pos 03e3: 582 */ 0xE6 /* 'f' -> */, -/* pos 03e4: 583 */ 0xE5 /* 'e' -> */, -/* pos 03e5: 584 */ 0xF2 /* 'r' -> */, -/* pos 03e6: 585 */ 0xAD /* '-' -> */, -/* pos 03e7: 586 */ 0xE5 /* 'e' -> */, -/* pos 03e8: 587 */ 0xEE /* 'n' -> */, -/* pos 03e9: 588 */ 0xE3 /* 'c' -> */, -/* pos 03ea: 589 */ 0xEF /* 'o' -> */, -/* pos 03eb: 590 */ 0xE4 /* 'd' -> */, -/* pos 03ec: 591 */ 0xE9 /* 'i' -> */, -/* pos 03ed: 592 */ 0xEE /* 'n' -> */, -/* pos 03ee: 593 */ 0xE7 /* 'g' -> */, -/* pos 03ef: 594 */ 0xBA /* ':' -> */, -/* pos 03f0: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, -/* pos 03f2: 596 */ 0xE5 /* 'e' -> */, -/* pos 03f3: 597 */ 0xF2 /* 'r' -> */, -/* pos 03f4: 598 */ 0xAD /* '-' -> */, -/* pos 03f5: 599 */ 0xE1 /* 'a' -> */, -/* pos 03f6: 600 */ 0xE7 /* 'g' -> */, -/* pos 03f7: 601 */ 0xE5 /* 'e' -> */, -/* pos 03f8: 602 */ 0xEE /* 'n' -> */, -/* pos 03f9: 603 */ 0xF4 /* 't' -> */, -/* pos 03fa: 604 */ 0xBA /* ':' -> */, -/* pos 03fb: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, -/* pos 03fd: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x0404 state 607) */, - 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0409 state 611) */, +/* pos 0392: 505 */ 0xEE /* 'n' -> */, +/* pos 0393: 506 */ 0xF4 /* 't' -> */, +/* pos 0394: 507 */ 0xE9 /* 'i' -> */, +/* pos 0395: 508 */ 0xE3 /* 'c' -> */, +/* pos 0396: 509 */ 0xE1 /* 'a' -> */, +/* pos 0397: 510 */ 0xF4 /* 't' -> */, +/* pos 0398: 511 */ 0xE5 /* 'e' -> */, +/* pos 0399: 512 */ 0xBA /* ':' -> */, +/* pos 039a: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, +/* pos 039c: 514 */ 0xF2 /* 'r' -> */, +/* pos 039d: 515 */ 0xE9 /* 'i' -> */, +/* pos 039e: 516 */ 0xFA /* 'z' -> */, +/* pos 039f: 517 */ 0xE1 /* 'a' -> */, +/* pos 03a0: 518 */ 0xF4 /* 't' -> */, +/* pos 03a1: 519 */ 0xE9 /* 'i' -> */, +/* pos 03a2: 520 */ 0xEF /* 'o' -> */, +/* pos 03a3: 521 */ 0xEE /* 'n' -> */, +/* pos 03a4: 522 */ 0xBA /* ':' -> */, +/* pos 03a5: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, +/* pos 03a7: 524 */ 0xE5 /* 'e' -> */, +/* pos 03a8: 525 */ 0xF3 /* 's' -> */, +/* pos 03a9: 526 */ 0xE8 /* 'h' -> */, +/* pos 03aa: 527 */ 0xBA /* ':' -> */, +/* pos 03ab: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, +/* pos 03ad: 529 */ 0xF2 /* 'r' -> */, +/* pos 03ae: 530 */ 0xF9 /* 'y' -> */, +/* pos 03af: 531 */ 0xAD /* '-' -> */, +/* pos 03b0: 532 */ 0xE1 /* 'a' -> */, +/* pos 03b1: 533 */ 0xE6 /* 'f' -> */, +/* pos 03b2: 534 */ 0xF4 /* 't' -> */, +/* pos 03b3: 535 */ 0xE5 /* 'e' -> */, +/* pos 03b4: 536 */ 0xF2 /* 'r' -> */, +/* pos 03b5: 537 */ 0xBA /* ':' -> */, +/* pos 03b6: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, +/* pos 03b8: 539 */ 0xF6 /* 'v' -> */, +/* pos 03b9: 540 */ 0xE5 /* 'e' -> */, +/* pos 03ba: 541 */ 0xF2 /* 'r' -> */, +/* pos 03bb: 542 */ 0xBA /* ':' -> */, +/* pos 03bc: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, +/* pos 03be: 544 */ 0xAD /* '-' -> */, +/* pos 03bf: 545 */ 0xE3 /* 'c' -> */, +/* pos 03c0: 546 */ 0xEF /* 'o' -> */, +/* pos 03c1: 547 */ 0xEF /* 'o' -> */, +/* pos 03c2: 548 */ 0xEB /* 'k' -> */, +/* pos 03c3: 549 */ 0xE9 /* 'i' -> */, +/* pos 03c4: 550 */ 0xE5 /* 'e' -> */, +/* pos 03c5: 551 */ 0xBA /* ':' -> */, +/* pos 03c6: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, +/* pos 03c8: 553 */ 0xF2 /* 'r' -> */, +/* pos 03c9: 554 */ 0xE9 /* 'i' -> */, +/* pos 03ca: 555 */ 0xE3 /* 'c' -> */, +/* pos 03cb: 556 */ 0xF4 /* 't' -> */, +/* pos 03cc: 557 */ 0xAD /* '-' -> */, +/* pos 03cd: 558 */ 0xF4 /* 't' -> */, +/* pos 03ce: 559 */ 0xF2 /* 'r' -> */, +/* pos 03cf: 560 */ 0xE1 /* 'a' -> */, +/* pos 03d0: 561 */ 0xEE /* 'n' -> */, +/* pos 03d1: 562 */ 0xF3 /* 's' -> */, +/* pos 03d2: 563 */ 0xF0 /* 'p' -> */, +/* pos 03d3: 564 */ 0xEF /* 'o' -> */, +/* pos 03d4: 565 */ 0xF2 /* 'r' -> */, +/* pos 03d5: 566 */ 0xF4 /* 't' -> */, +/* pos 03d6: 567 */ 0xAD /* '-' -> */, +/* pos 03d7: 568 */ 0xF3 /* 's' -> */, +/* pos 03d8: 569 */ 0xE5 /* 'e' -> */, +/* pos 03d9: 570 */ 0xE3 /* 'c' -> */, +/* pos 03da: 571 */ 0xF5 /* 'u' -> */, +/* pos 03db: 572 */ 0xF2 /* 'r' -> */, +/* pos 03dc: 573 */ 0xE9 /* 'i' -> */, +/* pos 03dd: 574 */ 0xF4 /* 't' -> */, +/* pos 03de: 575 */ 0xF9 /* 'y' -> */, +/* pos 03df: 576 */ 0xBA /* ':' -> */, +/* pos 03e0: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, +/* pos 03e2: 578 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x03E9 state 579) */, + 0x65 /* 'e' */, 0x84, 0x00 /* (to 0x0469 state 680) */, 0x08, /* fail */ -/* pos 0404: 607 */ 0xF2 /* 'r' -> */, -/* pos 0405: 608 */ 0xF9 /* 'y' -> */, -/* pos 0406: 609 */ 0xBA /* ':' -> */, -/* pos 0407: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, -/* pos 0409: 611 */ 0xE1 /* 'a' -> */, -/* pos 040a: 612 */ 0xBA /* ':' -> */, -/* pos 040b: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, -/* pos 040d: 614 */ 0xF7 /* 'w' -> */, -/* pos 040e: 615 */ 0xF7 /* 'w' -> */, -/* pos 040f: 616 */ 0xAD /* '-' -> */, -/* pos 0410: 617 */ 0xE1 /* 'a' -> */, -/* pos 0411: 618 */ 0xF5 /* 'u' -> */, -/* pos 0412: 619 */ 0xF4 /* 't' -> */, -/* pos 0413: 620 */ 0xE8 /* 'h' -> */, -/* pos 0414: 621 */ 0xE5 /* 'e' -> */, -/* pos 0415: 622 */ 0xEE /* 'n' -> */, -/* pos 0416: 623 */ 0xF4 /* 't' -> */, -/* pos 0417: 624 */ 0xE9 /* 'i' -> */, -/* pos 0418: 625 */ 0xE3 /* 'c' -> */, -/* pos 0419: 626 */ 0xE1 /* 'a' -> */, -/* pos 041a: 627 */ 0xF4 /* 't' -> */, -/* pos 041b: 628 */ 0xE5 /* 'e' -> */, -/* pos 041c: 629 */ 0xBA /* ':' -> */, -/* pos 041d: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, -/* pos 041f: 631 */ 0xF4 /* 't' -> */, -/* pos 0420: 632 */ 0xE3 /* 'c' -> */, -/* pos 0421: 633 */ 0xE8 /* 'h' -> */, -/* pos 0422: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, -/* pos 0424: 635 */ 0xF4 /* 't' -> */, -/* pos 0425: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, -/* pos 0427: 637 */ 0xEC /* 'l' -> */, -/* pos 0428: 638 */ 0xE5 /* 'e' -> */, -/* pos 0429: 639 */ 0xF4 /* 't' -> */, -/* pos 042a: 640 */ 0xE5 /* 'e' -> */, -/* pos 042b: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, -/* pos 042d: 642 */ 0xE9 /* 'i' -> */, -/* pos 042e: 643 */ 0xAD /* '-' -> */, -/* pos 042f: 644 */ 0xE1 /* 'a' -> */, -/* pos 0430: 645 */ 0xF2 /* 'r' -> */, -/* pos 0431: 646 */ 0xE7 /* 'g' -> */, -/* pos 0432: 647 */ 0xF3 /* 's' -> */, -/* pos 0433: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, -/* pos 0435: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, -/* pos 0437: 650 */ 0xAD /* '-' -> */, -/* pos 0438: 651 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x043F state 652) */, - 0x66 /* 'f' */, 0x10, 0x00 /* (to 0x044B state 662) */, +/* pos 03e9: 579 */ 0xE1 /* 'a' -> */, +/* pos 03ea: 580 */ 0xEE /* 'n' -> */, +/* pos 03eb: 581 */ 0xF3 /* 's' -> */, +/* pos 03ec: 582 */ 0xE6 /* 'f' -> */, +/* pos 03ed: 583 */ 0xE5 /* 'e' -> */, +/* pos 03ee: 584 */ 0xF2 /* 'r' -> */, +/* pos 03ef: 585 */ 0xAD /* '-' -> */, +/* pos 03f0: 586 */ 0xE5 /* 'e' -> */, +/* pos 03f1: 587 */ 0xEE /* 'n' -> */, +/* pos 03f2: 588 */ 0xE3 /* 'c' -> */, +/* pos 03f3: 589 */ 0xEF /* 'o' -> */, +/* pos 03f4: 590 */ 0xE4 /* 'd' -> */, +/* pos 03f5: 591 */ 0xE9 /* 'i' -> */, +/* pos 03f6: 592 */ 0xEE /* 'n' -> */, +/* pos 03f7: 593 */ 0xE7 /* 'g' -> */, +/* pos 03f8: 594 */ 0xBA /* ':' -> */, +/* pos 03f9: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, +/* pos 03fb: 596 */ 0xE5 /* 'e' -> */, +/* pos 03fc: 597 */ 0xF2 /* 'r' -> */, +/* pos 03fd: 598 */ 0xAD /* '-' -> */, +/* pos 03fe: 599 */ 0xE1 /* 'a' -> */, +/* pos 03ff: 600 */ 0xE7 /* 'g' -> */, +/* pos 0400: 601 */ 0xE5 /* 'e' -> */, +/* pos 0401: 602 */ 0xEE /* 'n' -> */, +/* pos 0402: 603 */ 0xF4 /* 't' -> */, +/* pos 0403: 604 */ 0xBA /* ':' -> */, +/* pos 0404: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, +/* pos 0406: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x040D state 607) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0412 state 611) */, 0x08, /* fail */ -/* pos 043f: 652 */ 0xE5 /* 'e' -> */, -/* pos 0440: 653 */ 0xE1 /* 'a' -> */, -/* pos 0441: 654 */ 0xEC /* 'l' -> */, -/* pos 0442: 655 */ 0xAD /* '-' -> */, -/* pos 0443: 656 */ 0xE9 /* 'i' -> */, -/* pos 0444: 657 */ 0xF0 /* 'p' -> */, -/* pos 0445: 658 */ 0xBA /* ':' -> */, -/* pos 0446: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, -/* pos 0448: 660 */ 0xA0 /* ' ' -> */, -/* pos 0449: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, -/* pos 044b: 662 */ 0xEF /* 'o' -> */, -/* pos 044c: 663 */ 0xF2 /* 'r' -> */, -/* pos 044d: 664 */ 0xF7 /* 'w' -> */, -/* pos 044e: 665 */ 0xE1 /* 'a' -> */, -/* pos 044f: 666 */ 0xF2 /* 'r' -> */, -/* pos 0450: 667 */ 0xE4 /* 'd' -> */, -/* pos 0451: 668 */ 0xE5 /* 'e' -> */, -/* pos 0452: 669 */ 0xE4 /* 'd' -> */, -/* pos 0453: 670 */ 0xAD /* '-' -> */, -/* pos 0454: 671 */ 0xE6 /* 'f' -> */, -/* pos 0455: 672 */ 0xEF /* 'o' -> */, -/* pos 0456: 673 */ 0xF2 /* 'r' -> */, -/* pos 0457: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, -/* pos 0459: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, -/* total size 1115 bytes */ +/* pos 040d: 607 */ 0xF2 /* 'r' -> */, +/* pos 040e: 608 */ 0xF9 /* 'y' -> */, +/* pos 040f: 609 */ 0xBA /* ':' -> */, +/* pos 0410: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, +/* pos 0412: 611 */ 0xE1 /* 'a' -> */, +/* pos 0413: 612 */ 0xBA /* ':' -> */, +/* pos 0414: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, +/* pos 0416: 614 */ 0xF7 /* 'w' -> */, +/* pos 0417: 615 */ 0xF7 /* 'w' -> */, +/* pos 0418: 616 */ 0xAD /* '-' -> */, +/* pos 0419: 617 */ 0xE1 /* 'a' -> */, +/* pos 041a: 618 */ 0xF5 /* 'u' -> */, +/* pos 041b: 619 */ 0xF4 /* 't' -> */, +/* pos 041c: 620 */ 0xE8 /* 'h' -> */, +/* pos 041d: 621 */ 0xE5 /* 'e' -> */, +/* pos 041e: 622 */ 0xEE /* 'n' -> */, +/* pos 041f: 623 */ 0xF4 /* 't' -> */, +/* pos 0420: 624 */ 0xE9 /* 'i' -> */, +/* pos 0421: 625 */ 0xE3 /* 'c' -> */, +/* pos 0422: 626 */ 0xE1 /* 'a' -> */, +/* pos 0423: 627 */ 0xF4 /* 't' -> */, +/* pos 0424: 628 */ 0xE5 /* 'e' -> */, +/* pos 0425: 629 */ 0xBA /* ':' -> */, +/* pos 0426: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, +/* pos 0428: 631 */ 0xF4 /* 't' -> */, +/* pos 0429: 632 */ 0xE3 /* 'c' -> */, +/* pos 042a: 633 */ 0xE8 /* 'h' -> */, +/* pos 042b: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, +/* pos 042d: 635 */ 0xF4 /* 't' -> */, +/* pos 042e: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, +/* pos 0430: 637 */ 0xEC /* 'l' -> */, +/* pos 0431: 638 */ 0xE5 /* 'e' -> */, +/* pos 0432: 639 */ 0xF4 /* 't' -> */, +/* pos 0433: 640 */ 0xE5 /* 'e' -> */, +/* pos 0434: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, +/* pos 0436: 642 */ 0xE9 /* 'i' -> */, +/* pos 0437: 643 */ 0xAD /* '-' -> */, +/* pos 0438: 644 */ 0xE1 /* 'a' -> */, +/* pos 0439: 645 */ 0xF2 /* 'r' -> */, +/* pos 043a: 646 */ 0xE7 /* 'g' -> */, +/* pos 043b: 647 */ 0xF3 /* 's' -> */, +/* pos 043c: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, +/* pos 043e: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, +/* pos 0440: 650 */ 0xAD /* '-' -> */, +/* pos 0441: 651 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x0448 state 652) */, + 0x66 /* 'f' */, 0x10, 0x00 /* (to 0x0454 state 662) */, + 0x08, /* fail */ +/* pos 0448: 652 */ 0xE5 /* 'e' -> */, +/* pos 0449: 653 */ 0xE1 /* 'a' -> */, +/* pos 044a: 654 */ 0xEC /* 'l' -> */, +/* pos 044b: 655 */ 0xAD /* '-' -> */, +/* pos 044c: 656 */ 0xE9 /* 'i' -> */, +/* pos 044d: 657 */ 0xF0 /* 'p' -> */, +/* pos 044e: 658 */ 0xBA /* ':' -> */, +/* pos 044f: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, +/* pos 0451: 660 */ 0xA0 /* ' ' -> */, +/* pos 0452: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, +/* pos 0454: 662 */ 0xEF /* 'o' -> */, +/* pos 0455: 663 */ 0xF2 /* 'r' -> */, +/* pos 0456: 664 */ 0xF7 /* 'w' -> */, +/* pos 0457: 665 */ 0xE1 /* 'a' -> */, +/* pos 0458: 666 */ 0xF2 /* 'r' -> */, +/* pos 0459: 667 */ 0xE4 /* 'd' -> */, +/* pos 045a: 668 */ 0xE5 /* 'e' -> */, +/* pos 045b: 669 */ 0xE4 /* 'd' -> */, +/* pos 045c: 670 */ 0xAD /* '-' -> */, +/* pos 045d: 671 */ 0xE6 /* 'f' -> */, +/* pos 045e: 672 */ 0xEF /* 'o' -> */, +/* pos 045f: 673 */ 0xF2 /* 'r' -> */, +/* pos 0460: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, +/* pos 0462: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, +/* pos 0464: 676 */ 0xE1 /* 'a' -> */, +/* pos 0465: 677 */ 0xE4 /* 'd' -> */, +/* pos 0466: 678 */ 0xA0 /* ' ' -> */, +/* pos 0467: 679 */ 0x00, 0x52 /* - terminal marker 82 - */, +/* pos 0469: 680 */ 0xBA /* ':' -> */, +/* pos 046a: 681 */ 0x00, 0x53 /* - terminal marker 83 - */, +/* total size 1132 bytes */ diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 692414f00..4fe4b2efd 100755 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -111,6 +111,15 @@ lws_free_wsi(struct lws *wsi) wsi->peer = NULL; #endif +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2 || wsi->http2_substream) { + lws_hpack_destroy_dynamic_header(wsi); + + if (wsi->u.h2.h2n) + lws_free_set_NULL(wsi->u.h2.h2n); + } +#endif + lws_pt_unlock(pt); /* since we will destroy the wsi, make absolutely sure now */ @@ -342,13 +351,79 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) wsi->child_list = NULL; } +#if defined(LWS_WITH_HTTP2) + + if (wsi->u.h2.parent_wsi) { + lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, wsi->u.h2.parent_wsi); + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.parent_wsi->u.h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, u.h2.sibling_list); + } + + if (wsi->upgraded_to_http2 || wsi->http2_substream) { + lwsl_info("closing %p: parent %p\n", wsi, wsi->u.h2.parent_wsi); + + if (wsi->u.h2.child_list) { + lwsl_info(" parent %p: closing children: list:\n", wsi); + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, u.h2.sibling_list); + /* trigger closing of all of our http2 children first */ + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { + lwsl_info(" closing child %p\n", *w); + /* disconnect from siblings */ + wsi2 = (*w)->u.h2.sibling_list; + (*w)->u.h2.sibling_list = NULL; + (*w)->socket_is_permanently_unusable = 1; + lws_close_free_wsi(*w, reason); + *w = wsi2; + continue; + } lws_end_foreach_llp(w, u.h2.sibling_list); + } + } + + if (wsi->upgraded_to_http2) { + /* remove pps */ + struct lws_h2_protocol_send *w = wsi->u.h2.h2n->pps, *w1; + while (w) { + w1 = wsi->u.h2.h2n->pps->next; + free(w); + w = w1; + } + wsi->u.h2.h2n->pps = NULL; + } + + if (wsi->http2_substream && wsi->u.h2.parent_wsi) { + lwsl_info(" %p: disentangling from siblings\n", wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->u.h2.parent_wsi->u.h2.child_list) { + /* disconnect from siblings */ + if (*w == wsi) { + wsi2 = (*w)->u.h2.sibling_list; + (*w)->u.h2.sibling_list = NULL; + *w = wsi2; + lwsl_info(" %p disentangled from sibling %p\n", wsi, wsi2); + break; + } + } lws_end_foreach_llp(w, u.h2.sibling_list); + wsi->u.h2.parent_wsi->u.h2.child_count--; + wsi->u.h2.parent_wsi = NULL; + if (wsi->u.h2.pending_status_body) + lws_free_set_NULL(wsi->u.h2.pending_status_body); + } + + if (wsi->upgraded_to_http2 && wsi->u.h2.h2n && + wsi->u.h2.h2n->rx_scratch) + lws_free_set_NULL(wsi->u.h2.h2n->rx_scratch); +#endif + if (wsi->mode == LWSCM_RAW_FILEDESC) { - lws_remove_child_from_any_parent(wsi); - remove_wsi_socket_from_fds(wsi); - wsi->protocol->callback(wsi, - LWS_CALLBACK_RAW_CLOSE_FILE, - wsi->user_space, NULL, 0); - goto async_close; + lws_remove_child_from_any_parent(wsi); + remove_wsi_socket_from_fds(wsi); + wsi->protocol->callback(wsi, + LWS_CALLBACK_RAW_CLOSE_FILE, + wsi->user_space, NULL, 0); + goto async_close; } #ifdef LWS_WITH_CGI @@ -394,7 +469,8 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) goto just_kill_connection; } - if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED && + if ((wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED || + wsi->mode == LWSCM_HTTP2_SERVING) && wsi->u.http.fop_fd != NULL) { lws_vfs_file_close(&wsi->u.http.fop_fd); wsi->vhost->protocols->callback(wsi, @@ -438,7 +514,8 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE) goto just_kill_connection; - if (wsi->mode == LWSCM_HTTP_SERVING) { + if (wsi->mode == LWSCM_HTTP_SERVING || + wsi->mode == LWSCM_HTTP2_SERVING) { if (wsi->user_space) wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, @@ -536,6 +613,7 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) just_kill_connection: lws_remove_child_from_any_parent(wsi); + n = 0; if (wsi->user_space) { lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, @@ -576,8 +654,6 @@ just_kill_connection: reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && !wsi->socket_is_permanently_unusable) { #ifdef LWS_OPENSSL_SUPPORT - int shutdown_error; - if (lws_is_ssl(wsi) && wsi->ssl) { n = SSL_shutdown(wsi->ssl); /* @@ -592,30 +668,28 @@ just_kill_connection: n = shutdown(wsi->desc.sockfd, SHUT_WR); break; default: - shutdown_error = SSL_get_error(wsi->ssl, n); - lwsl_debug("SSL_shutdown %d, get_error: %d\n", - n, shutdown_error); - switch (shutdown_error) { - case SSL_ERROR_WANT_READ: - lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); + if (SSL_want_read(wsi->ssl)) { + lws_change_pollfd(wsi, 0, LWS_POLLIN); n = 0; break; - case SSL_ERROR_WANT_WRITE: - lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLOUT); - n = 0; - break; - default: /* real error, close */ - n = shutdown(wsi->desc.sockfd, SHUT_WR); - break; } + if (SSL_want_write(wsi->ssl)) { + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + n = 0; + break; + } + n = shutdown(wsi->desc.sockfd, SHUT_WR); + break; } } else #endif { - lwsl_info("%s: shuttdown conn: %p (sock %d, state %d)\n", + lwsl_info("%s: shutdown conn: %p (sock %d, state %d)\n", __func__, wsi, (int)(long)wsi->desc.sockfd, wsi->state); - n = shutdown(wsi->desc.sockfd, SHUT_WR); + if (!wsi->socket_is_permanently_unusable && + lws_sockfd_valid(wsi->desc.sockfd)) + n = shutdown(wsi->desc.sockfd, SHUT_WR); } if (n) lwsl_debug("closing: shutdown (state %d) ret %d\n", @@ -627,7 +701,9 @@ just_kill_connection: */ #if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP32) /* libuv: no event available to guarantee completion */ - if (!LWS_LIBUV_ENABLED(context)) { + if (!wsi->socket_is_permanently_unusable && + lws_sockfd_valid(wsi->desc.sockfd) && + !LWS_LIBUV_ENABLED(context)) { lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); wsi->state = LWSS_SHUTDOWN; lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, @@ -716,11 +792,14 @@ just_kill_connection: if (!wsi->told_user_closed && wsi->mode != LWSCM_RAW && wsi->protocol && wsi->protocol->callback && - ((wsi->state_pre_close == LWSS_ESTABLISHED) || - (wsi->state_pre_close == LWSS_RETURNED_CLOSE_ALREADY) || - (wsi->state_pre_close == LWSS_AWAITING_CLOSE_ACK) || - (wsi->state_pre_close == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION) || - (wsi->state_pre_close == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) || + (wsi->state_pre_close == LWSS_ESTABLISHED || + wsi->state_pre_close == LWSS_HTTP2_ESTABLISHED || + wsi->state_pre_close == LWSS_HTTP_BODY || + wsi->state_pre_close == LWSS_HTTP || + wsi->state_pre_close == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state_pre_close == LWSS_AWAITING_CLOSE_ACK || + wsi->state_pre_close == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state_pre_close == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE || (wsi->mode == LWSCM_WS_CLIENT && wsi->state_pre_close == LWSS_HTTP) || (wsi->mode == LWSCM_WS_SERVING && wsi->state_pre_close == LWSS_HTTP))) { lwsl_debug("calling back CLOSED %d %d\n", wsi->mode, wsi->state); @@ -750,7 +829,8 @@ async_close: wsi->socket_is_permanently_unusable = 1; #ifdef LWS_WITH_LIBUV - if (!wsi->parent_carries_io) + if (!wsi->parent_carries_io && + lws_sockfd_valid(wsi->desc.sockfd)) if (LWS_LIBUV_ENABLED(context)) { if (wsi->listener) { lwsl_debug("%s: stop listener poll\n", __func__); @@ -776,7 +856,7 @@ lws_close_free_wsi_final(struct lws *wsi) { int n; - if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->desc.sockfd)) { + if (lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { #if LWS_POSIX n = compatible_close(wsi->desc.sockfd); if (n) @@ -941,6 +1021,11 @@ lws_get_peer_simple(struct lws *wsi, char *name, int namelen) int af = AF_INET; void *p, *q; +#if defined(LWS_WITH_HTTP2) + if (wsi->http2_substream) + wsi = wsi->u.h2.parent_wsi; +#endif + if (wsi->parent_carries_io) wsi = wsi->parent; @@ -1056,6 +1141,23 @@ lws_protocol_get(struct lws *wsi) return wsi->protocol; } +LWS_VISIBLE struct lws * +lws_get_network_wsi(struct lws *wsi) +{ + if (!wsi) + return NULL; + +#if defined(LWS_WITH_HTTP2) + if (!wsi->http2_substream) + return wsi; + + while (wsi->u.h2.parent_wsi) + wsi = wsi->u.h2.parent_wsi; +#endif + + return wsi; +} + LWS_VISIBLE LWS_EXTERN const struct lws_protocols * lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name) { @@ -1309,13 +1411,40 @@ lws_latency(struct lws_context *context, struct lws *wsi, const char *action, #endif LWS_VISIBLE int -lws_rx_flow_control(struct lws *wsi, int enable) +lws_rx_flow_control(struct lws *wsi, int _enable) { - if (enable == (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) + int en = _enable; + + lwsl_info("%s: %p 0x%x\n", __func__, wsi, _enable); + + if (!(_enable & LWS_RXFLOW_REASON_APPLIES)) { + /* + * convert user bool style to bitmap style... in user simple + * bool style _enable = 0 = flow control it, = 1 = allow rx + */ + en = LWS_RXFLOW_REASON_APPLIES | LWS_RXFLOW_REASON_USER_BOOL; + if (_enable & 1) + en |= LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT; + } + + /* any bit set in rxflow_bitmap DISABLEs rxflow control */ + if (en & LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT) + wsi->rxflow_bitmap &= ~(en & 0xff); + else + wsi->rxflow_bitmap |= en & 0xff; + + if ((LWS_RXFLOW_PENDING_CHANGE | (!wsi->rxflow_bitmap)) == + wsi->rxflow_change_to) return 0; - lwsl_info("%s: (0x%p, %d)\n", __func__, wsi, enable); - wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !!enable; + wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !wsi->rxflow_bitmap; + + lwsl_info("%s: 0x%p: bitmap 0x%x: en 0x%x, ch 0x%x\n", __func__, wsi, + wsi->rxflow_bitmap, en, wsi->rxflow_change_to); + + if (_enable & LWS_RXFLOW_REASON_FLAG_PROCESS_NOW || + !wsi->rxflow_will_be_applied) + return _lws_rx_flow_control(wsi); return 0; } @@ -1353,7 +1482,9 @@ int user_callback_handle_rxflow(lws_callback_function callback_function, { int n; + wsi->rxflow_will_be_applied = 1; n = callback_function(wsi, reason, user, in, len); + wsi->rxflow_will_be_applied = 0; if (!n) n = _lws_rx_flow_control(wsi); @@ -1532,13 +1663,13 @@ int lws_ensure_user_space(struct lws *wsi) { if (!wsi->protocol) - return 1; + return 0; /* allocate the per-connection user memory (if any) */ if (wsi->protocol->per_session_data_size && !wsi->user_space) { wsi->user_space = lws_zalloc(wsi->protocol->per_session_data_size, "user space"); - if (wsi->user_space == NULL) { + if (wsi->user_space == NULL) { lwsl_err("%s: OOM\n", __func__); return 1; } @@ -1606,14 +1737,44 @@ lwsl_timestamp(int level, char *p, int len) return 0; } +static const char * const colours[] = { + "[31;1m", /* LLL_ERR */ + "[36;1m", /* LLL_WARN */ + "[35;1m", /* LLL_NOTICE */ + "[32;1m", /* LLL_INFO */ + "[34;1m", /* LLL_DEBUG */ + "[33;1m", /* LLL_PARSER */ + "[33;1m", /* LLL_HEADER */ + "[33;1m", /* LLL_EXT */ + "[33;1m", /* LLL_CLIENT */ + "[33;1m", /* LLL_LATENCY */ + "[30;1m", /* LLL_USER */ +}; + #ifndef LWS_PLAT_OPTEE LWS_VISIBLE void lwsl_emit_stderr(int level, const char *line) { #if !defined(LWS_WITH_ESP8266) char buf[50]; + static char tty; + int n, m = ARRAY_SIZE(colours) - 1; + + if (!tty) + tty = isatty(2) | 2; lwsl_timestamp(level, buf, sizeof(buf)); - fprintf(stderr, "%s%s", buf, line); + + if (tty == 3) { + n = 1 << (ARRAY_SIZE(colours) - 1); + while (n) { + if (level & n) + break; + m--; + n >>= 1; + } + fprintf(stderr, "%c%s%s%s%c[0m", 27, colours[m], buf, line, 27); + } else + fprintf(stderr, "%s%s", buf, line); #endif } #endif @@ -1692,18 +1853,6 @@ lws_partial_buffered(struct lws *wsi) return !!wsi->trunc_len; } -void lws_set_protocol_write_pending(struct lws *wsi, - enum lws_pending_protocol_send pend) -{ - lwsl_info("setting pps %d\n", pend); - - if (wsi->pps) - lwsl_err("pps overwrite\n"); - wsi->pps = pend; - lws_rx_flow_control(wsi, 0); - lws_callback_on_writable(wsi); -} - LWS_VISIBLE size_t lws_get_peer_write_allowance(struct lws *wsi) { @@ -1711,11 +1860,8 @@ lws_get_peer_write_allowance(struct lws *wsi) /* only if we are using HTTP2 on this connection */ if (wsi->mode != LWSCM_HTTP2_SERVING) return -1; - /* user is only interested in how much he can send, or that he can't */ - if (wsi->u.http2.tx_credit <= 0) - return 0; - return wsi->u.http2.tx_credit; + return lws_h2_tx_cr_get(wsi); #else (void)wsi; return -1; @@ -2591,9 +2737,15 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len WSI_TOKEN_PUT_URI, WSI_TOKEN_PATCH_URI, WSI_TOKEN_DELETE_URI, + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, + #ifdef LWS_WITH_HTTP2 + WSI_TOKEN_HTTP_COLON_PATH, + #endif }; static const char * const meth_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", + "CONNECT", ":path" }; if (script_uri_path_len >= 0) @@ -2609,15 +2761,23 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len // if (script_uri_path_len < 0) // uritok = 0; - lws_snprintf(cgi_path, sizeof(cgi_path) - 1, "REQUEST_URI=%s", - lws_hdr_simple_ptr(wsi, uritok)); - cgi_path[sizeof(cgi_path) - 1] = '\0'; - env_array[n++] = cgi_path; + if (uritok >= 0) { + lws_snprintf(cgi_path, sizeof(cgi_path) - 1, "REQUEST_URI=%s", + lws_hdr_simple_ptr(wsi, uritok)); + cgi_path[sizeof(cgi_path) - 1] = '\0'; + env_array[n++] = cgi_path; + } - env_array[n++] = p; - p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", - meth_names[m]); - p++; + if (m >= 0) { + env_array[n++] = p; + if (m < 8) + p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", + meth_names[m]); + else + p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD)); + p++; + } env_array[n++] = p; p += lws_snprintf(p, end - p, "QUERY_STRING="); @@ -2829,13 +2989,21 @@ static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = { "transfer-encoding: chunked", }; +enum header_recode { + HR_NAME, + HR_WHITESPACE, + HR_ARG, + HR_CRLF, +}; + LWS_VISIBLE LWS_EXTERN int lws_cgi_write_split_stdout_headers(struct lws *wsi) { - int n, m; + int n, m, cmd; unsigned char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start, - *end = &buf[sizeof(buf) - 1 - LWS_PRE]; - char c; + *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name, + *value = NULL; + char c, hrs; if (!wsi->cgi) return -1; @@ -2846,10 +3014,9 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) * since they need to be handled separately */ switch (wsi->hdr_state) { - case LHCS_RESPONSE: - lwsl_info("LHCS_RESPONSE: issuing response %d\n", - wsi->cgi->response_code); + lwsl_debug("LHCS_RESPONSE: issuing response %d\n", + wsi->cgi->response_code); if (lws_add_http_header_status(wsi, wsi->cgi->response_code, &p, end)) return 1; @@ -2859,12 +3026,93 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return 1; - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, - (unsigned char *)"close", 5, &p, end)) - return 1; + if (!(wsi->http2_substream)) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, + (unsigned char *)"close", 5, &p, end)) + return 1; n = lws_write(wsi, start, p - start, - LWS_WRITE_HTTP_HEADERS); + LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN); + /* + * so we have a bunch of http/1 style ascii headers + * starting from wsi->cgi->headers_buf through + * wsi->cgi->headers_pos. These are OK for http/1 + * connections, but they're no good for http/2 conns. + * + * Let's redo them at headers_pos forward using the + * correct coding for http/1 or http/2 + */ + if (!wsi->http2_substream) + goto post_hpack_recode; + + p = wsi->cgi->headers_start; + wsi->cgi->headers_start = wsi->cgi->headers_pos; + wsi->cgi->headers_dumped = wsi->cgi->headers_start; + hrs = HR_NAME; + name = buf; + + while (p < wsi->cgi->headers_start) { + //lwsl_notice("%c (%d)\n", *p, (int)(wsi->cgi->headers_start - p)); + switch (hrs) { + case HR_NAME: + /* + * in http/2 upper-case header names + * are illegal. So convert to lower- + * case. + */ + if (name - buf > 64) + return -1; + if (*p != ':') { + if (*p >= 'A' && *p <= 'Z') + *name++ = (*p++) + ('a' - 'A'); + else + *name++ = *p++; + } else { + p++; + *name++ = '\0'; + value = name; + hrs = HR_WHITESPACE; + } + break; + case HR_WHITESPACE: + if (*p == ' ') { + p++; + break; + } + hrs = HR_ARG; + /* fallthru */ + case HR_ARG: + if (name > end - 64) + return -1; + + if (*p != '\x0a' && *p != '\x0d') { + *name++ = *p++; + break; + } + hrs = HR_CRLF; + /* fallthru */ + case HR_CRLF: + if ((*p != '\x0a' && *p != '\x0d') || + p + 1 == wsi->cgi->headers_start) { + *name = '\0'; + if ((strcmp((const char *)buf, "transfer-encoding") + )) { + lwsl_debug("adding header %s: %s\n", buf, value); + if (lws_add_http_header_by_name(wsi, buf, + (unsigned char *)value, name - value, + (unsigned char **)&wsi->cgi->headers_pos, + (unsigned char *)wsi->cgi->headers_end)) + return 1; + hrs = HR_NAME; + name = buf; + break; + } + } + p++; + break; + } + } +post_hpack_recode: /* finalize cached headers before dumping them */ if (lws_finalize_http_header(wsi, (unsigned char **)&wsi->cgi->headers_pos, @@ -2888,8 +3136,14 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n); + cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION; + if (wsi->cgi->headers_dumped + n != wsi->cgi->headers_pos) { + lwsl_notice("adding no fin flag\n"); + cmd |= LWS_WRITE_NO_FIN; + } + m = lws_write(wsi, (unsigned char *)wsi->cgi->headers_dumped, - n, LWS_WRITE_HTTP_HEADERS); + n, cmd); if (m < 0) { lwsl_debug("%s: write says %d\n", __func__, m); return -1; @@ -2913,14 +3167,18 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) if (!wsi->cgi->headers_buf) { /* if we don't already have a headers buf, cook one up */ n = 2048; - wsi->cgi->headers_buf = lws_malloc(n, "cgi hdr buf"); + if (wsi->http2_substream) + n = 4096; + wsi->cgi->headers_buf = lws_malloc(n + LWS_PRE, + "cgi hdr buf"); if (!wsi->cgi->headers_buf) { lwsl_err("OOM\n"); return -1; } lwsl_debug("allocated cgi hdrs\n"); - wsi->cgi->headers_pos = wsi->cgi->headers_buf; + wsi->cgi->headers_start = wsi->cgi->headers_buf + LWS_PRE; + wsi->cgi->headers_pos = wsi->cgi->headers_start; wsi->cgi->headers_dumped = wsi->cgi->headers_pos; wsi->cgi->headers_end = wsi->cgi->headers_buf + n - 1; @@ -3071,7 +3329,7 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) return -1; } if (n > 0) { - if (m) { + if (!wsi->http2_substream && m) { char chdr[LWS_HTTP_CHUNK_HDR_SIZE]; m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3, "%X\x0d\x0a", n); @@ -3080,16 +3338,22 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) memcpy(start + m + n, "\x0d\x0a", 2); n += m + 2; } - m = lws_write(wsi, (unsigned char *)start, n, LWS_WRITE_HTTP); + cmd = LWS_WRITE_HTTP; + if (wsi->cgi->content_length_seen + n == wsi->cgi->content_length) + cmd = LWS_WRITE_HTTP_FINAL; + m = lws_write(wsi, (unsigned char *)start, n, cmd); //lwsl_notice("write %d\n", m); if (m < 0) { lwsl_debug("%s: stdout write says %d\n", __func__, m); return -1; } - wsi->cgi->content_length_seen += m; + wsi->cgi->content_length_seen += n; } else { if (wsi->cgi_stdout_zero_length) { lwsl_debug("%s: stdout is POLLHUP'd\n", __func__); + if (wsi->http2_substream) + m = lws_write(wsi, (unsigned char *)start, 0, + LWS_WRITE_HTTP_FINAL); return 1; } wsi->cgi_stdout_zero_length = 1; @@ -3385,10 +3649,13 @@ lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs) cs->rx += vh->conn_stats.rx; cs->tx += vh->conn_stats.tx; - cs->conn += vh->conn_stats.conn; - cs->trans += vh->conn_stats.trans; + cs->h1_conn += vh->conn_stats.h1_conn; + cs->h1_trans += vh->conn_stats.h1_trans; + cs->h2_trans += vh->conn_stats.h2_trans; cs->ws_upg += vh->conn_stats.ws_upg; - cs->http2_upg += vh->conn_stats.http2_upg; + cs->h2_upg += vh->conn_stats.h2_upg; + cs->h2_alpn += vh->conn_stats.h2_alpn; + cs->h2_subs += vh->conn_stats.h2_subs; cs->rejected += vh->conn_stats.rejected; vh = vh->vhost_next; @@ -3422,11 +3689,14 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) " \"sts\":\"%d\",\n" " \"rx\":\"%llu\",\n" " \"tx\":\"%llu\",\n" - " \"conn\":\"%lu\",\n" - " \"trans\":\"%lu\",\n" + " \"h1_conn\":\"%lu\",\n" + " \"h1_trans\":\"%lu\",\n" + " \"h2_trans\":\"%lu\",\n" " \"ws_upg\":\"%lu\",\n" " \"rejected\":\"%lu\",\n" - " \"http2_upg\":\"%lu\"" + " \"h2_upg\":\"%lu\",\n" + " \"h2_alpn\":\"%lu\",\n" + " \"h2_subs\":\"%lu\"" , vh->name, vh->listen_port, #ifdef LWS_OPENSSL_SUPPORT @@ -3436,10 +3706,14 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) #endif !!(vh->options & LWS_SERVER_OPTION_STS), vh->conn_stats.rx, vh->conn_stats.tx, - vh->conn_stats.conn, vh->conn_stats.trans, + vh->conn_stats.h1_conn, + vh->conn_stats.h1_trans, + vh->conn_stats.h2_trans, vh->conn_stats.ws_upg, vh->conn_stats.rejected, - vh->conn_stats.http2_upg + vh->conn_stats.h2_upg, + vh->conn_stats.h2_alpn, + vh->conn_stats.h2_subs ); if (vh->mount_list) { @@ -3598,14 +3872,23 @@ lws_json_dump_context(const struct lws_context *context, char *buf, int len, "],\n\"listen_wsi\":\"%d\",\n" " \"rx\":\"%llu\",\n" " \"tx\":\"%llu\",\n" - " \"conn\":\"%lu\",\n" - " \"trans\":\"%lu\",\n" + " \"h1_conn\":\"%lu\",\n" + " \"h1_trans\":\"%lu\",\n" + " \"h2_trans\":\"%lu\",\n" " \"ws_upg\":\"%lu\",\n" " \"rejected\":\"%lu\",\n" - " \"http2_upg\":\"%lu\"", - listening, - cs.rx, cs.tx, cs.conn, cs.trans, - cs.ws_upg, cs.rejected, cs.http2_upg); + " \"h2_alpn\":\"%lu\",\n" + " \"h2_subs\":\"%lu\",\n" + " \"h2_upg\":\"%lu\"", + listening, cs.rx, cs.tx, + cs.h1_conn, + cs.h1_trans, + cs.h2_trans, + cs.ws_upg, + cs.rejected, + cs.h2_alpn, + cs.h2_subs, + cs.h2_upg); #ifdef LWS_WITH_CGI for (n = 0; n < context->count_threads; n++) { diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 1d1daf0e3..5d358fd00 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -2346,9 +2346,17 @@ struct lws_context_creation_info { /**< CONTEXT: max number of wsi a single IP may use simultaneously. * 0 is no limit. This is a hard limit, connections from * the same IP will simply be dropped once it acquires the - * amount of simultanoues wsi / accepted connections + * amount of simultaneous wsi / accepted connections * given here. */ + uint32_t http2_settings[7]; + /**< CONTEXT: after context creation http2_settings[1] thru [6] have + * been set to the lws platform default values. + * VHOST: if http2_settings[0] is nonzero, the values given in + * http2_settings[1]..[6] are used instead of the lws + * platform default values. + * Just leave all at 0 if you don't care. + */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility @@ -3367,6 +3375,8 @@ enum lws_token_indexes { WSI_TOKEN_HTTP1_0 = 79, WSI_TOKEN_X_FORWARDED_FOR = 80, WSI_TOKEN_CONNECT = 81, + WSI_TOKEN_HEAD_URI = 82, + WSI_TOKEN_TE = 83, /****** add new things just above ---^ ******/ /* use token storage to stash these internally, not for @@ -3403,7 +3413,6 @@ struct lws_token_limits { LWS_VISIBLE LWS_EXTERN const unsigned char * lws_token_to_string(enum lws_token_indexes token); - /** * lws_hdr_total_length: report length of all fragments of a header totalled up * The returned length does not include the space for a @@ -3735,6 +3744,9 @@ lws_urlencode(char *escaped, const char *string, int len); * * Since urldecoding only shrinks the output string, it is possible to * do it in-place, ie, string == escaped + * + * Returns 0 if completed OK or nonzero for urldecode violation (non-hex chars + * where hex required, etc) */ LWS_VISIBLE LWS_EXTERN int lws_urldecode(char *string, const char *escaped, int len); @@ -4029,6 +4041,9 @@ enum lws_write_protocol { * to be compatible with both in the future,header response part should * be sent using this regardless of http version expected) */ + LWS_WRITE_HTTP_HEADERS_CONTINUATION = 9, + /**< Continuation of http/2 headers + */ /****** add new things just above ---^ ******/ @@ -4037,6 +4052,11 @@ enum lws_write_protocol { LWS_WRITE_NO_FIN = 0x40, /**< This part of the message is not the end of the message */ + LWS_WRITE_H2_STREAM_END = 0x80, + /**< Flag indicates this packet should go out with STREAM_END if h2 + * STREAM_END is allowed on DATA or HEADERS. + */ + LWS_WRITE_CLIENT_IGNORE_XOR_MASK = 0x80 /**< client packet payload goes out on wire unmunged * only useful for security tests since normal servers cannot @@ -4328,6 +4348,24 @@ LWS_VISIBLE LWS_EXTERN size_t lws_get_peer_write_allowance(struct lws *wsi); ///@} +enum { + /* + * Flags for enable and disable rxflow with reason bitmap and with + * backwards-compatible single bool + */ + LWS_RXFLOW_REASON_USER_BOOL = (1 << 0), + LWS_RXFLOW_REASON_HTTP_RXBUFFER = (1 << 6), + LWS_RXFLOW_REASON_H2_PPS_PENDING = (1 << 7), + + LWS_RXFLOW_REASON_APPLIES = (1 << 14), + LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT = (1 << 13), + LWS_RXFLOW_REASON_APPLIES_ENABLE = LWS_RXFLOW_REASON_APPLIES | + LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT, + LWS_RXFLOW_REASON_APPLIES_DISABLE = LWS_RXFLOW_REASON_APPLIES, + LWS_RXFLOW_REASON_FLAG_PROCESS_NOW = (1 << 12), + +}; + /** * lws_rx_flow_control() - Enable and disable socket servicing for * received packets. @@ -4337,6 +4375,15 @@ lws_get_peer_write_allowance(struct lws *wsi); * * \param wsi: Websocket connection instance to get callback for * \param enable: 0 = disable read servicing for this connection, 1 = enable + * + * If you need more than one additive reason for rxflow control, you can give + * iLWS_RXFLOW_REASON_APPLIES_ENABLE or _DISABLE together with one or more of + * b5..b0 set to idicate which bits to enable or disable. If any bits are + * enabled, rx on the connection is suppressed. + * + * LWS_RXFLOW_REASON_FLAG_PROCESS_NOW flag may also be given to force any change + * in rxflowbstatus to benapplied immediately, this should be used when you are + * changing a wsi flow control state from outside a callback on that wsi. */ LWS_VISIBLE LWS_EXTERN int lws_rx_flow_control(struct lws *wsi, int enable); @@ -4652,9 +4699,299 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, } /** - * lws_ring: generic ringbuffer struct + * lws_snprintf(): snprintf that truncates the returned length too * - * all of the members are opaque and manipulated by lws_ring_...() apis. + * \param str: destination buffer + * \param size: bytes left in destination buffer + * \param format: format string + * \param ...: args for format + * + * This lets you correctly truncate buffers by concatenating lengths, if you + * reach the limit the reported length doesn't exceed the limit. + */ +LWS_VISIBLE LWS_EXTERN int +lws_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3); + +/** + * lws_get_random(): fill a buffer with platform random data + * + * \param context: the lws context + * \param buf: buffer to fill + * \param len: how much to fill + * + * This is intended to be called from the LWS_CALLBACK_RECEIVE callback if + * it's interested to see if the frame it's dealing with was sent in binary + * mode. + */ +LWS_VISIBLE LWS_EXTERN int +lws_get_random(struct lws_context *context, void *buf, int len); +/** + * lws_daemonize(): make current process run in the background + * + * \param _lock_path: the filepath to write the lock file + * + * Spawn lws as a background process, taking care of various things + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_daemonize(const char *_lock_path); +/** + * lws_get_library_version(): return string describing the version of lws + * + * On unix, also includes the git describe + */ +LWS_VISIBLE LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT +lws_get_library_version(void); + +/** + * lws_wsi_user() - get the user data associated with the connection + * \param wsi: lws connection + * + * Not normally needed since it's passed into the callback + */ +LWS_VISIBLE LWS_EXTERN void * +lws_wsi_user(struct lws *wsi); + +/** + * lws_wsi_set_user() - set the user data associated with the client connection + * \param wsi: lws connection + * \param user: user data + * + * By default lws allocates this and it's not legal to externally set it + * yourself. However client connections may have it set externally when the + * connection is created... if so, this api can be used to modify it at + * runtime additionally. + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_wsi_user(struct lws *wsi, void *user); + +/** + * lws_parse_uri: cut up prot:/ads:port/path into pieces + * Notice it does so by dropping '\0' into input string + * and the leading / on the path is consequently lost + * + * \param p: incoming uri string.. will get written to + * \param prot: result pointer for protocol part (https://) + * \param ads: result pointer for address part + * \param port: result pointer for port part + * \param path: result pointer for path part + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse_uri(char *p, const char **prot, const char **ads, int *port, + const char **path); + +/** + * lws_now_secs(): return seconds since 1970-1-1 + */ +LWS_VISIBLE LWS_EXTERN unsigned long +lws_now_secs(void); + +/** + * lws_get_context - Allow geting lws_context from a Websocket connection + * instance + * + * With this function, users can access context in the callback function. + * Otherwise users may have to declare context as a global variable. + * + * \param wsi: Websocket connection instance + */ +LWS_VISIBLE LWS_EXTERN struct lws_context * LWS_WARN_UNUSED_RESULT +lws_get_context(const struct lws *wsi); + +/** + * lws_get_count_threads(): how many service threads the context uses + * + * \param context: the lws context + * + * By default this is always 1, if you asked for more than lws can handle it + * will clip the number of threads. So you can use this to find out how many + * threads are actually in use. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_get_count_threads(struct lws_context *context); + +/** + * lws_get_parent() - get parent wsi or NULL + * \param wsi: lws connection + * + * Specialized wsi like cgi stdin/out/err are associated to a parent wsi, + * this allows you to get their parent. + */ +LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_get_parent(const struct lws *wsi); + +/** + * lws_get_child() - get child wsi or NULL + * \param wsi: lws connection + * + * Allows you to find a related wsi from the parent wsi. + */ +LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_get_child(const struct lws *wsi); + +/** + * lws_parent_carries_io() - mark wsi as needing to send messages via parent + * + * \param wsi: child lws connection + */ + +LWS_VISIBLE LWS_EXTERN void +lws_set_parent_carries_io(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void * +lws_get_opaque_parent_data(const struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void +lws_set_opaque_parent_data(struct lws *wsi, void *data); + +LWS_VISIBLE LWS_EXTERN int +lws_get_child_pending_on_writable(const struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void +lws_clear_child_pending_on_writable(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN int +lws_get_close_length(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN unsigned char * +lws_get_close_payload(struct lws *wsi); + +/** + * lws_get_network_wsi() - Returns wsi that has the tcp connection for this wsi + * + * \param wsi: wsi you have + * + * Returns wsi that has the tcp connection (which may be the incoming wsi) + * + * HTTP/1 connections will always return the incoming wsi + * HTTP/2 connections may return a different wsi that has the tcp connection + */ +LWS_VISIBLE LWS_EXTERN +struct lws *lws_get_network_wsi(struct lws *wsi); + +/* + * \deprecated DEPRECATED Note: this is not normally needed as a user api. + * It's provided in case it is + * useful when integrating with other app poll loop service code. + */ +LWS_VISIBLE LWS_EXTERN int +lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len); + +/** + * lws_set_allocator() - custom allocator support + * + * \param realloc + * + * Allows you to replace the allocator (and deallocator) used by lws + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_allocator(void *(*realloc)(void *ptr, size_t size, const char *reason)); +///@} + +/** \defgroup wsstatus Websocket status APIs + * ##Websocket connection status APIs + * + * These provide information about ws connection or message status + */ +///@{ +/** + * lws_send_pipe_choked() - tests if socket is writable or not + * \param wsi: lws connection + * + * Allows you to check if you can write more on the socket + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_send_pipe_choked(struct lws *wsi); + +/** + * lws_is_final_fragment() - tests if last part of ws message + * + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_final_fragment(struct lws *wsi); + +/** + * lws_is_first_fragment() - tests if first part of ws message + * + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_first_fragment(struct lws *wsi); + +/** + * lws_get_reserved_bits() - access reserved bits of ws frame + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN unsigned char +lws_get_reserved_bits(struct lws *wsi); + +/** + * lws_partial_buffered() - find out if lws buffered the last write + * \param wsi: websocket connection to check + * + * Returns 1 if you cannot use lws_write because the last + * write on this connection is still buffered, and can't be cleared without + * returning to the service loop and waiting for the connection to be + * writeable again. + * + * If you will try to do >1 lws_write call inside a single + * WRITEABLE callback, you must check this after every write and bail if + * set, ask for a new writeable callback and continue writing from there. + * + * This is never set at the start of a writeable callback, but any write + * may set it. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_partial_buffered(struct lws *wsi); + +/** + * lws_frame_is_binary(): true if the current frame was sent in binary mode + * + * \param wsi: the connection we are inquiring about + * + * This is intended to be called from the LWS_CALLBACK_RECEIVE callback if + * it's interested to see if the frame it's dealing with was sent in binary + * mode. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_frame_is_binary(struct lws *wsi); + +/** + * lws_is_ssl() - Find out if connection is using SSL + * \param wsi: websocket connection to check + * + * Returns 0 if the connection is not using SSL, 1 if using SSL and + * using verified cert, and 2 if using SSL but the cert was not + * checked (appears for client wsi told to skip check on connection) + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_ssl(struct lws *wsi); +/** + * lws_is_cgi() - find out if this wsi is running a cgi process + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_cgi(struct lws *wsi); + +#ifdef LWS_OPENSSL_SUPPORT +/** + * lws_get_ssl() - Return wsi's SSL context structure + * \param wsi: websocket connection + * + * Returns pointer to the SSL library's context structure + */ +LWS_VISIBLE LWS_EXTERN SSL* +lws_get_ssl(struct lws *wsi); +#endif +///@} + +/** \defgroup lws_ring LWS Ringbuffer APIs + * ##lws_ring: generic ringbuffer struct + * + * Provides an abstract ringbuffer api supporting one head and one or an + * unlimited number of tails. + * + * All of the members are opaque and manipulated by lws_ring_...() apis. * * The lws_ring and its buffer is allocated at runtime on the heap, using * @@ -4664,6 +5001,11 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, * It may contain any type, the size of the "element" stored in the ring * buffer and the number of elements is given at creation time. * + * When you create the ringbuffer, you can optionally provide an element + * destroy callback that frees any allocations inside the element. This is then + * automatically called for elements with no tail behind them, ie, elements + * which don't have any pending consumer are auto-freed. + * * Whole elements may be inserted into the ringbuffer and removed from it, using * * - lws_ring_insert() @@ -4697,6 +5039,7 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, * * - lws_ring_update_oldest_tail() */ +///@{ struct lws_ring; /** @@ -4856,7 +5199,6 @@ LWS_VISIBLE LWS_EXTERN int lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start, size_t *bytes); - /** * lws_ring_bump_head(): used to write directly into the ring * @@ -4865,283 +5207,8 @@ lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start, */ LWS_VISIBLE LWS_EXTERN void lws_ring_bump_head(struct lws_ring *ring, size_t bytes); - - -/** - * lws_snprintf(): snprintf that truncates the returned length too - * - * \param str: destination buffer - * \param size: bytes left in destination buffer - * \param format: format string - * \param ...: args for format - * - * This lets you correctly truncate buffers by concatenating lengths, if you - * reach the limit the reported length doesn't exceed the limit. - */ -LWS_VISIBLE LWS_EXTERN int -lws_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3); - -/** - * lws_get_random(): fill a buffer with platform random data - * - * \param context: the lws context - * \param buf: buffer to fill - * \param len: how much to fill - * - * This is intended to be called from the LWS_CALLBACK_RECEIVE callback if - * it's interested to see if the frame it's dealing with was sent in binary - * mode. - */ -LWS_VISIBLE LWS_EXTERN int -lws_get_random(struct lws_context *context, void *buf, int len); -/** - * lws_daemonize(): make current process run in the background - * - * \param _lock_path: the filepath to write the lock file - * - * Spawn lws as a background process, taking care of various things - */ -LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_daemonize(const char *_lock_path); -/** - * lws_get_library_version(): return string describing the version of lws - * - * On unix, also includes the git describe - */ -LWS_VISIBLE LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT -lws_get_library_version(void); - -/** - * lws_wsi_user() - get the user data associated with the connection - * \param wsi: lws connection - * - * Not normally needed since it's passed into the callback - */ -LWS_VISIBLE LWS_EXTERN void * -lws_wsi_user(struct lws *wsi); - -/** - * lws_wsi_set_user() - set the user data associated with the client connection - * \param wsi: lws connection - * \param user: user data - * - * By default lws allocates this and it's not legal to externally set it - * yourself. However client connections may have it set externally when the - * connection is created... if so, this api can be used to modify it at - * runtime additionally. - */ -LWS_VISIBLE LWS_EXTERN void -lws_set_wsi_user(struct lws *wsi, void *user); - -/** - * lws_parse_uri: cut up prot:/ads:port/path into pieces - * Notice it does so by dropping '\0' into input string - * and the leading / on the path is consequently lost - * - * \param p: incoming uri string.. will get written to - * \param prot: result pointer for protocol part (https://) - * \param ads: result pointer for address part - * \param port: result pointer for port part - * \param path: result pointer for path part - */ -LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_parse_uri(char *p, const char **prot, const char **ads, int *port, - const char **path); - -/** - * lws_now_secs(): return seconds since 1970-1-1 - */ -LWS_VISIBLE LWS_EXTERN unsigned long -lws_now_secs(void); - -/** - * lws_get_context - Allow geting lws_context from a Websocket connection - * instance - * - * With this function, users can access context in the callback function. - * Otherwise users may have to declare context as a global variable. - * - * \param wsi: Websocket connection instance - */ -LWS_VISIBLE LWS_EXTERN struct lws_context * LWS_WARN_UNUSED_RESULT -lws_get_context(const struct lws *wsi); - -/** - * lws_get_count_threads(): how many service threads the context uses - * - * \param context: the lws context - * - * By default this is always 1, if you asked for more than lws can handle it - * will clip the number of threads. So you can use this to find out how many - * threads are actually in use. - */ -LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_get_count_threads(struct lws_context *context); - -/** - * lws_get_parent() - get parent wsi or NULL - * \param wsi: lws connection - * - * Specialized wsi like cgi stdin/out/err are associated to a parent wsi, - * this allows you to get their parent. - */ -LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT -lws_get_parent(const struct lws *wsi); - -/** - * lws_get_child() - get child wsi or NULL - * \param wsi: lws connection - * - * Allows you to find a related wsi from the parent wsi. - */ -LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT -lws_get_child(const struct lws *wsi); - -/** - * lws_parent_carries_io() - mark wsi as needing to send messages via parent - * - * \param wsi: child lws connection - */ - -LWS_VISIBLE LWS_EXTERN void -lws_set_parent_carries_io(struct lws *wsi); - -LWS_VISIBLE LWS_EXTERN void * -lws_get_opaque_parent_data(const struct lws *wsi); - -LWS_VISIBLE LWS_EXTERN void -lws_set_opaque_parent_data(struct lws *wsi, void *data); - -LWS_VISIBLE LWS_EXTERN int -lws_get_child_pending_on_writable(const struct lws *wsi); - -LWS_VISIBLE LWS_EXTERN void -lws_clear_child_pending_on_writable(struct lws *wsi); - -LWS_VISIBLE LWS_EXTERN int -lws_get_close_length(struct lws *wsi); - -LWS_VISIBLE LWS_EXTERN unsigned char * -lws_get_close_payload(struct lws *wsi); - -/* - * \deprecated DEPRECATED Note: this is not normally needed as a user api. - * It's provided in case it is - * useful when integrating with other app poll loop service code. - */ -LWS_VISIBLE LWS_EXTERN int -lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len); - -/** - * lws_set_allocator() - custom allocator support - * - * \param realloc - * - * Allows you to replace the allocator (and deallocator) used by lws - */ -LWS_VISIBLE LWS_EXTERN void -lws_set_allocator(void *(*realloc)(void *ptr, size_t size, const char *reason)); ///@} -/** \defgroup wsstatus Websocket status APIs - * ##Websocket connection status APIs - * - * These provide information about ws connection or message status - */ -///@{ -/** - * lws_send_pipe_choked() - tests if socket is writable or not - * \param wsi: lws connection - * - * Allows you to check if you can write more on the socket - */ -LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_send_pipe_choked(struct lws *wsi); - -/** - * lws_is_final_fragment() - tests if last part of ws message - * - * \param wsi: lws connection - */ -LWS_VISIBLE LWS_EXTERN int -lws_is_final_fragment(struct lws *wsi); - -/** - * lws_is_first_fragment() - tests if first part of ws message - * - * \param wsi: lws connection - */ -LWS_VISIBLE LWS_EXTERN int -lws_is_first_fragment(struct lws *wsi); - -/** - * lws_get_reserved_bits() - access reserved bits of ws frame - * \param wsi: lws connection - */ -LWS_VISIBLE LWS_EXTERN unsigned char -lws_get_reserved_bits(struct lws *wsi); - -/** - * lws_partial_buffered() - find out if lws buffered the last write - * \param wsi: websocket connection to check - * - * Returns 1 if you cannot use lws_write because the last - * write on this connection is still buffered, and can't be cleared without - * returning to the service loop and waiting for the connection to be - * writeable again. - * - * If you will try to do >1 lws_write call inside a single - * WRITEABLE callback, you must check this after every write and bail if - * set, ask for a new writeable callback and continue writing from there. - * - * This is never set at the start of a writeable callback, but any write - * may set it. - */ -LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_partial_buffered(struct lws *wsi); - -/** - * lws_frame_is_binary(): true if the current frame was sent in binary mode - * - * \param wsi: the connection we are inquiring about - * - * This is intended to be called from the LWS_CALLBACK_RECEIVE callback if - * it's interested to see if the frame it's dealing with was sent in binary - * mode. - */ -LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_frame_is_binary(struct lws *wsi); - -/** - * lws_is_ssl() - Find out if connection is using SSL - * \param wsi: websocket connection to check - * - * Returns 0 if the connection is not using SSL, 1 if using SSL and - * using verified cert, and 2 if using SSL but the cert was not - * checked (appears for client wsi told to skip check on connection) - */ -LWS_VISIBLE LWS_EXTERN int -lws_is_ssl(struct lws *wsi); -/** - * lws_is_cgi() - find out if this wsi is running a cgi process - * \param wsi: lws connection - */ -LWS_VISIBLE LWS_EXTERN int -lws_is_cgi(struct lws *wsi); - -#ifdef LWS_OPENSSL_SUPPORT -/** - * lws_get_ssl() - Return wsi's SSL context structure - * \param wsi: websocket connection - * - * Returns pointer to the SSL library's context structure - */ -LWS_VISIBLE LWS_EXTERN SSL* -lws_get_ssl(struct lws *wsi); -#endif -///@} - - /** \defgroup sha SHA and B64 helpers * ##SHA and B64 helpers * diff --git a/lib/lws-plat-esp32.c b/lib/lws-plat-esp32.c index 517f344d5..b5adabe1f 100644 --- a/lib/lws-plat-esp32.c +++ b/lib/lws-plat-esp32.c @@ -60,17 +60,21 @@ lws_get_random(struct lws_context *context, void *buf, int len) LWS_VISIBLE int lws_send_pipe_choked(struct lws *wsi) { + struct lws *wsi_eff = wsi; fd_set writefds; struct timeval tv = { 0, 0 }; +#if defined(LWS_WITH_HTTP2) + wsi_eff = lws_get_network_wsi(wsi); +#endif /* treat the fact we got a truncated send pending as if we're choked */ - if (wsi->trunc_len) + if (wsi_eff->trunc_len) return 1; FD_ZERO(&writefds); - FD_SET(wsi->desc.sockfd, &writefds); + FD_SET(wsi_eff->desc.sockfd, &writefds); - if (select(wsi->desc.sockfd + 1, NULL, &writefds, NULL, &tv) < 1) + if (select(wsi_eff->desc.sockfd + 1, NULL, &writefds, NULL, &tv) < 1) return 1; return 0; @@ -535,6 +539,21 @@ _lws_plat_file_write(lws_fop_fd_t fops_fd, lws_filepos_t *amount, return 0; } +#if defined(LWS_WITH_HTTP2) +/* + * These are the default SETTINGS used on this platform. The user + * can selectively modify them for a vhost during vhost creation. + */ +const struct http2_settings const lws_h2_defaults_esp32 = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 512, + /* H2SET_ENABLE_PUSH */ 0, + /* H2SET_MAX_CONCURRENT_STREAMS */ 8, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 512, +}}; +#endif LWS_VISIBLE int lws_plat_init(struct lws_context *context, @@ -556,11 +575,14 @@ lws_plat_init(struct lws_context *context, if (info->plugin_dirs) lws_plat_plugins_init(context, info->plugin_dirs); #endif +#if defined(LWS_WITH_HTTP2) + /* override settings */ + context->set = lws_h2_defaults_esp32; +#endif return 0; } - LWS_VISIBLE void esp32_uvtimer_cb(TimerHandle_t t) { struct timer_mapping *p = pvTimerGetTimerID(t); @@ -568,24 +590,6 @@ LWS_VISIBLE void esp32_uvtimer_cb(TimerHandle_t t) p->cb(p->t); } -void ERR_error_string_n(unsigned long e, char *buf, size_t len) -{ - strncpy(buf, "unknown", len); -} - -void ERR_free_strings(void) -{ -} - -char *ERR_error_string(unsigned long e, char *buf) -{ - if (buf) - strcpy(buf, "unknown"); - - return "unknown"; -} - - /* helper functionality */ #include "romfs.h" @@ -1499,7 +1503,7 @@ lws_esp32_wlan_start_station(void) tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]); esp_wifi_set_auto_connect(1); - ESP_ERROR_CHECK( esp_wifi_connect()); + //ESP_ERROR_CHECK( esp_wifi_connect()); lws_esp32_scan_timer_cb(NULL); } @@ -1591,13 +1595,13 @@ lws_esp32_set_creation_defaults(struct lws_context_creation_info *info) (void)part; info->port = 443; - info->fd_limit_per_thread = 30; - info->max_http_header_pool = 3; - info->max_http_header_data = 1024; - info->pt_serv_buf_size = 4096; + info->fd_limit_per_thread = 10; + info->max_http_header_pool = 16; + info->max_http_header_data = 512; + info->pt_serv_buf_size = 2048; info->keepalive_timeout = 30; info->timeout_secs = 30; - info->simultaneous_ssl_restriction = 3; + info->simultaneous_ssl_restriction = 4; info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c index cb5e47c3f..f55369129 100644 --- a/lib/lws-plat-unix.c +++ b/lib/lws-plat-unix.c @@ -47,12 +47,16 @@ LWS_VISIBLE int lws_send_pipe_choked(struct lws *wsi) { struct lws_pollfd fds; + struct lws *wsi_eff = wsi; +#if defined(LWS_WITH_HTTP2) + wsi_eff = lws_get_network_wsi(wsi); +#endif /* treat the fact we got a truncated send pending as if we're choked */ - if (wsi->trunc_len) + if (wsi_eff->trunc_len) return 1; - fds.fd = wsi->desc.sockfd; + fds.fd = wsi_eff->desc.sockfd; fds.events = POLLOUT; fds.revents = 0; diff --git a/lib/mbedtls_wrapper/include/internal/ssl_types.h b/lib/mbedtls_wrapper/include/internal/ssl_types.h index 5d7d9387c..45198bc97 100644 --- a/lib/mbedtls_wrapper/include/internal/ssl_types.h +++ b/lib/mbedtls_wrapper/include/internal/ssl_types.h @@ -19,6 +19,12 @@ extern "C" { #endif +#include +#if defined(LWS_WITH_ESP32) +#undef MBEDTLS_CONFIG_FILE +#define MBEDTLS_CONFIG_FILE +#endif + #include "ssl_code.h" typedef void SSL_CIPHER; diff --git a/lib/output.c b/lib/output.c index 4d6897e01..0c5f41c87 100644 --- a/lib/output.c +++ b/lib/output.c @@ -53,7 +53,7 @@ LWS_VISIBLE void lwsl_hexdump(const void *vbuf, size_t len) char line[80]; char *p; - lwsl_parser("\n"); + lwsl_debug("\n"); for (n = 0; n < len;) { start = n; @@ -117,7 +117,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) #else lwsl_err("****** %p: Sending new %lu (%s), pending truncated ...\n" " It's illegal to do an lws_write outside of\n" - " the writable callback: fix your code", + " the writable callback: fix your code\n", wsi, (unsigned long)len, dump); #endif assert(0); @@ -133,7 +133,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) goto handle_truncated_send; } - if (!lws_socket_is_valid(wsi->desc.sockfd)) + if (!wsi->http2_substream && !lws_socket_is_valid(wsi->desc.sockfd)) lwsl_warn("** error invalid sock but expected to send\n"); /* limit sending */ @@ -300,9 +300,10 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, lws_restart_ws_ping_pong_timer(wsi); - if (wp == LWS_WRITE_HTTP || - wp == LWS_WRITE_HTTP_FINAL || - wp == LWS_WRITE_HTTP_HEADERS) + if ((wp & 0x1f) == LWS_WRITE_HTTP || + (wp & 0x1f) == LWS_WRITE_HTTP_FINAL || + (wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION || + (wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) goto send_raw; /* if not in a state to send stuff, then just send nothing */ @@ -508,45 +509,61 @@ do_more_inside_frame: } send_raw: - switch ((int)wp) { + switch ((int)(wp & 0x1f)) { case LWS_WRITE_CLOSE: /* lwsl_hexdump(&buf[-pre], len); */ case LWS_WRITE_HTTP: case LWS_WRITE_HTTP_FINAL: case LWS_WRITE_HTTP_HEADERS: + case LWS_WRITE_HTTP_HEADERS_CONTINUATION: case LWS_WRITE_PONG: case LWS_WRITE_PING: #ifdef LWS_WITH_HTTP2 if (wsi->mode == LWSCM_HTTP2_SERVING) { unsigned char flags = 0; - n = LWS_HTTP2_FRAME_TYPE_DATA; - if (wp == LWS_WRITE_HTTP_HEADERS) { - n = LWS_HTTP2_FRAME_TYPE_HEADERS; - flags = LWS_HTTP2_FLAG_END_HEADERS; - if (wsi->u.http2.send_END_STREAM) - flags |= LWS_HTTP2_FLAG_END_STREAM; + n = LWS_H2_FRAME_TYPE_DATA; + if ((wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) { + n = LWS_H2_FRAME_TYPE_HEADERS; + if (!(wp & LWS_WRITE_NO_FIN)) + flags = LWS_H2_FLAG_END_HEADERS; + if (wsi->u.h2.send_END_STREAM || (wp & LWS_WRITE_H2_STREAM_END)) { + flags |= LWS_H2_FLAG_END_STREAM; + wsi->u.h2.send_END_STREAM = 1; + } } - if ((wp == LWS_WRITE_HTTP || - wp == LWS_WRITE_HTTP_FINAL) && - wsi->u.http.content_length) { - wsi->u.http.content_remain -= len; + if ((wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION) { + n = LWS_H2_FRAME_TYPE_CONTINUATION; + if (!(wp & LWS_WRITE_NO_FIN)) + flags = LWS_H2_FLAG_END_HEADERS; + if (wsi->u.h2.send_END_STREAM || (wp & LWS_WRITE_H2_STREAM_END)) { + flags |= LWS_H2_FLAG_END_STREAM; + wsi->u.h2.send_END_STREAM = 1; + } + } + + if (((wp & 0x1f) == LWS_WRITE_HTTP || + (wp & 0x1f) == LWS_WRITE_HTTP_FINAL) && + wsi->u.http.tx_content_length) { + wsi->u.http.tx_content_remain -= len; lwsl_info("%s: content_remain = %llu\n", __func__, - (unsigned long long)wsi->u.http.content_remain); - if (!wsi->u.http.content_remain) { + (unsigned long long)wsi->u.http.tx_content_remain); + if (!wsi->u.http.tx_content_remain) { lwsl_info("%s: selecting final write mode\n", __func__); wp = LWS_WRITE_HTTP_FINAL; } } - if (wp == LWS_WRITE_HTTP_FINAL && wsi->u.http2.END_STREAM) { + if ((wp & 0x1f) == LWS_WRITE_HTTP_FINAL || (wp & LWS_WRITE_H2_STREAM_END)) { + //lws_get_network_wsi(wsi)->u.h2.END_STREAM) { lwsl_info("%s: setting END_STREAM\n", __func__); - flags |= LWS_HTTP2_FLAG_END_STREAM; + flags |= LWS_H2_FLAG_END_STREAM; + wsi->u.h2.send_END_STREAM = 1; } - return lws_http2_frame_write(wsi, n, flags, - wsi->u.http2.my_stream_id, len, buf); + return lws_h2_frame_write(wsi, n, flags, + wsi->u.h2.my_sid, len, buf); } #endif return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre); @@ -600,13 +617,15 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) 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; + unsigned char *p, *pstart; #if defined(LWS_WITH_RANGES) unsigned char finished = 0; #endif int n, m; - while (wsi->http2_substream || !lws_send_pipe_choked(wsi)) { + lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream); + + while (!lws_send_pipe_choked(wsi)) { if (wsi->trunc_len) { if (lws_issue_raw(wsi, wsi->trunc_alloc + @@ -623,7 +642,9 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) n = 0; - p = pt->serv_buf; + pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH; + + p = pstart; #if defined(LWS_WITH_RANGES) if (wsi->u.http.range.count_ranges && !wsi->u.http.range.inside) { @@ -638,7 +659,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) wsi->u.http.filepos = wsi->u.http.range.start; if (wsi->u.http.range.count_ranges > 1) { - n = lws_snprintf((char *)p, context->pt_serv_buf_size, + 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" @@ -656,15 +677,30 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) } #endif - poss = context->pt_serv_buf_size - n; + poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH; /* * 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) + if (wsi->protocol->tx_packet_size && + poss > wsi->protocol->tx_packet_size) poss = wsi->protocol->tx_packet_size; +#if defined(LWS_WITH_HTTP2) + m = lws_h2_tx_cr_get(wsi); + if (!m) { + lwsl_info("%s: came here with no tx credit", __func__); + return 0; + } + if (m < poss) + poss = m; + /* + * consumption of the actual payload amount sent will be handled + * when the http2 data frame is sent + */ +#endif + #if defined(LWS_WITH_RANGES) if (wsi->u.http.range.count_ranges) { if (wsi->u.http.range.count_ranges > 1) @@ -686,7 +722,10 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) if (wsi->sending_chunked) n = (int)amount; else - n = (p - pt->serv_buf) + (int)amount; + n = (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); @@ -705,14 +744,14 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) n = args.len; p = (unsigned char *)args.p; } else - p = pt->serv_buf; + p = pstart; #if defined(LWS_WITH_RANGES) if (wsi->u.http.range.send_ctr + 1 == wsi->u.http.range.count_ranges && // last range wsi->u.http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart) wsi->u.http.range.budget - amount == 0) {// final part - n += lws_snprintf((char *)pt->serv_buf + n, 6, + n += lws_snprintf((char *)pstart + n, 6, "_lws\x0d\x0a"); // append trailing boundary lwsl_debug("added trailing boundary\n"); } @@ -751,8 +790,9 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) goto file_had_it; } } + all_sent: - if ((!wsi->trunc_len && wsi->u.http.filepos == wsi->u.http.filelen) + if ((!wsi->trunc_len && wsi->u.http.filepos >= wsi->u.http.filelen) #if defined(LWS_WITH_RANGES) || finished) #else @@ -765,13 +805,30 @@ all_sent: lwsl_debug("file completed\n"); - if (wsi->protocol->callback) - /* ignore callback returned value */ - if (user_callback_handle_rxflow( - wsi->protocol->callback, wsi, - LWS_CALLBACK_HTTP_FILE_COMPLETION, - wsi->user_space, NULL, 0) < 0) - return -1; + 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 */ } diff --git a/lib/parsers.c b/lib/parsers.c index 99119e132..f4c412d82 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -110,6 +110,7 @@ _lws_header_table_reset(struct allocated_headers *ah) { /* init the ah to reflect no headers or data have appeared yet */ memset(ah->frag_index, 0, sizeof(ah->frag_index)); + memset(ah->frags, 0, sizeof(ah->frags)); ah->nfrag = 0; ah->pos = 0; ah->http_response = 0; @@ -440,6 +441,7 @@ int lws_header_table_detach(struct lws *wsi, int autoservice) /* he has been stuck waiting for an ah, but now his wait is * over, let him progress */ + _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); } @@ -676,6 +678,7 @@ issue_char(struct lws *wsi, unsigned char c) if (frag_len == wsi->u.hdr.current_token_limit) { if (lws_pos_in_bounds(wsi)) return -1; + wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = '\0'; lwsl_warn("header %i exceeds limit %d\n", wsi->u.hdr.parser_state, @@ -685,21 +688,213 @@ issue_char(struct lws *wsi, unsigned char c) return 1; } +int +lws_parse_urldecode(struct lws *wsi, uint8_t *_c) +{ + struct allocated_headers *ah = wsi->u.hdr.ah; + unsigned int enc = 0; + uint8_t c = *_c; + + /* + * PRIORITY 1 + * special URI processing... convert %xx + */ + switch (wsi->u.hdr.ues) { + case URIES_IDLE: + if (c == '%') { + wsi->u.hdr.ues = URIES_SEEN_PERCENT; + goto swallow; + } + break; + case URIES_SEEN_PERCENT: + if (char_to_hex(c) < 0) + /* illegal post-% char */ + goto forbid; + + wsi->u.hdr.esc_stash = c; + wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1; + goto swallow; + + case URIES_SEEN_PERCENT_H1: + if (char_to_hex(c) < 0) + /* illegal post-% char */ + goto forbid; + + *_c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) | + char_to_hex(c); + c = *_c; + enc = 1; + wsi->u.hdr.ues = URIES_IDLE; + break; + } + + /* + * PRIORITY 2 + * special URI processing... + * convert /.. or /... or /../ etc to / + * convert /./ to / + * convert // or /// etc to / + * leave /.dir or whatever alone + */ + + switch (wsi->u.hdr.ups) { + case URIPS_IDLE: + if (!c) + return -1; + /* genuine delimiter */ + if ((c == '&' || c == ';') && !enc) { + if (issue_char(wsi, c) < 0) + return -1; + /* swallow the terminator */ + ah->frags[ah->nfrag].len--; + /* link to next fragment */ + ah->frags[ah->nfrag].nfrag = ah->nfrag + 1; + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + /* start next fragment after the & */ + wsi->u.hdr.post_literal_equal = 0; + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + goto swallow; + } + /* uriencoded = in the name part, disallow */ + if (c == '=' && enc && + ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] && + !wsi->u.hdr.post_literal_equal) { + c = '_'; + *_c =c; + } + + /* after the real =, we don't care how many = */ + if (c == '=' && !enc) + wsi->u.hdr.post_literal_equal = 1; + + /* + to space */ + if (c == '+' && !enc) { + c = ' '; + *_c = c; + } + /* issue the first / always */ + if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + break; + case URIPS_SEEN_SLASH: + /* swallow subsequent slashes */ + if (c == '/') + goto swallow; + /* track and swallow the first . after / */ + if (c == '.') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT; + goto swallow; + } + wsi->u.hdr.ups = URIPS_IDLE; + break; + case URIPS_SEEN_SLASH_DOT: + /* swallow second . */ + if (c == '.') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT; + goto swallow; + } + /* change /./ to / */ + if (c == '/') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + goto swallow; + } + /* it was like /.dir ... regurgitate the . */ + wsi->u.hdr.ups = URIPS_IDLE; + if (issue_char(wsi, '.') < 0) + return -1; + break; + + case URIPS_SEEN_SLASH_DOT_DOT: + + /* /../ or /..[End of URI] --> backup to last / */ + if (c == '/' || c == '?') { + /* + * back up one dir level if possible + * safe against header fragmentation because + * the method URI can only be in 1 fragment + */ + if (ah->frags[ah->nfrag].len > 2) { + ah->pos--; + ah->frags[ah->nfrag].len--; + do { + ah->pos--; + ah->frags[ah->nfrag].len--; + } while (ah->frags[ah->nfrag].len > 1 && + ah->data[ah->pos] != '/'); + } + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + if (ah->frags[ah->nfrag].len > 1) + break; + goto swallow; + } + + /* /..[^/] ... regurgitate and allow */ + + if (issue_char(wsi, '.') < 0) + return -1; + if (issue_char(wsi, '.') < 0) + return -1; + wsi->u.hdr.ups = URIPS_IDLE; + break; + } + + if (c == '?' && !enc && + !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI arguments */ + if (wsi->u.hdr.ues != URIES_IDLE) + goto forbid; + + /* seal off uri header */ + if (issue_char(wsi, '\0') < 0) + return -1; + + /* move to using WSI_TOKEN_HTTP_URI_ARGS */ + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + + wsi->u.hdr.post_literal_equal = 0; + ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; + wsi->u.hdr.ups = URIPS_IDLE; + goto swallow; + } + + return LPUR_CONTINUE; + +swallow: + return LPUR_SWALLOW; + +forbid: + return LPUR_FORBID; + +excessive: + return LPUR_EXCESSIVE; +} + +static const unsigned char methods[] = { + WSI_TOKEN_GET_URI, + WSI_TOKEN_POST_URI, + WSI_TOKEN_OPTIONS_URI, + WSI_TOKEN_PUT_URI, + WSI_TOKEN_PATCH_URI, + WSI_TOKEN_DELETE_URI, + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, +}; + int LWS_WARN_UNUSED_RESULT lws_parse(struct lws *wsi, unsigned char c) { - static const unsigned char methods[] = { - WSI_TOKEN_GET_URI, - WSI_TOKEN_POST_URI, - WSI_TOKEN_OPTIONS_URI, - WSI_TOKEN_PUT_URI, - WSI_TOKEN_PATCH_URI, - WSI_TOKEN_DELETE_URI, - WSI_TOKEN_CONNECT, - }; struct allocated_headers *ah = wsi->u.hdr.ah; struct lws_context *context = wsi->context; - unsigned int n, m, enc = 0; + unsigned int n, m; + int r; assert(wsi->u.hdr.ah); @@ -753,172 +948,19 @@ lws_parse(struct lws *wsi, unsigned char c) goto start_fragment; } - /* - * PRIORITY 1 - * special URI processing... convert %xx - */ - - switch (wsi->u.hdr.ues) { - case URIES_IDLE: - if (c == '%') { - wsi->u.hdr.ues = URIES_SEEN_PERCENT; - goto swallow; - } + r = lws_parse_urldecode(wsi, &c); + switch (r) { + case LPUR_CONTINUE: break; - case URIES_SEEN_PERCENT: - if (char_to_hex(c) < 0) - /* illegal post-% char */ - goto forbid; - - wsi->u.hdr.esc_stash = c; - wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1; + case LPUR_SWALLOW: goto swallow; - - case URIES_SEEN_PERCENT_H1: - if (char_to_hex(c) < 0) - /* illegal post-% char */ - goto forbid; - - c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) | - char_to_hex(c); - enc = 1; - wsi->u.hdr.ues = URIES_IDLE; - break; + case LPUR_FORBID: + goto forbid; + case LPUR_EXCESSIVE: + goto excessive; + default: + return -1; } - - /* - * PRIORITY 2 - * special URI processing... - * convert /.. or /... or /../ etc to / - * convert /./ to / - * convert // or /// etc to / - * leave /.dir or whatever alone - */ - - switch (wsi->u.hdr.ups) { - case URIPS_IDLE: - if (!c) - return -1; - /* genuine delimiter */ - if ((c == '&' || c == ';') && !enc) { - if (issue_char(wsi, c) < 0) - return -1; - /* swallow the terminator */ - ah->frags[ah->nfrag].len--; - /* link to next fragment */ - ah->frags[ah->nfrag].nfrag = ah->nfrag + 1; - ah->nfrag++; - if (ah->nfrag >= ARRAY_SIZE(ah->frags)) - goto excessive; - /* start next fragment after the & */ - wsi->u.hdr.post_literal_equal = 0; - ah->frags[ah->nfrag].offset = ah->pos; - ah->frags[ah->nfrag].len = 0; - ah->frags[ah->nfrag].nfrag = 0; - goto swallow; - } - /* uriencoded = in the name part, disallow */ - if (c == '=' && enc && - ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] && - !wsi->u.hdr.post_literal_equal) - c = '_'; - - /* after the real =, we don't care how many = */ - if (c == '=' && !enc) - wsi->u.hdr.post_literal_equal = 1; - - /* + to space */ - if (c == '+' && !enc) - c = ' '; - /* issue the first / always */ - if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) - wsi->u.hdr.ups = URIPS_SEEN_SLASH; - break; - case URIPS_SEEN_SLASH: - /* swallow subsequent slashes */ - if (c == '/') - goto swallow; - /* track and swallow the first . after / */ - if (c == '.') { - wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT; - goto swallow; - } - wsi->u.hdr.ups = URIPS_IDLE; - break; - case URIPS_SEEN_SLASH_DOT: - /* swallow second . */ - if (c == '.') { - wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT; - goto swallow; - } - /* change /./ to / */ - if (c == '/') { - wsi->u.hdr.ups = URIPS_SEEN_SLASH; - goto swallow; - } - /* it was like /.dir ... regurgitate the . */ - wsi->u.hdr.ups = URIPS_IDLE; - if (issue_char(wsi, '.') < 0) - return -1; - break; - - case URIPS_SEEN_SLASH_DOT_DOT: - - /* /../ or /..[End of URI] --> backup to last / */ - if (c == '/' || c == '?') { - /* - * back up one dir level if possible - * safe against header fragmentation because - * the method URI can only be in 1 fragment - */ - if (ah->frags[ah->nfrag].len > 2) { - ah->pos--; - ah->frags[ah->nfrag].len--; - do { - ah->pos--; - ah->frags[ah->nfrag].len--; - } while (ah->frags[ah->nfrag].len > 1 && - ah->data[ah->pos] != '/'); - } - wsi->u.hdr.ups = URIPS_SEEN_SLASH; - if (ah->frags[ah->nfrag].len > 1) - break; - goto swallow; - } - - /* /..[^/] ... regurgitate and allow */ - - if (issue_char(wsi, '.') < 0) - return -1; - if (issue_char(wsi, '.') < 0) - return -1; - wsi->u.hdr.ups = URIPS_IDLE; - break; - } - - if (c == '?' && !enc && - !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI arguments */ - if (wsi->u.hdr.ues != URIES_IDLE) - goto forbid; - - /* seal off uri header */ - if (issue_char(wsi, '\0') < 0) - return -1; - - /* move to using WSI_TOKEN_HTTP_URI_ARGS */ - ah->nfrag++; - if (ah->nfrag >= ARRAY_SIZE(ah->frags)) - goto excessive; - ah->frags[ah->nfrag].offset = ah->pos; - ah->frags[ah->nfrag].len = 0; - ah->frags[ah->nfrag].nfrag = 0; - - wsi->u.hdr.post_literal_equal = 0; - ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; - wsi->u.hdr.ups = URIPS_IDLE; - goto swallow; - } - check_eol: /* bail at EOL */ if (wsi->u.hdr.parser_state != WSI_TOKEN_CHALLENGE && @@ -946,7 +988,7 @@ swallow: /* collecting and checking a name part */ case WSI_TOKEN_NAME_PART: - lwsl_parser("WSI_TOKEN_NAME_PART '%c' (mode=%d)\n", c, wsi->mode); + lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X (mode=%d) wsi->u.hdr.lextable_pos=%d\n", c, c, wsi->mode, wsi->u.hdr.lextable_pos); wsi->u.hdr.lextable_pos = lextable_decode(wsi->u.hdr.lextable_pos, c); @@ -954,7 +996,7 @@ swallow: * Server needs to look out for unknown methods... */ if (wsi->u.hdr.lextable_pos < 0 && - wsi->mode == LWSCM_HTTP_SERVING) { + (wsi->mode == LWSCM_HTTP_SERVING)) { /* this is not a header we know about */ for (m = 0; m < ARRAY_SIZE(methods); m++) if (ah->frag_index[methods[m]]) { @@ -1041,10 +1083,12 @@ excessive: ah->frags[ah->nfrag].offset = ah->pos; ah->frags[ah->nfrag].len = 0; ah->frags[ah->nfrag].nfrag = 0; + ah->frags[ah->nfrag].flags = 2; n = ah->frag_index[wsi->u.hdr.parser_state]; if (!n) { /* first fragment */ ah->frag_index[wsi->u.hdr.parser_state] = ah->nfrag; + ah->hdr_token_idx = wsi->u.hdr.parser_state; break; } /* continuation */ @@ -1101,6 +1145,7 @@ set_parsing_complete: forbid: lwsl_notice(" forbidding on uri sanitation\n"); lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); + return -1; } diff --git a/lib/pollfd.c b/lib/pollfd.c index 5f9d94d55..4d6704d41 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -385,35 +385,38 @@ lws_callback_on_writable(struct lws *wsi) if (wsi->mode != LWSCM_HTTP2_SERVING) goto network_sock; - if (wsi->u.http2.requested_POLLOUT) { + if (wsi->u.h2.requested_POLLOUT) { lwsl_info("already pending writable\n"); return 1; } - if (wsi->u.http2.tx_credit <= 0) { + /* is this for DATA or for control messages? */ + if (wsi->upgraded_to_http2 && !wsi->u.h2.h2n->pps && + !lws_h2_tx_cr_get(wsi)) { /* - * other side is not able to cope with us sending - * anything so no matter if we have POLLOUT on our side. + * other side is not able to cope with us sending DATA + * anything so no matter if we have POLLOUT on our side if it's + * DATA we want to send. * * Delay waiting for our POLLOUT until peer indicates he has * space for more using tx window command in http2 layer */ - lwsl_info("%s: %p: waiting_tx_credit (%d)\n", __func__, wsi, - wsi->u.http2.tx_credit); - wsi->u.http2.waiting_tx_credit = 1; + lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi, wsi->u.h2.tx_cr); + wsi->u.h2.skint = 1; return 0; } - network_wsi = lws_http2_get_network_wsi(wsi); - already = network_wsi->u.http2.requested_POLLOUT; + wsi->u.h2.skint = 0; + network_wsi = lws_get_network_wsi(wsi); + already = network_wsi->u.h2.requested_POLLOUT; /* mark everybody above him as requesting pollout */ wsi2 = wsi; while (wsi2) { - wsi2->u.http2.requested_POLLOUT = 1; + wsi2->u.h2.requested_POLLOUT = 1; lwsl_info("mark %p pending writable\n", wsi2); - wsi2 = wsi2->u.http2.parent_wsi; + wsi2 = wsi2->u.h2.parent_wsi; } /* for network action, act only on the network wsi */ @@ -428,7 +431,7 @@ network_sock: return 1; if (wsi->position_in_fds_table < 0) { - lwsl_err("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd); + lwsl_debug("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd); return -1; } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 7d30078f4..16ddcba19 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -525,13 +525,6 @@ enum http_connection_type { HTTP_CONNECTION_KEEP_ALIVE }; -enum lws_pending_protocol_send { - LWS_PPS_NONE, - LWS_PPS_HTTP2_MY_SETTINGS, - LWS_PPS_HTTP2_ACK_SETTINGS, - LWS_PPS_HTTP2_PONG, -}; - enum lws_rx_parse_state { LWS_RXPS_NEW, @@ -776,6 +769,7 @@ struct allocated_headers { int16_t rxlen; uint32_t pos; uint32_t http_response; + int hdr_token_idx; #ifndef LWS_NO_CLIENT char initial_handshake_hash_base64[30]; @@ -856,12 +850,29 @@ struct lws_context_per_thread { struct lws_conn_stats { unsigned long long rx, tx; - unsigned long conn, trans, ws_upg, http2_upg, rejected; + unsigned long h1_conn, h1_trans, h2_trans, ws_upg, h2_alpn, h2_subs, + h2_upg, rejected; }; void lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs); + +enum lws_h2_settings { + H2SET_HEADER_TABLE_SIZE = 1, + H2SET_ENABLE_PUSH, + H2SET_MAX_CONCURRENT_STREAMS, + H2SET_INITIAL_WINDOW_SIZE, + H2SET_MAX_FRAME_SIZE, + H2SET_MAX_HEADER_LIST_SIZE, + + H2SET_COUNT /* always last */ +}; + +struct http2_settings { + uint32_t s[H2SET_COUNT]; +}; + /* * virtual host -related context information * vhostwide SSL context @@ -884,6 +895,9 @@ struct lws_vhost { #if !defined(LWS_WITH_ESP8266) char http_proxy_address[128]; char proxy_basic_auth_token[128]; +#if defined(LWS_WITH_HTTP2) + struct http2_settings set; +#endif #if defined(LWS_WITH_SOCKS5) char socks_proxy_address[128]; char socks_user[96]; @@ -999,6 +1013,9 @@ struct lws_context { time_t time_up; const struct lws_plat_file_ops *fops; struct lws_plat_file_ops fops_platform; +#if defined(LWS_WITH_HTTP2) + struct http2_settings set; +#endif #if defined(LWS_WITH_ZIP_FOPS) struct lws_plat_file_ops fops_zip; #endif @@ -1373,63 +1390,124 @@ struct _lws_http_mode_related { enum http_version request_version; enum http_connection_type connection_type; - lws_filepos_t content_length; - lws_filepos_t content_remain; + lws_filepos_t tx_content_length; + lws_filepos_t tx_content_remain; + lws_filepos_t rx_content_length; + lws_filepos_t rx_content_remain; }; +#define LWS_H2_FRAME_HEADER_LENGTH 9 + #ifdef LWS_WITH_HTTP2 -enum lws_http2_settings { - LWS_HTTP2_SETTINGS__HEADER_TABLE_SIZE = 1, - LWS_HTTP2_SETTINGS__ENABLE_PUSH, - LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS, - LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE, - LWS_HTTP2_SETTINGS__MAX_FRAME_SIZE, - LWS_HTTP2_SETTINGS__MAX_HEADER_LIST_SIZE, +enum lws_h2_wellknown_frame_types { + LWS_H2_FRAME_TYPE_DATA, + LWS_H2_FRAME_TYPE_HEADERS, + LWS_H2_FRAME_TYPE_PRIORITY, + LWS_H2_FRAME_TYPE_RST_STREAM, + LWS_H2_FRAME_TYPE_SETTINGS, + LWS_H2_FRAME_TYPE_PUSH_PROMISE, + LWS_H2_FRAME_TYPE_PING, + LWS_H2_FRAME_TYPE_GOAWAY, + LWS_H2_FRAME_TYPE_WINDOW_UPDATE, + LWS_H2_FRAME_TYPE_CONTINUATION, - LWS_HTTP2_SETTINGS__COUNT /* always last */ + LWS_H2_FRAME_TYPE_COUNT /* always last */ }; -enum lws_http2_wellknown_frame_types { - LWS_HTTP2_FRAME_TYPE_DATA, - LWS_HTTP2_FRAME_TYPE_HEADERS, - LWS_HTTP2_FRAME_TYPE_PRIORITY, - LWS_HTTP2_FRAME_TYPE_RST_STREAM, - LWS_HTTP2_FRAME_TYPE_SETTINGS, - LWS_HTTP2_FRAME_TYPE_PUSH_PROMISE, - LWS_HTTP2_FRAME_TYPE_PING, - LWS_HTTP2_FRAME_TYPE_GOAWAY, - LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE, - LWS_HTTP2_FRAME_TYPE_CONTINUATION, +enum lws_h2_flags { + LWS_H2_FLAG_END_STREAM = 1, + LWS_H2_FLAG_END_HEADERS = 4, + LWS_H2_FLAG_PADDED = 8, + LWS_H2_FLAG_PRIORITY = 0x20, - LWS_HTTP2_FRAME_TYPE_COUNT /* always last */ + LWS_H2_FLAG_SETTINGS_ACK = 1, }; -enum lws_http2_flags { - LWS_HTTP2_FLAG_END_STREAM = 1, - LWS_HTTP2_FLAG_END_HEADERS = 4, - LWS_HTTP2_FLAG_PADDED = 8, - LWS_HTTP2_FLAG_PRIORITY = 0x20, - - LWS_HTTP2_FLAG_SETTINGS_ACK = 1, +enum lws_h2_errors { + H2_ERR_NO_ERROR, /* Graceful shutdown */ + H2_ERR_PROTOCOL_ERROR, /* Protocol error detected */ + H2_ERR_INTERNAL_ERROR, /* Implementation fault */ + H2_ERR_FLOW_CONTROL_ERROR, /* Flow-control limits exceeded */ + H2_ERR_SETTINGS_TIMEOUT, /* Settings not acknowledged */ + H2_ERR_STREAM_CLOSED, /* Frame received for closed stream */ + H2_ERR_FRAME_SIZE_ERROR, /* Frame size incorrect */ + H2_ERR_REFUSED_STREAM, /* Stream not processed */ + H2_ERR_CANCEL, /* Stream cancelled */ + H2_ERR_COMPRESSION_ERROR, /* Compression state not updated */ + H2_ERR_CONNECT_ERROR, /* TCP connection error for CONNECT method */ + H2_ERR_ENHANCE_YOUR_CALM, /* Processing capacity exceeded */ + H2_ERR_INADEQUATE_SECURITY, /* Negotiated TLS parameters not acceptable */ + H2_ERR_HTTP_1_1_REQUIRED, /* Use HTTP/1.1 for the request */ }; -#define LWS_HTTP2_STREAM_ID_MASTER 0 -#define LWS_HTTP2_FRAME_HEADER_LENGTH 9 -#define LWS_HTTP2_SETTINGS_LENGTH 6 - -struct http2_settings { - unsigned int setting[LWS_HTTP2_SETTINGS__COUNT]; +enum lws_h2_states { + LWS_H2_STATE_IDLE, + /* + * Send PUSH_PROMISE -> LWS_H2_STATE_RESERVED_LOCAL + * Recv PUSH_PROMISE -> LWS_H2_STATE_RESERVED_REMOTE + * Send HEADERS -> LWS_H2_STATE_OPEN + * Recv HEADERS -> LWS_H2_STATE_OPEN + * + * - Only PUSH_PROMISE + HEADERS valid to send + * - Only HEADERS or PRIORITY valid to receive + */ + LWS_H2_STATE_RESERVED_LOCAL, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Send HEADERS -> LWS_H2_STATE_HALF_CLOSED_REMOTE + * + * - Only HEADERS, RST_STREAM, or PRIORITY valid to send + * - Only RST_STREAM, PRIORITY, or WINDOW_UPDATE valid to receive + */ + LWS_H2_STATE_RESERVED_REMOTE, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv HEADERS -> LWS_H2_STATE_HALF_CLOSED_LOCAL + * + * - Only RST_STREAM, WINDOW_UPDATE, or PRIORITY valid to send + * - Only HEADERS, RST_STREAM, or PRIORITY valid to receive + */ + LWS_H2_STATE_OPEN, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Send END_STREAM flag -> LWS_H2_STATE_HALF_CLOSED_LOCAL + * Recv END_STREAM flag -> LWS_H2_STATE_HALF_CLOSED_REMOTE + */ + LWS_H2_STATE_HALF_CLOSED_REMOTE, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Send END_STREAM flag -> LWS_H2_STATE_CLOSED + * + * - Any frame valid to send + * - Only WINDOW_UPDATE, PRIORITY, or RST_STREAM valid to receive + */ + LWS_H2_STATE_HALF_CLOSED_LOCAL, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv END_STREAM flag -> LWS_H2_STATE_CLOSED + * + * - Only WINDOW_UPDATE, PRIORITY, and RST_STREAM valid to send + * - Any frame valid to receive + */ + LWS_H2_STATE_CLOSED, + /* + * - Only PRIORITY, WINDOW_UPDATE (IGNORE) and RST_STREAM (IGNORE) + * may be received + * + * - Only PRIORITY valid to send + */ }; +#define LWS_H2_STREAM_ID_MASTER 0 +#define LWS_H2_SETTINGS_LEN 6 + enum http2_hpack_state { - - /* optional before first header block */ - HPKS_OPT_PADDING, - HKPS_OPT_E_DEPENDENCY, - HKPS_OPT_WEIGHT, - - /* header block */ HPKS_TYPE, HPKS_IDX_EXT, @@ -1438,36 +1516,160 @@ enum http2_hpack_state { HPKS_HLEN_EXT, HPKS_DATA, - - /* optional after last header block */ - HKPS_OPT_DISCARD_PADDING, }; +/* + * lws general parsimonious header strategy is only store values from known + * headers, and refer to them by index. + * + * That means if we can't map the peer header name to one that lws knows, we + * will drop the content but track the indexing with associated_lws_hdr_idx = + * LWS_HPACK_IGNORE_ENTRY. + */ + enum http2_hpack_type { - HPKT_INDEXED_HDR_7, - HPKT_INDEXED_HDR_6_VALUE_INCR, - HPKT_LITERAL_HDR_VALUE_INCR, - HPKT_INDEXED_HDR_4_VALUE, - HPKT_LITERAL_HDR_VALUE, + HPKT_INDEXED_HDR_7, /* 1xxxxxxx: just "header field" */ + HPKT_INDEXED_HDR_6_VALUE_INCR, /* 01xxxxxx: NEW indexed hdr with value */ + HPKT_LITERAL_HDR_VALUE_INCR, /* 01000000: NEW literal hdr with value */ + HPKT_INDEXED_HDR_4_VALUE, /* 0000xxxx: indexed hdr with value */ + HPKT_INDEXED_HDR_4_VALUE_NEVER, /* 0001xxxx: indexed hdr with value NEVER NEW */ + HPKT_LITERAL_HDR_VALUE, /* 00000000: literal hdr with value */ + HPKT_LITERAL_HDR_VALUE_NEVER, /* 00010000: literal hdr with value NEVER NEW */ HPKT_SIZE_5 }; +#define LWS_HPACK_IGNORE_ENTRY 0xffff + + struct hpack_dt_entry { - int token; /* additions that don't map to a token are ignored */ - int arg_offset; - int arg_len; + char *value; /* malloc'd */ + uint16_t value_len; + uint16_t hdr_len; /* virtual, for accounting */ + uint16_t lws_hdr_idx; /* LWS_HPACK_IGNORE_ENTRY = IGNORE */ }; struct hpack_dynamic_table { - struct hpack_dt_entry *entries; - char *args; - int pos; - int next; - int num_entries; - int args_length; + struct hpack_dt_entry *entries; /* malloc'd */ + uint32_t virtual_payload_usage; + uint32_t virtual_payload_max; + uint16_t pos; + uint16_t used_entries; + uint16_t num_entries; }; -struct _lws_http2_related { +enum lws_h2_protocol_send_type { + LWS_PPS_NONE, + LWS_H2_PPS_MY_SETTINGS, + LWS_H2_PPS_ACK_SETTINGS, + LWS_H2_PPS_PONG, + LWS_H2_PPS_GOAWAY, + LWS_H2_PPS_RST_STREAM, + LWS_H2_PPS_UPDATE_WINDOW, +}; + +struct lws_h2_protocol_send { + struct lws_h2_protocol_send *next; /* linked list */ + enum lws_h2_protocol_send_type type; + + union uu { + struct { + char str[32]; + uint32_t highest_sid; + uint32_t err; + } ga; + struct { + uint32_t sid; + uint32_t err; + } rs; + struct { + uint8_t ping_payload[8]; + } ping; + struct { + uint32_t sid; + uint32_t credit; + } update_window; + } u; +}; + +struct lws_h2_ghost_sid { + struct lws_h2_ghost_sid *next; + uint32_t sid; +}; + +#define LWS_H2_RX_SCRATCH_SIZE 512 + +/* + * http/2 connection info that is only used by the root connection that has + * the network connection. + * + * h2 tends to spawn many child connections from one network connection, so + * it's necessary to make members only needed by the network connection + * distinct and only malloc'd on network connections. + * + * There's only one HPACK parser per network connection. + * + * But there is an ah per logical child connection... the network connection + * fills it but it belongs to the logical child. + */ +struct lws_h2_netconn { + struct http2_settings set; + struct hpack_dynamic_table hpack_dyn_table; + uint8_t ping_payload[8]; + uint8_t one_setting[LWS_H2_SETTINGS_LEN]; + char goaway_str[32]; /* for rx */ + struct lws *swsi; + struct lws_h2_protocol_send *pps; /* linked list */ + char *rx_scratch; + + enum http2_hpack_state hpack; + enum http2_hpack_type hpack_type; + + unsigned int huff:1; + unsigned int value:1; + unsigned int unknown_header:1; + unsigned int cont_exp:1; + unsigned int cont_exp_headers:1; + unsigned int we_told_goaway:1; + unsigned int pad_length:1; + unsigned int collected_priority:1; + unsigned int is_first_header_char:1; + unsigned int seen_nonpseudoheader:1; + unsigned int zero_huff_padding:1; + unsigned int last_action_dyntable_resize:1; + + uint32_t hdr_idx; + uint32_t hpack_len; + uint32_t hpack_e_dep; + uint32_t count; + uint32_t preamble; + uint32_t length; + uint32_t sid; + uint32_t inside; + uint32_t highest_sid; + uint32_t highest_sid_opened; + uint32_t cont_exp_sid; + uint32_t dep; + uint32_t goaway_last_sid; + uint32_t goaway_err; + uint32_t hpack_hdr_len; + + uint32_t rx_scratch_pos; + uint32_t rx_scratch_len; + + uint16_t hpack_pos; + + uint8_t frame_state; + uint8_t type; + uint8_t flags; + uint8_t padding; + uint8_t weight_temp; + uint8_t huff_pad; + char first_hdr_char; + uint8_t hpack_m; + uint8_t ext_count; +}; + +struct _lws_h2_related { /* * having this first lets us also re-use all HTTP union code * and in turn, http_mode_related has allocated headers in right @@ -1475,52 +1677,36 @@ struct _lws_http2_related { */ struct _lws_http_mode_related http; /* MUST BE FIRST IN STRUCT */ - struct http2_settings my_settings; - struct http2_settings peer_settings; - + struct lws_h2_netconn *h2n; /* malloc'd for root net conn */ struct lws *parent_wsi; - struct lws *next_child_wsi; + struct lws *child_list; + struct lws *sibling_list; - struct hpack_dynamic_table *hpack_dyn_table; - struct lws *stream_wsi; - unsigned char ping_payload[8]; - unsigned char one_setting[LWS_HTTP2_SETTINGS_LENGTH]; + char *pending_status_body; - unsigned int count; - unsigned int length; - unsigned int stream_id; - enum http2_hpack_state hpack; - enum http2_hpack_type hpack_type; - unsigned int header_index; - unsigned int hpack_len; - unsigned int hpack_e_dep; - int tx_credit; - unsigned int my_stream_id; + int tx_cr; + int peer_tx_cr_est; + unsigned int my_sid; unsigned int child_count; int my_priority; + uint32_t dependent_on; unsigned int END_STREAM:1; unsigned int END_HEADERS:1; unsigned int send_END_STREAM:1; unsigned int GOING_AWAY; unsigned int requested_POLLOUT:1; - unsigned int waiting_tx_credit:1; - unsigned int huff:1; - unsigned int value:1; + unsigned int skint:1; - unsigned short round_robin_POLLOUT; - unsigned short count_POLLOUT_children; - unsigned short hpack_pos; + uint16_t round_robin_POLLOUT; + uint16_t count_POLLOUT_children; + uint8_t h2_state; /* the RFC7540 state of the connection */ + uint8_t weight; - unsigned char type; - unsigned char flags; - unsigned char frame_state; - unsigned char padding; - unsigned char hpack_m; - unsigned char initialized; + uint8_t initialized; }; -#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->u.http2.parent_wsi) +#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->u.h2.parent_wsi) #endif @@ -1589,6 +1775,7 @@ struct lws_cgi { struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */ struct lws *wsi; /* owner */ unsigned char *headers_buf; + unsigned char *headers_start; unsigned char *headers_pos; unsigned char *headers_dumped; unsigned char *headers_end; @@ -1620,6 +1807,13 @@ enum lws_chunk_parser { }; #endif +enum lws_parse_urldecode_results { + LPUR_CONTINUE, + LPUR_SWALLOW, + LPUR_FORBID, + LPUR_EXCESSIVE, +}; + struct lws_rewrite; #ifdef LWS_WITH_ACCESS_LOG @@ -1640,7 +1834,7 @@ struct lws { union u { struct _lws_http_mode_related http; #ifdef LWS_WITH_HTTP2 - struct _lws_http2_related http2; + struct _lws_h2_related h2; #endif struct _lws_header_related hdr; struct _lws_websocket_related ws; @@ -1715,8 +1909,8 @@ struct lws { #endif /* ints */ int position_in_fds_table; - int rxflow_len; - int rxflow_pos; + uint32_t rxflow_len; + uint32_t rxflow_pos; unsigned int trunc_alloc_len; /* size of malloc */ unsigned int trunc_offset; /* where we are in terms of spilling */ unsigned int trunc_len; /* how much is buffered */ @@ -1727,6 +1921,7 @@ struct lws { unsigned int hdr_parsing_completed:1; unsigned int http2_substream:1; + unsigned int upgraded_to_http2:1; unsigned int listener:1; unsigned int user_space_externally_allocated:1; unsigned int socket_is_permanently_unusable:1; @@ -1745,6 +1940,8 @@ struct lws { unsigned int parent_carries_io:1; unsigned int parent_pending_cb_on_writable:1; unsigned int cgi_stdout_zero_length:1; + unsigned int seen_zero_length_recv:1; + unsigned int rxflow_will_be_applied:1; #if defined(LWS_WITH_ESP8266) unsigned int pending_send_completion:3; @@ -1787,17 +1984,17 @@ struct lws { #ifndef LWS_NO_EXTENSIONS unsigned char count_act_ext; #endif - unsigned char ietf_spec_revision; + uint8_t ietf_spec_revision; char mode; /* enum connection_mode */ char state; /* enum lws_connection_states */ char state_pre_close; char lws_rx_parse_state; /* enum lws_rx_parse_state */ char rx_frame_type; /* enum lws_write_protocol */ char pending_timeout; /* enum pending_timeout */ - char pps; /* enum lws_pending_protocol_send */ char tsi; /* thread service index we belong to */ char protocol_interpret_idx; char redirects; + uint8_t rxflow_bitmap; #ifdef LWS_WITH_CGI char cgi_channel; /* which of stdin/out/err */ char hdr_state; @@ -1810,6 +2007,8 @@ struct lws { #endif }; +#define lws_is_flowcontrolled(w) (!!(wsi->rxflow_bitmap)) + LWS_EXTERN int log_level; LWS_EXTERN int @@ -1849,15 +2048,15 @@ lws_latency(struct lws_context *context, struct lws *wsi, const char *action, int ret, int completion); #endif -LWS_EXTERN void -lws_set_protocol_write_pending(struct lws *wsi, - enum lws_pending_protocol_send pend); LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_client_rx_sm(struct lws *wsi, unsigned char c); LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_parse(struct lws *wsi, unsigned char c); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse_urldecode(struct lws *wsi, uint8_t *_c); + LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_http_action(struct lws *wsi); @@ -1963,21 +2162,19 @@ user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); #ifdef LWS_WITH_HTTP2 -LWS_EXTERN struct lws *lws_http2_get_network_wsi(struct lws *wsi); -struct lws * lws_http2_get_nth_child(struct lws *wsi, int n); +struct lws * lws_h2_get_nth_child(struct lws *wsi, int n); +LWS_EXTERN void lws_h2_init(struct lws *wsi); LWS_EXTERN int -lws_http2_interpret_settings_payload(struct http2_settings *settings, +lws_h2_settings(struct lws *nwsi, struct http2_settings *settings, unsigned char *buf, int len); -LWS_EXTERN void lws_http2_init(struct http2_settings *settings); LWS_EXTERN int -lws_http2_parser(struct lws *wsi, unsigned char c); -LWS_EXTERN int lws_http2_do_pps_send(struct lws_context *context, - struct lws *wsi); -LWS_EXTERN int lws_http2_frame_write(struct lws *wsi, int type, int flags, +lws_h2_parser(struct lws *wsi, unsigned char c); +LWS_EXTERN int lws_h2_do_pps_send(struct lws *wsi); +LWS_EXTERN int lws_h2_frame_write(struct lws *wsi, int type, int flags, unsigned int sid, unsigned int len, unsigned char *buf); LWS_EXTERN struct lws * -lws_http2_wsi_from_id(struct lws *wsi, unsigned int sid); +lws_h2_wsi_from_id(struct lws *wsi, unsigned int sid); LWS_EXTERN int lws_hpack_interpret(struct lws *wsi, unsigned char c); LWS_EXTERN int @@ -1994,10 +2191,26 @@ LWS_EXTERN int lws_add_http2_header_status(struct lws *wsi, unsigned int code, unsigned char **p, unsigned char *end); -LWS_EXTERN -void lws_http2_configure_if_upgraded(struct lws *wsi); +LWS_EXTERN int +lws_h2_configure_if_upgraded(struct lws *wsi); +LWS_EXTERN void +lws_hpack_destroy_dynamic_header(struct lws *wsi); +LWS_EXTERN int +lws_hpack_dynamic_size(struct lws *wsi, int size); +LWS_EXTERN int +lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason); +LWS_EXTERN int +lws_h2_tx_cr_get(struct lws *wsi); +LWS_EXTERN void +lws_h2_tx_cr_consume(struct lws *wsi, int consumed); +LWS_EXTERN int +lws_hdr_extant(struct lws *wsi, enum lws_token_indexes h); +LWS_EXTERN void +lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pss); + +LWS_EXTERN const struct http2_settings lws_h2_defaults; #else -#define lws_http2_configure_if_upgraded(x) +#define lws_h2_configure_if_upgraded(x) #endif LWS_EXTERN int diff --git a/lib/server.c b/lib/server.c index b915759c4..3ddbb67f8 100644 --- a/lib/server.c +++ b/lib/server.c @@ -22,14 +22,12 @@ #include "private-libwebsockets.h" -#if defined(_DEBUG) || defined(LWS_WITH_ACCESS_LOG) - static const char * const method_names[] = { - "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", +static const char * const method_names[] = { + "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", #ifdef LWS_WITH_HTTP2 - ":path", + ":path", #endif }; -#endif #if defined (LWS_WITH_ESP8266) #undef memcpy @@ -51,10 +49,12 @@ lws_context_init_server(struct lws_context_creation_info *info, struct lws *wsi; int m = 0; + (void)method_names; (void)opt; /* set up our external listening socket we serve on */ - if (info->port == CONTEXT_PORT_NO_LISTEN || info->port == CONTEXT_PORT_NO_LISTEN_SERVER) + if (info->port == CONTEXT_PORT_NO_LISTEN || + info->port == CONTEXT_PORT_NO_LISTEN_SERVER) return 0; vh = vhost->context->vhost_list; @@ -513,7 +513,8 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, return -1; n = lws_write(wsi, start, p - start, - LWS_WRITE_HTTP_HEADERS); + LWS_WRITE_HTTP_HEADERS | + LWS_WRITE_H2_STREAM_END); if (n != (p - start)) { lwsl_err("_write returned %d from %ld\n", n, (long)(p - start)); @@ -603,6 +604,8 @@ lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) if (hm->origin_protocol == LWSMPRO_CALLBACK || ((hm->origin_protocol == LWSMPRO_CGI || lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || + (wsi->http2_substream && + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH)) || hm->protocol) && hm->mountpoint_len > best)) { best = hm->mountpoint_len; @@ -684,7 +687,7 @@ lws_unauthorised_basic_auth(struct lws *wsi) if (lws_finalize_http_header(wsi, &p, end)) return -1; - n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); if (n < 0) return -1; @@ -732,12 +735,12 @@ int lws_clean_url(char *p) * */ #ifdef LWS_WITH_ACCESS_LOG +static const char * const hver[] = { + "http/1.0", "http/1.1", "http/2" +}; static void lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth) { - static const char * const hver[] = { - "http/1.0", "http/1.1", "http/2" - }; #ifdef LWS_WITH_IPV6 char ads[INET6_ADDRSTRLEN]; #else @@ -765,7 +768,12 @@ lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth) if (!pa) pa = "(unknown)"; - me = method_names[meth]; + if (wsi->http2_substream) + me = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + else + me = method_names[meth]; + if (!me) + me = "(null)"; lws_snprintf(wsi->access_log.header_log, l, "%s - - [%s] \"%s %s %s\"", @@ -817,6 +825,7 @@ static const unsigned char methods[] = { WSI_TOKEN_PATCH_URI, WSI_TOKEN_DELETE_URI, WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, #ifdef LWS_WITH_HTTP2 WSI_TOKEN_HTTP_COLON_PATH, #endif @@ -835,7 +844,9 @@ lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len) return -1; } - if (count != 1) { + if (count != 1 && + !(wsi->http2_substream && + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH))) { lwsl_warn("multiple methods?\n"); return -1; } @@ -870,7 +881,7 @@ lws_http_action(struct lws *wsi) }; meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); - if (meth < 0) + if (meth < 0 || meth >= ARRAY_SIZE(method_names)) goto bail_nuke_ah; /* we insist on absolute paths */ @@ -881,24 +892,24 @@ lws_http_action(struct lws *wsi) goto bail_nuke_ah; } - lwsl_info("Method: %s request for '%s'\n", method_names[meth], uri_ptr); + lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], meth, uri_ptr); if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; /* HTTP header had a content length? */ - wsi->u.http.content_length = 0; + wsi->u.http.rx_content_length = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI)) - wsi->u.http.content_length = 100 * 1024 * 1024; + wsi->u.http.rx_content_length = 100 * 1024 * 1024; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lws_hdr_copy(wsi, content_length_str, sizeof(content_length_str) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH); - wsi->u.http.content_length = atoll(content_length_str); + wsi->u.http.rx_content_length = atoll(content_length_str); } if (wsi->http2_substream) { @@ -1026,20 +1037,26 @@ lws_http_action(struct lws *wsi) lwsl_debug("Doing 301 '%s' org %s\n", s, hit->origin); - if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) - goto bail_nuke_ah; - /* > at start indicates deal with by redirect */ if (hit->origin_protocol == LWSMPRO_REDIR_HTTP || hit->origin_protocol == LWSMPRO_REDIR_HTTPS) n = lws_snprintf((char *)end, 256, "%s%s", oprot[hit->origin_protocol & 1], hit->origin); - else - n = lws_snprintf((char *)end, 256, - "%s%s%s/", oprot[!!lws_is_ssl(wsi)], - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), - uri_ptr); + else { + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)) + goto bail_nuke_ah; + n = lws_snprintf((char *)end, 256, + "%s%s%s/", oprot[!!lws_is_ssl(wsi)], + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY), + uri_ptr); + } else + n = lws_snprintf((char *)end, 256, + "%s%s%s/", oprot[!!lws_is_ssl(wsi)], + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), + uri_ptr); + } lws_clean_url((char *)end); n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, @@ -1300,10 +1317,15 @@ deal_body: * In any case, return 0 and let lws_read decide how to * proceed based on state */ - if (wsi->state != LWSS_HTTP_ISSUING_FILE) + if (wsi->state != LWSS_HTTP_ISSUING_FILE) { /* Prepare to read body if we have a content length: */ - if (wsi->u.http.content_length > 0) + lwsl_debug("wsi->u.http.rx_content_length %lld %d %d\n", (long long)wsi->u.http.rx_content_length, wsi->upgraded_to_http2, wsi->http2_substream); + if (wsi->u.http.rx_content_length > 0) { + lwsl_notice("%s: %p: LWSS_HTTP_BODY state set\n", __func__, wsi); wsi->state = LWSS_HTTP_BODY; + wsi->u.http.rx_content_remain = wsi->u.http.rx_content_length; + } + } return 0; @@ -1397,10 +1419,13 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) assert(0); } + lwsl_hexdump(*buf, len); + while (len--) { wsi->more_rx_waiting = !!len; if (wsi->mode != LWSCM_HTTP_SERVING && + wsi->mode != LWSCM_HTTP2_SERVING && wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED) { lwsl_err("%s: bad wsi mode %d\n", __func__, wsi->mode); goto bail_nuke_ah; @@ -1461,10 +1486,12 @@ raw_transition: } else lwsl_info("no host\n"); - wsi->vhost->conn_stats.trans++; - if (!wsi->conn_stat_done) { - wsi->vhost->conn_stats.conn++; - wsi->conn_stat_done = 1; + if (wsi->mode != LWSCM_HTTP2_SERVING) { + wsi->vhost->conn_stats.h1_trans++; + if (!wsi->conn_stat_done) { + wsi->vhost->conn_stats.h1_conn++; + wsi->conn_stat_done = 1; + } } /* check for unwelcome guests */ @@ -1533,7 +1560,7 @@ raw_transition: #ifdef LWS_WITH_HTTP2 if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "h2c")) { - wsi->vhost->conn_stats.http2_upg++; + wsi->vhost->conn_stats.h2_upg++; lwsl_info("Upgrade to h2c\n"); goto upgrade_h2c; } @@ -1588,18 +1615,24 @@ upgrade_h2c: /* http2 union member has http union struct at start */ wsi->u.http.ah = ah; - lws_http2_init(&wsi->u.http2.peer_settings); - lws_http2_init(&wsi->u.http2.my_settings); + if (!wsi->u.h2.h2n) { + wsi->u.h2.h2n = lws_zalloc(sizeof(*wsi->u.h2.h2n), "h2n"); + if (!wsi->u.h2.h2n) + return 1; + } + + lws_h2_init(wsi); /* HTTP2 union */ - lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings, + lws_h2_settings(wsi, &wsi->u.h2.h2n->set, (unsigned char *)protocol_list, n); - strcpy(protocol_list, - "HTTP/1.1 101 Switching Protocols\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Upgrade: h2c\x0d\x0a\x0d\x0a"); + lws_hpack_dynamic_size(wsi, wsi->u.h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); + + strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Upgrade: h2c\x0d\x0a\x0d\x0a"); n = lws_issue_raw(wsi, (unsigned char *)protocol_list, strlen(protocol_list)); if (n != strlen(protocol_list)) { @@ -2053,8 +2086,8 @@ lws_create_new_server_wsi(struct lws_vhost *vhost) } new_wsi->tsi = n; - lwsl_debug("Accepted wsi %p to context %p, tsi %d\n", new_wsi, - vhost->context, new_wsi->tsi); + lwsl_debug("new wsi %p joining vhost %s, tsi %d\n", new_wsi, + vhost->name, new_wsi->tsi); new_wsi->vhost = vhost; new_wsi->context = vhost->context; @@ -2073,7 +2106,7 @@ lws_create_new_server_wsi(struct lws_vhost *vhost) /* * these can only be set once the protocol is known - * we set an unestablished connection's protocol pointer + * we set an un-established connection's protocol pointer * to the start of the supported list, so it can look * for matching ones during the handshake */ @@ -2100,6 +2133,8 @@ lws_http_transaction_completed(struct lws *wsi) { int n = NO_PENDING_TIMEOUT; + lwsl_info("%s: wsi %p\n", __func__, wsi); + lws_access_log(wsi); if (!wsi->hdr_parsing_completed) { @@ -2109,6 +2144,12 @@ lws_http_transaction_completed(struct lws *wsi) lwsl_debug("%s: wsi %p\n", __func__, wsi); /* if we can't go back to accept new headers, drop the connection */ + if (wsi->http2_substream) + return 0; + + if (wsi->seen_zero_length_recv) + return 1; + if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { lwsl_info("%s: %p: close connection\n", __func__, wsi); return 1; @@ -2120,8 +2161,8 @@ lws_http_transaction_completed(struct lws *wsi) /* otherwise set ourselves up ready to go again */ wsi->state = LWSS_HTTP; wsi->mode = LWSCM_HTTP_SERVING; - wsi->u.http.content_length = 0; - wsi->u.http.content_remain = 0; + wsi->u.http.tx_content_length = 0; + wsi->u.http.tx_content_remain = 0; wsi->hdr_parsing_completed = 0; #ifdef LWS_WITH_ACCESS_LOG wsi->access_log.sent = 0; @@ -2144,7 +2185,7 @@ lws_http_transaction_completed(struct lws *wsi) * reset the existing header table and keep it. */ if (wsi->u.hdr.ah) { - lwsl_info("%s: wsi->more_rx_waiting=%d\n", __func__, + lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, wsi->more_rx_waiting); if (!wsi->more_rx_waiting) { @@ -2426,7 +2467,7 @@ adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) ah = wsi->u.hdr.ah; memcpy(ah->rx, readbuf, len); ah->rxpos = 0; - ah->rxlen = len; + ah->rxlen = (int16_t)len; lwsl_notice("%s: calling service on readbuf ah\n", __func__); pt = &wsi->context->pt[(int)wsi->tsi]; @@ -2539,6 +2580,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, #if !defined(LWS_WITH_ESP8266) if (wsi->favoured_pollin && (pollfd->revents & pollfd->events & LWS_POLLOUT)) { + lwsl_notice("favouring pollout\n"); wsi->favoured_pollin = 0; goto try_pollout; } @@ -2565,7 +2607,10 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, ah->rxpos = 0; switch (ah->rxlen) { case 0: - lwsl_info("%s: read 0 len\n", __func__); + lwsl_info("%s: read 0 len a\n", __func__); + wsi->seen_zero_length_recv = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + goto try_pollout; /* fallthru */ case LWS_SSL_CAPABLE_ERROR: goto fail; @@ -2592,25 +2637,31 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, /* just ignore incoming if waiting for close */ if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && wsi->state != LWSS_HTTP_ISSUING_FILE) { + /* + * otherwise give it to whoever wants it + * according to the connection state + */ + n = lws_read(wsi, ah->rx + ah->rxpos, ah->rxlen - ah->rxpos); if (n < 0) /* we closed wsi */ return 1; - if (wsi->u.hdr.ah) { - if ( wsi->u.hdr.ah->rxlen) - wsi->u.hdr.ah->rxpos += n; - lwsl_debug("%s: wsi %p: ah read rxpos %d, rxlen %d\n", - __func__, wsi, - wsi->u.hdr.ah->rxpos, - wsi->u.hdr.ah->rxlen); + if (!wsi->u.hdr.ah) + break; + if ( wsi->u.hdr.ah->rxlen) + wsi->u.hdr.ah->rxpos += n; + + lwsl_debug("%s: wsi %p: ah read rxpos %d, rxlen %d\n", + __func__, wsi, wsi->u.hdr.ah->rxpos, + wsi->u.hdr.ah->rxlen); + + if (lws_header_table_is_in_detachable_state(wsi) && + (wsi->mode != LWSCM_HTTP_SERVING && + wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED && + wsi->mode != LWSCM_HTTP2_SERVING)) + lws_header_table_detach(wsi, 1); - if (lws_header_table_is_in_detachable_state(wsi) && - (wsi->mode != LWSCM_HTTP_SERVING && - wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED && - wsi->mode != LWSCM_HTTP2_SERVING)) - lws_header_table_detach(wsi, 1); - } break; } @@ -2622,7 +2673,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, lwsl_debug("%s: wsi %p read %d\r\n", __func__, wsi, len); switch (len) { case 0: - lwsl_info("%s: read 0 len\n", __func__); + lwsl_info("%s: read 0 len b\n", __func__); /* fallthru */ case LWS_SSL_CAPABLE_ERROR: @@ -2661,6 +2712,13 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, wsi->favoured_pollin = 1; break; } + /* + * he may have used up the + * writability above, if we will defer POLLOUT + * processing in favour of POLLIN, note it + */ + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; try_pollout: @@ -3050,7 +3108,9 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, wsi->u.http.filepos = 0; wsi->state = LWSS_HTTP_ISSUING_FILE; - return lws_serve_http_file_fragment(wsi); + lws_callback_on_writable(wsi); + + return 0; } int @@ -3069,7 +3129,7 @@ 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_change_to & LWS_RXFLOW_ALLOW)) { + if (wsi->rxflow_bitmap) { lws_rxflow_cache(wsi, *buf, 0, len); lwsl_parser("%s: cached %ld\n", __func__, (long)len); return 1; @@ -3083,8 +3143,10 @@ lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len) } /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) + if (wsi->rxflow_buffer) { wsi->rxflow_pos++; + assert(wsi->rxflow_pos <= wsi->rxflow_len); + } /* consume payload bytes efficiently */ if ( diff --git a/lib/service.c b/lib/service.c index 23d51bbf7..49760f828 100644 --- a/lib/service.c +++ b/lib/service.c @@ -73,7 +73,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) int write_type = LWS_WRITE_PONG; struct lws_tokens eff_buf; #ifdef LWS_WITH_HTTP2 - struct lws *wsi2; + struct lws **wsi2, *wsi2a; #endif int ret, m, n; @@ -95,6 +95,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) * corrupted. */ if (wsi->trunc_len) { + //lwsl_notice("%s: completing partial\n", __func__); if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, wsi->trunc_len) < 0) { lwsl_info("%s signalling to close\n", __func__); @@ -115,18 +116,18 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) /* * Priority 2: protocol packets */ - if (wsi->pps) { - lwsl_info("servicing pps %d\n", wsi->pps); - switch (wsi->pps) { - case LWS_PPS_HTTP2_MY_SETTINGS: - case LWS_PPS_HTTP2_ACK_SETTINGS: - lws_http2_do_pps_send(lws_get_context(wsi), wsi); - break; - default: - break; + if (wsi->upgraded_to_http2 && wsi->u.h2.h2n->pps) { + lwsl_info("servicing pps\n"); + if (lws_h2_do_pps_send(wsi)) { + wsi->socket_is_permanently_unusable = 1; + goto bail_die; } - wsi->pps = LWS_PPS_NONE; - lws_rx_flow_control(wsi, 1); + if (wsi->u.h2.h2n->pps) + goto bail_ok; + + /* we can resume whatever we were doing */ + lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_ENABLE | + LWS_RXFLOW_REASON_H2_PPS_PENDING); goto bail_ok; /* leave POLLOUT active */ } @@ -385,33 +386,146 @@ user_service_go_again: goto notify; } - wsi->u.http2.requested_POLLOUT = 0; - if (!wsi->u.http2.initialized) { + wsi->u.h2.requested_POLLOUT = 0; + if (!wsi->u.h2.initialized) { lwsl_info("pollout on uninitialized http2 conn\n"); goto bail_ok; } - lwsl_info("%s: doing children\n", __func__); +// if (SSL_want_read(wsi->ssl) || SSL_want_write(wsi->ssl)) { +// lws_callback_on_writable(wsi); +// goto bail_ok; +// } + + lwsl_info("%s: %p: children waiting for POLLOUT service:\n", __func__, wsi); + wsi2a = wsi->u.h2.child_list; + while (wsi2a) { + if (wsi2a->u.h2.requested_POLLOUT) + lwsl_debug(" * %p\n", wsi2a); + else + lwsl_debug(" %p\n", wsi2a); + + wsi2a = wsi2a->u.h2.sibling_list; + } + + wsi2 = &wsi->u.h2.child_list; + if (!*wsi2) + goto bail_ok; - wsi2 = wsi; do { - wsi2 = wsi2->u.http2.next_child_wsi; - lwsl_info("%s: child %p\n", __func__, wsi2); - if (!wsi2) - continue; - if (!wsi2->u.http2.requested_POLLOUT) - continue; - wsi2->u.http2.requested_POLLOUT = 0; - if (lws_calllback_as_writeable(wsi2)) { - lwsl_debug("Closing POLLOUT child\n"); - lws_close_free_wsi(wsi2, LWS_CLOSE_STATUS_NOSTATUS); + struct lws *w, **wa; + + wa = &(*wsi2)->u.h2.sibling_list; + if (!(*wsi2)->u.h2.requested_POLLOUT) { + lwsl_debug(" child %p doesn't want POLLOUT\n", *wsi2); + goto next_child; } - wsi2 = wsi; - } while (wsi2 != NULL && !lws_send_pipe_choked(wsi)); - lwsl_info("%s: completed\n", __func__); + /* + * we're going to do writable callback for this child. + * move him to be the last child + */ + + lwsl_debug("servicing child %p\n", *wsi2); + + w = *wsi2; + while (w) { + if (!w->u.h2.sibling_list) { /* w is the current last */ + lwsl_debug("w=%p, *wsi2 = %p\n", w, *wsi2); + if (w == *wsi2) /* we are already last */ + break; + w->u.h2.sibling_list = *wsi2; /* last points to us as new last */ + *wsi2 = (*wsi2)->u.h2.sibling_list; /* guy pointing to us until now points to our old next */ + w->u.h2.sibling_list->u.h2.sibling_list = NULL; /* we point to nothing because we are last */ + w = w->u.h2.sibling_list; /* w becomes us */ + break; + } + w = w->u.h2.sibling_list; + } + + w->u.h2.requested_POLLOUT = 0; + lwsl_info("%s: child %p (state %d)\n", __func__, (*wsi2), (*wsi2)->state); + + if (w->u.h2.pending_status_body) { + w->u.h2.send_END_STREAM = 1; + n = lws_write(w, + (uint8_t *)w->u.h2.pending_status_body + LWS_PRE, + strlen(w->u.h2.pending_status_body + LWS_PRE), + LWS_WRITE_HTTP_FINAL); + lws_free_set_NULL(w->u.h2.pending_status_body); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); + wa = &wsi->u.h2.child_list; + goto next_child; + } + + if (w->state == LWSS_HTTP_ISSUING_FILE) { + + w->leave_pollout_active = 0; + + /* >0 == completion, <0 == error + * + * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when + * it's done. That's the case even if we just completed the + * send, so wait for that. + */ + n = lws_serve_http_file_fragment(w); + lwsl_debug("lws_serve_http_file_fragment says %d\n", n); + + /* + * We will often hear about out having sent the final + * DATA here... if so close the actual wsi + */ + if (n < 0 || w->u.h2.send_END_STREAM) { + lwsl_debug("Closing POLLOUT child %p\n", w); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); + wa = &wsi->u.h2.child_list; + goto next_child; + } + if (n > 0) + if (lws_http_transaction_completed(w)) + goto bail_die; + if (!n) { + lws_callback_on_writable(w); + (w)->u.h2.requested_POLLOUT = 1; + } + + goto next_child; + } + + if (lws_calllback_as_writeable(w) || w->u.h2.send_END_STREAM) { + lwsl_debug("Closing POLLOUT child\n"); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); + wa = &wsi->u.h2.child_list; + } + +next_child: + wsi2 = wa; + } while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi)); + + lwsl_info("%s: %p: children waiting for POLLOUT service: %p\n", __func__, wsi, wsi->u.h2.child_list); + wsi2a = wsi->u.h2.child_list; + while (wsi2a) { + if (wsi2a->u.h2.requested_POLLOUT) + lwsl_debug(" * %p\n", wsi2a); + else + lwsl_debug(" %p\n", wsi2a); + + wsi2a = wsi2a->u.h2.sibling_list; + } + + + wsi2a = wsi->u.h2.child_list; + while (wsi2a) { + if (wsi2a->u.h2.requested_POLLOUT) { + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + break; + } + wsi2a = wsi2a->u.h2.sibling_list; + } goto bail_ok; + + notify: #endif wsi->handling_pollout = 0; @@ -502,15 +616,42 @@ lws_service_timeout_check(struct lws *wsi, unsigned int sec) int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len) { +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + + assert(h2n->rx_scratch); + buf += n; + len -= n; + assert ((char *)buf >= (char *)h2n->rx_scratch && + (char *)&buf[len] <= (char *)&h2n->rx_scratch[LWS_H2_RX_SCRATCH_SIZE]); + + h2n->rx_scratch_pos = ((char *)buf - (char *)h2n->rx_scratch); + h2n->rx_scratch_len = len; + + lwsl_info("%s: %p: pausing h2 rx_scratch\n", __func__, wsi); + + return 0; + } +#endif /* his RX is flowcontrolled, don't send remaining now */ if (wsi->rxflow_buffer) { - /* rxflow while we were spilling prev rxflow */ - lwsl_info("stalling in existing rxflow buf\n"); - return 1; + if (buf >= wsi->rxflow_buffer && + &buf[len - 1] < &wsi->rxflow_buffer[wsi->rxflow_len]) { + /* rxflow while we were spilling prev rxflow */ + lwsl_info("%s: staying in rxflow buf\n", __func__); + return 1; + } else { + lwsl_err("%s: conflicting rxflow buf, " + "current %p len %d, new %p len %d\n", __func__, + wsi->rxflow_buffer, wsi->rxflow_len, buf, len); + assert(0); + return 1; + } } /* a new rxflow, buffer it and warn caller */ - lwsl_info("new rxflow input buffer len %d\n", len - n); + lwsl_info("%s: new rxflow input buffer len %d\n", __func__, len - n); wsi->rxflow_buffer = lws_malloc(len - n, "rxflow buf"); if (!wsi->rxflow_buffer) return -1; @@ -735,9 +876,9 @@ spin_chunks: if (wsi->chunked && !wsi->chunk_remaining) return 0; - if (wsi->u.http.content_remain && - wsi->u.http.content_remain < *len) - n = (int)wsi->u.http.content_remain; + if (wsi->u.http.rx_content_remain && + wsi->u.http.rx_content_remain < *len) + n = (int)wsi->u.http.rx_content_remain; else n = *len; @@ -775,10 +916,10 @@ spin_chunks: return 0; /* if we know the content length, decrement the content remaining */ - if (wsi->u.http.content_length > 0) - wsi->u.http.content_remain -= n; + if (wsi->u.http.rx_content_length > 0) + wsi->u.http.rx_content_remain -= n; - if (wsi->u.http.content_remain || !wsi->u.http.content_length) + if (wsi->u.http.rx_content_remain || !wsi->u.http.rx_content_length) return 0; completed: @@ -1071,7 +1212,7 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t default: n = SSL_get_error(wsi->ssl, n); - if (n != SSL_ERROR_SYSCALL) { + if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) { if (SSL_want_read(wsi->ssl)) { lwsl_debug("(wants read)\n"); lws_change_pollfd(wsi, 0, LWS_POLLIN); @@ -1194,12 +1335,23 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t */ break; - if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) + if (lws_is_flowcontrolled(wsi)) /* We cannot deal with any kind of new RX * because we are RX-flowcontrolled. */ break; +#if defined(LWS_WITH_HTTP2) + wsi1 = lws_get_network_wsi(wsi); + if (wsi1 && wsi1->trunc_len) + /* We cannot deal with any kind of new RX + * because we are dealing with a partial send + * (new RX may trigger new http_action() that expect + * to be able to send) + */ + break; +#endif + /* 2: RX Extension needs to be drained */ @@ -1228,13 +1380,14 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t */ break; - /* 3: RX Flowcontrol buffer needs to be drained + /* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained */ if (wsi->rxflow_buffer) { lwsl_info("draining rxflow (len %d)\n", wsi->rxflow_len - wsi->rxflow_pos ); + assert(wsi->rxflow_pos < wsi->rxflow_len); /* well, drain it */ eff_buf.token = (char *)wsi->rxflow_buffer + wsi->rxflow_pos; @@ -1243,14 +1396,36 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t goto drain; } +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + + if (h2n->rx_scratch_len) { + lwsl_info("%s: %p: resuming h2 rx_scratch pos = %d len = %d\n", + __func__, wsi, h2n->rx_scratch_pos, h2n->rx_scratch_len); + eff_buf.token = (char *)h2n->rx_scratch + + h2n->rx_scratch_pos; + eff_buf.token_len = h2n->rx_scratch_len; + + h2n->rx_scratch_len = 0; + goto drain; + } + } +#endif + /* 4: any incoming (or ah-stashed incoming rx) data ready? * notice if rx flow going off raced poll(), rx flow wins */ if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) break; - read: + if (lws_is_flowcontrolled(wsi)) { + lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n", + __func__, wsi, wsi->rxflow_bitmap); + break; + } + /* all the union members start with hdr, so even in ws mode * we can deal with the ah via u.hdr */ @@ -1267,15 +1442,32 @@ read: * as to what it can output...) has to go in per-wsi rx buf area. * Otherwise in large temp serv_buf area. */ - eff_buf.token = (char *)pt->serv_buf; - if (lws_is_ws_with_ext(wsi)) { - eff_buf.token_len = wsi->u.ws.rx_ubuf_alloc; - } else { - eff_buf.token_len = context->pt_serv_buf_size; + +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + if (!wsi->u.h2.h2n->rx_scratch) { + wsi->u.h2.h2n->rx_scratch = lws_malloc(LWS_H2_RX_SCRATCH_SIZE, "h2 rx scratch"); + if (!wsi->u.h2.h2n->rx_scratch) + goto close_and_handled; + } + eff_buf.token = wsi->u.h2.h2n->rx_scratch; + eff_buf.token_len = LWS_H2_RX_SCRATCH_SIZE; + } else +#endif + { + eff_buf.token = (char *)pt->serv_buf; + if (lws_is_ws_with_ext(wsi)) { + eff_buf.token_len = wsi->u.ws.rx_ubuf_alloc; + } else { + eff_buf.token_len = context->pt_serv_buf_size; + } + + if ((unsigned int)eff_buf.token_len > context->pt_serv_buf_size) + eff_buf.token_len = context->pt_serv_buf_size; } - if ((unsigned int)eff_buf.token_len > context->pt_serv_buf_size) - eff_buf.token_len = context->pt_serv_buf_size; + if ((int)pending > eff_buf.token_len) + pending = eff_buf.token_len; eff_buf.token_len = lws_ssl_capable_read(wsi, (unsigned char *)eff_buf.token, pending ? pending : @@ -1361,7 +1553,6 @@ drain: * around again it will pick up from where it * left off. */ - n = lws_read(wsi, (unsigned char *)eff_buf.token, eff_buf.token_len); if (n < 0) { @@ -1376,8 +1567,7 @@ drain: } while (more); if (wsi->u.hdr.ah) { - lwsl_notice("%s: %p: detaching\n", - __func__, wsi); + lwsl_debug("%s: %p: detaching\n", __func__, wsi); lws_header_table_force_to_detachable_state(wsi); /* we can run the normal ah detach flow despite * being in ws union mode, since all union members @@ -1398,7 +1588,7 @@ drain: if (draining_flow && wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { - lwsl_info("flow buffer: drained\n"); + lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); lws_free_set_NULL(wsi->rxflow_buffer); /* having drained the rxflow buffer, can rearm POLLIN */ #ifdef LWS_NO_SERVER diff --git a/lib/ssl-http2.c b/lib/ssl-http2.c index 9c25646f1..4931d90a4 100644 --- a/lib/ssl-http2.c +++ b/lib/ssl-http2.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2014 Andy Green + * Copyright (C) 2010-2017 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -49,8 +49,8 @@ #include "private-libwebsockets.h" -#ifndef LWS_NO_SERVER -#ifdef LWS_OPENSSL_SUPPORT +#if !defined(LWS_NO_SERVER) +#if defined(LWS_OPENSSL_SUPPORT) #if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) @@ -59,29 +59,19 @@ struct alpn_ctx { unsigned short len; }; -static int -npn_cb(SSL *s, const unsigned char **data, unsigned int *len, void *arg) -{ - struct alpn_ctx *alpn_ctx = arg; - - lwsl_info("%s\n", __func__); - *data = alpn_ctx->data; - *len = alpn_ctx->len; - - return SSL_TLSEXT_ERR_OK; -} static int alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { +#if !defined(LWS_WITH_MBEDTLS) struct alpn_ctx *alpn_ctx = arg; if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_ctx->data, alpn_ctx->len, in, inlen) != OPENSSL_NPN_NEGOTIATED) return SSL_TLSEXT_ERR_NOACK; - +#endif return SSL_TLSEXT_ERR_OK; } #endif @@ -93,9 +83,6 @@ lws_context_init_http2_ssl(struct lws_vhost *vhost) static struct alpn_ctx protos = { (unsigned char *)"\x02h2" "\x08http/1.1", 6 + 9 }; - SSL_CTX_set_next_protos_advertised_cb(vhost->ssl_ctx, npn_cb, &protos); - - // ALPN selection callback SSL_CTX_set_alpn_select_cb(vhost->ssl_ctx, alpn_cb, &protos); lwsl_notice(" HTTP2 / ALPN enabled\n"); #else @@ -105,34 +92,36 @@ lws_context_init_http2_ssl(struct lws_vhost *vhost) #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L } -void lws_http2_configure_if_upgraded(struct lws *wsi) +int lws_h2_configure_if_upgraded(struct lws *wsi) { #if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) struct allocated_headers *ah; - const char *method = "alpn"; - const unsigned char *name; + const unsigned char *name = NULL; + char cstr[10]; unsigned len; SSL_get0_alpn_selected(wsi->ssl, &name, &len); - if (!len) { - SSL_get0_next_proto_negotiated(wsi->ssl, &name, &len); - method = "npn"; + lwsl_info("no ALPN upgrade\n"); + return 0; } - if (!len) { - lwsl_info("no npn/alpn upgrade\n"); - return; - } + if (len > sizeof(cstr) - 1) + len = sizeof(cstr) - 1; - (void)method; - lwsl_info("negotiated %s using %s\n", name, method); + memcpy(cstr, name, len); + cstr[len] = '\0'; + + lwsl_info("negotiated '%s' using ALPN\n", cstr); wsi->use_ssl = 1; if (strncmp((char *)name, "http/1.1", 8) == 0) - return; + return 0; /* http2 */ + wsi->upgraded_to_http2 = 1; + wsi->vhost->conn_stats.h2_alpn++; + /* adopt the header info */ ah = wsi->u.hdr.ah; @@ -143,13 +132,21 @@ void lws_http2_configure_if_upgraded(struct lws *wsi) /* http2 union member has http union struct at start */ wsi->u.http.ah = ah; + wsi->u.h2.h2n = lws_zalloc(sizeof(*wsi->u.h2.h2n), "h2n"); + if (!wsi->u.h2.h2n) + return 1; - lws_http2_init(&wsi->u.http2.peer_settings); - lws_http2_init(&wsi->u.http2.my_settings); + lws_h2_init(wsi); /* HTTP2 union */ + + lws_hpack_dynamic_size(wsi, wsi->u.h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); + wsi->u.h2.tx_cr = 65535; + + lwsl_info("%s: wsi %p: configured for h2\n", __func__, wsi); + + return 0; #endif } - #endif #endif diff --git a/lib/ssl-server.c b/lib/ssl-server.c index a7590bb00..a9516f223 100644 --- a/lib/ssl-server.c +++ b/lib/ssl-server.c @@ -141,18 +141,13 @@ lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info, static int lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) { - struct lws_context *context; + struct lws_context *context = (struct lws_context *)arg; struct lws_vhost *vhost, *vh; const char *servername; - int port; if (!ssl) return SSL_TLSEXT_ERR_NOACK; - context = (struct lws_context *)SSL_CTX_get_ex_data( - SSL_get_SSL_CTX(ssl), - openssl_SSL_CTX_private_data_index); - /* * We can only get ssl accepted connections by using a vhost's ssl_ctx * find out which listening one took us and only match vhosts on the @@ -165,22 +160,31 @@ lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) vh = vh->vhost_next; } - assert(vh); /* we cannot get an ssl without using a vhost ssl_ctx */ - port = vh->listen_port; + if (!vh) { + assert(vh); /* can't match the incoming vh? */ + return SSL_TLSEXT_ERR_OK; + } servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) { + /* the client doesn't know what hostname it wants */ + lwsl_info("SNI: Unknown ServerName: %s\n", servername); - if (servername) { - vhost = lws_select_vhost(context, port, servername); - if (vhost) { - lwsl_debug("SNI: Found: %s (port %d)\n", - servername, port); - SSL_set_SSL_CTX(ssl, vhost->ssl_ctx); - return SSL_TLSEXT_ERR_OK; - } - lwsl_err("SNI: Unknown ServerName: %s\n", servername); + return SSL_TLSEXT_ERR_OK; } + vhost = lws_select_vhost(context, vh->listen_port, servername); + if (!vhost) { + lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port); + + return SSL_TLSEXT_ERR_OK; + } + + lwsl_info("SNI: Found: %s:%d\n", servername, vh->listen_port); + + /* select the ssl ctx from the selected vhost for this conn */ + SSL_set_SSL_CTX(ssl, vhost->ssl_ctx); + return SSL_TLSEXT_ERR_OK; } #endif @@ -319,6 +323,7 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info, #if !defined(LWS_WITH_MBEDTLS) && !defined(OPENSSL_NO_TLSEXT) SSL_CTX_set_tlsext_servername_callback(vhost->ssl_ctx, lws_ssl_server_name_cb); + SSL_CTX_set_tlsext_servername_arg(vhost->ssl_ctx, context); #endif /* @@ -405,18 +410,20 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info, p = NULL; #endif - if (alloc_pem_to_der_file(vhost->context, - info->ssl_private_key_filepath, &p, &flen)) { - lwsl_err("couldn't find cert file %s\n", - info->ssl_cert_filepath); + if (info->ssl_private_key_filepath) { + if (alloc_pem_to_der_file(vhost->context, + info->ssl_private_key_filepath, &p, &flen)) { + lwsl_err("couldn't find cert file %s\n", + info->ssl_cert_filepath); - return 1; - } - err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen); - if (!err) { - lwsl_err("Problem loading key\n"); + return 1; + } + err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen); + if (!err) { + lwsl_err("Problem loading key\n"); - return 1; + return 1; + } } #if !defined(LWS_WITH_ESP32) diff --git a/lib/ssl.c b/lib/ssl.c index 450fbc382..15bc810b9 100644 --- a/lib/ssl.c +++ b/lib/ssl.c @@ -94,7 +94,7 @@ int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, size_t s; int n = 0; - f =fopen(filename, "rb"); + f = fopen(filename, "rb"); if (f == NULL) { n = 1; goto bail; @@ -131,7 +131,9 @@ int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, *amount = s; bail: - fclose(f); + if (f) + fclose(f); + return n; } @@ -414,10 +416,7 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - int n = 0; -#if !defined(LWS_WITH_MBEDTLS) - int ssl_read_errno = 0; -#endif + int n = 0, m; if (!wsi->ssl) return lws_ssl_capable_read_no_ssl(wsi, buf, len); @@ -445,29 +444,18 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len) lwsl_debug("%p: SSL_read says %d\n", wsi, n); /* manpage: returning 0 means connection shut down */ if (!n) { - n = lws_ssl_get_error(wsi, n); - lwsl_debug("%p: ssl err %d errno %d\n", wsi, n, errno); - if (n == SSL_ERROR_ZERO_RETURN) - return LWS_SSL_CAPABLE_ERROR; - - if (n == SSL_ERROR_SYSCALL) { -#if !defined(LWS_WITH_MBEDTLS) - int err = ERR_get_error(); - if (err == 0 && (ssl_read_errno == EPIPE || - ssl_read_errno == ECONNABORTED || - ssl_read_errno == 0)) - return LWS_SSL_CAPABLE_ERROR; -#endif - } - - lwsl_info("%s failed: %s\n",__func__, - ERR_error_string(lws_ssl_get_error(wsi, 0), NULL)); - lws_ssl_elaborate_error(); + wsi->socket_is_permanently_unusable = 1; return LWS_SSL_CAPABLE_ERROR; } if (n < 0) { + m = lws_ssl_get_error(wsi, n); + lwsl_debug("%p: ssl err %d errno %d\n", wsi, m, errno); + if (m == SSL_ERROR_ZERO_RETURN || + m == SSL_ERROR_SYSCALL) + return LWS_SSL_CAPABLE_ERROR; + if (SSL_want_read(wsi->ssl)) { lwsl_debug("%s: WANT_READ\n", __func__); lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); @@ -478,10 +466,7 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len) lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); return LWS_SSL_CAPABLE_MORE_SERVICE; } - - lwsl_info("%s failed2: %s\n",__func__, - ERR_error_string(lws_ssl_get_error(wsi, 0), NULL)); - lws_ssl_elaborate_error(); + wsi->socket_is_permanently_unusable = 1; return LWS_SSL_CAPABLE_ERROR; } @@ -542,10 +527,7 @@ lws_ssl_pending(struct lws *wsi) LWS_VISIBLE int lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len) { - int n; -#if !defined(LWS_WITH_MBEDTLS) - int ssl_read_errno = 0; -#endif + int n, m; if (!wsi->ssl) return lws_ssl_capable_write_no_ssl(wsi, buf, len); @@ -554,33 +536,29 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len) if (n > 0) return n; - n = lws_ssl_get_error(wsi, n); - if (n == SSL_ERROR_WANT_READ || n == SSL_ERROR_WANT_WRITE) { - if (n == SSL_ERROR_WANT_WRITE) { - lwsl_debug("%s: WANT_WRITE\n", __func__); - lws_set_blocking_send(wsi); + m = lws_ssl_get_error(wsi, n); + if (m != SSL_ERROR_SYSCALL) { + + if (SSL_want_read(wsi->ssl)) { + lwsl_notice("%s: want read\n", __func__); + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + + if (SSL_want_write(wsi->ssl)) { + lws_set_blocking_send(wsi); + + lwsl_notice("%s: want write\n", __func__); + + return LWS_SSL_CAPABLE_MORE_SERVICE; } - return LWS_SSL_CAPABLE_MORE_SERVICE; } - if (n == SSL_ERROR_ZERO_RETURN) - return LWS_SSL_CAPABLE_ERROR; - -#if !defined(LWS_WITH_MBEDTLS) - if (n == SSL_ERROR_SYSCALL) { - int err = ERR_get_error(); - - if (err == 0 && (ssl_read_errno == EPIPE || - ssl_read_errno == ECONNABORTED || - ssl_read_errno == 0)) - return LWS_SSL_CAPABLE_ERROR; - } -#endif - - lwsl_info("%s failed: %s\n",__func__, - ERR_error_string(lws_ssl_get_error(wsi, 0), NULL)); + lwsl_debug("%s failed: %s\n",__func__, ERR_error_string(m, NULL)); lws_ssl_elaborate_error(); + wsi->socket_is_permanently_unusable = 1; + return LWS_SSL_CAPABLE_ERROR; } @@ -653,7 +631,8 @@ lws_ssl_close(struct lws *wsi) #endif n = SSL_get_fd(wsi->ssl); - SSL_shutdown(wsi->ssl); + if (!wsi->socket_is_permanently_unusable) + SSL_shutdown(wsi->ssl); compatible_close(n); SSL_free(wsi->ssl); wsi->ssl = NULL; @@ -676,6 +655,7 @@ LWS_VISIBLE int lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) { struct lws_context *context = wsi->context; + struct lws_vhost *vh; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; int n, m; #if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) @@ -856,9 +836,11 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) m = lws_ssl_get_error(wsi, n); #if defined(LWS_WITH_MBEDTLS) - if (m == 5 && errno == 11) + if (m == SSL_ERROR_SYSCALL && errno == 11) m = SSL_ERROR_WANT_READ; #endif + if (m == SSL_ERROR_SYSCALL || m == SSL_ERROR_SSL) + goto failed; go_again: if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) { @@ -880,6 +862,7 @@ go_again: break; } +failed: lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1); lwsl_info("SSL_accept failed socket %u: %s\n", wsi->desc.sockfd, @@ -897,6 +880,18 @@ accepted: wsi->accept_start_us = time_in_microseconds(); #endif + /* adapt our vhost to match the SNI SSL_CTX that was chosen */ + vh = context->vhost_list; + while (vh) { + if (!vh->being_destroyed && + vh->ssl_ctx == SSL_get_SSL_CTX(wsi->ssl)) { + lwsl_info("setting wsi to vh %s\n", vh->name); + wsi->vhost = vh; + break; + } + vh = vh->vhost_next; + } + /* OK, we are accepted... give him some time to negotiate */ lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, context->timeout_secs); @@ -905,9 +900,10 @@ accepted: wsi->mode = LWSCM_RAW; else wsi->mode = LWSCM_HTTP_SERVING; - - lws_http2_configure_if_upgraded(wsi); - +#if defined(LWS_WITH_HTTP2) + if (lws_h2_configure_if_upgraded(wsi)) + goto fail; +#endif lwsl_debug("accepted new SSL conn\n"); break; } diff --git a/lwsws/main.c b/lwsws/main.c index eae913ef1..16307f527 100644 --- a/lwsws/main.c +++ b/lwsws/main.c @@ -54,6 +54,7 @@ static int opts = 0, do_reload = 1; static uv_loop_t loop; static uv_signal_t signal_outer; static int pids[32]; +void lwsl_emit_stderr(int level, const char *line); #define LWSWS_CONFIG_STRING_SIZE (32 * 1024) @@ -119,7 +120,7 @@ context_creation(void) memset(&info, 0, sizeof(info)); info.external_baggage_free_on_destroy = config_strings; - info.max_http_header_pool = 256; + info.max_http_header_pool = 1024; info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_LIBUV; diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c index 8d1426086..950c7b723 100644 --- a/plugins/protocol_dumb_increment.c +++ b/plugins/protocol_dumb_increment.c @@ -132,6 +132,7 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, callback_dumb_increment, \ sizeof(struct per_session_data__dumb_increment), \ 10, /* rx buf size must be >= permessage-deflate rx size */ \ + 0, NULL, 0 \ } #if !defined (LWS_PLUGIN_STATIC) diff --git a/plugins/protocol_esp32_lws_ota.c b/plugins/protocol_esp32_lws_ota.c index a1dc14bee..d3e8808d4 100644 --- a/plugins/protocol_esp32_lws_ota.c +++ b/plugins/protocol_esp32_lws_ota.c @@ -126,7 +126,7 @@ ota_file_upload_cb(void *data, const char *name, const char *filename, return 1; } - lwsl_debug("writing 0x%lx... 0x%lx\n", + lwsl_notice("writing 0x%lx... 0x%lx\n", pss->part->address + pss->file_length, pss->part->address + pss->file_length + len); if (esp_ota_write(pss->otahandle, buf, len) != ESP_OK) { @@ -193,6 +193,7 @@ callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ //lwsl_notice("LWS_CALLBACK_HTTP_BODY (ota) %d %d %p\n", (int)pss->file_length, (int)len, pss->spa); + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); if (!pss->spa) { pss->spa = lws_spa_create(wsi, ota_param_names, ARRAY_SIZE(ota_param_names), 4096, @@ -228,7 +229,7 @@ callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason, if (lws_finalize_http_header(wsi, &p, end)) goto bail; - n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); if (n < 0) goto bail; @@ -236,6 +237,8 @@ callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_HTTP_WRITEABLE: + if (!pss->result_len) + break; lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, diff --git a/plugins/protocol_esp32_lws_scan.c b/plugins/protocol_esp32_lws_scan.c index 686599c25..da82dc6b0 100644 --- a/plugins/protocol_esp32_lws_scan.c +++ b/plugins/protocol_esp32_lws_scan.c @@ -378,7 +378,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason, struct timeval t; uint8_t mac[6]; struct lws_esp32_image i; - char img_factory[512], img_ota[512], group[16], role[16]; + char img_factory[384], img_ota[384], group[16], role[16]; int grt; case SCAN_STATE_INITIAL: @@ -418,12 +418,21 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason, strcpy(img_factory, " { \"date\": \"Empty\" }"); strcpy(img_ota, " { \"date\": \"Empty\" }"); - lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP, - ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL), &i, - img_factory, sizeof(img_factory)); - lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP, - ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL), &i, - img_ota, sizeof(img_ota)); + if (grt != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) { + lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL), &i, + img_factory, sizeof(img_factory) - 1); + img_factory[sizeof(img_factory) - 1] = '\0'; + if (img_factory[0] == 0xff || strlen(img_factory) < 8) + strcpy(img_factory, " { \"date\": \"Empty\" }"); + + lws_esp32_get_image_info(esp_partition_find_first(ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL), &i, + img_ota, sizeof(img_ota) - 1); + img_ota[sizeof(img_ota) - 1] = '\0'; + if (img_ota[0] == 0xff || strlen(img_ota) < 8) + strcpy(img_ota, " { \"date\": \"Empty\" }"); + } p += snprintf((char *)p, end - p, "{ \"model\":\"%s\",\n" @@ -440,9 +449,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason, " \"conn_mask\":\"%s\",\n" " \"conn_gw\":\"%s\",\n" " \"group\":\"%s\",\n" - " \"role\":\"%s\",\n" - " \"img_factory\": %s,\n" - " \"img_ota\": %s,\n", + " \"role\":\"%s\",\n", lws_esp32.model, grt == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON, lws_esp32.serial, @@ -455,11 +462,15 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason, lws_esp32.sta_ip, lws_esp32.sta_mask, lws_esp32.sta_gw, - group, role, + group, role); + p += snprintf((char *)p, end - p, + " \"img_factory\": %s,\n" + " \"img_ota\": %s,\n", img_factory, img_ota ); + n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN; pss->scan_state = SCAN_STATE_INITIAL_MANIFEST; pss->ap_record = 0; @@ -563,6 +574,7 @@ scan_state_final: } issue: // lwsl_notice("issue: %d (%d)\n", p - start, n); + lwsl_hexdump(start, p - start); m = lws_write(wsi, (unsigned char *)start, p - start, n); if (m < 0) { lwsl_err("ERROR %d writing to di socket\n", m); diff --git a/plugins/protocol_lws_meta.c b/plugins/protocol_lws_meta.c index db25badaa..4944cff1a 100644 --- a/plugins/protocol_lws_meta.c +++ b/plugins/protocol_lws_meta.c @@ -501,7 +501,7 @@ callback_lws_meta(struct lws *wsi, enum lws_callback_reasons reason, return -1; } - lwsl_debug("%s: RX len %d\n", __func__, (int)len); + // lwsl_debug("%s: RX len %d\n", __func__, (int)len); if (lws_get_protocol(cwsi)->callback(cwsi, LWS_CALLBACK_RECEIVE, diff --git a/plugins/protocol_lws_mirror.c b/plugins/protocol_lws_mirror.c index a9fb0657b..3683a12b1 100644 --- a/plugins/protocol_lws_mirror.c +++ b/plugins/protocol_lws_mirror.c @@ -27,13 +27,13 @@ #include #include -#define QUEUELEN 64 +#define QUEUELEN 32 /* queue free space below this, rx flow is disabled */ #define RXFLOW_MIN (4) /* queue free space above this, rx flow is enabled */ #define RXFLOW_MAX (QUEUELEN / 3) -#define MAX_MIRROR_INSTANCES 10 +#define MAX_MIRROR_INSTANCES 3 struct mirror_instance; @@ -44,6 +44,7 @@ struct per_session_data__lws_mirror { uint32_t tail; }; +/* this is the element in the ring */ struct a_message { void *payload; size_t len; @@ -53,6 +54,7 @@ struct mirror_instance { struct mirror_instance *next; struct per_session_data__lws_mirror *same_mi_pss_list; struct lws_ring *ring; + int messages_allocated; char name[30]; char rx_enabled; }; @@ -99,7 +101,7 @@ mirror_rxflow_instance(struct mirror_instance *mi, int enable) static int mirror_update_worst_tail(struct mirror_instance *mi) { - uint32_t wai, worst = 0, worst_tail, oldest; + uint32_t wai, worst = 0, worst_tail = 0, oldest; struct per_session_data__lws_mirror *worst_pss = NULL; oldest = lws_ring_get_oldest_tail(mi->ring); @@ -317,10 +319,6 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, update_worst = oldest_tail == pss->tail; sent_something = 0; - lwsl_debug(" original oldest %d, free elements %ld\n", - oldest_tail, - lws_ring_get_count_free_elements(pss->mi->ring)); - do { msg = lws_ring_get_element(pss->mi->ring, &pss->tail); if (!msg) @@ -385,6 +383,7 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, lwsl_notice("OOM: dropping\n"); break; } + memcpy((char *)amsg.payload + LWS_PRE, in, len); if (!lws_ring_insert(pss->mi->ring, &amsg, 1)) { mirror_destroy_message(&amsg); @@ -414,6 +413,7 @@ req_writable: callback_lws_mirror, \ sizeof(struct per_session_data__lws_mirror), \ 128, /* rx buf size must be >= permessage-deflate rx size */ \ + 0, NULL, 0 \ } #if !defined (LWS_PLUGIN_STATIC) diff --git a/plugins/protocol_lws_server_status.c b/plugins/protocol_lws_server_status.c index 96b2ef298..9619eb66b 100644 --- a/plugins/protocol_lws_server_status.c +++ b/plugins/protocol_lws_server_status.c @@ -23,6 +23,8 @@ #include "../lib/libwebsockets.h" #include #include +#include +#include #include struct lws_ss_load_sample { @@ -102,8 +104,9 @@ uv_timeout_cb_server_status(uv_timer_t *w contents[n] = '\0'; lws_json_purify(pure, contents, sizeof(pure)); - n = lws_snprintf(p, l, "{\"path\":\"%s\",\"val\":\"%s\"}", - fp->filepath, pure); + n = lws_snprintf(p, l, + "{\"path\":\"%s\",\"val\":\"%s\"}", + fp->filepath, pure); p += n; l -= n; first = 0; @@ -162,20 +165,22 @@ callback_lws_server_status(struct lws *wsi, enum lws_callback_reasons reason, if (!strcmp(pvo->name, "filepath")) { fp = malloc(sizeof(*fp)); fp->next = NULL; - lws_snprintf(&fp->filepath[0], sizeof(fp->filepath), "%s", pvo->value); + lws_snprintf(&fp->filepath[0], + sizeof(fp->filepath), "%s", + pvo->value); *fp_old = fp; fp_old = &fp->next; } pvo = pvo->next; } v->context = lws_get_context(wsi); - uv_timer_init(lws_uv_getloop(v->context, 0), &v->timeout_watcher); + uv_timer_init(lws_uv_getloop(v->context, 0), + &v->timeout_watcher); uv_timer_start(&v->timeout_watcher, - uv_timeout_cb_server_status, 2000, period); + uv_timeout_cb_server_status, 2000, period); break; case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */ - // lwsl_notice("ss: LWS_CALLBACK_PROTOCOL_DESTROY: v=%p, ctx=%p\n", v, v->context); if (!v) break; uv_timer_stop(&v->timeout_watcher); @@ -213,7 +218,7 @@ static const struct lws_protocols protocols[] = { LWS_EXTERN LWS_VISIBLE int init_protocol_lws_server_status(struct lws_context *context, - struct lws_plugin_capability *c) + struct lws_plugin_capability *c) { if (c->api_magic != LWS_PLUGIN_API_MAGIC) { lwsl_err("Plugin API %d, library API %d", @@ -234,4 +239,3 @@ destroy_protocol_lws_server_status(struct lws_context *context) { return 0; } - diff --git a/plugins/protocol_lws_status.c b/plugins/protocol_lws_status.c index 5b32139b7..d9240caed 100644 --- a/plugins/protocol_lws_status.c +++ b/plugins/protocol_lws_status.c @@ -165,6 +165,7 @@ callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason, break; } + strcpy(ip, "unknown"); lws_get_peer_simple(pss->walk_next->wsi, ip, sizeof(ip)); p += lws_snprintf(p, end - p, "{\"peer\":\"%s\",\"time\":\"%ld\"," @@ -238,6 +239,7 @@ walk_final: callback_lws_status, \ sizeof(struct per_session_data__lws_status), \ 512, /* rx buf size must be >= permessage-deflate rx size */ \ + 0, NULL, 0 \ } #if !defined (LWS_PLUGIN_STATIC) diff --git a/plugins/protocol_post_demo.c b/plugins/protocol_post_demo.c index 7560bf5a7..8880fe2e4 100644 --- a/plugins/protocol_post_demo.c +++ b/plugins/protocol_post_demo.c @@ -40,7 +40,7 @@ struct per_session_data__post_demo { char filename[64]; long file_length; -#if !defined(LWS_WITH_ESP8266) +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) lws_filefd_type fd; #endif }; @@ -65,7 +65,9 @@ file_upload_cb(void *data, const char *name, const char *filename, { struct per_session_data__post_demo *pss = (struct per_session_data__post_demo *)data; +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) int n; +#endif switch (state) { case LWS_UFS_OPEN: @@ -73,7 +75,7 @@ file_upload_cb(void *data, const char *name, const char *filename, /* we get the original filename in @filename arg, but for * simple demo use a fixed name so we don't have to deal with * attacks */ -#if !defined(LWS_WITH_ESP8266) +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) pss->fd = (lws_filefd_type)open("/tmp/post-file", O_CREAT | O_TRUNC | O_RDWR, 0600); #endif @@ -87,7 +89,7 @@ file_upload_cb(void *data, const char *name, const char *filename, if (pss->file_length > 100000) return 1; -#if !defined(LWS_WITH_ESP8266) +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) n = write((int)pss->fd, buf, len); lwsl_notice("%s: write %d says %d\n", __func__, len, n); #else @@ -96,7 +98,7 @@ file_upload_cb(void *data, const char *name, const char *filename, } if (state == LWS_UFS_CONTENT) break; -#if !defined(LWS_WITH_ESP8266) +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) close((int)pss->fd); pss->fd = LWS_INVALID_FILE; #endif @@ -185,6 +187,8 @@ callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_HTTP_WRITEABLE: + if (!pss->result_len) + break; lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, @@ -225,6 +229,7 @@ try_to_reuse: callback_post_demo, \ sizeof(struct per_session_data__post_demo), \ 1024, \ + 0, NULL, 0 \ } #if !defined (LWS_PLUGIN_STATIC) diff --git a/plugins/server-status.html b/plugins/server-status.html index a49513252..e59888b29 100644 --- a/plugins/server-status.html +++ b/plugins/server-status.html @@ -5,40 +5,37 @@ LWS Server Status